总结:ECMAScript支持面向对象编程,但不使用类或者结构。对象可以在代码执行过程中创建和增强。
在没有类的情况下,可以采用下列模式创建对象。
【】工厂模式:使用简单的函数创建对象,为对象添加属性和方法,然后返回对象。
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; } var person1 = createPerson("Yoyo",24,"Software Engineer");
【】构造函数模式:可以创建自定义引用类型,可以像创建内置对象实例一样使用new操作符。
function Person(name,age,job){ this.name = name; this.age = age; this.job = job; this.sayName = function(){ alert(this.name); }; } var p1 = new Person("Yoyo",24,"Software Engineer");
【】原型模式:使用构造函数的prototype属性来指定那些应该共享的属性和方法。
function Person(){}; Person.prototype.name = "Yoyo"; Person.prototype.age = 24; Person.prototype.job = "Engineer"; Person.prototype.sayName = function(){ alert(this.name); } var p1 = new Person(); p1.sayName(); //Yoyo
【】组合使用构造函数和原型模式:使用该构造函数定义实例属性,使用原型定义共享的属性和方法。
function Person(name,age,job){ this.name=name; this.age = age; this.job = job; this.friends=["Shelby","Brown"]; }; Person.prototype = { constructor:Person, sayName:function(){ alert(this.name); } }; var p1 = new Person("Yoyo",24,"Software Engineer");
Javascript主要通过原型链实现继承。原型链的构建是通过将一个类型的实例赋值给另一个构造函数的原型实现的。
【】组合继承:使用原型链继承共享的属性和方法,通过借用构造函数继承实例属性。(使用最对的继承模式)
function SuperType(name){ this.name = name; this.colors = ["red","blue"]; } SuperType.prototype.sayName = function(){ alert(this.name); }; function SubType(name,age){ //继承属性 SuperType.call(this,name); //第二次调用SuperType() this.age = age } //继承方法 SubType.prototype = new SuperType(); //第一次调用SuperType() SubType.prototype.sayAge = function(){ alert(this.age); }; var instance1 = new SubType("Nicolas",24);
【】原型式继承:本质是执行对给定对象的浅复制
【】寄生式继承:基于某个对象或某些信息创建一个对象,然后增强对象,最后返回对象。
【】寄生组合式继承:将寄生式继承和组合继承的优点与一身,是实现基于类型继承的最有效方式。
1.对象的属性
ECMAScript中有两种属性:数据属性和访问器属性。
1)数据属性:有4个描述其行为的特性。
- [[writable]]:表示能否修改属性的值,默认是true。
var person = {}; alert(typeof person); //object Object.defineProperty(person,"name",{ writable:false, value:"Yoyo" }); alert(person.name); //Yoyo person.name = "Anne"; alert(person.name); //Yoyo
var person = {}; alert(typeof person); //object Object.defineProperty(person,"name",{ writable:true, value:"Yoyo" }); alert(person.name); //Yoyo person.name = "Anne"; alert(person.name); //Anne
- [[configurable]]:表示能否通过delete删除属性从而从新定义属性,能否修改属性的特性,或者能否把属性修改为访问器属性。默认值为true。
var person = {}; Object.defineProperty(person,"name",{ configurable:false, value:"Yoyo" }); alert(person.name); //Yoyo delete(person.name); alert(person.name); //Yoyo
var person = {}; Object.defineProperty(person,"name",{ configurable:true, value:"Yoyo" }); alert(person.name); //Yoyo delete(person.name); alert(person.name); //undefined
- [[Enumerable]]:能否通过for-in循环返回属性,默认是true
- [[Value]]:包含这个属性的数据值。读取属性值的时候,从这个位置读;写入属性值的时候,把新值保存在这个位置。默认值为undefined。
2)访问器属性
访问器属性不包含数据值。包含一对儿getter和setter函数。
访问器属性具有如下4个特性:
- [[configurable]]:表示能否删除属性从新定义,能否修改,能否把属性修改为数据属性。
- [[enumerable]]:表示能否通过for-in循环返回属性
- [[get]]:在读取属性时调用的函数。默认为undefined
- [[set]]:在写入属性时调用的函数。默认是undefined
访问属性不能直接定义,必须使用Object.defineProperty()来定义。
var person={ year:0, age:0 } Object.defineProperty(person,"year",{ get:function(){ return this.age; }, set:function(newValue){ this.age += newValue - 1990; } }); person.year=2005; alert(person.age);
Object.defineProperties():定义多个属性
var person={}; Object.defineProperties(person,{ _year:{ value:2004 }, age:{ value:0 }, year:{ get:function(){ return this._year; }, set:function(newValue){ if(newValue>1990){ this._year = newValue; this.age += newValue-1990; } } } });
Object.getOwnPropertyDescriptor()方法
取得给定属性的描述符。接收两个参数:属性所在的对象和要读取其描述符的属性名称。返回值是一个对象。
var person={}; Object.defineProperties(person,{ _year:{ value:2004 }, age:{ value:0 }, year:{ get:function(){ return this._year; }, set:function(newValue){ if(newValue>1990){ this._year = newValue; this.age += newValue-1990; } } } });
//对于数据属性_year,value等于最初的值。configurable是false,而get等于undefined。 var descriptor = Object.getOwnPropertyDescriptor(person,"_year"); alert(typeof descriptor); //object alert(descriptor.value); //2004 alert(descriptor.configurable);//false
//对于访问器属性year,value等于undefined,enumerable是False,而get是一个指向getter函数的指针。
var descriptor = Object.getOwnPropertyDescriptor(person,"year"); alert(descriptor.value); //undefined descriptor.set(2005); alert(descriptor.get()); //2005 alert(typeof descriptor.get); //function alert(descriptor.enumerable); //false
2、创建对象
1) 工厂模式
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; } var person1 = createPerson("Yoyo",24,"Software Engineer"); var person2 = createPerson("Greg",27,"Doctor"); person1.sayName(); //Yoyo alert(person1.name +" " +person1.age+" "+person1.job); alert(person2.name +" " +person2.age+" "+person2.job);
可以多次调用这个函数,每次都会返回一个包含三个属性一个方法的对象。工厂模式解决了创建多个相似对象的问题,但却没有解决对象识别的问题。
2)构造函数模式
function Person(name,age,job){ this.name = name; this.age = age; this.job = job; this.sayName = function(){ alert(this.name); }; } var p1 = new Person("Yoyo",24,"Software Engineer"); var p2 = new Person("Greg",27,"Docotor"); alert(p1.constructor == Person); //true alert(p1 instanceof Object); //true alert(p1 instanceof Person); //true; alert(typeof Person); //function
- p1 和 p2分别保存着Person的一个不同的实例。这两个对象都有一个constructor(构造函数)属性,该属性指向Person。
- 对象的constructor属性最初是用来标识对象类型的。检测对象类型,还是instanceof操作符更可靠一些。
- 在这个例子中创建的所有对象既是object的实例,同时也是Person的实例。
@1@ 将构造函数当做函数
构造函数与其他函数的唯一区别,就在于调用它们的方式不同。任何函数,只要通过new操作符来调用,那它就可以作为构造函数;而任何函数,如果不通过new操作符来调用个,那它跟普通函数也不会有什么两样。
//当作构造函数使用 var p1 = new Person("Yoyo",24,"Software Engineer"); p1.sayName(); //Yoyo //作为普通函数调用 Person("Grey",27,"Doctor"); window.sayName(); //Grey
alert(window.name); //Gery
alert(age); //27
//在另一个对象的作用域中调用 var o = new Object(); Person.call(o,"Kristen",25,"Nurse"); o.sayName(); //"Kristen"
- 作为普通函数调用:
当在全局作用域中调用一个函数时,this对象总是指向Global对象(在浏览器中就是window对象)
- 使用call()或者apply()
使用call()或者apply()在某个特殊对象的作用域中调用Person()函数。这里是在对象o的作用域中调用的,因此调用后就拥有了所有属性和sayName()方法。
@2@ 构造函数的问题
- 按照上述定义类的方式,不同实例上的同名函数是不相同的。
var p1 = new Person("Yoyo",24,"Software Engineer"); var p2 = new Person("Greg",27,"Docotor"); alert(p1.sayName == p2.sayName); //false
- 把函数定义(方法定义)转移到构造函数外部,解决【在执行代码前就把函数绑定到特定对象上面】的问题
function Person(name,age,job){ this.name = name; this.age = age; this.job = job; this.sayName =sayName, this.testFun =testFun; } function sayName(){ alert(this.name); } function testFun(){ alert(this.name); } var p1 = new Person("Yoyo",24,"Software Engineer"); var p2 = new Person("Greg",27,"Docotor"); alert(p1.sayName == p2.sayName); //true alert(p1.testFun == p2.testFun); //true
在构造函数内部,将sayName属性设置成等于全局的sayName函数。sayName包含的是一个指向函数的指针,因此p1和p2对象就共享了在全局作用域中定义的同一个sayName()函数。
可是这样带来一个新的问题:
(1)在全局作用域中定义的函数实际上只能被某个对象调用,折让全局作用域有点名不副实。
(2)如果对象需要定义很多方法,那么就要定义很多个全局函数,这样这个自定义的引用类型就丝毫没有封装性可言了。
要解决这个问题,可以使用原型模式。
3)原型模式
- prototype(原型)属性
这个属性是一个指针,指向一个对象。prototype就是通过调用构造函数而穿件的那个对象实例的原型对象。
使用原型对象的好处:可以让所有对象实例共享它所包含的属性和方法。
function Person(){}; Person.prototype.name = "Yoyo"; Person.prototype.age = 24; Person.prototype.job = "Engineer"; Person.prototype.sayName = function(){ alert(this.name); } var p1 = new Person(); p1.sayName(); //Yoyo var p2 = new Person(); p2.sayName(); //Yoyo alert(p1.sayName == p2.sayName); //true
图6-1展示了Person构造函数、Person的原型属性以及Person现有的两个实例之间的关系。
- 如果[[Prototype]]指向调用isPrototypeOf()方法的对象Person.prototype,那么这个方法就返回true。
alert(Person.prototype.isPrototypeOf(p1));//true;说明p1内部有一个指向Person.prototype的指针
- Object.getPrototypeOf()返回[[Prototype]]的值
alert(Object.getPrototypeOf(p1).name); //Yoyo
- 当代码读取某个对象的属性时,都会执行一次搜索,目标是具有给定名字的属性。
例如:调用p1.sayName()时,(1)会先搜索对象实例本身p1。【如果在实例中找到了具有给定名字的属性,则返回该属性的值;】
(2)没有找到,则继续搜索指针指向的原型对象Person.Prototype,在原型对象中查找具有给定名字的属性。【如果在原型对象中找到了这个属性,则返回该属性的值。】
这就是多对象实例共享原型所保存的属性和方法的原理。
- 添加属性和delete操作符
添加属性:为对象实例添加一个属性时,这个属性就会屏蔽原型对象中的同名属性,即添加的这个属性只会阻止我们访问原型中的那个属性,但不会修改这个属性。
delet操作符:可以完全删除实例属性,从而能够重新访问原型中的属性。
var p1 = new Person(); var p2 = new Person(); p1.name = "Grey"; alert(p1.name); //Grey --来自实例 alert(p2.name); //Yoyo --来自原型 delete p1.name; alert(p1.name); //Yoyo delete p2.name; alert(p2.name);
- hasOwnProperty()方法
该方法检测一个属性是存在于实例中,还是存在于原型中。只有在给定属性存在于对象实例中时,才会返回true。
var p1 = new Person(); var p2 = new Person(); alert(p1.hasOwnProperty("name")); //false p1.name = "Grey"; alert(p1.name); //Grey -- 来自实例 alert(p1.hasOwnProperty("name")); //true alert(p2.name); //Yoyo -- 来自原型 alert(p2.hasOwnProperty("name")); //false delete p1.name; alert(p1.name); //Yoyo -- 来自原型 alert(p1.hasOwnProperty("name")); //false
图6-2 反应了什么时候是实例属性,什么时候是原型属性。
- 原型与in操作符
in操作符会在通过对象能够访问给定属性时返回true,无论该属性存在于实例还是原型中。
function Person(){}; Person.prototype.name = "Yoyo"; Person.prototype.age = 24; Person.prototype.job = "Engineer"; Person.prototype.sayName = function(){ alert(this.name); } var p1 = new Person(); var p2 = new Person(); alert(p1.hasOwnProperty("name")); //false alert("name" in p1); //true -- 在原型对象中 p1.name = "Grey"; alert(p1.name); //Grey alert(p1.hasOwnProperty("name")); //true --来自对象实例 alert("name" in p1); //true delete p1.name; alert(p1.name); //Yoyo -- 来自原型 alert(p1.hasOwnProperty("name")); //false alert("name" in p1); //true
- 同时使用hasOwnProperty()方法和in操作符,就可以确定该属性到底是存在于对象中还是存在于原型中。
function hasPrototypeProperty(object,name){ return !object.hasOwnProperty(name)&&(name in object); } p1.name = "Grey"; alert(hasPrototypeProperty(p1,"name")); //false --name 来自实例 alert(hasPrototypeProperty(p1,"age")); //true --age来自原型
in操作符:只要通过对象能够访问到属性就返回true。
hasOwnProperty()只在属性存在于实例中时才返回true。
只要in操作符返回true而hasOwnProperty()返回false,就可以确定属性是原型中的属性。
- Object.keys()方法,取得对象上所有可枚举的实例属性。
这个方法接收一个对象作为参数,返回一个包含所有可枚举属性的字符串数组。
function Person(){}; Person.prototype.name = "Yoyo"; Person.prototype.age = 24; Person.prototype.job = "Engineer"; Person.prototype.sayName = function(){ alert(this.name); } var keys = Object.keys(Person.prototype); alert(keys); //name,age,job,sayName -- 原生的constructor属性是不可枚举的。 var p1 = new Person(); keys = Object.keys(p1); alert(keys); //null p1.name = "Rob"; p1.age = 20; var p1keys = Object.keys(p1); alert(p1keys); //name,age
- 更简单的原型语法
function Person(){}; Person.prototype.name = "Yoyo"; Person.prototype.age = 24; Person.prototype.job = "Engineer"; Person.prototype.sayName = function(){ alert(this.name); } var p1 = new Person(); alert(p1 instanceof Person); //true alert(p1 instanceof Object); //true alert(p1.constructor == Person); //true --constructor属性指向Person alert(p1.constructor == Object); //false //简化后的语法(减少了Person.prototype的输入): function Person(){}; Person.prototype = { name:"Yoyo", age:24, job:"Software Engineer", sayName:function(){ alert(this.name); } }; var f = new Person(); alert(f instanceof Object); //true alert(f instanceof Person); //true alert(f.constructor == Person); //false -- constructor属性不在指向Person,而是指向Object构造函数 alert(f.constructor == Object); //true
如果constructor的值真的很重要,使用下面格式特意将它设置回适当的值。
function Person(){}; Person.prototype = { constructor:Person, name:"Yoyo", age:24, job:"Software Engineer", sayName:function(){ alert(this.name); } }; var f = new Person(); alert(f.constructor == Person); //true -- constructor属性指向Person alert(f.constructor == Object); //false
但以这种凡事重设constructor属性会导致它的[[Enumerable]]特性被设置为true,默认情况下,原生的constructor属性是不可枚举的。
var keys = Object.keys(Person.prototype); alert(keys); //constructor,name,age,job,sayName
使用Object.defineProperty()设置constructor的值 和 [[enumerable]]属性
function Person(){}; Person.prototype = { name:"Yoyo", age:24, job:"Software Engineer", sayName:function(){ alert(this.name); } }; Object.defineProperty(Person.prototype,"constructor",{ enumerable:false, value:Person }); keys = Object.keys(Person.prototype); alert(keys); //name,age,job,sayName
- 原型的动态性
情况一:先创建实例后修改原型。
function Person(){}; Person.prototype = { name:"Yoyo", age:24, job:"Software Engineer", sayName:function(){ alert(this.name); } }; var friend = new Person(); Person.prototype.sayHi = function(){ alert("hi"); } friend.sayHi(); //hi
对原型对象所做的任何修改都能够立即从实例上反映出来。即使Person实例是在添加新方法之前创建的,但它仍然可以访问这个新对象。搜索过程:当调用person.sayHi()时,首先会在实例中搜索名为sayHi的属性,若没有找到,会继续搜索原型。
情况二:先创建实例,后重写整个原型对象
function Person(){}; var p1 = new Person(); Person.prototype = { name:"Yoyo", age:24, job:"Software Engineer", sayName:function(){ alert(this.name); } }; Person.prototype.sayHi = function(){ alert("hi"); } p1.sayHi();//TypeError: p1.sayHi is not a function p1.sayName(); //TypeError: p1.sayName is not a function
调用该构造函数时,会为实例添加一个指向最初原型的[[Prototype]]指针,而把原型修改为另外一个对象就等于切断了构造函数与最初原型之间的联系。记住:实例中的指针仅指向原型,而不指向构造函数。
- 原生对象的原型
所有原生引用类型(Object 、Array、String等等)都在其构造函数的原型上定义了方法。
例如Array.prototype.filter()等等
- 原型对象存在的问题
function Person(){}; var p1 = new Person(); Person.prototype = { name:"Yoyo", age:24, job:"Software Engineer", friends:["Shelby","Brown"], sayName:function(){ alert(this.name); } }; var p1 = new Person(); var p2 = new Person(); p1.friends.push("Vans"); alert(p1.friends); //Shelby,Brown,Vans alert(p2.friends); //Shelby,Brown,Vans alert(p1.friends == p2.friends); //true
由于friends数组存在于Person.prototype而非p1中,所以 p1.friends.push("Vans")修改数组,也会反映到p2.friends上。
4)组合使用构造函数和原型模式
构造函数模式用于定义实例属性,原型模式用于定于方法和共享的属性。这样:
(1)每个实例都会有自己的一份实例属性的副本,通同时又共享着对方法的引用个,最大限度地节省了内存。
(2)这种混合模式还支持向构造函数传递参数
function Person(name,age,job){ this.name=name; this.age = age; this.job = job; this.friends=["Shelby","Brown"]; }; Person.prototype = { constructor:Person, sayName:function(){ alert(this.name); } }; var p1 = new Person("Yoyo",24,"Software Engineer"); var p2 = new Person("Grey",23,"Nurse"); p1.friends.push("Vans"); alert(p1.friends); //Shelby,Brown,Vans alert(p2.friends); //Shelby,Brown alert(Object.keys(p1)); //name,age,job,friends alert(Object.keys(p2));//name,age,job,friends alert(Object.keys(Person.prototype)); //constructor,sayName
在这个例子中,实例属性都是在构造函数中定义的。所有实例共享的属性constructor和方法sayName()则是在原型中定义的。
5)动态原型模式
可以通过检查某个应该存在的方法是否有效,来决定是否需要初始化原型。
function Person(name,age,job){ //属性 this.name=name; this.age = age; this.job = job; //方法 if(typeof this.sayName != "function"){ Person.prototype.sayName = function(){ alert(this.name); }; } }; var p1 = new Person("Yoyo",24,"Software Engineer"); p1.sayName(); alert(Object.keys(p1)); //name,age,job alert(Object.keys(Person.prototype)); //sayName alert(p1 instanceof Person); //true --采用这种模式创建的对象,可以使用instanceof操作符确定它的类型
3.继承
1)原型链
function SuperType(){ this.property = "super"; } SuperType.prototype.getSuperValue = function(){ return this.property; } function SubType(){ this.subproperty = "sub"; } SubType.prototype = new SuperType(); SubType.prototype.getSubValue = function(){ return this.subproperty; } var instance = new SubType(); alert(instance.getSuperValue()); //super alert(instance.getSubValue()); //sub
alert(Object.keys(SubType.prototype)); //property,getSubValue alert(Object.keys(SuperType.prototype)); //getSubperValue alert(Object.keys(instance)); //subproperty
图6-5:SubType继承了SuperType,而SuperType继承了Object。
注意:
alert(instance.constructor == SubType); //false alert(instance.constructor == SuperType); //true
因为SubTyper的原型指向了另一个对象--SuperType的原型,而这个原型对象的constructor属性指向的是SuperType。因此,instance.constructor现在指向的是SuperType。
调用instance.getSuperValue()的搜索过程:
(1)搜索实例。(2)搜索SubType.prototype.(3)搜索SuperType.prototype。
- 确定原型和实例的关系
》 使用instanceof操作符
var instance = new SubType();
alert(instance instanceof Object); //true alert(instance instanceof SuperType); //true alert(instance instanceof SubType); //true
var instance1 = new SuperType(); alert(instance1 instanceof Object); //true alert(instance1 instanceof SuperType); //true alert(instance1 instanceof SubType); //false
由于原型链的关系,可以说instance是Object、SuperType、SubType中任何一个类型的实例。
instance1 是Object、SuperType的实例。
》isPrototypeof()方法
var instance = new SubType();
alert(Object.prototype.isPrototypeOf(instance)); //true alert(SuperType.prototype.isPrototypeOf(instance)); //true alert(SubType.prototype.isPrototypeOf(instance)); //true
var instance1 = new SuperType(); alert(Object.prototype.isPrototypeOf(instance1)); //true alert(SuperType.prototype.isPrototypeOf(instance1)); //true alert(SubType.prototype.isPrototypeOf(instance1)); //false
同样,只要在原型链中出现过的原型,都可以说是该原型连所派生实例的原型。
- 重写超类型中的方法
function SuperType(){ this.property = "super"; } SuperType.prototype.getSuperValue = function(){ return this.property; } function SubType(){ this.subproperty = "sub"; } //继承了SuperType SubType.prototype = new SuperType(); //添加新方法 SubType.prototype.getSubValue = function(){ return this.subproperty; }; //重写超类型中的方法 SubType.prototype.getSuperValue = function(){ return "NewValue"; } var instance = new SubType(); alert(instance.getSuperValue()); //NewValue var instance1 = new SuperType(); alert(instance1.getSuperValue()); //super
注意:在通过原型链实现继承时,不能使用对象字面量创建原型方法。因为这样就会重写原型链
function SuperType(){ this.property = "super"; } SuperType.prototype.getSuperValue = function(){ return this.property; } function SubType(){ this.subproperty = "sub"; } //继承了SuperType SubType.prototype = new SuperType(); //使用字面量添加新方法,会导致上一行代码无效 SubType.prototype={ getSubValue : function(){ return this.subproperty; }, someOtherMethod : function(){ return "someOtherMethod"; } }; var instance = new SubType(); alert(instance.getSubValue()); //sub //当调用getSuperValue()时出错 alert(instance.getSuperValue()); //TypeError: instance.getSuperValue is not a function
- 原型链的问题
问题一:对引用类型的共享问题
function SuperType(){ this.colors = ["red","blue","green"]; } function SubType(){}; SubType.prototype = new SuperType(); var instance1 = new SubType(); instance1.colors.push("black"); alert(instance1.colors); //red,blue,green,black var instance2 = new SuperType(); alert(instance2.colors); //red,blue,green var instance3 = new SubType(); alert(instance3.colors); //red,blue,green,black
发现instance1和instance3共享了colors属性。
当SubType通过原型链继承了SuperType之后,SubType.Prototype就变成了SuperType的一个实例,因此它拥有了一个它自己的colors书信个,就跟专门创建了一个SubType.prototype.colors属性一样。但结果就是SubType的所有实例都会共享这个colors属性。
2)借用构造函数
借用构造函数的技术,可以结果引用类型值所带来的问题。在子类构造函数的内部调用超类型构造函数。
函数只不过是在特定环境中执行代码的对象,因此通过使用apply()和call()方法也可以在新创建的对象上执行构造函数。
function SuperType(){ this.colors = ["red","blue","green"]; } function SubType(){ SuperType.call(this); } var instance1 = new SubType(); instance1.colors.push("black"); alert(instance1.colors); //red,blue,green,black var instance2 = new SuperType(); alert(instance2.colors); //red,blue,green var instance3 = new SubType(); alert(instance3.colors); //red,blue,green
借用构造函数的技术很少单独使用
3) 组合继承
将原型链和借用构造函数的技术组合到一块,使用原型链实现对原型属性和方法的继承,通过借用构造函数来实现对实例属性的继承。这样既通过在原型上定义方法实现了函数服用,又能够保证每个实例都有它自己的属性。
function SuperType(name){ this.name = name; this.colors = ["red","blue"]; } SuperType.prototype.sayName = function(){ alert(this.name); }; function SubType(name,age){ //继承属性 SuperType.call(this,name); //第二次调用SuperType() this.age = age } //继承方法 SubType.prototype = new SuperType(); //第一次调用SuperType() SubType.prototype.sayAge = function(){ alert(this.age); }; var instance1 = new SubType("Nicolas",24); instance1.colors.push("yellow"); alert(instance1.colors); //red,blue,yellow instance1.sayName(); //Nicolas instance1.sayAge(); //24 var instance2 = new SubType("Yoyo",25); alert(instance2.colors); //red,blue instance2.sayName(); //Yoyo instance2.sayAge(); //25
var instance3 = new SuperType("Grey"); alert(Object.keys(instance3)); //name,colors alert(Object.keys(SuperType.prototype)); //sayName alert(Object.keys(SubType.prototype)); //name,colors,sayAge alert(Object.keys(instance1)); //name,colors,age
组合继承是Javascript最常用的继承模式,但组合继承最大的问题是:无论在什么情况下,都会调用两次超类构造函数:一次是在创建子类型原型的时候,另一次是在子类型构造函数内部。
在第一次调用SuperType构造函数时,SubType.prototype会得到两个属性:name和colors。它们都是SuperType的实例属性,只不过现在位于SubType的原型中。
第二次调用SuperType构造函数,发生在当调用SubType构造函数时,这一次又在新对象上创建了实例属性name和colors。于是,这两个属性就屏蔽了原型中的两个同名属性。图6-6解释了该过程。
有两组name和colors属性:一组在实例上,一组在SubType原型中。这就是调用两次SuperType构造函数的结果。解决这个问题的办法---寄生组合继承
4) 原型式继承
Object.create()方法:方法接收两个参数:一个用作心对象原型的对象和(可选的)一个为新对象定义额外属性的对象。
@1@ Object.create()方法只接收一个参数时
var person = { name:"Nicholas", friends:["Shelby","Court","Vans"] }; var anotherPerson = Object.create(person); anotherPerson.name = "Grey"; anotherPerson.friends.push("Rob"); var yetAnotherPerson = Object.create(person); yetAnotherPerson.name = "Linda"; yetAnotherPerson.friends.push("Brabie"); alert(person.friends); //Shelby,Court,Vans,Rob,Brabie alert(anotherPerson.friends); //Shelby,Court,Vans,Rob,Brabie
@2@ Object.create()方法的第二个参数
与Object.defineProperties()方法的第二个参数格式相同:每个书信个都是通过自己的描述符定义的。以这种方式制定的任何属性都会覆盖原型对象上的同名属性。
var person = { name:"Nicholas", friends:["Shelby","Court","Vans"] }; var anotherPerson = Object.create(person,{ name:{ value:"Grey" }, friends:{ value:"Rob" } }); alert(person.friends); //Shelby,Court,Vans alert(anotherPerson.friends); //Rob
5) 寄生式继承
思路:与寄生构造函数和工厂模式类似,即创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再像真地是它做了所有工作一样返回对象。
function object(o){ function F(){} F.prototype = o; return new F(); }
在object()函数内部,先创建了一个临时性的构造函数,然后将传入的对象作为这个构造函数的原型,最后返回了这个临时类型的一个新实例。
从本质上讲,object()对传入其中的对象执行了一次浅复制。
function createAnother(original){ var clone = object(original); clone.sayHi = function(){ alert("Hi"); }; return clone; }
createAnother()函数接收了一个参数,也就是将要作为新对象基础的对象。然后把这个对象original传递给object()函数,将返回的结果赋值给clone。再为clone对象添加一个新方法sayHi(),最后返回clone对象。
var person = { name:"Nicloas", friends:["Shelby","Court","Van"] } alert(person.friends);//Shelby,Court,Van
person.sayHi();//TypeError: person.sayHi is not a function
var anotherPerson = createAnother(person); anotherPerson.friends.push("Yoyo"); alert(anotherPerson.friends);//Shelby,Court,Van,Yoyo anotherPerson.sayHi();//Hi
这个例子,基于person返回了一个新对象--anotherPerson。新对象不仅具有person的所有属性和方法,而且还有自己的sayHi()方法。
6) 寄生组合式继承
寄生组合式继承:通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。本质上,使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型。
function object(o){ function F(){} F.prototype = o; return new F(); } //inheritPrototype()函数实现了寄生组合式继承的最简单形式。 function inheritPrototype(subType,superType){ var prototype = object(superType.prototype); //创建对象 prototype.constructor = subType; //增强对象 subType.prototype = prototype; //指定对象 }
inheritPrototype()函数实现了寄生组合继承的最简单形式。接收两个参数:子类型构造函数进而超类型构造函数。
在函数内部,第一步创建超类型原型的一个副本。第二步为创建的副本添加constructor属性,从而弥补因重写原型而失去的默认的constructor属性。最后一步,将新创建的对象(即副本)赋值给子类型的原型。
function SuperType(name){ this.name = name; this.colors = ["red","blue","green"]; } SuperType.prototype.sayName = function(){ alert(this.name); }; function SubType(colors,age){ SuperType.call(this,colors); this.age = age; } inheritPrototype(SubType,SuperType); SubType.prototype.sayAge = function(){ alert(this.age); }; var s1 = new SubType(); s1.colors.push("yellow"); alert(s1.colors); //red,blue,green,yellow var s2 = new SubType(); alert(s2.colors);//red,blue,green
这个例子的高效体现在它只调用一次SuperType构造函数,并且因此避免了在SubType.prototype上创建不必要的、多余的属性。