ECMA-262把对象定义为:“无序属性的集合,其属性可以包含基本值、对象或者函数。”
说白了就是一组无序名值对,其中值可以是数据或函数。
(1)方式:创建一个Object类型的实例,然后再为它添加属性和方法。
(2)例子:
var person = new Object(); person.name = "kwan"; person.age = 22; person.job = "student"; person.sayName = function () { alert(this.name); };
(3)优点:简单,容易理解。
(4)缺点:使用同一个接口创建很多对象,会产生大量重复代码,且无法识别对象类型。
(5)总结:这是创建自定义对象的最简单的方式。
(1)方式:在JS中定义一个工厂函数来实现这一模式。
(2)例子:
function createPerson(name, age, job) { var o = new Object(); o.name = name; o.age = age; o.job = job; o.sayName = function () { alert(this.name); }; return o; }
(3)优点:减少重复使用的代码。
(4)缺点:无法识别对象的类型。
(5)总结:工厂模式抽象了创建具体对象的细节,工厂函数就像一个真正的工厂,只需为它传递参数,就可以流水线式产生相应的对象。
(1)方式:创建自定义的构造函数,从而定义自定义对象类型的属性和方法。
(2)例子:
function Person(name, age, job) { this.name = name; this.age = age; this.job = job; this.sayName = function () { alert(this.name); }; }
(3)优点:可将它的实例标识为一种特定的类型。
(4)缺点:每个方法都要在每个实例上重新创建一遍,但其实方法是可以让所有实例共享的。
而如果简单地将函数定义转移到外部,让对象引用它,那么该模式又失去封装性。
(5)总结:
1.构造函数模式中:
a.没有显式创建对象
b.直接将属性和方法赋给了this对象
c.也没有return语句
d.且函数名首字母大写(惯例)
2.使用new操作符调用构造函数创建新实例的机制是:
a.创建一个新对象
b.将构造函数的作用域赋给新对象(因此this指向这个新对象)
c.执行构造函数中的代码
d.返回新对象
3.新实例具有一个constructor属性,该属性指向构造函数。
4.构造函数与其他函数的唯一区别,就在于调用它们的方式不同。调用构造函数时需用new操作符。任何函数通过new操作符调用都会变成构造函数,而原本的构造函数若不使用new操作符而是直接调用,那和普通的函数没什么两样,此时的this指代调用该函数的作用域。
(1)方式:通过函数的prototype属性,将可以共享的信息直接添加到原型对象中。
(2)例子:
function Person() {} Person.prototype.name = "kwan"; Person.prototype.age = "22"; Person.prototype.job = "student"; Person.prototype.sayName = function () { alert(this.name); };
(3)优点:可让所有对象实例共享其原型对象所包含的属性和方法。
(4)缺点:首先,它省略了为构造函数传递初始化参数这一环节,结果所有实例在默认情况下都将取得相同的属性值;
其次,由于其共享的本性,当属性值为引用类型时,会导致很大问题。
(5)总结:
1.我们创建的每个函数都有一个prototype属性,该属性值为指针,指向一个对象,即原型对象,它的用途是包含可以由特定类型的所有实例共享的属性和方法。
2.使用原型模式创建的对象都在使用同一组属性和函数,这便是该模式的共享特性。
3.若实例属性与原型对象属性同名,则优先取实例属性值(屏蔽)
4.实例的内部属性__proto__指向原型对象,这便是两者之间的连接。继承的实现,以及对象属性的查找,其实都是依赖于这个连接。
5.读取对象属性的过程是:先找实例,后找原型。
6.原型是动态的,因此我们对原型对象所做的任何修改都能够立即从实例上反映出来。
(1)方式:构造函数模式用于定义实例属性,而原型模式用于定义方法和共享属性。
(2)例子:
function Person(name, age, job) { this.name = name; this.age = age; this.job = job; this.friends = ["kwan", "soyi"]; } Person.prototype = { constructor : Person, sayName : function () { alert(this.name); } }
(3)优点:每个实例都会有自己的一份实例属性的副本,同时又共享对方法和共享属性的引用,最大限度地节省了内存;另外,该模式支持向构造函数传递参数。
(4)缺点:封装性不足。
(5)总结:挺好用。
(1)方式:把所有信息都封装在了构造函数中,通过检查某个应该存在的方法是否有效,来决定是否需要初始化原型。
(2)例子:
function Person(name, age, job) { this.name = name; this.age = age; this.job = job; this.friends = ["kwan", "soyi"]; if (typeof this.sayName != "function") {//这个判断是必要的,且只需写一个条件 Person.prototype.sayName = function () { alert(this.name); }; } }
(3)优点:仅在必要情况下通过在构造函数中初始化原型,保持同时使用构造函数和原型的优点,封装度更高。
(4)缺点:暂无。
(5)总结:
通过if条件的判断,只有在第一个实例被new出来的时候才会为原型对象添加属性和方法,接下来的所有对象的实例化均不会调用添加操作,而且这样写可将所有信息封装在构造函数中。
(1)方式:创建一个函数,该函数的作用仅仅是封装创建对象的代码,然后再返回新创建的对象。
(2)例子:
function Person(name, age, job) { var o = new Ojbect(); o.name = name; o.age = age; o.job = job; o.sayName = function () { alert(this.name); }; return o; }
(3)优点:暂无。
(4)缺点:返回的对象与构造函数或构造函数的原型之间没有关系,为此不能依赖instanceof操作符来确定对象类型。
(5)总结:
1.构造函数在不返回值的情况下,默认会返回新对象实例。而通过在构造函数的末尾添加一个return语句,可以重写返回值。
2.这个模式的应用条件是:在特殊情况下用来为对象创建构造函数。尽量不要用这个模式。
(1)方式:稳妥构造函数遵循与寄生构造函数类似的模式,但区别是:新创建对象的实例方法不引用this,并且不使用new操作符调用构造函数。
(2)例子:
function Person(name, age, job) { var o = new Ojbect(); o.sayName = function () { alert(name); }; return o; }
(3)优点:暂无。
(4)缺点:返回的对象与构造函数或构造函数的原型之间没有关系,为此不能依赖instanceof操作符来确定对象类型。
(5)总结:
1.所谓稳妥对象,指的是没有公共属性,而且其方法也不引用this的对象。
2.稳妥对象最适合在一些安全的环境中(这些环境中会禁止使用this和new),或者在防止数据被其他应用程序(如Mashup)改动时使用。
在ECMAScript中无法实现接口继承,它只支持实现继承,而且其实现继承主要是依靠原型链来实现的。
(1)什么是原型链?
原型链的基本思想是:利用原型让一个引用类型继承另一个引用类型的属性和方法。
我们让原型对象A等于另一个类型的实例,此时的原型A将包含一个指向另一个原型B的指针,相应地,原型B中也包含着一个指向另一个构造函数的指针。假如原型B又是另一个原型C的实例,那么上述关系依然成立,如此层层推进,就构成了实例与原型的链条(A->B->C)。这就是原型链的概念。
(2)例子:
function SuperType(){ //父类 this.property=true; } //在父类原型中添加方法 SuperType.prototype.getSuperValue=function(){ return this.property; }; function SubType(){ //子类 this.subproperty=false; } //实现继承的代码: SubType.prototype=new SuperType(); //在子类的新原型中添加方法 SubType.prototype.getSubValue=function(){ return this.subproperty; };
(3)原型链的本质是什么?
利用原型链实现继承的本质是重写原型对象,代之以一个新类型的实例。
同时,通过实现原型链,本质上扩展了原型搜索机制。
在通过原型链实现继承后,搜索属性或方法的过程就得以沿着原型链继续向上。
(4)或许来一个图,会更加清晰:
(5)原型链的问题:
1.当存在包含引用类型值的原型时,某个实例对引用类型值的修改,会反映在其余所有实例上;
2.在创建子类型的实例时,没有办法在不影响所有对象实例的情况下,给超类型的构造函数传递参数。
(1)基本思想:在子类型构造函数的内部调用超类型构造函数。
(2)例子:
function SuperType() { //父类 this.colors = ["red", "blue", "green"]; } function SubType() { //继承SuperType SuperType.call(this); }
(3)优点:可在子类型构造函数中向超类型构造函数传递参数。
(4)缺点:仅使用此技术实现继承,函数无法复用。而且在超类型的原型中定义的方法,对子类型而言也是不可见的。
(5)总结:
仅仅借用构造函数实现继承,本质上是没有利用到原型链的。因此很少单独使用该模式。
(1)方式:将原型链和借用构造函数的技术组合在一块。
其基本思想是:使用原型链实现对原型属性和方法的继承,通过借用构造函数来实现对实例属性的继承。
(2)例子:
function SuperType(name) { //父类 this.name = name; this.colors = ["red", "blue", "green"]; } SuperType.prototype.sayName = function () { alert(this.name); }; function SubType(name, age) { //继承SuperType的实例属性 SuperType.call(this, name); //第二次调用超类型构造函数 this.age = age; } //继承方法 SubType.prototype = new SuperType(); //第一次调用超类型构造函数 SubType.prototype.sayAge = function () { alert(this.age); };
(3)优点:既通过在原型上定义方法实现了函数复用,又能够保证每个实例都有它自己的属性。
(4)缺点:无论在什么情况下,都会调用两次超类型构造函数,以至于重写超类型对象的实例属性,造成空间的浪费。(这个自己画图或看书理解)
(5)总结:最常用的继承模式。
(1)方式:通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。
(2)基本思路:不必为了指定子类型的原型而调用超类型的构造函数,我们所需要的无非就是超类型原型的一个副本而已。本质上,就是使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型。
(3)例子:
首先,寄生组合式继承的基本模式是:
function inheritPrototype(subType, superType) { var prototype = object(superType.prototype); //创建对象 prototype.constructor = subType; //增强对象 subType.prototype = prototype; //指定对象 } //原型式继承 function object(o) { function F() {} F.prototype = o; return new F(); }
示例中,object函数首先生成一个空白构造函数F,再将传入的对象o指定为F的原型对象,最后返回一个F的实例。
这样一来,返回的对象的原型就是o,而本身这个对象是空白的。
而inheritPrototype函数的作用是:
首先调用object函数,传入超类型的原型,从而得到一个F的空白实例,此时该实例的原型就是超类型的原型;
然后,将这个实例的构造函数指针指定为subType,因此这个实例是用作subType的原型的;
最后,将第一步所得的空白实例对象指定为subType的原型。
至此,subType的原型不再是superType的实例,而是一个空白的实例,但这个实例原型又是suerType。
下面给出示例2:
function SuperType(name) { //父类 this.name = name; this.colors = ["red", "blue", "green"]; } SuperType.prototype.sayName = function () { alert(this.name); }; function SubType(name, age) { //继承SuperType的实例属性 SuperType.call(this, name); //仅这一次调用超类型构造函数 this.age = age; } inheritPrototype(SubType, SuperType); SubType.prototype.sayAge = function () { alert(this.age); }; //继承方法 SubType.prototype = new SuperType(); //第一次调用超类型构造函数 SubType.prototype.sayAge = function () { alert(this.age); };
(4)优点:这个例子的高效率体现在它只调用一次SuperType的构造函数,并且因此避免了在SubType.prototype上创建不必要的、多余的属性。与此同时,原型链还能保持不变。
(1)方式:借助原型可以基于已有的对象创建新对象,同时还不必因此创建自定义类型。
(2)例子:
//原型式继承 function object(o) { function F() {} F.prototype = o; return new F(); }
(3)本质:object()对传入其中的对象执行了一次浅复制。
(4)应用条件:这种继承模式要求必须有一个对象可以作为另一个对象的基础。如果有这么一个对象的话,可以把它传递给object()函数,然后再根据具体需求对得到的对象加以修改即可。
(1)思路:与寄生构造函数和工厂模式类似,即创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再像是真的是它做了所有工作一样返回对象。
(2)例子:
function createAnother(original) { var clone = object(original); //通过调用函数创建一个新对象 clone.sayHi = function () { alert("hi"); }; //以某种方式来增强这个对象 return clone; }
这一篇总结写到我吐血...估计看的人也吐血了吧...已经尽可能简单化了,希望各位喜欢。
共勉。