原型链之前一直都不是很理解,这两天把《你不知道的JavaScript》和《JavaScript高级程序设计》的原型链那章看完后有所理解,在这里先记下来,加深印象。
什么是原型对象
要讲清楚什么是原型链需要从原型对象开始谈,那么什么是原型对象呢?《JavaScript高级程序设计》中是这样讲的:
无论什么时候,只要创建了一个新函数,就会根据一组特定的规则为该函数创建一个prototype属性,这个属性指向函数的原型对象。
简单来说,原型对象也是对象,但是通过原型对象可以实现对象的属性继承。
这里用《JavaScript高级程序设计》这本书上的demo来解释一下:
function Person () {
}
Person.prototype.name = 'Nicholas'
Person.prototype.age = 29
Person.prototype.job = 'Software Engineer'
Person.prototype.sayName = function () {
console.log(this.name)
}
var person1 = new Person()
var person2 = new Person()
这里声明了一个Person函数,没有定义任何属性;但是在Person的原型对象里定义了name,age,job属性和sayName方法。之后创建了两个Person的实例对象,person1和person2。在这里构造函数,原型对象,实例对象三者的关系用一张图片表示就是
(图片来源谷歌,侵删)
如图所示,Person的prototype指针指向它的原型对象,实例对象的[[Prototype]]指针也指向它的原型对象。这里简单说明一下什么是[[Prototype]]指针:
调用构造函数创建一个新实例之后,该实例的内部将包含一个指针(内部属性),指向构造函数的原型对象,这就是[[Prototype]]指针。
现在,person1和person2的[[Prototype]]都指向了Person.prototype,这样的话Person.prototype里的方法和属性是被person1和person2共用的。
如何证明他们共用Person.prototype里面的属性和方法?请执行下面的语句:
person1.sayName() // 'Nicholas'
person2.sayName() // 'Nicholas'
有人说了,你这初始化的name属性只有一个,执行的结果当然都一样啊。那么请再试试下面这句:
console.log(person1.sayName === person2.sayName) //true
结果很明显,person1.sayName和person2.sayName指向的是同一个方法。到这里可能有人会疑惑:一开始的代码中Person函数里并没有定义任何属性和方法,为什么person1和person2能执行sayName方法?那么这就要来谈谈对象属性调用的过程了。
以上面代码为例,当你执行person1.name时,解析器会开始查找person1中有没有name属性,如果找到了则返回属性值;如果没找到,则在person1的原型对象中继续找,找到了则返回属性值;如果还没找到,就沿着原型链往上继续找,如果最终还是没找到就返回undefined。
到这里大家也明白了多个对象实例共享原型对象的属性和方法的基本原理了,那么有人又会问了,如果我通过给实例对象属性赋值能不能重写原型对象里的属性和方法呢?
答案是不行的,在实例对象中对原型对象中的同名属性赋值会屏蔽原型对象中的属性。简单解释就是,对person1中的name属性赋值会直接在person1中添加name属性。但是有两种情况下,当name属性不存在于person1中而存在于原型对象中时,直接给person1.name赋值会有不一样事情发生:
1.当原型对象中的name属性标记为只读(writable: false)时,对name属性的赋值不会在person1添加name属性,也不会修改原型对象中的name属性,在严格模式下还会报错。
2.当原型对象中的name属性是一个setter,那么对person1中的name属性执行赋值语句就会调用setter,但name不会被添加到person1中。
这部分如果不懂什么是只读和setter,大家可以去看一下Object.defineProperty。
什么是原型链
其实讲到这里,原型对象是什么已经基本清楚了。那么原型链就很简单了,继续上面的demo:
function Parent () {
this.parentName = 'noOne'
}
function Person () {
this.name = 'Nicholas'
}
Person.prototype = new Parent()
var person1 = new Person()
这个例子中,我们把Parent的实例对象赋给了Person的原型对象。Person.prototype中的[[Prototype]]此时指向了Parent.prototype。举一反三,Parent.prototype也可以是另一个原型的实例对象,这样不断地层层递进便构成了原型链。
原型链的主要作用便是实现继承,这部分后续的文章我会继续讲。
本人经验尚浅,目前对于前端仍在不断摸索和学习,文章如有错误,欢迎各位指正。最后附上本人博客地址和原文链接,希望能向各位多多学习。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。