前言
ECMAScript标准是深入学习JavaScript原理最好的资料,没有其二。
通过增加对ECMAScript语言的理解,理解javascript现象后面的逻辑,提升个人编码能力。
欢迎关注和订阅专栏 重学前端-ECMAScript协议上篇
前戏
问:new 后面跟着的是什么呢?
答:是函数对象ECMAScript Function Objects。
这不还有 class 嘛?
javascript
typeof class {} // 'function'
从本质上讲,class 是function 的一种语法糖,简化了编程的语法而已。
本章节和本册都不会深入 class, class 是下册的内容。
函数对象 (简单了解)
class 类本质也是函数对象,所以下面的会出现很多与class相关的字段。
协议对应的内容在 Table 30: Internal Slots of ECMAScript Function Objects。
不用记,有个印象即可。
Internal Slot | Type | Description |
---|---|---|
[[Environment]] | 环境记录 Environment Record | 函数创建时关联的环境记录 |
[[PrivateEnvironment]] | Environment Record 或者 null | 如果此函数在语法上不包含在class中,则为null。当评估函数代码时,用作内部类的外部私有环境。何为内部类,表格下方有示例。 |
[[FormalParameters]] | 解析节点 Parse Node | 形参列表 |
[[ECMAScriptCode]] | 解析节点 Parse Node | 函数对象对应的解析节点 |
[[ConstructorKind]] | BASE or DERIVED | DERIVED为派生类构造函数,会进行super调用。 |
[[Realm]] | 领域记录 Realm Record | 提供内在对象 |
[[ScriptOrModule]] | 脚本记录Script Record 或者 模块记录Module Record | 函数对象换脸的模块脚本或者普通脚本。 |
[[ThisMode]] | LEXICAL, STRICT, or GLOBAL | 定义了函数中this引用在形式参数和函数体内部的解释规则。LEXICAL:箭头函数STRICT:严格模式Global:非严格模式下,如果函数内的this值为undefined或null |
[[Strict]] | a Boolean | 如果这是一个严格函数,则为 true; 如果这是一个非严格函数,则为 false。 |
[[HomeObject]] | an Object | 如果函数使用了super关键字,这个对象的[[GetPrototypeOf]]内部方法会提供一个对象,该对象作为super属性查找的起始点 |
[[SourceText]] | 一串Unicode码点序列 | 函数对应的原文本 |
[[Fields]] | ClassFieldDefinition Records 列表 | 如果该函数是一个类(class),则这部分是一个记录列表,用于表示类的非静态字段(成员变量)及其对应的初始化器(初始化表达式) |
[[PrivateMethods]] | PrivateElements 列表 | 如果该函数是一个类(class),这部分则是一个列表,代表了类的非静态私有方法和访问器。 |
[[ClassFieldInitializerName]] | a String, a Symbol, a Private Name, or EMPTY | 当函数是用来初始化类中某个字段的,默认情况下,会提供一个名称来标识该字段以进行特定的初始化过程;如果不是用于字段初始化,则此字段留空。 |
[[IsClassConstructor]] | a Boolean | 指示函数是否为类构造函数。(如果为真,调用函数的[[ Call ]]将立即抛出 TypeError 异常。) |
内部类示例:
javascript
class OuterClass {
constructor() {
// 内部类
class InnerClass {
innerMethod() {
console.log("Inner method");
}
}
this.innerInstance = new InnerClass();
}
outerMethod() {
this.innerInstance.innerMethod();
}
}
[[IsClassConstructor]]
来识别是普通的函数还是class,而[[ConstructorKind]]
是来识别是不是派生类。
回顾一下 函数对象 Function Objects 另外两个特别的内置方法

一个 [[Call]]
管调用, 一个是 [[Construct]]
管构造,即可以被new。
什么能 new
答案:有内部方法[[Construct]]
的函数。
协议内部就是通过 IsConstructor 检查 是不是有 [[Construct]]
内部方法。

非函数对象自然没有这个内部方法,当然也不是所有函数对象都有这个方法。
那函数对象的 [[Construct]]
这个内部方式又是什么时候被设置上的呢?当然是之前章节函数实例化 InstantiateFunctionObject 的时候 。
InstantiateFunctionObject 这个抽象操作把 函数对应的解析节点转为 函数对象Function Object
, 根据函数类型不同,解析逻辑也是不同的
- InstantiateOrdinaryFunctionObject 实例化普通函数
- InstantiateGeneratorFunctionObject 实例化生成器函数
- InstantiateAsyncGeneratorFunctionObject 实例化异步生成器函数
- InstantiateAsyncFunctionObject 实例化异步函数
普通函数可以new
普通函数实例化对应协议内容 InstantiateOrdinaryFunctionObject
普通函数实例化核心三步
- 创建普通函数对象
- 设置函数名
- 制作构造函数:设置 [[Construct]]属性和原型

制作构造函数是通过 MakeConstructor来实现的。

MakeConstructor 这个过程除了设置 [[Construct]]
,此外脚本产生的函数,还设置了 原型prototype, 以及其constructor
属性。


生成器函数,异步函数和异步生成器函数
这三种函数都是不可以被new的,实例化的过程 都没有 MakeConstructor 的过程
- InstantiateGeneratorFunctionObject 实例化生成器函数

- InstantiateAsyncGeneratorFunctionObject 实例化异步生成器函数

- InstantiateAsyncFunctionObject 实例化异步函数

注意, 异步函数自身是没有设置过 原型(prototype)的
javascript
async function asyncFun(){}
Object.getOwnPropertyDescriptor(asyncFun, "prototype") // undefined
function *genFun(){}
Object.getOwnPropertyDescriptor(genFun, "prototype") // {value: Generator, writable: true, enumerable: false, configurable: false}
async function asyncGenFun(){}
Object.getOwnPropertyDescriptor(genFun, "prototype") // {value: Generator, writable: true, enumerable: false, configurable: false}
这三种函数没有 [[Construct]]
内部字段,自然不能被 new
javascript
async function asyncFun(){}
new asyncFun(); // Uncaught TypeError: asyncFun is not a constructor
function *genFun(){}
new genFun() // Uncaught TypeError: genFun is not a constructor
async function asyncGenFun(){}
new asyncGenFun(); // Uncaught TypeError: asyncGenFun is not a constructor
箭头函数 和 异步箭头函数
箭头函数部分对应协议 15.3 Arrow Function Definitions, 实例化的逻辑位于 15.3.4 Runtime Semantics: InstantiateArrowFunctionExpression。

其也没有设置 [[Construct]]
, 所以呢,你懂的

异步箭头函数是类似的


属性方法
class 原型方法定义和对象的方法定义 对应协议内容 15章ECMAScript Language: Functions and Classes 的 第四节 15.4 Method Definitions,对应的解析节点是 MethodDefinition。

普通方法属性
普通方法底又都走的是 DefineMethod,其函数实例化过程并未设置 [[Construct]]。


对象字面量初始化时,属性定义的逻辑是包含 MethodDefinition。

一起从解析节点来看看 var a = { funA(){} }
这行代码对应的解析节点大致如下:

同样的 class classA { method(){} }
对应的解析节点大致如下:

从上面的解析节点也可以知道,对象上的普通方法属性和class上的普通方法属性 的节点类型都是 MethodDefinition, 只不过 ClassElementName , FunctionBody 以及 UniqueFormalParameters 不同。
异步方法,迭代器方法和异步迭代器方法属性 也是类似的情况。
那么再通过一段代码升华一下:
javascript
var obj = {
objFun(){},
}
obj.AssignmentFun = function (){};
new obj.AssignmentFun(); // obj.AssignmentFun {}
new obj.objFun(); // Uncaught TypeError: obj.objFun is not a constructor
你会发现,同样是对象属性,后赋值而产生的方法属性,是可以被new的,随着字面量产生的却是不可以。
至于为什么,大脑 + 小脑一起想一想。
异步方法,迭代器方法和异步迭代器方法属性
这三种方法(函数)和不作为属性时表现一致,三种方法属性都未设置 [[Construct]]
,所以都不能被new
class 属性:
javascript
class classA {
*generatorMethod() {}
async asyncMethod() {}
async*asyncGeneratorMethod() {}
}
const instanceA = new classA();
// Uncaught TypeError: instanceA.generatorMethod is not a constructor
new instanceA.generatorMethod();
//
// TypeError: instanceA.asyncMethod is not a constructor
new instanceA.asyncMethod();
// Uncaught TypeError: instanceA.asyncGeneratorMethod is not a constructor
new instanceA.asyncGeneratorMethod();
对象属性:
javascript
const objA = {
*generatorMethod() {},
async asyncMethod() {},
async*asyncGeneratorMethod() {},
}
// Uncaught TypeError: objA.generatorMethod is not a constructor
new objA.generatorMethod();
//
// TypeError: objA.asyncMethod is not a constructor
new objA.asyncMethod();
// Uncaught TypeError: objA.asyncGeneratorMethod is not a constructor
new objA.asyncGeneratorMethod();
下面就是各自的对应的协议部分:
迭代器方法属性

异步迭代器方法属性

异步方法属性

箭头函数 和 异步箭头函数 属性方法
不作为属性时表现一致,方法属性都未设置 [[Construct]]
,所以都不能被new
javascript
var obj = {
arrowFun: () => {},
arrowAsycFun: async ()=> {}
}
new obj.arrowFun(); // Uncaught TypeError: obj.arrowFun is not a constructor
new obj.arrowAsycFun(); // Uncaught TypeError: obj.arrowAsycFun is not a constructor
class 相关的函数
class 自然是能new的,细节在专题会讨论。
需要注意的点
- class的静态方法是不可以被new的
javascript
class classA {
constructor(){
}
static staticMethod(){
}
get getFun(){
return function fun(){}
}
arrowFun = ()=> {
}
commonMethod(){
}
}
new ClassA.staticMethod(); // VM92:1 Uncaught TypeError: classA.staticMethod is not a constructor
const ins = new classA();
new ins.constructor(); // classA {arrowFun: ƒ}
new ins.arrowFun(); // Uncaught TypeError: ins.arrowFun is not a constructor
new ins.getFun // fun {}
new 的 逻辑
new 操作的对应协议 The new Operator 。
EvaluateNew ( constructExpr, arguments )

调用链路:
- 13.3.5.1.1 EvaluateNew ( constructExpr, arguments )
处理参数和检查是不是一个构造函数 - 7.3.14 Construct ( F [ , argumentsList [ , newTarget ] ] )
检查newTarget 和 argumentsList。(newTarget和class,Reflect, Proxy相关,也许,下册会单独说) - 10.2.2 [[Construct]] ( argumentsList, newTarget )
调用函数对象上的[[Construct]]
方法。
前面的逻辑基本都是做检查和参数处理,核心还是 函数对象的 [[Construct]]
[[Construct]]的逻辑
对应者协议的 10.2.2 [[Construct]] ( argumentsList, newTarget ), 重点看红色部分。

- 非派生类的情况下,会新建一个对象作为 this 的值(派生类是由父类创建的)
- this 的值是执行时确定的,会尝试在词法环境记录上去绑定this的值,也是有例外的,如果是箭头函数,那么不会进行绑定,当然箭头函数是不可以被new的。
- 构造函数会被执行,为返回的对象添加属性以及其他操作
- 返回值大有讲究
- 如果有return语句
- 返回的是对象,直接返回
- 如果不是对象且不是派生类,返回 this
- 如果返回的不是 undefined , 抛出TypeError
- 不是return语句
- 如果this 不是 对象, 抛出异常
- 返回 this
- 如果有return语句
此时,再回顾一下,网传的new做了什么
- 创建一个新对象
- 设置原型
- 设置this,执行构造函数中的代码(为这个新对象添加属性)
- 返回新对象
基本大差不差,稍微提一下
- 如果是衍生类,this 是在 父类中创建的
- 返回的不一定是内部创建的新对象
返回值
为了更好的理解返回值,必须进一步了解 函数对象内部字段 [[ConstructorKind]], 也是逻辑描述中kind。
Table 30: Internal Slots of ECMAScript Function Objects。
Internal Slot | Type | Description |
---|---|---|
[[ConstructorKind]] | BASE or DERIVED | DERIVED为派生类构造函数,会进行super调用。 |
因为普通函数和非衍生类,值是BASE, OK, 知道这么多就够了, 一起来练习练习。
示例1
javascript
function A(){
this.a = 'a'
return undefined
}
new A()
提示:

示例2
javascript
function A(){
this.a = 'a'
}
new A()
提示:

示例3
javascript
class Super {
constructor(){}
}
class Suber extends Super{
constructor(){
super()
this.a = 'a'
return undefined
}
}
new Suber()
提示:

示例4
javascript
class Super {
constructor(){}
}
class Suber extends Super{
constructor(){
super()
this.a = 'a'
return 1
}
}
new Suber()
提示:

小结
- 有内部方法
[[Construct]]
的函数 可以被 new (class 是 function 的一种语法糖) - 函数的
[[Construct]]
方法是 实例化时被创建的- 只有普通方法有
[[Construct]]
- 不是所有普通方法都有
[[Construct]]
- 只有普通方法有
- 网传四步曲 大致描述了整个 new 的过程
- 创建一个新对象
- 设置原型
- 设置this,执行构造函数中的代码(为这个新对象添加属性)
- 返回新对象
- return 的值大有玄机