目录

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

1.序言

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

2.不同返回值的构造函数

先看几个示例:

(1)没有 return 的构造函数

function Foo(x) {
this.x = x
} var foo = new Foo(10) console.log(foo.x) // 10

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

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

简单分析一下:

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

第(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 调用函数 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]] 内部方法做了些什么,将在下面从规范层次做出解读。

第9、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

深入理解JS中的对象(二):new 的工作原理的更多相关文章

  1. 深入理解JS中的对象(三):class 的工作原理

    目录 序言 class 是一个特殊的函数 class 的工作原理 class 继承的原型链关系 参考 1.序言 ECMAScript 2015(ES6) 中引入的 JavaScript 类实质上是 J ...

  2. 深入理解JS中的对象(一)

    目录 一切皆是对象吗? 对象 原型与原型链 构造函数 参考 1.一切皆是对象吗? 首先,"在 JavaScript 中,一切皆是对象"这种表述是不完全正确的. JavaScript ...

  3. 【学习笔记】六:面向对象的程序设计——理解JS中的对象属性、创建对象、JS中的继承

    ES中没有类的概念,这也使其对象和其他语言中的对象有所不同,ES中定义对象为:“无序属性的集合,其属性包含基本值.对象或者函数”.现在常用的创建单个对象的方法为对象字面量形式.在常见多个对象时,使用工 ...

  4. 和我一起理解js中的事件对象

    我们知道在JS中常用的事件有: 页面事件:load: 焦点事件:focus,blur: 鼠标事件:click,mouseout,mouseover,mousemove等: 键盘事件:keydown,k ...

  5. 怎么理解js中的事件委托

    怎么理解js中的事件委托 时间 2015-01-15 00:59:59  SegmentFault 原文  http://segmentfault.com/blog/sunchengli/119000 ...

  6. 图文结合深入理解 JS 中的 this 值

    图文结合深入理解 JS 中的 this 值 在 JS 中最常见的莫过于函数了,在函数(方法)中 this 的出现频率特别高,那么 this 到底是什么呢,今天就和大家一起学习总结一下 JS 中的 th ...

  7. 浅解析js中的对象

    浅解析js中的对象 原文网址:http://www.cnblogs.com/foodoir/p/5971686.html,转载请注明出处. 前面的话: 说到对象,我首先想到的是每到过年过节见长辈的时候 ...

  8. JavaScript学习12 JS中定义对象的几种方式

    JavaScript学习12 JS中定义对象的几种方式 JavaScript中没有类的概念,只有对象. 在JavaScript中定义对象可以采用以下几种方式: 1.基于已有对象扩充其属性和方法 2.工 ...

  9. JavaScript学习12 JS中定义对象的几种方式【转】

    avaScript学习12 JS中定义对象的几种方式 转自:  http://www.cnblogs.com/mengdd/p/3697255.html JavaScript中没有类的概念,只有对象. ...

随机推荐

  1. H - Tempter of the Bone DFS

    小明做了一个很久很久的梦,醒来后他竟发现自己和朋友在一个摇摇欲坠的大棋盘上,他们必须得想尽一切办法逃离这里.经过长时间的打探,小明发现,自己所在的棋盘格子上有个机关,上面写着“你只有一次机会,出发后t ...

  2. Git敏捷开发--stash命令

    save 执行git stash,默认以commit info保存当前的stash信息 当在某个commit下,执行多次stash时,无法友好地区分每个stash的改动.save 命令可以清晰地标识每 ...

  3. windows下常用快捷指令记忆

    快速打开环境变量窗口 sysdm.cpl --系统设置 快速打开远程桌面程序 mstsc ---Microsoft terminal services client 快速打开事件查看器 eventvw ...

  4. HuggingFace-transformers系列的介绍以及在下游任务中的使用

    内容介绍 这篇博客主要面向对Bert系列在Pytorch上应用感兴趣的同学,将涵盖的主要内容是:Bert系列有关的论文,Huggingface的实现,以及如何在不同下游任务中使用预训练模型. 看过这篇 ...

  5. ISWC 2018概览:知识图谱与机器学习

    语义网的愿景活跃且良好,广泛应用于行业 语义网的愿景是「对计算机有意义」的数据网络(正如 Tim Berners Lee.James Hendler 和 Ora Lassila 在<科学美国人& ...

  6. Java 解析 xml 常见的4中方式:DOM SAX JDOM DOM4J

    Java 四种解析 XML 的特点 1.DOM 解析: 形成了树结构,有助于更好的理解.掌握,且代码容易编写. 解析过程中,树结构保存在内存中,方便修改. 2.SAX 解析: 采用事件驱动模式,对内存 ...

  7. Java 多线程 -- 协作模型:生产消费者实现方式一:管程法

    多线程通过管程法实现生产消费者模式需要借助中间容器作为换从区,还包括生产者.消费者.下面以蒸馒头为列,写一个demo. 中间容器: 为了防止数据错乱,还需要给生产和消费方法加锁 并且生产者在容器写满的 ...

  8. web前端该怎么入门?web前端入门教程(非常详细)

    初学编程的小伙伴经常会遇到的问题,1.没资源 2.没人带 3.不知道从何开始 ,小编也是从新手期过来的,所以很能理解萌新的难处,现在整理一些以前自己学习的一些资料送给大家,希望对广大初学小伙伴有帮助! ...

  9. 一图解析MongoDB

    了解MongoDB,这一张图就够了: 版权所有,转载请注明出处.

  10. Centos史上新版最详细步骤-Linux无脑命令式oracle11g静默安装

    1. 关闭selinux 1.1 sed -i "s/SELINUX=enforcing/SELINUX=disabled/" /etc/selinux/config 1.2 或者 ...