JavaScript面向对象
JavaScript是一种无类语言,但可以使用函数来模拟,这就涉及到设计模式。模式是一种已经验证过的可复用的解决方案,可用于解决软件设计中遇到的常见的问题,通常将这些解决方案制作成模板来复用。
而JavaScript模拟类常用的方式是定义一个JavaScript函数,使用this来定义属性和方法,然后使用new关键字创建对象实例。如
代码如下 | 复制代码 |
function webSite(){ this.name="开源视窗"; this.url="oseye.net"; this.getInfo=function(){ return this.name+" : "+this.url; } } var wb=new webSite(); console.log(wb.getInfo()); |
创建对象
在JavaScript中,创建新对象的两种常用方法如:
代码如下 | 复制代码 |
var newObject={}; var newObject=new Object(); |
然而这两种方式只是创建一个空对象,没有任何用处,但有一下四种方式可以扩展它:
代码如下 | 复制代码 |
var webSite={}; //"点"语法 webSite.url="oseye.net"; webSite.getInfo=function(){ return this.url; } console.log(webSite.getInfo()); console.log(webSite.url); //中括号语法 webSite["name"]="开源视窗"; console.log(webSite.name); console.log(webSite["name"]); //用Object.defineProperty Object.defineProperty(webSite,"url",{ value:"oseye.net by defineProperty", writable:true, enumerable:true, configurable:true }); console.log(webSite.url); //使用Object.defineProperties Object.defineProperties(webSite,{ "url":{ value:"oseye.net by defineProperties", writable:true, enumerable:true, configurable:true }, "name":{ value:"开源视窗 by defineProperties", writable:true } }); console.log(webSite.url + webSite.name); |
而使用Object.create可以实现继承,如:
var webSite={};
webSite.url="oseye.net";
//创建myWebSite继承自webSite
var myWebSite=Object.create(webSite);
console.log(myWebSite.url);
Constructor(构造器)模式
在经典面向对象编程语言中,构造器是一种在内存已经分配给对象的情况下,用于初始化新创建对象的特殊方法。虽然JavaScript不支持类的概念,但它确实支持与对象一起用的特殊构造器函数,如:
代码如下 | 复制代码 |
function webSite(name,url){ this.url=url; this.name=name; this.getInfo=function(){ return this.url+" - "+this.name; } } var ws=new webSite("开源视窗","oseye.net"); console.log(ws.getInfo()); |
Module(模块)模式
模块模式是基于对象字面量的,因此需要认识对象字面量的含义。在对象字面量表示法中,一个对象被描述为一组包含大括号({})中、以逗号分隔的name/value对,而谨记对象的最后一个name/value后面不需要逗号,否则语法错误。语法如下:
代码如下 | 复制代码 |
var newObject={ variableKey:variableValue, fucntionKey:function(){ //..... } }; |
示例:
代码如下 | 复制代码 |
var webSite={ url:"oseye.net", getInfo:function(){ return this.url; } }; console.log(webSite.getInfo()); |
Prototype(原型)模式
每个JavaScript对象都有一个prototype属性,使用使用prototype属性,可以给类动态地添加方法,以便在JavaScript中实现“继承”的效果。其实prototype属性是指向一个对象的引用,这个对象称为原型对象,原型对象包含函数实例共享的方法和属性,也就是说将函数用作构造函数调用(使用new操作符调用)的时候,新创建的对象会从原型对象上继承属性和方法。
看到上面的描述,新手一定云里雾里的!在理解prototype之前我们来看几个面向对象中常见的东东:
私有变量、函数:在函数内定义的变量和函数如果不对外提供接口,那么外部将无法访问到,也就是变为私有变量和私有函数
代码如下 | 复制代码 |
function webSite(){ url="oseye.net"; var getInfo=function(){ } }; //在webSite对象外部无法访问变量url和函数getInfo,它们就变成私有的,只能在webSite内部使用. //即使是函数webSite的实例仍然无法访问这些变量和函数,异常提示“undefined” console.log(new webSite().getInfo()); |
静态变量、函数:当定义一个函数后通过 “.”为其添加的属性和函数,通过对象本身仍然可以访问得到,但是其实例却访问不到,这样的变量和函数分别被称为静态变量和静态函数,用过Java、C#的同学很好理解静态的含义。
代码如下 | 复制代码 |
function webSite(){}; webSite.url="oseye.net"; console.log(webSite().url); //oseye.net console.log(new webSite().url); //undefined |
实例变量、函数:在面向对象编程中除了一些库函数我们还是希望在对象定义的时候同时定义一些属性和方法,实例化后可以访问,JavaScript是通过this做到的
代码如下 | 复制代码 |
function webSite(){ this.url="oseye.net"; this.getInfo=function(){ return this.url; } }; console.log(new webSite().url); //oseye.net console.log(new webSite().getInfo()); //oseye.net |
看着还不错,但你再看
代码如下 | 复制代码 |
function webSite(){ this.url="oseye.net"; this.getInfo=function(){ return this.url; } }; var ws=new webSite(); ws.url="test.oseye.net"; ws.getInfo=function(){ return "override getInfo"; } console.log(ws.url); //test.oseye.net console.log(ws.getInfo()); //override getInfo var ws2=new webSite(); console.log(ws2.url); //oseye.net console.log(ws2.getInfo()); //oseye.net |
由此可以得出一个结论:每个实例的属性和方法都是对象属性和方法的一个复制。如果一个对象有成千上万个实力方法,那么每个实例都要这么一个复制,有点小恐怖!!这时prototype应运而生,
无论什么时候,只要创建了一个新函数,就会根据一组特定的规则为该函数创建一个prototype属性,默认情况下prototype属性会默认获得一个constructor(构造函数)属性,这个属性是一个指向prototype属性所在函数的指针,有些绕了啊,写代码、上图!
代码如下 | 复制代码 |
function Person(){} |
根据上图可以看出Person对象会自动获得prototyp属性,而prototype也是一个对象,会自动获得一个constructor属性,该属性正是指向Person对象。
当调用构造函数创建一个实例的时候,实例内部将包含一个内部指针(很多浏览器这个指针名字为__proto__)指向构造函数的prototype,这个连接存在于实例和构造函数的prototype之间,而不是实例与构造函数之间。
代码如下 | 复制代码 |
function Person(name){ this.name=name; } Person.prototype.printName=function(){ alert(this.name); } var person1=new Person('Byron'); var person2=new Person('Frank'); |
Person的实例person1中包含了name属性,同时自动生成一个__proto__属性,该属性指向Person的prototype,可以访问到prototype内定义的printName方法,大概就是这个样子的
写段程序测试一下看看prototype内属性、方法是能够共享
代码如下 | 复制代码 |
function Person(name){ this.name=name; } Person.prototype.share=[]; Person.prototype.printName=function(){ alert(this.name); } var person1=new Person('Byron'); var person2=new Person('Frank'); person1.share.push(1); person2.share.push(2); console.log(person2.share); //[1,2] |
果不其然!实际上当代码读取某个对象的某个属性的时候,都会执行一遍搜索,目标是具有给定名字的属性,搜索首先从对象实例开始,如果在实例中找到该属性则返回,如果没有则查找prototype,如果还是没有找到则继续递归prototype的prototype对象,直到找到为止,如果递归到object仍然没有则返回错误。同样道理如果在实例中定义如prototype同名的属性或函数,则会覆盖prototype的属性或函数。
当然prototype不是专门为解决上面问题而定义的,但是却解决了上面问题。了解了这些知识就可以构建一个科学些的、复用率高的对象,如果希望实例对象的属性或函数则定义到prototype中,如果希望每个实例单独拥有的属性或方法则定义到this中,可以通过构造函数传递实例化参数。