JavaScript进阶笔记(六):原型链和继承的关系
JavaScript进阶笔记(六):原型链和继承的关系
镇长一、原型链
回忆一下什么是原型链,即每个对象拥有一个原型对象,通过 __proto__
指针指向上一个原型 ,并从中继承方法和属性,同时原型对象也可能拥有原型,这样一层一层,最终指向 null,这种关系被称为原型链(prototype chain)。
原型上的方法和属性并不是复制到新对象中。
1 | function Foo(name) { |
原型上的属性和方法定义在 prototype
对象上,当访问一个属性或者方法时,不仅仅在对象中查找,还会查找对象的原型的原型,一级一级向上查找,直到找到或者原型链的末尾(null)。
当调用 foo.valueOf()
时,方法的查找顺序?
- 首先从 foo 对象中查找
- 如果没有,检查 foo 对象的原型(Foo.prototype) 是否具有该方法。
- 如果没有,检查
Foo.prototype
所指向的原型对象(Object.prototype)是否具有该方法。此时有该方法,被调用。
1.1 prototype
和 __proto__
原型对象的 prototype
是构造函数的属性,__proto__
是实例对象的属性。两个指向同一个对象。
原型链依赖 prototype
还是 __proto__
?
通过上图发现,prototype
并没有构成原型链,他只是指向原型链中的某一处。原型链依赖 __proto__
。
1.2 instanceof 原理及其实现
instanceof
用来检测构造函数的prototype
属性是否出现在对象的原型链中的任意位置。
1 | function A (){} |
instanceof
原理是一层层查找 __proto__
, 如果 constructor.prototype
相等则返回 true,如果没有查找到则返回 false。
知道了原理,模拟实现一个 instanceOf
:
1 | function instance_of (L, R) { // L:左表达式;R:右表达式 |
二、原型链继承
原型链继承的本质是重写原型对象,代之以一个新类型的实例。
1 | function Person () { |
原型链继承方案有以下缺点:
- 多个实例对引用类型的操作会被篡改
- 子类型的原型上的 constructor 属性被重写了
- 给子类型原型添加属性和方法必须在替换原型之后
- 创建子类型实例时无法向父类型的构造函数传参
2.1 问题1
原型链继承方案中,原型实际是另一个类型的实例。如,Student.prototype
变成了 Person
的实例,所以 Person
的实例属性 names
变成了 Student.prototype
的属性。
而原型属性上的引用类型会被所有实例共享,这就导致多个实例可以同时修改。
1 | function Person () { |
2.2 问题二
子类原型上的 constructor 被属性被重写了。
1 | Student.prototype = new Person() // 覆盖原型 |
解决方法:手动重写
1 | Student.prototype.constructor === Student |
2.3 问题三
给子类原型添加属性和方法必须在替换原型之后,不然会被覆盖丢失。
2.4 属性遮蔽
上面的代码中,我们在 Person
的原型中添加一个 eat
方法。那么,在 Student
的原型中添加一个同名的 eat
方法。
此时返回的是 Student
中新增的方法。称这种现象为属性遮蔽。
那么如何访问被遮蔽的属性呢?通过 __proto__
调用原型链上的属性即可。
小结
- 每个对象拥有一个原型对象,通过
__proto__
指针指向上一个原型 ,并从中继承方法和属性,同时原型对象也可能拥有原型,这样一层一层,最终指向 null,这种关系被称为**原型链 ** - 当访问一个对象的属性 / 方法时,它不仅仅在该对象上查找,还会查找该对象的原型,以及该对象的原型的原型,一层一层向上查找,直到找到一个名字匹配的属性 / 方法或到达原型链的末尾(null)。
- 原型链的构建依赖于
__proto__
,一层一层最终链接到 null。 instanceof
原理就是一层一层查找__proto__
,如果和constructor.prototype
相等则返回 true,如果一直没有查找成功则返回 false。- 原型链继承的本质是重写原型对象,代之以一个新类型的实例
其他的继承方案:JS常见的八种继承方案
三、判断数组是否为空的三种方法及其优缺点
Object.prototype.toString.call()、instanceof、Array.isArray()
3.1 Object.prototype.toString.call()
每个继承 Object
的对象都有 toString
方法,如果没有被重写,则返回 [object xxx]
,其中的 xxx
是对象的类型。但是除了 Object
类型之外,其他的类型的 toString 方法,会直接返回内容的字符串。所以使用 call
或者 apply
方法改变 toString
的执行上下文。
1 | let arr = ['Owenli', 'laal'] |
常用于判断浏览器内置对象。
3.2 instanceof
通过判断对象的原型链中是否能找到 prototype。
1 | [] instanceof Array // true |
3.3 Array.isArray()
该方法是在 ES5 中增加的。
1 | Array.isArray([]) // true |
在检测 Array 实例时,Array.isArray
优于 instanceof
因为前者可以检测 iframes