总结的文章略长,甚点。
知识点预热
Object类型
在ECMAScript中(就像在Java中的 java.lang.Object 对象一样), Object 类型是所有它的实例的基础, Object 类型所具有的任何属性和方法也同样可被更具体的对象所用。
JavaScript主要是通过原型链实现了面向对象中的实现继承(区分接口继承和实现继承),所以每当构造一个实例对象时便继承了 Object.prototype 上的方法,但这种原型链式的继承并不是复制方法的副本,而是引用(指向)式的继承。
person.hasOwnProperty(‘constructor‘);// false Object.prototype.hasOwnProperty(‘constructor‘);// true
//Number,String,Boolean类型返回新的字符串,其实是在包装类的实例上调用toString或toLocaleString var number=10; number.toString();// "10" var str=‘xx‘; var strnew=str.toString();// "xx" str==strnew;// true 两个string基本类型的字符串比较内容而已所以为true str+=‘add‘;// "xxadd" 修改str指向的内容,进一步确定toString()返回的是副本并不是str的引用 strnew;// "xx" var bol=true; bol.toString();// "true" //引用类型构造函数及其实例对象调用toString/toLocaleString返回 Object.toString();// "function Object() { [native code] }" var person=new Object(); person.toString();// "[object Object]" Array.toString();// "function Array() { [native code] }" var a=new Array(); a.toString();// "" 即调用数组每一项的toString()方法,然后拼接成字符串 Functiom.toString();// "function Function() { [native code] }" new Function(‘console.log(1)‘).toString();//"function anonymous() {console.log(1)}" Boolean.toString();// "function Boolean() { [native code] }" new Boolean(true).toString();// "true" String.toString();// "function String() { [native code] }" new String(‘xx‘).toString();// "xx" Number.toString();// "function Number() { [native code] }" new Number(10).toString();// "10"
//基本类型Number,String,Boolean var num=1; var numnew=num.valueOf(); num+=2;// 3 numnew;// 1 var str=‘xx‘; str.valueOf(); //"xx" var bol=true; bol.valueOf();// true; //包装类的实例 new Number().valueOf();// 0 new String().valueOf();// "" new Boolean().valueOf();// false //其他引用类型返回自身的引用 Object.valueOf()==Object;// true var o=new Object(); o.valueOf()==o;// true
创建Object实例:不论用哪种方式效果是一样的
var o=new Object();
o.name="xx";
o.say=function(){
console.log(‘hi‘)
}
//如果不传参,可以省略圆括号,但不推荐
var o=new Object;
var o={
name:‘xx‘,
say:function(){
console.log(‘hi‘);
}
}
ECMAScript中表达式上下文的定义:该上下文期待的一个值(表达式)。在这个例子中,左边的花括号 ({) 表示对象字面量的开始 ,因为它出现在表达式上下文中。赋值操作符表示后面是一个值,所以左花括号在这里表示一个表达式的开始。
ECMAScript中语句上下文的定义:同样的花括号,例如跟在if条件语句后面,则表示一个语句块的开始。
在通过对象字面量定义的对象时,实际上不会调用 Object 构造函数。此话出自JavaScript高级程序设计第三版,不明白不调用 Object 构造函数那是通过什么幕后形式创建的对象,js引擎?
属性名可以使用字符串定义,也可以像本代码一样简写,JavasScript会自动转化为字符串。
for(var i in o){
console.log(typeof i);//string
}
对象字面量也是向函数传递大量可选的参数的首选方式。
function displayInfo(args){
var output="";
for(var i in args){
if(args.hasOwnProperty(i)){
if(typeof args[i]==‘function‘){
output+=args[i]();
}
else{
output+=args[i]+‘ ‘;
}
}
}
console.log(output);
};
displayInfo({
name:‘xx‘,
say:function(){
return ‘hi‘;
}
});
方括号语法的主要优点是通过可以通过变量来访问属性,如果属性名中包含会导致语法错误的字符,或者属性名使用的是关键字或保留字,也可以使用方括号表示法访问。将要访问的属性以字符串形式放在方括号中。
ECMA-262把对象定义为
无序属性的集合,其属性可以包含基本值,对象,函数。相当于说对象是一组没有特定顺序的值。对象的每个属性或方法都有一个名字,而每个名字都映射到一个值,可以把ECMAScript中的对象想象成散列表,无非就是一组名值对,其中的值可以是数据或函数。每个对象都是基于一个引用类型创建的,这个引用类型可以是原生类型也可以是自定义类型。
创建对象:虽然 Object 构造函数或对象字面量都可以用来创建单个对象,但这些方法明显有缺点:使用同一个接口创建很多对象,会产生大量重复的代码。所以产生如下七种模式。
1.工厂模式:抽象了创建具体对象的过程,考虑到在ECMAScript中无法创建类,所以就用函数封装代码实现特定功能。实质就是用函数封装以特定接口来创建对象的细节(细节是指构造对象实例的方式)。实际想想在软件工程领域中我们经常这样做,将一个功能封装起来只给外界提供接口。
function createPerson(name,age,job){
var o=new Object();
o.name=name;
o.age=age;
o.job=job;
o.say=function(){
console.log(this.name);
};
return o;
}
var person1=createPerson(‘xx‘,20,‘student‘);
var person2=createPerson(‘mm‘,22,‘student‘);
person1==person2;// false
有点感觉了,像java/C++中类里面的构造函数有没有,可以返回同功能的不同的多个实例。
缺点:没有解决对象识别问题(即怎样知道一个对象的类型),虽然你可以用代码试探实例是那种类型(比如可以用 person1.__proto__ 的方法),但却没法直观地知道 person1 和 person2 到底是哪种类型的实例。
2.构造函数模式:ECMAScript中的原生构造函数可以用来创建特定类型的对象,此外也可以创建自定义构造函数,从而定义自定义对象类型的属性和方法。
function Person(name,age,job){ this.name=name; this.age=age; this.job=job; this.say=function(){ console.log(this.name); }; } var p1=new Person(‘xx‘,20,‘student‘); var p2=new Person(‘mm‘,22,‘student‘); p1==p2;// false p1.constructor==Person;//true
p1 和 p2 这两个对象都有一个 constructor (构造器)属性(javascript高程上这么说其实并不准确, constructor 其实并不是 p1 和 p2 自身有的属性而是通过原型链继承来的属性), constructor 指向 Person 。
这里所创建的 p1 和 p2 既是 Object 的实例,又是 Person 的实例,原因是原型链上继承关系,后面有说。 p1 instanceof Object;// true
p1 instanceof Person;// true
创建自定义的构造函数意味着将来可以将它的实例标识为一种特定的类型,明确的知道实例的类型这正是构造函数模式胜于工厂模式的地方。
在另一个对象的作用域中调用:在某个特殊对象的作用域中调用 Person 函数。
var o=new Object(); Person.call(o,‘xx‘,20,‘student‘); o.say();// xx
缺点: say 这个方法要在每个实例上都创建一遍, p1.say==p2.say;// false p1和p2上的say方法虽然内容一样但却是完全不同的两个 Function 类型实例, this.say=function(){ console.log(this.name);}; 相当于 this.say=new Function("console.log(this.name)"); 以这种方式创建函数,会导致不同的作用域链和标识符解析(变量,函数,属性,参数的名字),但创建 Function 新实例的机制仍然是相同的。
解决方案:鉴于 say 函数对于 p1 和 p2 完成的功能一样,那么可以不用在执行代码前就把该函数绑定到特定对象上面,通过把完成特定功能的函数单独拿出来,而让实例对象的 "say" 属性指内存堆里同一个函数来解决问题。这样 p1 和 p2 就共享了在全局作用域中定义的同一个函数。
function Person(name,age,job){ this.name=name; this.age=age; this.job=job; this.say=say; }; function say(){ console.log(this.name); } var p1=new Person(‘xx‘,20,‘student‘); var p2=new Person(‘mm‘,20,‘student‘);
缺点:
3.原型模式:JavaScript中每个函数都有一个 prototype (原型)属性,这个属性是一个指针,指向一个对象。 prototype 是这个属性的名字,这个指针的内容就是该函数原型属性对象在堆内存中的地址。这个原型对象的作用就是包含可以由特定类型的所有实例共享的属性和方法。也可以这么理解, prototype 就是通过调用构造函数而创建的那个对象实例的原型对象。使用原型好处就是可以让所有对象实例共享它所包含的属性和方法。即不必在构造函数中定义对象实例的信息,而是可以将这些信息添加到原型对象中。
function Person(){} Person.prototype.name=‘xx‘; Person.prototype.age=29; Person.prototype.job="student"; Person.prototype.say=function(){ console.log(this.name); }; var p1=new Person(); p1.name;// ‘xx‘ var p2=new Person(); p1==p2;// false p1.say==p2.say;// true
//字面量创建对象属性 var p={ name:‘xx‘, say:function(){} } Object.getOwnPropertyDescriptor(p,‘name‘);// Object {value: "xx", writable: true, enumerable: true, configurable: true} //直接在对象上添加属性 var o=new Object(); o.name=‘xx‘;// "xx" Object.getOwnPropertyDescriptor(o,‘name‘);// Object {value: "xx", writable: true, enumerable: true, configurable: true} //Object.definedProperty定义属性 var p={}; Object.defineProperty(p,‘name‘,{ value:‘xx‘ });// Object {name: "xx"} 一旦定义某一属性后就不能通过这种方式再次定义该属性,因为此时writable默认为false,除非当时显式声明为true Object.getOwnPropertyDescriptor(p,name);// Object {value: "xx", writable: false, enumerable: false, configurable: false}
var o={ toString:function(){ console.log(‘我是实例上的toString‘); } }; for(var i in o){ console.log(i); };// toString
注:上面的覆写 toString 代码会在IE早期版本中出现bug,不会打印出 toString ,因为IE认为原型的 toString 方法被打上了值为 false 的 [[Enumerable]] 标记,因此会跳过该属性。
解决方案:
1.改变原型上方法的可枚举性
Object.defineProperty(Object.prototype,"valueOf",{enumerable:true }); Object.defineProperty(o,"valueOf",{enumerable:true }); Object.getOwnPropertyDescriptor(Object.prototype,‘valueOf‘);// Object {writable: true, enumerable: true, configurable: true} for(var i in o){ console.log(o[i]); }
2.如果你想得到所有实例属性而不论它们是否都可枚举,使用 Object.getOwnPropertyNames() ,返回类型为 "[object Object]" 类数组:
Object.getOwnPropertyNames(Person.prototype);// ["constructor", "name", "age", "job", "say"] Object.getOwnPropertyNames(Object.prototype);/* ["constructor", "toString", "toLocaleString", "valueOf", "hasOwnProperty", "isPrototypeOf", "propertyIsEnumerable", "__defineGetter__", "__lookupGetter__", "__defineSetter__", "__lookupSetter__", "__proto__"] */
ECMAScript5中 Object.keys() 方法可以取得对象上所有可枚举的实例属性,这样就不用 for-in 和 hasOwnProperty 筛选判断了:参数为对象,返回一个包含所有可枚举属性的字符串数组
Object.keys(Person.prototype);// ["name", "age", "job", "say"] p1.sex=‘女‘; Object.keys(p1);// ["sex"]
更简单的原型语法:为了更好的封装原型的功能,将原型上定义的属性和方法用对象字面量重写整个原型
Person.prototype={ name:‘xx‘, age:22, say:function(){console.log(this.name)} }
但这样做法会使原型上的 constructor 属性不见了, Object.getOwnPropertyNames(Person.prototype);// ["name", "age", "say"] ,为什么呢?因为这种方式是在重定义原型属性的内容(即让 prototype 指向了别处对象),而之前的方式是在默认的原型属性上添加新属性而已。我们知道,每创建一个函数,就会同时创建它的 prototype 对象,这个对象也就会自动获得 constructor 属性,但现在我们让 prototype 指向了别处新对象,原来带 constructor 的那个对象在内存中就没人引用了,如果也没有实例对象的引用情况下就等待垃圾处理机制的回收。有意思的是虽说 Person.prototype 没有 construcor 属性了,但是再次访问 Person.prototype.constructor;// Object() { [native code] } 什么鬼?不是说没有这个属性不能访问到了吗?原来现在的 constructor 其实是在自己对象上搜索不到的属性,便顺着原型链继承自 Object.prototype.constructor 来的。此时已经不能用 constructor 来判断实例对象的类型了。
Person.prototype.hasOwnProperty(‘constructor‘);// false Object.prototype.hasOwnProperty(‘constructor‘);// true
如果需要 constructor ,可以特意将 constructor 设回适当的值。
Person.prototype={ name:‘xx‘, age:22, say:function(){console.log(this.name);}, constructor:Person }
但是这样会将它的 [[Enumerable]] 特性被设置为 true 。当然再加一步,通过 Object.defineProperty() 将特性重新赋为 false 。
function Person(){} Person.prototype={ constructor:Person, name:‘xx‘, hobby:[‘a‘,‘b‘] }; var p1=new Person(); var p2=new Person(); p1.hobby.push(‘c‘); p1.hobby;// [‘a‘,‘b‘,‘c‘] p2.hobby;// [‘a‘,‘b‘,‘c‘] p1.hobby===p2.hobby
4.组合使用构造函数模式和原型模式:构造函数模式用于定义实例上的属性,原型模式定义方法和共享的属性。这样每个实例都会有自己的一份实例属性的副本,但同时又共享着方法的引用,节省了内存。
function Person(name,age){ this.name=name; this.age=age; this.hobby=[‘a‘,‘b‘]; } Person.prototype={ constructor:Person, say:function(){ console.log(this.name); } } var p1=new Person(‘xx‘,20); var p2=new Person(‘mm‘,20); p1.hobby.push(‘c‘); p2.hobby;// [‘a‘,‘b‘];
5.动态原型模式(在构造函数中初始化原型):动态原型是把所有信息都封装在了构造函数中,这样构造函数和原型就不独立开了,符合了OO语言中功能在一块的习惯。通过在构造函数中初始化原型(仅在必要的情况下)又保持了同时使用构造函数和原型的优点。换句话说可以通过检查某个应该存在的方法是否有效来决定是否需要初始化原型。
function Person(name,age){ this.name=name; this.age=age; if(typeof this.say!=‘function‘){ //或用instanceof判断 Person.prototype.say=function(){ console.log(this.name); } } } var f=new Person("xx",20); f.say();// ‘xx‘
这段代码只会初次调用构造函数时才会执行,此后原型已经完成初始化,需要再做什么改变了。 if 语句检查的可以是初始化之后应该存在的任何属性或方法。不必用一大堆 if 语句检查每个属性和每个方法,只要检查其中一个就好。
缺点:不能用对象字面量的形式重写原型,因为实例先于修改原型创建,执行 new 时,调用构造函数,为实例添加一个指向默认原型的 [[prototype]] ,然后再初始化,所以是在修改原型之前。
6.寄生构造函数模式:工厂模式和构造函数模式的结合。用一个函数封装创建创建对象的代码然后返回新创建的对象。除了使用 new 操作符并把使用的包装函数叫构造函数外,这个模式和工厂模式其实一样。这里涉及到JavaScript中构造函数的返回值
function Person(name,age){ var o=new Object(); o.name=name; o.age=age; o.say=function(){ console.log(this.name); } return o; } var p=new Person(‘xx‘,20); p.say();
应用:假设想创建一个具有额外方法的特殊数组。由于不能直接修改Array构造函数,因此可以使用这个模式
function SpecialArray(){ var values=new Array(); values.push.apply(values,arguments);//初始化数组 values.toPipedString=function(){ return values.join(‘|‘); }; return values; } var hobbits=new SpecialArray(‘a‘,‘b‘,‘c‘); hobbits.toPipedString();// "a|b|c"
关于寄生构造函数模式,说明一点,返回的对象与构造函数或者与构造函数的原型属性之间没有关系。即构造函数返回的对象与在构造函数外部创建的对象没什么区别,为此不能依赖instanceof操作符来确定对象类型。此种模式不推荐。
7.稳妥构造函数模式:
稳妥对象:没有公共属性,而且其方法也不引用 this 的对象。稳妥对象最适合在一些安全的环境中(这些环境会禁止使用 this 和 new )或者防止数据被其他应用程序改动时使用。
稳妥构造函数遵循与寄生构造函数类似的模式,有两点不同:一是创建对象的实例方法不引用 this ,二是不使用 new 操作符调用构造函数。
function Person(name,age){ var o=new Object(); //在这里定义私有变量和函数 o.say=function(){ console.log(name); } return o; } var p=Person(‘xx‘,20); p.say();// ‘xx‘
注意这种模式创建的对象中,除了使用say方法外没有别的方法可以访问其数据成员。即使有其他代码会给这个对象添加方法或数据成员,但也不可能有别的方法访问传入到构造函数中的原始数据。使用稳妥构造函数模式创建的对象与构造函数之间也没有什么关系,因此也不能用 instanceof 判断类型。
继承
许多OO语言都支持两种继承方式,接口继承和实现继承。接口继承只继承方法名,实现继承继承实际的方法。由于函数没有签名,在ECMAMScript中无法实现接口继承。ECMAScript只支持实现继承,而且其实现继承主要是依靠原型链来实现的。
1.原型链:作为实现继承的主要方法。基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。假如让原型对象等于另一个类型的实例,此时的原型对象将包含一个指向另一个原型的指针,相应地,另一个原型中也包含着一个指向另一个构造函数的指针。假如另一个原型又是另一个类型的实例,那么上述关系依然成立,如此层层递进,就构成了实例与原型的链条。
function SuperType(){ this.property=true; } SuperType.prototype.getSuperValue=function(){ return this.property; } function SuberType(){ this.subproperty=false; } SuberType.prototype=new SuperType(); SuberType.prototype.getSubValue=function(){ return this.subproperty; } var instance=new SuberType(); instance.getSuperValue();// true
调用 insatnce.getSuperValue() 会经历:搜索实例;搜索 SubType.prototype ;搜索 SuperType.prototype ,最后一步才会找到该方法。
instance instanceof Object;// true instance instanceof SuperType;// true instance instanceof SuberType;// true
Object.prototype.isPrototypeOf(instance);// true SuperType.prototype.isPrototypeOf(instance);// true SuberType.prototype.isPrototypeOf(instance);// true
2.借用构造函数:解决了原型中包含引用类型值所带来的问题。即在子类型的构造函数中调用超类型的构造函数(看来也是借鉴了Java中的 super() ),至于是怎么实现的?JavaScript中函数只不过是在特定环境中执行代码的对象,因此通过 apply 和 call 方法可以在将来新创建的对象上执行构造函数。
//父类 function SuperType(){ this.colors=["a","b"]; } //子类 function SuberType(){ SuperType.call(this); } var instance1=new SuberType(); instance1.colors.push(‘c‘);// 3 colors值为["a","b","c"]; var instance2=new SuberType(); instance2.colors;// ["a","b"];
通过使用 call 方法或 apply 方法,我们实际是在(未来将要)新创建的 SuberType 实例的环境下调用 SuperType 构造函数,毕竟每当 new 一个实例的时候是先将构造函数的作用域赋给新对象( this 指向确定)。这样一来,就会在新的 SuberType 对象上执行 SuperType 函数中定义的所有对象初始化代码。这样, SuberType 的每个实例就都会有自己的 colors 属性的副本了。
优点:可以在子类的构造函数中向超类构造函数传递参数(这点Java的 super() 传递参数也可做到)
function SuperType(name){ this.name=name; } function SuberType(age){ //继承了SuperType还传递了参数 SuperType.call(this,"xx"); //实例的其他属性 this.age=age; } var instance=new SuberType(20); instance.name;// "xx" instance.age;// 20
为了确保 SuperType 构造函数不会重写子类的属性,所以在子类中定义的属性写在调用的后面。
缺点:如果仅仅使用构造函数来完成继承,那么也无法避免构造函数模式中存在的问题,即方法都在构造函数中定义,就没有函数的复用了。在超类原型中定义的方法,对子类型而言也是不可见的,结果所有类型就只能使用构造函数模式。考虑到这些,借用构造函数的技术也很少单独使用。
3.组合继承:原型链和构造函数的技术结合起来,使用原型链实现实现对原型属性和方法的继承,借用构造函数实现对实例属性的继承。
//父类 function SuperType(name){ this.name=name; this.colors=["a","b"]; } SuperType.prototype.sayName=function(){ console.log(this.name); } //子类 function SuberType(name,age){ SuperType.call(this,name); this.age=age; } SuberType.prototype=new SuperType();
SuberType.prototype.constructor=SuberType; SuberType.prototype.sayAge=function(){ console.log(this.age); }
var a1=new SuberType(‘xx‘,20);
a1.colors.push(‘c‘); a1.colors;// ["a","b","c"] a1.sayAge();// 20; a1.sayName();// xx
这种方式 instanceof 和 isPrototypeOf 同样可用。但注意到 SuberType.prototype 有个 name 和 colors 的无用属性。
缺点:会调用两次超类的构造函数,一次是在创建子类原型的时候,另一次是在子类构造函数内部的 call 或 apply 。子类型最终会包含超类型对象的全部实例属性,但我们不得不在调用子类型构造函数时重写这些属性。解决办法是寄生组合继承。
4.原型式继承:这种方法并没有使用严格意义上的构造函数,而是借助原型可以基于已有的对象创建新对象,同时还不必因此创建自定义类型。
function object(o){ function F(){} F.prototype=o; return new F(); }
先创建一个临时性的构造函数,然后将传入的对象作为这个构造函数的原型,返回了临时类型的一个新实例。
var person={ name:"xx", friends:["aa","bb","cc"] }; var p1=object(person); p1.name;// "xx" 原型继承 p1.name="xixi"; p1.friends.push("dd"); person.friends;// ["aa", "bb", "cc", "dd"] var p2=object(person);// 再次调用object(),虽然是重新执行了F.prototype但是o参数仍指向原来的person p2.name="xuxu"; p2.friends.push("ee"); person.friends;// ["aa", "bb", "cc", "dd", "ee"]
ECMA5新增的 Object.create() 规范化了原型式继承,两个参数,一个用作新对象原型的对象,(可选的)为一个新对象定义额外属性的对象。在传入一个参数情况下, Object.create() 和 object() 没什么区别。
var person={ name:"xx", friends:["aa","bb","cc"] }; var p1=Object.create(person); p1.name="xixi"; p1.friends.push("dd"); var p2=Object.create(person); p2.name="xuxu"; p2.friends.push("ee"); person.friends;// ["aa", "bb", "cc", "dd", "ee"]
Object.create() 方法的第二个参数与 Object.defineProperties() 方法的第二个参数格式相同,每个属性都是通过自己的描述符定义的。以这种方式指定的任何属性都会覆盖原型对象上的同名属性。
var person={ name:‘xx‘, friends:["aa","bb","cc"] }; var p1=Object.create(person,{ name:{ value:"xixi" } }); p1.name;// "xixi" person.name;// "xx"
在没有必要创建构造函数,而只是想让一个对象与另一个对象保持类似情况下,原型式继承是完全可以的。不过缺点还是在的,比如包含引用类型值得属性始终都是共享的。
5.寄生式继承:是一种与原型式继承紧密相关的思路。寄生式继承的思路与寄生构造函数和工厂模式类似,即创建一个仅用于封装继承过程的函数,该函数在内部以某种方式增强对象,最后再像是它做了所有工作一样返回对象。
function createAnother(original){ var clone=object(original); //调用函数创建一个新对象 clone.say=function(){ //增强这个对象 console.log("hi"); } return clone; }
var person={ name:‘xx‘, friends:["aa","bb","cc"] } var p1=createAnother(person); p1.name;// "xx" p1.friends;// ["aa", "bb", "cc"] p1.say();// hi
在主要考虑对象而不是自定义类型的构造函数情况下,寄生式继承也是一种有用方法,而且 object() 函数也不是必须的,任何能够返回新对象的函数都适用此模式。
缺点:由于是增强对象给对象添加函数,所以不能函数复用。这点与构造函数模式相似。
6.寄生组合式继承:通过构造函数继承属性,原型链的混成继承方法。说白了就是不必为了指定子类型的原型而调用超类型的构造函数而造成子类型原型上出现一些用不到的属性,我们所要的无非就是超类型原型的一个副本实现原型链之间的继承关系而已。那么为何不考虑结合寄生式继承因为寄生式继承可以为一个对象指定原型啊。这样使用寄生式继承来继承超类型的原型,然后再将返回的结果指定给子类原型。
function inheritPrototype(suberType,superType){ var prototype=object(superType.prototype);// 创建对象 prototype.constructor=suberType;// 增强对象 suberType.prototype=prototype;// 指定对象 }
function SuperType(name){ this.name=name; this.colors=["aa","bb","cc"]; } SuperType.prototype.say=function(){ console.log(this.name); } function SuberType(age,name){ SuperType.call(this,name); this.age=age; } inheritPrototype(SuberType,SuperType); SuberType.prototype.say=function(){ console.log(this.age); }
只调用一次父类构造函数不仅提高了效率,这样做还能正常地使用 instanceof 和 isPrototypeOf 。寄生组合式继承是引用类型最理想的继承范式。YUI的 YAHOO.lang.extend 就用到了寄生组合继承(https://yui.github.io/yui2/docs/yui_2.3.0/docs/Lang.js.html)
参考 :《JavaScript高级程序设计》
原文:http://www.cnblogs.com/venoral/p/5251691.html