关键词

原型、原型链、继承

原型、原型链

__proto__和prototype的关系:__proto__constructor是对象独有的,prototype是函数独有的。

在js中我们使用构造函数来新建一个对象,每一个构造函数的内部都有一个prototype属性,这个属性是一个对象,这个对象包含了可以由该构造函数的所有实例共享的方法和属性。当我们使用构造函数新建一个对象之后,在这个对象的内部将包含一个指针,这个指针指向构造函数的prototype属性对应的值,在es5中这个指针被称为对象的原型。一般来说不能获取到这个值,但是浏览器下载都实现了proto属性让我们来访问,但是最好是不要使用这个属性,因为他不是规范的,最好使用Object.getPrototypeOf()来获取对象的原型

当我们访问一个对象的属性的时候,如果这个对象内部不存在这个属性,那么他就会去他的原型对象里面找这个属性,这个原型对象又会有自己的原型,于是就一直找下去,也就是原型链的概念。原型链的尽头一般来说是Object.prototype所以这就是我们新建的对象为什么能够使用toString等方法的原因

特点:js的对象都是通过引用来传递的,我们创建的每个新对象实体中并没有一份属于自己的原型副本。当我们修改原型的时候,与之相关的对象也会继承这一改变。

  • 原型,一个简单的对象,用于实现对象的属性继承。
  • 构造函数 可以通过new来创建对象的一个函数
  • 实例 通过构造函数和new创建出来的对象。通过__proto__指向原型,通过constructor指向构造函数。

Object为例,他是一个构造函数,因此可以用它创建实例

1
2
// 实例
const instance = new Object()

则此时, 实例为instance, 构造函数为Object,我们知道,构造函数拥有一个prototype的属性指向原型,因此原型为:

1
2
// 原型
const prototype = Object.prototype

三者关系:

  • 实例.__proto__ === 原型
  • 原型.constructor === 构造函数
  • 构造函数.prototype = 原型

原型链

原型链是由原型对象组成,每个对象都有__proto__属性,指向了创建该对象的构造函数的原型。__proto__将对象连接起来组成了原型链,是一个用来实现继承和共享属性的有限的对象链。

  • 属性查找机制:当查找对象的属性时,如果实例对象自身不存在该属性,则沿着原型链往上一级查找,找到时则输出,不存在时,则继续沿着原型链往上一级查找,直至最顶级的原型对象Object.prototype,如还是没找到,则输出undefined
  • 属性修改机制: 只会修改实例对象本身的属性,如果不存在,则进行添加该属性,如果需要修改原型的属性时,则可以用: b.prototype.x = 2;但是这样会造成所有继承于该对象的实例的属性发生改变。

js获取原型的方法

  • p.__proto__
  • p.constructor.prototype
  • Object.getPrototypeOf(p)

另外:

  • 每个函数都有prototype属性,除了Function.prototype.bind() 该属性指向原型。
  • 每个对象都有__proto__属性,指向了创建该对象的构造函数的原型
  • 对象可以通过__proto__来寻找不属于该对象的属性,__proto__ 将对象连接起来组成了原型链。

继承


涉及面试题:原型如何实现继承?class如何实现继承?class本质


首先讲一下class,其实在js中不存在类,class只是语法糖,本质还是函数。

1
2
class Person {}
Person instanceof Function // true

组合继承

组合继承是最常用的继承方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function Parent(value){
this.val = value;
}
Parent.prototype.getValue = function(){
console.log(this.val);
}
function Child(value){
//通过call的方式来继承父类的属性(调用构造x1)
Parent.call(this,value);
}
//通过改变子类的原型来继承父类的函数(x2 此时继承了构造函数,多了不必要的属性)
Child.prototype = new Parent();

const children = new Child('aaa');
children.getValue()
console.log(children instanceof Parent)//true
  • 通过call来继承父类的属性,然后改变子类的原型来继承父类的函数。
  • 优点:构造函数可以传参,不会和父类引用属性共享,可以复用父类的函数。
  • 缺点:继承父类的时候调用了两次父类的构造函数,把父类不必要的属性也给继承了。内存上面浪费

寄生组合继承

这种方法对组合继承进行了优化,组合继承的缺点在于继承父类函数时调用了构造函数,我们只需要优化掉这点即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function Parent(value) {
this.val = value
}
Parent.prototype.getValue = function() {
console.log(this.val)
}
function Child(value){
Parent.call(this,value);
}
Child.prototype = Object.create(Parent.prototype,{
constructor:{
value:Child,
enumerable:false,
writable:true,
configurable:true
}
})
const child = new Child(1);
child.getValue();
console.log(child instanceof Parent)

以上继承将父类的原型赋值给了子类,并且将构造函数设置成了子类,这样解决了无用的父类属性问题,还能正确的找到子类的构造函数。

Class继承

以上两种方式都是通过原型去解决的,在es6中我们可以通过class去实现继承,并且实现起来很简单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Parent{
constructor(value){
this.val = value;
}
getValue(){
console.log(this.val);
}
}
class Child extends Parent{
constructor(value){
super(value)
this.val = value;
}
}
let child = new Child(1);
child.getValue();
console.log(child instanceof Parent)

class实现继承的核心在于使用extends表明继承自哪个父类,并且在子类构造函数中必须调用super
super可以看作Parent.call(this,value)

其实这样做还是有缺陷的,比如父类的静态方法还是不能继承。

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
//寄生组合继承
function Parent(value) {
this.val = value
}
Parent.prototype.getValue = function () {
console.log(this.val)
}
Parent.staticMethod = function(){
console.log('我是静态')
}
function Child(value) {
Parent.call(this, value);
}
Child.prototype = Object.create(Parent.prototype, {
constructor: {
value: Child,
enumerable: false,
writable: true,
configurable: true
}
})
const child = new Child(1);
child.getValue();
//Child.staticMethod()//报错
Parent.staticMethod()

而class继承则可以

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Parent{
constructor(value){
this.val = value;
}
getValue(){
console.log(this.val);
}
static caseA(){
console.log('我是静态')
}
}
class Child extends Parent{
constructor(value){
super(value)
this.val = value;
}
}
let child = new Child(1);
child.getValue();
Child.caseA()
console.log(child instanceof Parent)

其他继承