继承:构造函数式

写在前面:
以前在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');

欢迎交流,完。兄弟篇——继承:非构造函数式

你可能感兴趣的:(继承:构造函数式)