已知:对于默认的取值操作来说,如果无法在对象本身找到需要的属性,就会继续访问对象的prototype链,如下例
- var anotherOnject ={
- a:2
- }
- var myObject = Object.create(anotherOnject)
- console.log(myObject.a); //2
- 复制代码
稍后我会介绍object.create()的原理,现在只需要知道它会创建一个对象并把这个对象的prototype关联到指定对象上。
也就是说现在myObject对象的prototype关联到了anotherObject。显然myObject.a并不存在,但尽管如此,属性访问仍然成功地找到了a的值2。如果还没有找到,那就一直在prototype链上持续查找下去,尽管尽头是undefined。
使用for...in遍历对象时的原理和查找prototype链基本类似,任何可以通过原型链访问到的属性都会被枚举出来。使用in操作符来检查属性在对象中是否存在,同样会查找对象的整条原型链(无论是否可枚举)
- var anotherObject ={
- a:2
- }
- //创建一个关联到anotherObject的对象
- var myObject = Object.create(anotherObject)
- for (const k in myObject) {
- console.log(k,"k"); //a
- }
- console.log("a" in myObject); //true
- 复制代码
下面来理解一下prototype
我们来验证一下:
- function Foo(){
- }
- var a = new Foo()
- Object.getPrototypeOf(a) = Foo.prototype //true
- 复制代码
最直接的解释就是,a这个对象是在调用new Foo()时创建的,其中的会给a一个內部的prototype链接,关联到Foo.prototype指向的那个对象。
即new Foo()会产生一个新对象a,这个新对象的内部链接关联的是Foo.prototype对象。
实际上,new Foo()这个函数调用并没有直接的创建关联,这个关联只是意外的副作用,new Foo()只是间接的完成了我们的目标(一个对象关联到其他对象的新对象,也就是关联Foo.prototype的对象a)
那么有没有更直接一点的呢?当然,功臣就是Object.create(),顺便说一下原型继承。
- function Foo(){
- this.name=name
- }
- Foo.prototype.myName = function(){
- return this.name
- }
- function Bar(name,label){
- Foo.call(this,name)
- this.label= label
- }
- Bar.prototype=Object.create(Foo.prototype)
- Bar.prototype.myLabel = function(){
- return this.label
- }
- var a = new Bar("a","obj a")
- a.myName()
- a.myLabel()
- 复制代码
以上为典型的原型继承风格。
这段代码核心部分就是Bar.prototype=Object.create(Foo.prototype)。调用object.create()会凭空 创建一个“新对象” 并把新对象内部的prototype关联到你指定的Foo.prototype中(本例子是如此)
换句话说,这代码的意思是,创建一个新的Bar.prototype对象并把它关联到Foo.prototype
ES6开始之后可以直接修改现有的Bar.prototype了
- Object.setPrototypeOf(Bar.prototype,Foo.prototype)
- 复制代码
思考下面的代码:
- function Foo(){
- }
- Foo.prototype.blah=...;
- var a = new Foo()
- 复制代码
我们如何通过内省找出a的祖先是Foo呢?通过instanceof
- a instanceof Foo //true
- 复制代码
Instanceof操作符的左操作数是一个普通的对象,右操作数是一个函数。
instanceof回答的问题是:在a的整条prototype链中是否有指向Foo.prototype的对象?
下面是第二种判断prototype反射的方法,它更加简洁:
- Foo.prototype.isPrototypeOf(a) //true
- 复制代码
同样也是提问Foo.prototype是否出现在a的prototype中? 是的。
我们 也可以直接获取一个对象的prototype链:
- Object.getPrototypeOf(a)
- 复制代码
获取到原型链后也会与Foo.prototype确定一下是否存在。
- Object.getPrototypeOf(a) === Foo.prototype //true
- 复制代码
最后一种,直接使用原型链:
- a.__proto__ = Foo.prototype //true
- 复制代码
关于__proto__的实现大致是这样的:
- Object.defineProperty(Object.prototype,"__proto__",{
- get:function(){
- return Object.getPrototypeOf(this)
- },
- set:function(o){
- Object.setPrototypeOf(this,o)
- return o;
- }
- })
- 复制代码
在object.prototype中设置了key: __ proto __ ,value 为 {get: ,set: } 的键值对。
因此,访问a.__ proto__时,实际上是调用了get函数由于是隐式绑定了this,所以this指向a,所以和Object.getPrototypeOf(a)的结果相同。
_ proto _是可设置属性,之前的代码中使用ES6中object.setPrototypeOf()进行设置。
JavaScript对于双斜线有一个非官方的称呼,叫它笨蛋proto。
现在我们知道了prototype机制就是存在于对象中的一个内部链接,它会引用其他对象。通常来说,这个链接的作用是,如果没有找到需要的属性或者方法引用,引擎就会在继续prototype关联的对象上进行查找,以此类推,这一系列对象的链接被称为原型链。
它的原理:创建一个新对象,把它关联到我们指定的对象上去。
- object.create = function(o){
- function F(){} //创建新对象
- F.prototype = o //与o对象关联
- return new F()
- }
- 复制代码
由于Object.create可以被模拟,所以这个应用非常广泛,出于完整性的考虑,还是举个例子更深刻的理解一下:
- var anotherObject={
- a:2
- }
- var myObject = Object.create(anotherObject,{
- b:{
- enumerable:false,
- writable:true,
- configurable:false,
- value:3
- },
- c:{
- enumerable:false,
- writable:true,
- configurable:false,
- value:4
- }
- })
- 复制代码
使用create将myObject关联到anotherObject上去。
- myObject.hasOwnProperty("a") //false
- myObject.hasOwnProperty("b") //true
- myObject.hasOwnProperty("c") //true
- 复制代码
观察到第一个为什么是false呢?因为它只会分析myObject表面是否有这个对象(如果想更深一步判断的话可以用 “a” in myObject;它可以遍历到原型链中查找是否有a的存在,它会返回true。)
但仍然不影响从myObject上的三个a,b,c属性,因为即使没有在myObject本身上找到,也会去它的上一层原型链中查找是否存在该属性的,直至原型链尽头。
myObject.a; myObject.b; myObject.c这三个变量分别都能找到是2,3,4.
由于a这个变量是去原型链中的anotherObject找到的,这种myObject上本身不存在a变量但是也可以正常工作获取到a变量的场景,从內部来说,我们的实现遵循的就是委托设计模式。