你所不知道的JavaScript③--原型及周边
关键词
原型、原型链、继承
原型、原型链
__proto__
和prototype的关系:__proto__
和constructor
是对象独有的,prototype
是函数独有的。
在js中我们使用构造函数来新建一个对象,每一个构造函数的内部都有一个prototype属性,这个属性是一个对象,这个对象包含了可以由该构造函数的所有实例共享的方法和属性。当我们使用构造函数新建一个对象之后,在这个对象的内部将包含一个指针,这个指针指向构造函数的prototype属性对应的值,在es5中这个指针被称为对象的原型。一般来说不能获取到这个值,但是浏览器下载都实现了proto
属性让我们来访问,但是最好是不要使用这个属性,因为他不是规范的,最好使用Object.getPrototypeOf()
来获取对象的原型
当我们访问一个对象的属性的时候,如果这个对象内部不存在这个属性,那么他就会去他的原型对象里面找这个属性,这个原型对象又会有自己的原型,于是就一直找下去,也就是原型链的概念。原型链的尽头一般来说是Object.prototype
所以这就是我们新建的对象为什么能够使用toString
等方法的原因
特点:js的对象都是通过引用来传递的,我们创建的每个新对象实体中并没有一份属于自己的原型副本。当我们修改原型的时候,与之相关的对象也会继承这一改变。
- 原型,一个简单的对象,用于实现对象的属性继承。
- 构造函数 可以通过new来创建对象的一个函数
- 实例 通过构造函数和new创建出来的对象。通过
__proto__
指向原型,通过constructor
指向构造函数。
以Object
为例,他是一个构造函数,因此可以用它创建实例
1 | // 实例 |
则此时, 实例为instance, 构造函数为Object,我们知道,构造函数拥有一个prototype的属性指向原型,因此原型为:
1 | // 原型 |
三者关系:
实例.__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 | class Person {} |
组合继承
组合继承是最常用的继承方式
1 | function Parent(value){ |
- 通过call来继承父类的属性,然后改变子类的原型来继承父类的函数。
- 优点:构造函数可以传参,不会和父类引用属性共享,可以复用父类的函数。
- 缺点:继承父类的时候调用了两次父类的构造函数,把父类不必要的属性也给继承了。内存上面浪费
寄生组合继承
这种方法对组合继承进行了优化,组合继承的缺点在于继承父类函数时调用了构造函数,我们只需要优化掉这点即可。
1 | function Parent(value) { |
以上继承将父类的原型赋值给了子类,并且将构造函数设置成了子类,这样解决了无用的父类属性问题,还能正确的找到子类的构造函数。
Class继承
以上两种方式都是通过原型去解决的,在es6中我们可以通过class去实现继承,并且实现起来很简单
1 | class Parent{ |
class实现继承的核心在于使用extends表明继承自哪个父类,并且在子类构造函数中必须调用super
super可以看作Parent.call(this,value)
其实这样做还是有缺陷的,比如父类的静态方法还是不能继承。
1 | //寄生组合继承 |
而class继承则可以1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21class 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)