你不知道的JavaScript-对象篇

1,838 阅读4分钟

本文正在参加「金石计划 . 瓜分6万现金大奖」 ”

前言

已知:对于默认的取值操作来说,如果无法在对象本身找到需要的属性,就会继续访问对象的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

使用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

它的原理:创建一个新对象,把它关联到我们指定的对象上去。

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变量的场景,从內部来说,我们的实现遵循的就是委托设计模式。