JavaScrip 继承2.0

JavaScrip 继承2.0

改变构造函数的原型并不是继承

假设有两个构造函数 User 和 Person . 现在想让 Person 的实例能够访问 User 的 showName() 方法。此时一种思路:直接将 Person 的原型修改为 User 的原型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function User(name){
this.name = name
}

User.prototype.showName = function(){
console.log(this.name)
}

function Person(name){
this.name = name
}

// 直接将 Person 的原型修改为 User 的原型
Person.prototype = User.prototype

let person = new Person("xiaofang")
person.showName() //此时可以正常访问

console.log(person.constructor === User) // true
console.log(person.constructor === Person) // false

本质上,将 User 直接作为 person 的构造函数,这样 person 就能够访问 User 原型对象的方法。

但是,这导致了一个问题:假设此时要给所有 person 实例添加一个 age 属性,由于 Person.prototype 指向了 User.prototype ,二者也就是同一个对象。因此给 Person.prototype 添加属性会导致 User 的原型也被添加了这个属性:

1
2
3
Person.prototype.age = 18
console.log(Person.prototype) // 有age属性
console.log(User.prototype) // User的原型也被添加了age属性

这显然不是我们想要的。

真正的继承应该是可以用到父辈的方法,同时自己添加或修改自己方法/属性的时候不会反过来影响父辈。

继承是原型的继承

首先为了便于理解,使用 __proto__ 的写法来寻找和设置实例对象的原型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let f = {}
// 实际开发中,一下写法是等价的:
console.log(Object.getPrototypeOf(f))
等价于:
console.log(__proto__)

Object.setPrototype(f,obj)
等价于:
f.__proto__ = obj
/* getPrototypeOf相当于找实例对象的爸爸,setPrototypeOf相当于指定实例第项的爸爸 */

// 补充:以上两个方法都是“儿子设置爸爸”,如何用“爸爸”创造“儿子”呢?使用 Object.create()
let f2 = Object.create(f)
// 等价于:
f2.__proto__ = f

以上代码 setPrototypeOf()Object.create() 二者几乎没有差别,只是前者设置“父亲”,后者设置“儿子”

由于构造函数和实例之间有这样一对关系:

1
Obj.prototype === obj.__proto__

即:构造函数的“叔叔”是实例对象的“爸爸”。

正常从原型链访问方法,都是延着父辈这条链访问,只要方法存在于父辈这条链上,就能够访问得到。并且,自己可以修改自己的方法/属性,并不会影响到父辈。

因此,如果想在继承其他构造函数时,实例对象自己能够添加和修改自身方法/属性,而不影响父辈。就不能去修改构造函数的原型,即不能改变原有“爸爸”。

正确的做法是,让待继承构造函数的“叔叔”进入实例对象的父亲链,而不是直接作为父亲。

因此,可以让实例对象的原型继承构造函数的prototype。即:Person.prototype.__proto__ = User.prototype

也就是说,不能让构造函数的“叔叔”作为实例对象的“爸爸”,但是可以让他作为实例对象的“爷爷”:

1
2
3
4
5
6
7
8
9
10
11
12
13
Person.prototype.__proto__ = User.prototype
// 即:
Person.prototype = new User()
// 即:
Person.prototype = Object.create(User.prototype)
// 亦即:
Object.setPrototype(Person.prototype, User.prototype)

// 测试:
Person.prototype.age = 18
let person = new Person("xiaofang")
console.log(Person.prototype) // 有 age 属性
console.log(User.prototype) // 没有 age 属性

继承的解决方案

一般使用所谓的组合模式来实现继承:

使用原型链继承原型上的属性和方法,而通过盗用构造函数继承实例属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
function User(name){
this.name = name
}
User.prototype.showName = function(){}

function Person(name,age){
// ① 通过盗用构造函数继承实例属性,防止引用值在关联对象中共享
User.call(this,name) // 或者:User.apply(this,arguments)
this.age = age
}

// ② 继承方法。浅拷贝,然后让构造函数进入原型链
// 设置:Person.prototype.__proto__ = User.prototype
function Temp(){}
Temp.prototype = User.prototype
Person.prototype = new Temp()
// 或者:Person.prototype = Object.create(User.prototype)
// 或者:Object.setPrototypeOf(Person.prototype, User.prototype)

// ③ 设置constructor
Object.defineProperty(Person.prototype,"constructor",{
value: Person,
enumerable: false
})

Person.prototype.sayAge = function(){}

let person1 = new Person("xiaofang",18)

类构造函数实现继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class User{
constructor(name){
this.name = name
}
showName(){}
}

class Person extends User{ // extends 关键字自动实现步骤 ② 和 ③
constructor(age){
super() // 相当于步骤 ①
this.age = age
}
sayAge(){}
}

let person1 = new Person("xiaofang",18)

instanceof 的实现原理

instanceof 用于判断一个构造函数的原型是否在一个实例对象的原型链上。

例如, a instanceof B 表示判断 B 的 叔叔是否是 a 的长辈

1
console.log(person1 instanceof User)		// true

这个关键字的实现原理就是,沿着实例对象的原型链向上攀升,看找不找得到构造函数的“叔叔”。以下使用一个函数来说明 instanceof 关键字的实现原理:

1
2
3
4
5
6
7
8
function checkPrototype(obj,constructor){
if(!obj.__proto__) return false
if(obj.__proto__ === constructor.prototype) return true
return checkPrototype(obj.__proto__,constructor)
}

//测试:
checkPrototype(person1, User) // true

instenceof 和 isPrototypeOf() 的区别

  • instanceof 用于判断一个构造函数的原型是否在一个实例对象的原型链上。即构造函数的叔叔是否是实例对象“爸爸链”成员

  • isPrototypeOf() 用于判断一个对象是否在另一个对象的原型链上。即一个对象是否是另一个对象的“爸爸链”成员

1
2
3
4
5
6
console.log(person1 instanceof User)	// true

console.log(User.isPrototypeOf(person1)) // false 构造函数和实例对象是堂兄弟关系
console.log(User.prototype.isPrototypeOf(person1)) // true

console.log(User.isPrototypeOf(Person)) // true

版权声明:本文作者为「Andy8421」.本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!