写在前面:
以前在coding时,经常在开发者面板中见到一个Object中见到__proto__
和prototype
这两个属性,也没有
深入去了解过,这里先给自己扫盲一个。__proto__
是Object.prototype
的一个属性,它被赋予了一个重要功能——访问一个对象原型链中的属性和方法。由于它是Object.prototype
的属性,故而在一个类被实例化后,该对象便能访问到它的__proto__
,说到这里就明白了,即是__proto__
拥有的属性和方法,取决于该类的prototype
,即该类的原型上定义的属性和方法。如下:
console.log((new Foo).__proto__ === Foo.prototype); // true
console.log((new Foo).prototype === undefined); // true
参考链接:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/proto
http://stackoverflow.com/questions/9959727/proto-vs-prototype-in-javascript
另外补充一点OO的概念:
何为类:具有相同属性和方法的一组对象的集合(略拗口),它为属于该类的全部对象提供了抽象的描述,主要包括了属性和方法两个部分。
何为对象(即实例):通过new
某一个类所产生的实例instance
,叫做该类的一个对象。
接下来进入正题:
方法一:在子类中调用call/apply
function Parent_1() {
this.name = 'parent_1';
}
Parent_1.prototype.sayName = function() { // 不能被继承
return this.name;
};
function Child_1() {
// 立即调用Parent_1,注意这里是直接调用,并非new
// 那么就会产生一个问题,即是子类只能继承到父类构造函数中的属性和方法,而不能继承到父类原型中定义的属性和方法
Parent_1.apply(this, arguments);
this.name = 'child_1';
}
// 最后实例化子类
var c1 = new Child_1;
缺点:不能继承父类原型上的属性和方法
结论:父类的属性和方法,需要写入其构造函数
方法二:子类的原型指向父类的实例
function Parent_2() {
this.name = 'parent_2';
}
function Child_2() {
this.name = 'child_2';
}
Child_2.prototype.sayName = function() { // 将在实例化过程中被清除
return this.name;
};
// 子类的原型指向父类的实例,which也意味着子类的constructor指向了错误的地方,此时子类的constructor指向Parent2
Child_2.prototype = new Parent_2;
// console.log(Child_2.prototype.constructor === Parent_2); // true
// 接下来需要给子类的原型增加一个constructor属性,使其指向正确的地方,即子类自身
Child_2.prototype.constructor = Child_2;
// 最后实例化子类
var c2 = new Child_2;
缺点:子类原型上的属性和方法将被清除掉
结论:子类的属性和方法,需要写入其构造函数。同时需要注意的一点是,一定要将子类的constructor指向子类自身,否则将造成继承链混乱
方法三:子类原型直接指向父类的原型
function Parent_3() {
this.name = 'parent_3';
}
function Child_3() {
this.name = 'child_3';
}
Child_3.prototype.sayName = function() { // 将在实例化过程中被清除
return this.name;
};
Child_3.prototype = Parent_3.prototype; // 将子类的原型直接指向父类的原型(原型是对象)
// 这里就会产生一个问题,即子类的原型和父类的原型指向了同一个内存地址,自此子类原型中属性的任何改动,都会同样改动到父类的原型上(凌乱了)
// 例如:
// Child_3.prototype.constructor = Child_3;
// console.log(Parent_3.prototype.constructor === Child3); // true
// 最后实例化子类
var c3 = new Child_3;
缺点:子类原型上的属性和方法将被清除掉;不能继承父类构造函数中的属性和方法;子类的原型和父类的原型指向了同一个内存地址,造成了继承链混乱,无法判断到底是谁继承了谁
优点:没有父类实例化的过程,节省了内存占用,提高了程序运行效率
结论:子类的属性和方法,需要写入其构造函数
方法四:利用一个空的构造函数作为“中介”,代替子类的原型,继承父类的原型(方法三的enhanced版本)
function Parent_4() {
this.name = 'parent_4';
}
function Child_4() {
this.name = 'child_4';
}
Child_4.prototype.sayName = function() { // 将在实例化过程中被清除
return this.name;
};
var F = function() {}; // 创建一个空的构造函数
F.prototype = Parent_4.prototype; // 将构造函数的原型指向父类
Child_4.prototype = new F; // 将子类的原型指向空构造函数的实例
Child_4.prototype.constructor = Child_4; // 将子类的constructor指向子类自身
// 接下来为子类设一个uber属性,这个属性直接指向父对象的prototype属性。
// (uber是一个德语词,意思是"向上"、"上一层"。)这等于在子类上打开一条通道,可以直接调用父对象的方法。
// 这一行放在这里,只是为了实现继承的完备性,纯属备用性质。
// 至于具体到应用层面上来解释的话,意思就是说,如果你确切知道一个属性/方法是继承自父类的原型的话,
// 就可以直接c4.uber.xxx来访问该属性/方法,而不用沿着原型链回溯一层一层地查找,提升了查询效率
Child_4.prototype.uber = Parent_4.prototype;
// 最后实例化子类
var c4 = new Child_4;
// console.log(c4.uber === Parent_4.prototype); // true
缺点:子类原型上的属性和方法将被清除掉;不能继承父类构造函数中的属性和方法
优点:子类原型指向了空构造函数的实例,从而解决了方法三中的继承链混乱问题,子类原型的改动不会影响到父类
结论:子类的属性和方法,需要写入其构造函数
方法五:浅拷贝继承父类
function Parent_5() {
this.name = 'parent_5';
}
Parent_5.prototype.pSayName = function() {
return this.name;
};
function Child_5() {
this.name = 'child_5';
}
Child_5.prototype.cSayName = function() {
return this.name;
};
// 将父类的原型中的属性和方法,复制到子类的原型中
// 问题是,在此过程中,子类原型中的同名属性/方法会被父类覆盖
// 注:其实也算不上问题,因为在面向对象的设计阶段,就应抽象出所有对象的公共属性和方法,故而子类原型中,
// 不应该出现与父类同名的属性和方法
for(var k in Parent_5.prototype) {
Child_5.prototype[k] = Parent_5.prototype[k];
}
// 对子类原型上属性的改动不会影响到父类原型,因为他们的原型相互并没有引用关系(对比方法三)
Child_5.prototype.uber = Parent_5.prototype;
// 最后实例化子类
var c5 = new Child_5;
缺点:不能继承父类构造函数中的属性和方法
优点:由于是key-value方式的复制,故子类的原型上的属性和方法不会被清除掉,从而对子类原型的改动不会影响到父类的原型
结论:父类的属性和方法,需写到父类的原型中
以上则是构造函数式继承的五种方法。
这是Javascript在创造之初,提供了这种模拟式的基于类的模式——伪类模式,这使得它真正的本质被掩盖——这其实是一种基于原型的语言。
总体来看,采用构造函数的方式来实现继承,都有各种缺点,我们可以采用方法一和方法五结合使用,即采用方法一来继承父类构造函数中的属性和方法,采用方法五来继承父类原型中的属性和方法。例如:
function Parent_6(name, age) {
this.name = name;
this.age = age;
}
Parent_6.prototype.sayName = function() {
return this.name;
}
function Child_6() {
Parent_6.apply(this, arguments);
this.occupation = arguments[2];
}
// 将父类原型上的属性/方法浅拷贝至子类
for(var k in Parent_6.prototype) {
Child_6.prototype[k] = Parent_6.prototype[k];
}
Child_6.prototype.uber = Parent_6.prototype;
// 实例化子类
var c6 = new Child_6('child_6', 18, 'pilot');
欢迎交流,完。兄弟篇——继承:非构造函数式