本文已参与「新人创作礼」活动,一起开启掘金创作之路。
首先了解两个概念。(看看就行)
原型:prototype是函数的一个属性,指向一个对象,这个对象用来定义一些需要被实例继承的成员。
原型链:每个 JavaScript 对象都拥有一个[[Prototype]]对象。 获取一个对象的属性时首先会搜索其自身,然后就是它的 [[Prototype]]对象,之后再搜索此[[Prototype]]对象的 [[Prototype]]对象,直到找到这个属性或者搜索链条达到终点。这个类似链条的查找过程被称为原型链。
通过以下的例子来理解(建议自己在控制台运行):
// 定义一个构造函数(使用new运算符实例化对象的函数可以称为构造函数,实际就是一个函数)
function Student (name, age) {
this.name = name
this.age = age
}
// 在函数的原型对象上添加一个函数
Student.prototype.study = function () {
console.log('study hard and make progress every day')
}
// 使用new操作符获得这个函数的实例对象
let s1 = new Student('jack', 3)
console.log(s1)
s1.study()
/** 打印的结果,实例本身并没有study
Student {name: 'jack', age: 3}
age: 3
name: "jack"
[[Prototype]]: Object
study: ƒ ()
constructor: ƒ Student(name, age)
[[Prototype]]: Object
constructor: ƒ Object()
hasOwnProperty: ƒ hasOwnProperty()
isPrototypeOf: ƒ isPrototypeOf()
propertyIsEnumerable: ƒ propertyIsEnumerable()
toLocaleString: ƒ toLocaleString()
toString: ƒ toString()
valueOf: ƒ valueOf()
__defineGetter__: ƒ __defineGetter__()
__defineSetter__: ƒ __defineSetter__()
__lookupGetter__: ƒ __lookupGetter__()
__lookupSetter__: ƒ __lookupSetter__()
__proto__: (...)
get __proto__: ƒ __proto__()
set __proto__: ƒ __proto__()
study hard and make progress every day
*/
指南:实例对象的[[Prototype]]属性指向函数的prototype函数。
这里为什么能访问到study呢?其实,当你访问s1 中的一个属性,浏览器首先会查看s1 中是否存在这个属性。如果 s1 不包含属性信息,那么浏览器会在 s1 的 [[Prototype]] 中进行查找 (同 Student.prototype). 如属性在 s1 的 [[Prototype]] 中查找到,则使用这个属性。
否则,如果 s1 中 [[Prototype]] 不具有该属性,则检查s1 的 [[Prototype]] 的 [[Prototype]] 是否具有该属性。
如果属性不存在 s1 的 [[Prototype]] 的 [[Prototype]] 中, 那么就会在s1 的 [[Prototype]] 的 [[Prototype]] 的 [[Prototype]] 中查找。然而,这里存在个问题:s1 的 [[Prototype]] 的 [[Prototype]] 的 [[Prototype]] 其实不存在。在 [[Prototype]] 的整个原型链被查看之后,这里没有更多的 [[Prototype]] , 浏览器断言该属性不存在,并给出属性值为 undefined 的结论。
注意区分这两个属性:prototype 是函数的属性,每个函数都有prototype属性;[[Prototype]]是实例对象的原型,本身也是一个属性,指向了构造函数的prototype 属性。
在new的过程中,实际上做了 s1.[[Prototype]] = Student.prototype 这一步,
但是由于 [[Prototype]] 不能直接访问,官方推荐这样写:Object.create(Student.prototype);另外 s1.proto = Student.prototype也是可以执行,但是不推荐。简而言之,prototype和[[Prototype]]都指向了同一个对象。
注意:对象的原型在 JavaScript 语言标准中用 [[Prototype]] 表示,可以使用ES6的 Object.getPrototypeOf() 获取对象的原型。然而,大多数现代浏览器还是提供了一个名为 __proto__ (前后各有2个下划线)的属性,其包含了对象的原型。你可以尝试输入 s1.__proto__ 和 s1.__proto__.__proto__,看看代码中的原型链是什么样的!__proto__是弃用的,尽量不在生产环境使用。
接着讲 prototype 有什么属性和各自的作用
首先study是我们自己添加的函数。
constructor 属性指向了构造函数本身,有两个用途:
1、假如我们现在只有对象(obj),没有构造函数,可以用new obj.constructor()创建另一个对象实例,但是一般不用。2、获取某个对象的构造器名字,可以用obj.constructor.name。
[[Prototype]] 指向了用于创建函数对象的构造函数的prototype属性(有点拗口),例如Student函数是Object构造函数创建的,也就是指向了Object.prototype;作用:用于继承属性或者方法,例如:s1拥有Object.prototype的方法。
总结一波:prototype通俗的讲就是函数与生俱来的属性;prototype.constructor指向了函数本身;[[Prototype]]是对象的属性,指向了其构造函数的prototype属性;原型链就是一连串的[[Prototype]],无限套娃;此外__proto__可以说是旧版的[[Prototype]],官方申明已废弃。
现在的__proto__指的是Object.prototype的 __proto__ 属性,它是一个访问器属性(一个 getter 函数和一个 setter 函数), 暴露了通过它访问的对象的内部[[Prototype]](一个对象或 null)。
也就是说s1.__proto__拿到的就是s1.[[Prototype]]。
这几个概念确实容易混,打开控制台输出看看好一点。最后就是很多老的博客把__proto__当做是对象的原型,新的浏览器是[[Prototype]]代表对象的原型。原型链也是在没有了[[Prototype]]属性的时候就没了。还有原型链的末端是null。
有问题欢迎留言