1. 原型链继承
它是下面这个形式的。
function F() { ?this.f_age = 8; ?this.f_name = 'father';}F.prototype.getFAge = function() { ?return this.f_age;}function S() { ?this.s_age = 3;}S.prototype = new F();S.prototype.getSAge = function() { ?return this.s_age;}let s = new S();console.log(s.getFAge()); // 8console.log(s.getSAge()); // 3console.log(s.constructor); ?// [Function: F]console.log(s instanceof S); // trueconsole.log(s instanceof F); // true
它的主要过程:
- 实例化父类 F -> f
- 将子类的原型对象 S.prototype 指向 f
- 实例化子类 S -> s
加深理解:s.__proto__
-> S.prototype
-> f.__proto__
-> F.prototype
需要注意的是:
- 本来 S 的 constructor 是 S,但是因为 S.prototype 重写了,所以现在 constructor 是 F.
查找机制: s -> S.prototype -> F实例 -> F.prototype
function F() { ?this.name = 'F';}F.prototype.name = 'F-prototype';function S() {}S.prototype = new F();let s = new S();console.log(s.name); // F
原型继承中 子类 重写 父类的方法
function F() { ?this.x = true;}F.prototype.getF = function() { ?return this.x;}function S() { ?this.y = false;}S.prototype = new F();S.prototype.getS = function() { ?return this.y;}// 重写父类型中的 getFS.prototype.getF = function() { ?return '33';}let s = new S();console.log(s.getS()); // false console.log(s.getF()); // 33, ??原来的 父类中 返回 truedelete(S.prototype.getF);console.log(s.getF()); // true
如果你想要改变(覆盖)父类的方法,必须在子类的 原型对象 被 父类的实例 赋值之后 覆盖。后来居上,你懂的。
后来居上的 字面量方法 也一样,如下
function F() {}function S() {}S.prototype = new F();// 下面的这句话重写了上面这句话。S.prototype = { ?getX: function() { ???return '33'; ?}}
原型链继承的特点和示例代码:
a. 原来的父类实例属性 变为了 子类的原型属性。共享性。
function F() { ?this.colors = ['red', 'blue'];}function S() {}S.prototype = new F();let s1 = new S();let s2 = new S();s1.colors.push('yellow');console.log(s1.colors); // ['red', 'blue', 'yellow']console.log(s2.colors); // ['red', 'blue', 'yellow']
b. 创建子类型的实例的时候,不能像超类型的构造函数传递参数。
2. 借用构造函数 继承
真是骚操作啊。
原理:
- A.谁调用了函数,函数中的 this 就指向谁。
- B.利用 apply 和 call 在 子类 内部调用 函数。
function F() { ?this.colors = ['red', 'blue'];}function S() { ?F.call(this);}let s1 = new S();let s2 = new S();s1.colors.push('yellow');console.log(s1.colors); ?// ['red', 'blue', 'yellow']console.log(s2.colors); ?// ['red', 'blue']
特点:
- 属性不会共享。(上面说了)
可以传参
function F(name) { ?this.name = name; ?this.colors = ['red', 'blue'];}function S(name) { ?F.call(this, name);}let s1 = new S('ccc');
缺点在于函数复用。
其实和 JS构造模式 是一个道理。在原型链 中的 共享的函数还是很有必要的。一些属性也是应该共享的。
3. 组合继承: 跟JS模式中的 组合构造模式 很像。
- 可以传参。
- 可以 选择 是否共享属性和方法。(非战争的年代,人们有权选择过自己的生活)
function F(name) { ?this.name = name; ?this.colors = ['red'];}F.prototype.getF = function() { ?console.log(this.name);}function S(name, age) { ?F.call(this, name); ?this.age = age;}S.prototype = new F();S.prototype.constructor = S;S.prototype.getS = function() { ?console.log(this.age);}// 强行让 constructor 为子类,缺点是 constructor 变为可枚举。let s1 = new S('s1', 18);let s2 = new S('s2', 20);s1.colors.push('yellow');console.log(s1); ???// S {name: 's1', colors: ['red', 'yellow'], age: 18}console.log(s2); ???// S {name: 's2', colors: ['red'], age: 20}delete(s1.colors); ?????// 删掉了 子类实例中,借用父类构造函数继承的 colorsconsole.log(s1.colors); // 子类实例中:父类原型中的 colors 还是存在的。// ---------------------------------------------------------------console.log(s1.constructor); // [Function: S]console.log(Object.keys(S.prototype)); // 其中包含 constructor 属性
4. 原型式继承: 可以说是 ES3中对 ES5中 Object.create 的实现了。
缺点很明显:共享属性。
function Create(o) { ?function F(){}; ?F.prototype = o; ?return new F();}let person = { ?name: '123', ?colors: ['red']}let f1 = Create(person);let f2 = Create(person);f1.colors.push('yellow');console.log(f2.colors); ??// ['red', 'yellow']
ES5中的 Object.create(用作新对象原型的对象,可选的定义额外属性的对象)
若是没有第二个参数,和我们上方自己写的 Create 方法相同。
let person = { ?name: '123', ?colors: ['red']}let f1 = Object.create(person);let f2 = Object.create(person);f1.colors.push('yellow');console.log(f2.colors); ??// ['red', 'yellow']
若是写了第二个参数,则会覆盖掉原型中的 属性。
let person = { ?name: '123', ?colors: ['red']}let f1 = Object.create(person, { ?colors: {value: ['red'] ?}});let f2 = Object.create(person, { ?colors: {value: ['red'] ?}});f1.colors.push('yellow');console.log(f2.colors); ??// ['red']
在没有必要兴师动众地 创建构造函数,只是想让 一个对象 与 另外一个对象保持 相似的情况下,使用 原型式继承 就可以了。
5. 寄生组合式继承。
产生的原因是组合继承 的缺点:
- 调用两次父类。
- 1.创建子类原型的时候
- 2.子类型构造函数内部的调用
- 最后造成的结果。子类会包含 父类的全部 实例属性。并且在调用子类构造函数的时候会重写(覆盖)一些属性。
function Create(o) { ?function F() {}; ?F.prototype = o; ?return new F();}// 1. 这一步为了 子类继承 父类的原型function Inherit(subType, superType) { ?let prototype = Create(superType.prototype); ?// 只承包了 父类的原型。 ?prototype.constructor = subType; ?// 增强对象,重写 被重写的 constructor ?subType.prototype = prototype;}function F(name) { ?this.name = name; ?this.colors = ['blue'];}F.prototype.say = function() { ?console.log(this.name);}// 2. 这一步为了 子类继承 父类的一些构造函数内部的东西function S(name, age) { ?F.call(this, name); ?this.age = age;}Inherit(S, F);
疑问:其实本质上没有啥差别。主要的骚操作就要在于:子类继承的类别变化了。最后其实我有一个疑问。。
组合继承 是这样的:调用了一次构造函数。
function S(name) { ?F.call(this, name);} ?????????????????????// 调用了一次S.prototype = new F(); // 调用了一次
于是我想,下面这个难道不对???
function F(name) { ?this.name = name;}F.prototype.getName = function() { ?console.log(this.name);}function S(name) { ?F.call(this, name);}S.prototype = F.prototype;S.prototype.constructor = S;let s1 = new S('csn');s1.getName(); ?????????????????// csnconsole.log(s1.constructor); ??// [Function: S]
F如果没有实例化,貌似我这样写没有什么错??还更简单?