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()); // 8
console.log(s.getSAge()); // 3
console.log(s.constructor); // [Function: F]
console.log(s instanceof S); // true
console.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;
}
// 重写父类型中的 getF
S.prototype.getF = function() {
return '33';
}
let s = new S();
console.log(s.getS()); // false
console.log(s.getF()); // 33, 原来的 父类中 返回 true
delete(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); // 删掉了 子类实例中,借用父类构造函数继承的 colors
console.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(); // csn
console.log(s1.constructor); // [Function: S]
F如果没有实例化,貌似我这样写没有什么错??还更简单?