深刻理解JS中的对象(二):new 的工做原理

目录html

  • 序言
  • 不一样返回值的构造函数
  • 深刻 new 调用函数原理
  • 总结
  • 参考

1.序言es6

深刻理解JS中的对象(一):原型、原型链和构造函数 中,咱们分析了JS中是否一切皆对象以及对象的原型、原型链和构造函数。在谈到构造函数时,应该有注意到箭头函数是不能做为构造函数的,也就是不能使用 new 关键字调用箭头函数,这是为何呢?咱们将在本篇深刻讨论剖析对象的构造(new)的工做原理。编程


2.不一样返回值的构造函数浏览器

先看几个示例:app

(1)没有 return 的构造函数函数

function Foo(x) {
  this.x = x
}

var foo = new Foo(10)

console.log(foo.x) // 10

(2) return 一个 object 的构造函数post

function Foo(x) {
  this.x = x
  
  return { y: 20 }
}

var foo = new Foo(10)

console.log(foo) // { y: 20 }
console.log(foo.x) // undifined
console.log(foo.y) // 20

(3) return 一个非 object 的构造函数测试

function Foo(x) {
  this.x = x
  
  return 20
}

var foo = new Foo(10)

console.log(foo.x) // 10

简单分析一下:this

第(1)中状况中,在构造函数中,没有任何显式的 return,最终返回的是 this 值。es5

第(2)种状况中,在构造函数中,彷佛this被舍弃掉了,最终返回的是显式 return 的 object。

第(3)中状况中,在构造函数中,虽然显式 return 了一个非对象的 number,但彷佛被舍弃掉了,最终返回的是 this 值。

从上述状况能够得出,构造函数显式的返回了对象类型的值,会影响最终建立的对象。要弄明白这是为何,咱们就须要明白 new 调用函数到底作了些什么操做。


3.深刻 new 调用函数原理

咱们来看看 EcmaScript 5.1标准的规定,了解一下 new 运算符 的规范。

针对有无参数进行执行提供了两种规范,因为二者区别很小,这里只选取无参规范分析:

产生式 NewExpression : new NewExpression 按照下面的过程执行 :

  1. 令 ref 为解释执行 NewExpression 的结果 .
  2. 令 constructor 为 GetValue(ref).
  3. 若是 Type(constructor) is not Object ,抛出一个 TypeError 异常 .
  4. 若是 constructor 没有实现 [[Construct]] 内部方法 ,抛出一个 TypeError 异常 .
  5. 返回调用 constructor 的 [[Construct]] 内部方法的结果 , 按无参数传入参数列表 ( 就是一个空的参数列表 ).

简单解析:

第1~3步,主要是从引用类型中获得一个对象真正的值(constructor),并判断其类型是否是一个对象。

第4步,判断构造函数是否实现了 [[Construct]] 内部方法,若是没有则抛出异常。

第5步,调用构造函数的 [[Construct]] 内部方法,并返回其结果。


解答第一个问题:箭头函数为何不能做为构造函数?

箭头函数恰好符合上述第4步中的状况,其没有实现 [[Construct]]方法,如下来自ES6中 Arrow functions 规范参考:

An arrow function is different from a normal function in only two ways:

  • The following constructs are lexical: arguments, super, this, new.target
  • It can’t be used as a constructor: Normal functions support new via the internal method [[Construct]] and the property prototype. Arrow functions have neither, which is why new (() => {}) throws an error.

在浏览器中测试用 new 调用箭头函数报错,以下图:

new 调用箭头函数报错


解答第二个问题:为何构造函数显式的返回了对象类型的值会影响最终建立的对象?

从 new 运算符的规范来看,用 new 调用函数 F,至关于触发 F 的 [[Construct]] 内部方法,因此咱们须要再看看 EcmaScript 5.1标准中的 [[Construct]] 的规范

当以一个可能的空的参数列表调用函数对象 F 的 [[Construct]] 内部方法,采用如下步骤:

  1. 令 obj 为新建立的 ECMAScript 原生对象。
  2. 依照 8.12 设定 obj 的全部内部属性。
  3. 设定 obj 的 [[Class]] 内部属性为 "Object"。
  4. 设定 obj 的 [[Extensible]] 内部属性为 true。
  5. 令 proto 为以参数 "prototype" 调用 F 的 [[Get]] 内部属性的值。
  6. 若是 Type(proto) 是 Object,设定 obj 的 [[Prototype]] 内部属性为 proto。
  7. 若是 Type(proto) 不是 Object,设定 obj 的 [[Prototype]] 内部属性为 15.2.4 描述的标准内部的 Object 的 prototype 对象。
  8. 以 obj 为 this 值, 传递给 [[Construct]] 的参数列表为 args,调用 F 的 [[Call]] 内部方法,令 result 为调用结果。
  9. 若是 Type(result) 是 Object,则返回 result。
  10. 返回 obj

简单解析:

第1~7步,主要建立了一个原生对象 obj,并给这个 obj 设定各类属性(包括 [[Prototype]] 内部属性,即对象的原型)。

第8步,至关于 result = F.[[Call]].apply(obj, args),为了更清楚 [[Call]] 内部方法作了些什么,将在下面从规范层次作出解读。

第九、10步,就是判断 result 的类型是否是对象?若是是对象,则返回 result;若是不是,则返回 obj。


EcmaScript 5.1标准中的 [[Call]] 的规范

当用一个 this 值,一个参数列表调用函数对象 F 的 [[Call]] 内部方法,采用如下步骤:

  1. 用 F 的 [[FormalParameters]] 内部属性值,参数列表 args,10.4.3 描述的 this 值来创建 函数代码 的一个新执行环境,令 funcCtx 为其结果。
  2. 令 result 为 FunctionBody(也就是 F 的 [[Code]] 内部属性,即函数 F 自身)解释执行的结果。若是 F 没有 [[Code]] 内部属性或其值是空的 FunctionBody,则 result 是 (normal, undefined, empty)。
  3. 退出 funcCtx 执行环境,恢复到以前的执行环境。
  4. 若是 result.type 是 throw 则抛出 result.value。
  5. 若是 result.type 是 return 则返回 result.value。
  6. 不然 result.type 一定是 normal。返回 undefined。

简单解析:首先,建立根据相关参数和属性建立一个新的执行上下文,而后执行函数 F 的代码,并令 result 为其调用结果, 而后退出当前执行上下文,最后根据 result.type 返回对应的值。(实质上就是执行了一遍函数,返回其结果)


所以,咱们能够对上面所列举的三个不一样返回值的构造函数的示例一个合理的解释了:

new 调用构造函数,若是构造函数中显式的 return 了值而且其类型是一个对象,那么这个值将替代建立的原生对象 obj 做为最终返回值,不然最终将返回建立的原生对象 obj。


4.总结

new 调用函数 F:

  1. 获取函数 F 引用的真正的值 constructor,若是其不是对象或其没有实现 [[Construct]] 内部方法,都会抛出异常
  2. 返回调用 constructor 的 [[Construct]] 内部方法的结果
    1. 新建立一个 ES 原生对象 obj
    2. 为 obj 设置各类属性(包括原型属性等)
    3. 令 result = constructor.[[Call]].apply(obj, args) ,其中 args 是传递给 [[Construct]] 的参数列表,[[Call]] 至关于函数 F 自身
    4. 若是 result 的类型是对象,则返回 result,不然返回 obj

5.参考

深刻理解JavaScript系列(18):面向对象编程之ECMAScript实现(推荐)

详解 JS 中 new 调用函数原理

ECMAScript5.1中文版

ES6 - Arrow functions

相关文章
相关标签/搜索