[[Prototype]]
JavaScript中的对象(函数也是对象)有一个非凡的[[Prototype]]内置属性,所谓的原型链就是由它“链”起来的。
属性查找
当援用对象的属性时会触发[[Get]]操作,能够了解为会执行[[Get]]()
,其逻辑是先查找以后对象是否存在该属性,如果存在就应用它。否则就去递归遍历,查找[[Prototype]]属性所援用的对象中是否存在要查找的属性,如果找到则返回,否则直到[[Prototype]]=null时查找完结,此时返回undefined。
在应用for in
遍历对象时原理和查找[[Prototype]]链相似,任何能够通过原型链拜访到并且是enumerable
的属性都会被枚举。应用in操作符来查看属性在对象中是否存在时,会查找对象的整条原型链。
属性设置和屏蔽
上面以myObject.foo = 'bar'
为例来阐明所有可能呈现的状况:
- 如果myObject本身存在foo属性,不论其[[Prototype]]链下层是否存在,都会产生屏蔽景象,会从新赋值。
- 如果myObject本身不存在,[[Prototype]]链上也不存在,则foo属性会被增加到myObject上。
如果myObject本身不存在,[[Prototype]]链上存在,会呈现如下三种状况:
- 如果[[Prototype]]链上的foo为一般数据拜访属性,并且没有被标记为只读,那就会在myObject上增加foo属性,它是屏蔽属性。
- 如果[[Prototype]]链上的foo被标记为只读,如果运行在严格模式下,会抛出一个谬误。否则,这条赋值语句被疏忽,并不会产生属性屏蔽。
- 如果[[Prototype]]链上的foo是一个setter,那么肯定会调用这个setter。foo不会增加到myObject,也不会从新定义foo这个setter。
如果心愿在第二种和第三种下也屏蔽foo,那就不能应用=
操作符来赋值,而是应用Object.defineProperty()
来向myObject增加foo。
上面来个例子领会一下,同时为原型继承做一个铺垫
var person = {name: 'a'};var fn = { getName: function() { return this.name; }};
- 先想一下person为什么能够调用到Object.prototype中定义的办法?
Object被定义为函数,上面会提到,只有是函数都会存在prototype属性,它指向一个对象,被称为原型对象,toString、hasOwnProperty等办法就定义在该原型对象上。var person = {name: 'a'};
执行时会创立一个新的对象,并且底层会先将person的[[Prototype]]属性值设置为Object.prototype,相当于执行Object.setPrototypeOf({name: a}, Object.prototype)
。这样在整个[[Prototype]]链上就能够找到这些办法了。
- 再想一下咱们怎么能使person对象能够调用fn中的getName办法呢?
- 应用Object.assign(person, fn)将fn的getName间接增加到person对象中。
- 应用Object.setPrototypeOf(person, fn),会将person中的[[Prototype]]属性由默认的Object改为指向fn对象,而fn中的[[Prototype]]会指向Object.prototype。在执行person.getName()时会进行属性查找,依据下面提到的规定,在其[[Prototype]]链上能够找到getName办法,其中的this利用的
隐式绑定
。
除此之外,还有另外一种实现形式,其原理和第二条一样:
var fn = { getName: function() { return this.name; }};// 创立一个新对象person,使其[[Prototype]]指向fnvar person = Object.create(fn);person.name = 'a';person.getName(); // 'a'
函数中的prototype
JavaScript中的函数有一种非凡个性:所有函数默认都会领有一个名为prototype
的私有并且不可枚举的属性,他会指向一个对象:这个对象通常被称为该函数的原型。该函数同时存在内置属性[[Prototype]],留神这两者的区别!
同时该prototype对象存在一个叫constructor
的属性,会持有该函数的援用。
这些个性与上面要说的“构造函数”没有任何关系,也就是说只有是函数就有这些个性。
构造函数
JavaScript中把首字母大写的办法称为构造函数,这只是一种约定,同时这也意味着要应用new关键字来调用。
应用new调用函数会执行上面的步骤:
- 创立一个全新的对象。
- 这个新对象会被执行[[Prototype]]连贯。
- 这个新对象会被绑定到函数调用的this。
- 如果函数没有返回其它对象。那么new表达式中的函数调用会主动返回这个对象。
上面是伪代码
function customNew(fn) { var o = {}; var rs = fn.apply(o, [].slice.call(arguments, 1)); Object.setPrototypeOf(o, fn.prototype); return typeof rs === 'undefined' ? o : rs;}function Person(name) { this.name = name;}Person.prototype.getName = function() { return this.name;};var p = customNew(Person, 'JS');p.getName(); // 'JS'console.log(p.constructor === Person); // true
Person的实例能够拜访到getName
和constructor
都是基于“属性拜访”的原理。
继承
继承形式有多种,“红皮书”里也有讲到,最流行的一种是“组合继承”,即“借用构造函数”与“原型继承”组合起的一种形式。
function Foo(name) { this.name = name;}Foo.prototype.myName = function() { return this.name;};function Bar(name, label) { // 借用Foo的构造函数 Foo.call(this, name); this.label = label;}// Bar的原型对象被赋值为一个全新的对象,// 该对象的[[Prototype]]指向Foo.prototype对象Bar.prototype = Object.create(Foo.prototype);// 设置不可枚举Object.defineProperty(Bar.prototype, 'constructor', { enumerable: false, writable: true, configurable: true, value: Bar});// 或者能够用ES6中的setPrototypeOf办法,它只单纯批改Bar.prototype中的[[Prototype]],使之指向Foo.prototype,并不会从新赋值,所以constructor不会失落// Object.setPrototypeOf(Bar.prototype, Foo.prototype);Bar.prototype.myLabel = function() { return this.label;};var a = new Bar("name", "label");console.log(a.myName()); // "name"console.log(a.myLabel()); // "label"console.log(Bar);
如果了解了属性查找及prototype和[[Prototype]],再来了解Js中的继承就容易多了。