原型和原型链
欢迎关注微信公众号【前端功成屋】分享一些前端技术、面试题、面试技巧等
对于使用过基于类的语言(如 Java 或 C++)的开发人员来说,JavaScript 有点令人困惑,因为它是动态的,并且本身不提供一个 class 实现。(在 ES6 中引入了 class 关键字,但那只是语法糖,JavaScript 仍然是基于原型的)。
什么是原型
JS 中的对象包含了一个 prototype 的内部属性,这个属性所对应的就是该对象的原型。
每个实例对象(object)都有一个私有属性(称之为 __proto__)指向它的构造函数的原型对象(prototype)。该原型对象也有一个自己的原型对象(__proto__),层层向上直到一个对象的原型对象为 null。
根据定义,
null没有原型,并作为这个原型链的最后一个环节。
这个 __proto__ 称作隐式原型 。prototype 称作显式原型。
小结
- 所有引用类型(函数,数组,对象)都拥有
__proto__属性(隐式原型) - 所有函数除了有
__proto__属性之外还拥有prototype属性(显式原型) - 原型对象:每创建一个函数,该函数会自动带有一个
prototype属性,该属性是一个指针,指向一个对象,我们称之为原型对象。 - 函数除了有
__proto__属性之外还拥有prototype属性,我们可以借助构造函数来说明下
function A() {
this.name = ‘tom’;
this.age = 12;
}
A.prototype = {
num: 12
}
var a = new A()
- 实例对象
a只有__proto__(隐式原型),构造函数既有__proto__(隐式原型)也有prototype(显式原型) __proto__和prototype都是一个对象,既然是对象,就表示他们也有一个__proto__- 实例对象
a的隐式原型指向它构造函数的显式原型,指向的意思是恒等于
a.__proto__ === A.prototype
当调用某种方法或查找某种属性时,首先会在自身调用和查找,如果自身并没有该属性或方法,则会去它的 __proto__ 属性中调用查找,也就是它构造函数的 prototype 中调用查找。
ECMAScript 5 中引入了一个新方法 Object.create() 可以调用这个方法来创建一个新对象。新对象的原型就是调用 create 方法时传入的第一个参数。
var a = {a: 1};
var b = Object.create(a);
var c = Object.create(null);
什么是原型链
Object.prototype.b = ‘b’;
function Person() {};
var p = new Person();
p.a // undefined
p.b // ‘b’
- 实例对象
p的隐式原型(__proto__)是一个对象,有两个属性值:constructor和__proto__ p.__proto__.constructor返回的结果为构造函数Personp.__proto__.__proto__.constructor返回的结果为Object()函数
结合上面所讲的显式原型和隐式原型之间的关系,等同与
p.__proto__.__proto__ = Object.prototype
所以 p.b 打印结果为 b,p 没有 b 属性,会一直通过 __proto__ 向上查找,最后当查找到 Object.prototype 时找到,最后打印出 b,向上查找的过程中,找不到 a 属性,所以结果为 undefined,这就是原型链,通过 __proto__ 向上进行查找,最终到 null 结束。
在原型链上查找属性比较耗时,对性能有副作用,这在性能要求苛刻的情况下很重要。
另外,试图访问不存在的属性时会遍历整个原型链。
遍历对象的属性时,原型链上的每个可枚举属性都会被枚举出来。要检查对象是否具有自己定义的属性,而不是其原型链上的某个属性,则必须使用所有对象从 Object.prototype 继承的 hasOwnProperty() 方法。
hasOwnProperty()是 JavaScript 中唯一一个处理属性并且不会遍历原型链的方法。
注意:检查属性是否为
undefined是不能够检查其是否存在的。该属性可能已存在,但其值恰好设置成undefined。
当你执行
var o = new Foo()
JavaScript 实际上执行的是
var o = new Object();
o.__proto__ = Foo.prototype;
Foo.call(o);
小结
查找属性,如果本身没有,则会去 __proto__ 中查找,也就是构造函数的显式原型中查找,如果构造函数的显式原型中也没有该属性,因为构造函数的显式原型也是对象,也有 __proto__,那么会去它的显式原型中查找,一直到 null,如果没有则返回 undefined。
什么是原型继承
Person.prototype 只是一个指针,指向的是原型对象,但是这个原型对象并不特别,它也只是一个普通对象。假设说,这时候,我们让 Person.prototype 不再指向最初的原型对象,而是另一个类
function Person(){};
function Animal() {
this.address = ‘abc’;
};
Person.prototype = new Animal();
var p = new Person();
p.address // ‘abc’
执行 Person.prototype = new Animal() 后,Person 的 prototype 指针指向发生了变化,指向了一个 Animal 实例。
当 p 去访问 address 属性时,js 回先在 p 的实例属性中查找,发现找不到后,就会去 Person 的原型对象上查找。因为 Person 的原型对象已经被我们换成了 animal 实例,所以就会先找 animal 实例的属性。
这就说明,我们可以通过原型链的方式,实现 Person 继承 Animal 的所有属性和方法。
在使用原型继承编写复杂代码之前,理解原型继承模型是至关重要的。
此外,请注意代码中原型链的长度,并在必要时将其分解,以避免可能的性能问题。此外,原生原型不应该被扩展,除非它是为了与新的 JavaScript 特性兼容。
希望对读完本文的你有帮助、有启发,如果有不足之处,欢迎批评指正交流!
欢迎关注微信公众号【前端功成屋】分享一些前端技术、面试题、面试技巧等
辛苦整理良久,还望手动点赞鼓励~
'摘抄'不是单纯的“粘贴->复制”,而是眼到,手到,心到的一字一句敲打下来。
声明:所有转载的文章、图片仅用于作者本人收藏学习目的,被要求或认为适当时,将标注署名与来源。若不愿某一作品被转用,请及时通知本站,本站将予以及时删除。