# 继承
# 原型链继承
# 实现
function Father() {
this.text = "1";
}
Father.prototype.someFn = function() {
console.log(1);
};
Father.prototype.someValue = "2";
function Son() {
this.text1 = "text1";
}
// 函数原型指向构造函数的实例
Son.prototype = new Father();
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
# 优点
简单易操作。
# 缺点
- 父类使用 this 声明的属性被所有实例共享。原因是实例化是父类一次性赋值到子类实例的原型上,它会将父类通过 this 声明的属性也赋值到子类原型上。例如在父类中一个数组值,在子类的多个实例中,无论哪一个实例去修改这个数组的值,都会影响到其他子类实例。
- 创建子类实例时,无法向父类构造函数传参,不够灵活。
# 借用构造函数(call)
# 实现
function Father(...arr) {
this.some = "父类属性";
this.params = arr;
}
Father.prototype.someFn = function() {
console.log(1);
};
Father.prototype.someValue = "2";
function Son(fatherParams, ...sonParams) {
// Father的this指向Son的this
// 使用call调用父类,Father将会立即被执行,并且将父类的Father的this执行Son
// 的this。实例化子类,this将指向new期间创建的新对象,返回该新对象。
Father.call(this, ...fatherParams);
this.text = "子类属性";
this.sonParams = sonParams;
}
var fatherParams = [];
var sonParams = [];
var sonInstance = new Son(fatherParams, ...sonParams);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 优点
- 可以向父类传递参数。
- 解决父类 this 声明的属性会被实例共享的问题。
# 缺点
- 只能继承父类通过 this 声明的属性/方法。不能继承父类 prototype 上的属性/方法。
- 父类方法无法复用。每次实例化子类,都要执行父类函数。重新声明父类所定义的方法,无法复用。
# 组合继承(call+new)
原理:通过原型链继承来将 this、prototype 上的属性和方法继承制子类的原型对象上。使用借用构造函数来继承父类通过 this 声明的属性和方法在之子类的实例属性上。
# 实现
function Father(...arr) {
this.some = "父类属性";
this.params = arr;
}
Father.prototype.someFn = function() {
console.log(1);
};
Father.prototype.someValue = "2";
function Son(fatherParams, ...sonParams) {
// 借用构造函数继承父类this什么的属性和方法到子类实例属性上
Father.call(this, ...fatherParams);
this.text = "子类属性";
this.sonParams = sonParams;
}
// 原型链继承,将`this`和`prototype`声明的属性/方法继承至子类的`prototype`上
Son.prototype = new Father("xxxxx");
var fatherParams = [];
var sonParams = [];
var sonInstance = new Son(fatherParams, ...sonParams);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 优点
- 解决原型链继承父类 this 声明的属性或者方法被共享的问题。
- 解决借用构造函数解决不能继承父类 prototype 对象上的属性/方法问题。
# 缺点
- 调用了父类函数两次,造成一定的性能问题。
- 因调用两次父类,导出父类通过 this 声明的属性和方法被生成两份的问题。
- 原型链上下文丢失,子类和父类通过 prototype 声明的属性和方法都存在与子类 prototype 上。
# 原型式继承
# 实现
function cloneObj(obj) {
function F() {}
// 将被继承的对象作为空函数的prototype
F.prototype = obj;
// 返回new期间创建的新对象,此对象的原型为被继承的对象,
// 通过原型链查找可以拿到被继承对象的属性
return new F();
}
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
# 优点
兼容性好,最简单的对象继承。
# 缺点
多少实例共享被继承的属性,存在被篡改的情况,不能传递参数。
# 寄生式继承(继承过程封装)
创建一个仅用于封装继承过程的函数,改函数在内部已某种方式类增强对象,最后返回对象。在原型式继承的基础上进行增强对象。
# 实现
function createAnother(original) {
var clone = cloneObject(original); // 继承一个对象 返回新函数
// do something 以某种方式来增强对象
clone.some = function() {}; // 方法
clone.obkoro1 = "封装继承过程"; // 属性
return clone; // 返回这个对象
}
1
2
3
4
5
6
7
2
3
4
5
6
7
# 优点
兼容性好,最简单的对象继承。 缺点
# 缺点
1、多少实例共享被继承的属性,存在被篡改的情况,不能传递参数。
# 寄生组合式继承(call+寄生式封装)
使用借用构造函数来继承父类 this 声明的属性和方法。2、使用寄生式继承来设置父类 prototype 为子类 prototype 的原型来继承父类的属性和方法。
# 实现
function Father(...arr) {
this.some = "父类属性";
this.params = arr;
}
Father.prototype.someFn = function() {
console.log(1);
};
Father.prototype.someValue = "2";
function Son() {
Father.call(this, "xxxx");
this.text = "2222";
}
function inhertPro(son, father) {
// 原型式继承
var fatherPrototype = Object.create(father.prototype);
// 设置Son.prototype的原型是Father.prototype
son.prototype = fatherPrototype;
// 修正constructor 指向
// constructor的作用:返回创建实例对象的Object构造函数的引用。
// 在这里保持constructor指向的一致性
son.prototype.constructor = son;
}
inhertPro(Son, Father);
var sonInstance = new Son();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 优点
- 寄生组合式继承是当前最成熟的继承方法,也是先也常用的继承方法,在大多数 Js 框架中都是用这个作为继承方案。 寄生组合式继承相对组合继承的优点:
- 只调用了父类构造函数一次,节约了性能。
- 避免生成了不必要的属性。
- 使用原型式继承保证了原型链上下文不变,子类的 prototype 只有子类通过 prototype 声明的属性和方法,父类的 prototype 只有父类通过 prototype 声明的属性和方法。
# ES6-extends 继承
# 实现
class Point {}
class ColorPoint extends Point {}
1
2
2
# 注意
子类必须在 constructor 方法中代用 super 方法,否则新建实例将会报错,这是因为子类自己的 this 对象,必须先通过父类的构造函数完成塑性,得到父类的属性和方法,然后对其加工,加上子类自己的属性和方法。如果不调用 super 方法,子类将得不到 this 对象。如果没有定义 constructor 方法,这个方法会被默认的添加。
# 转换
ES6 继承的原理跟寄生组合式继承是一样的。优缺点也相仿。 把 ES6 的代码装换为 ES5 www.babeljs.cn/repl 转换前:
class Point {}
class ColorPoint extends Point {}
1
2
2
转换后: 转换的结果核心代码如下:用于子类的 prototype 继承父类的 prototype 方法。
function _inherits(subClass, superClass) {
if (typeof superClass !== "function" && superClass !== null) {
throw new TypeError("Super expression must either be null or a function");
}
subClass.prototype = Object.create(superClass && superClass.prototype, {
constructor: {
value: subClass,
writable: true,
configurable: true
}
});
if (superClass) _setPrototypeOf(subClass, superClass);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
# 区别
ES5 的继承实质是先创建子类的实例对象 this,然后将父类的方法添加到 this 上。 ES6 的继承实质是先将父类实例对象的方法和属性加到 this 上面,然后在用子类的构造函数修改 this。
# 绝杀
function Rectangle(length, width) {
this.l = length;
this.w = width;
}
Rectangle.prototype.getArea = function() {
return this.l * this.w;
};
function Square(length) {
Rectangle.call(this, length, length);
}
Square.prototype = Object.create(Rectangle.prototype, {
constructor: {
value: Square
}
});
var square = new Square(3);
console.log(square.getArea());
console.log(square instanceof Square);
console.log(square instanceof Rectangle);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20