简介
在上一节上面介绍了原型和原型链,即每个对象拥有一个原型对象,通过 __proto__
指针指向上一个原型 ,并从中继承方法和属性,同时原型对象也可能拥有原型,这样一层一层,最终指向 null
,这种关系被称为原型链(prototype chain)
。
继承
是面向对象编程语言的一大核心功能点,JavaScript
是面向对象的只不过是比较特殊的面向对象的语言。它不像Java
是基于类的面向对象,而javaScript
是基于prototype
的面向对象。
会用一篇文章来介绍什么面向对象,javascript
是怎么实现继承
、封装
、多态
和javascript
面向对象的特殊之处。
原型链继承
JavaScript
对象是动态的属性“包”(指其自己的属性)。JavaScript
对象有一个指向一个原型对象的链。当试图访问一个对象的属性时,它不仅仅在该对象上搜寻,还会搜寻该对象的原型,以及该对象的原型的原型,依次层层向上搜索,直到找到一个名字匹配的属性或到达原型链的末尾。
原型链继承的本质是重写原型对象,代之以一个新类型的实例。将父类的实例作为子类的原型。看下面代码:
1 | function SubType() { |
优点
- 基于原型的方法所有子类都可以复用
缺点
- 多个实例对引用类型的操作会被篡改
- 子类型的原型上的 constructor 属性被重写了
- 创建子类型实例时无法向父类型的构造函数传参
主要分析一下它的缺点暂时不分析它的优点。
引用类型被修改
因为本质上每个实例的__proto__
都会指向构造函数的prototype
,实例上都是保存了一个引用地址,所以当prototype
中的引用类型修改所有实例都会被改变。在上面代码基础上修改如下:
1 | function SubType() { |
在构造函数SubType.prototype
新增Arr
属性并且赋值为['sub', 'subtype', 'Sub']
,通过new
关键字实例化两个实例sub1
、sub1
,当修改了sub2.Arr
的时候,sub1.Arr
的也会被影响。
实例 constructor 被重写
子类型原型上的 constructor
属性被重写, 执行 Sub.prototype = new SubType()
后原型被覆盖,Sub.prototype
上丢失了 constructor
属性, Sub.prototype
指向了 SubType.prototype
,而 SubType.prototype.constructor
指向了 SubType
,所以 Sub.prototype.constructor
指向了 SubType
。
如下图所示:
1 | function SubType() { |
通过Sub.prototype.constrcutor = Sub;
把Sub.prototype.constrcutor
指向Sub
,如果所示:
给子类型原型添加属性和方法必须在替换原型之后,原因在第二点已经解释过了,因为子类型的原型会被覆盖。
属性遮蔽
在Sub.prototype
上添加getName
方法,当调用sub
上得getName
时,访问到的是Sub.prototype.getName
而不是访问到SubType.prototype.getName
,这种情况称为属性遮蔽(property shadowing)。
1 | function SubType() { |
可以通过__proto__
调用原型链上的属性即可。
1 | console.log(sub.__proto__.__proto__.getName()); // undefined |
实现一个 new
1 | function create() { |
在这里不多做赘述了,详细内容我另一片博客javascript 中实现一个自己的 new
总结
- 每个对象拥有一个原型对象,通过
__proto__
指针指向上一个原型 ,并从中继承方法和属性,同时原型对象也可能拥有原型,这样一层一层,最终指向null
,这种关系被称为 原型链。 - 当访问一个对象的属性 / 方法时,它不仅仅在该对象上查找,还会查找该对象的原型,以及该对象的原型的原型,一层一层向上查找,直到找到一个名字匹配的属性 / 方法或到达原型链的末尾null。
- 原型链的构建依赖于
__proto__
,一层一层最终链接到null
。 instanceof
原理就是一层一层查找__proto__
,如果和constructor.prototype
相等则返回true
,如果一直没有查找成功则返回false
。- 原型链继承的本质是重写原型对象,代之以一个新类型的实例。