JavaScript_原型_原型链_继承

原型

每一个对象都有一个显式原型(prototype)和一个隐式原型(proto)。隐式原型引用了创建这个对象的函数的原型 prototype

1
fn.__proto__ === Fn.prototype

一个对象实例的 proto 和 创建这个对象实例的函数的 prototype 是一样的

1
2
3
typeof Object === 'function' // Object 是一个构造函数,用于创建对象
typeof Object.prototype === 'object'
  • Object.proto 指向的是 Function.prototype (Object 是一个对象构造函数,是被函数 Function 创建的)
  • Foo.proto 指向的也是 Function.prototype (Foo 是一个普通的构造函数,是被函数 Function 创建的)
  • Function.proto 指向的还是 Function.prototype (因为 Function 是被自身创建的)

Function.prototype 的 proto 指向的是 Object.prototype

typeof Function.prototype === ‘function’

Object.prototype.proto 指向 null

原型链

Instanceof 运算符的第一个变量是一个对象,暂时称为A;第二个变量一般是一个函数,暂时称为B。

Instanceof 的判断队则是:沿着A的proto这条线来找,同时沿着B的prototype这条线来找,如果两条线能找到同一个引用,即同一个对象,那么就返回true。如果找到终点还未重合,则返回false。

访问一个对象的属性时,先在自身基本属性中查找,如果没有,再沿着proto这条链向上找,这就是原型链。

call/apply 等方法是在 Function.prototype 中创建的

原型以及原型链图解

继承

利用原型以及原型链等方法,实现子类对于父类的继承

1
2
3
4
5
6
7
8
9
10
11
12
13
// 定义一个动物类
function Animal (name) {
// 属性
this.name = name || 'Animal';
// 实例方法
this.sleep = function(){
console.log(this.name + '正在睡觉!');
}
}
Animal.prototype.eat = function (food) {
console.log(this.name + '正在吃' + food)
}

原型链继承

将父类的实例作为子类的原型

1
2
3
4
function Cat(){
}
Cat.prototype = new Animal();
Cat.prototype.name = 'cat';

构造继承

使用父类的构造函数来增强子类实例,等于是复制父类的实例属性给子类(没用到原型)

1
2
3
4
function Cat (name) {
Animal.call(this) // 调用父类的构造函数
this.name = name
}

实例继承

为父类实例添加新特性,作为子类实例返回

1
2
3
4
5
function Cat (name) {
let obj = new Animal() // 创建父类的实例对象
obj.name = name
return obj
}

组合继承

通过调用父类构造,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现函数复用

1
2
3
4
5
6
7
function Cat (name) {
Animal.call(this)
this.name = name
}
Cat.prototype = new Animal()
Cat.prototype.constructor = Cat

寄生组合继承

通过寄生方式,砍掉父类的实例属性,这样,在调用两次父类的构造的时候,就不会初始化两次实例方法/属性,避免的组合继承的缺点

1
2
3
4
5
6
7
8
9
10
11
12
function Cat (name) {
Animal.call(this)
this.name = name
}
function inheritPrototype (subType, superType){
var prototype = Object.create(superType.prototype); //创建对象
prototype.constructor = subType; //增强对象
subType.prototype = prototype; //指定对象
}
inheritPrototype(Cat, Animal);

详细请看之前一篇博文红宝书系列读书笔记(二)

多重继承

多重继承,指的是一个子类对象继承了多个父类对象,主要是利用 call/apply 实现多重继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
let inheritPrototype = function(superType, subType) {
return function () {
superType.call(this, arguments[0])
for (let key in subType) {
if (subType.hasOwnProperty(key)) {
this[key] = subType[key]
}
}
}
}
function Animal (name) {
this.name = name
}
let Cat = inheritPrototype(Animal, {
say: function () {
console.log(this.name)
}
})
let cat = new Cat('Hello Kitty')
cat.say() // 输出 'Hello Kitty

上述代码中,inheritPrototype() 接收两个参数:父类构造函数和父类对象。通过 call 方法将父类构造函数绑定到子类中,然后再将另一个父类对象自身的属性全都复制给子类。

如果通过多个父类构造函数继承多个父类对象,那么代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function Base1 () {
this.name = 'peter'
}
function Base2 () {
this.sayName = function () {
console.log(this.name)
}
}
let inheritPrototype = function() {
let args = [].slice.call(arguments), len = args.length
return function () {
for (let i = 0; i < len; i++) {
args[i].call(this)
}
}
}
let objCreate = inheritPrototype(Base1, Base2)
let obj = new objCreate()
obj.sayName() // 输出 'peter'

ES5 与 ES6 继承的区别

ES5 和 ES6 实现的继承底层还是利用了原型链

ES5 的继承实质上是先创建子类的实例对象,然后再将父类的方法添加到 this 上(Parent.apply(this))
ES6 的继承机制完全不同,实质上是先创建父类的实例对象this(所以必须先调用父类的super()方法),然后再用子类的构造函数修改 this。

ES5 的继承时通过原型或构造函数机制来实现。

ES6 通过 class 关键字定义类,里面有构造方法,类之间通过 extends 关键字实现继承。子类必须在 constructor 方法中调用 super 方法,否则新建实例报错。因为子类没有自己的 this 对象,而是继承了父类的 this 对象,然后对其进行加工。如果不调用 super 方法,子类得不到 this 对象。

super 关键字指代父类的实例,即父类的 this 对象。

坚持原创技术分享,您的支持将鼓励我继续创作!