12. ECMAScript 6 中的可调用实体
本章介绍如何在 ES6 中正确使用可调用实体(通过函数调用、方法调用等)。
- 12.1. 概述
- 12.2. ES6 中的调用方式
- 12.2.1. 可以在任何地方进行的调用
- 12.2.2. 通过
super
进行的调用仅限于特定位置
- 12.2.3. 非方法函数与方法
- 12.3. 使用可调用实体的建议
- 12.3.1. 首选箭头函数作为回调
- 12.3.2. 首选函数声明作为独立函数
- 12.3.3. 首选方法定义作为方法
- 12.3.4. 方法与回调
- 12.3.5. 避免在 ES6 中使用 IIFE
- 12.3.6. 使用类作为构造函数
- 12.4. ES6 可调用实体详解
- 12.4.1. 速查表:可调用实体
- 12.4.2. 传统函数
- 12.4.3. 生成器函数
- 12.4.4. 方法定义
- 12.4.5. 生成器方法定义
- 12.4.6. 箭头函数
- 12.4.7. 类
- 12.5. ES5 和 ES6 中的分派方法调用和直接方法调用
- 12.5.1. 背景:原型链
- 12.5.2. 分派方法调用
- 12.5.3. 直接方法调用
- 12.5.4. 直接方法调用的用例
- 12.5.5.
Object.prototype
和 Array.prototype
的缩写
- 12.6. 函数的
name
属性
- 12.6.1. 为函数提供名称的构造函数
- 12.6.2. 注意事项
- 12.6.3. 更改函数的名称
- 12.6.4. 规范中的函数属性
name
- 12.7. 常见问题解答:可调用实体
- 12.7.1. 如何确定函数是否通过
new
调用?
12.1 概述
在 ES5 中,单个构造函数(传统函数)扮演着三个角色
在 ES6 中,有更多的专业化。这三个职责现在按如下方式处理。就函数定义和类定义而言,定义可以是声明或表达式。
- 真正的(非方法)函数
- 箭头函数(只有表达式形式)
- 传统函数(通过函数定义创建)
- 生成器函数(通过生成器函数定义创建)
- 方法
- 方法(通过对象字面量和类定义中的方法定义创建)
- 生成器方法(通过对象字面量和类定义中的生成器方法定义创建)
- 构造函数
特别是对于回调,箭头函数很方便,因为它们不会遮蔽周围作用域的 this
。
对于较长的回调和独立函数,传统函数是可以的。一些 API 使用 this
作为隐式参数。在这种情况下,您别无选择,只能使用传统函数。
请注意,我区分了
尽管它们的行为不同(稍后解释),但所有这些实体都是函数。例如
12.2 ES6 中的调用方式
有些调用可以在任何地方进行,而另一些调用则仅限于特定位置。
12.2.1 可以在任何地方进行的调用
在 ES6 中,可以在任何地方进行三种调用
- 函数调用:
func(3, 1)
- 方法调用:
obj.method('abc')
- 构造函数调用:
new Constr(8)
12.2.2 通过 super
进行的调用仅限于特定位置
可以通过 super
关键字进行两种调用;它们的使用仅限于特定位置
- 超级方法调用:
super.method('abc')
仅在对象字面量或派生类定义中的方法定义中可用。
- 超级构造函数调用:
super(8)
仅在派生类定义中的特殊方法 constructor()
中可用。
12.2.3 非方法函数与方法
非方法函数和方法之间的区别在 ECMAScript 6 中变得更加明显。现在有针对这两种函数的特殊实体,以及只有它们才能做的事情
- 箭头函数是为非方法函数设计的。它们从周围的作用域中获取
this
(“词法 this
”)。
- 方法定义是为方法设计的。它们为
super
提供支持,以引用超级属性并进行超级方法调用。
12.3 使用可调用实体的建议
本节提供有关使用可调用实体的技巧:何时最好使用哪个实体;等等。
12.3.1 首选箭头函数作为回调
作为回调,箭头函数与传统函数相比有两个优点
-
this
是词法的,因此使用起来更安全。
- 它们的语法更紧凑。这在函数式编程中尤其重要,因为函数式编程中有许多高阶函数和方法(参数为函数的函数和方法)。
12.3.1.1 问题:this
作为隐式参数
唉,一些 JavaScript API 使用 this
作为其回调的隐式参数,这会阻止您使用箭头函数。例如:B 行中的 this
是 A 行中函数的隐式参数。
这种模式不太明确,并且会阻止您使用箭头函数。
12.3.1.2 解决方案 1:更改 API
这很容易修复,但需要更改 API
我们已将 API 从隐式参数 this
转换为显式参数 api
。我喜欢这种明确性。
12.3.1.3 解决方案 2:以其他方式访问 this
的值
在某些 API 中,可以使用其他方法获取 this
的值。例如,以下代码使用 this
。
但是也可以通过 event.target
访问事件的目标
12.3.2 首选函数声明作为独立函数
作为独立函数(相对于回调),我更喜欢函数声明
好处是
- 主观上,我发现它们看起来更好。在这种情况下,冗长的关键字
function
是一个优势——您希望构造函数脱颖而出。
- 它们看起来像生成器函数声明,从而使代码在视觉上更加一致。
有一点需要注意:通常,您不需要在独立函数中使用 this
。如果您使用它,您希望访问周围作用域的 this
(例如,包含独立函数的方法)。唉,函数声明不允许您这样做——它们有自己的 this
,它会遮蔽周围作用域的 this
。因此,您可能希望 linter 警告您函数声明中的 this
。
独立函数的另一种选择是将箭头函数分配给变量。避免了 this
的问题,因为它是词法的。
12.3.3 首选方法定义作为方法
方法定义是创建使用 super
的方法的唯一方法。它们是对象字面量和类中的明显选择(它们是定义方法的唯一方法),但是如何将方法添加到现有对象中呢?例如
以下是在 ES6 中执行相同操作的快速方法(注意:Object.assign()
无法正确移动带有 super
的方法)。
有关更多信息和注意事项,请参阅有关 Object.assign()
的部分。
12.3.4 方法与回调
通常,应该通过方法定义创建函数值属性。但是,有时箭头函数是更好的选择。以下两个小节解释了何时使用哪种方法:前一种方法更适合于具有方法的对象,后一种方法更适合于具有回调的对象。
12.3.4.1 属性为方法的对象
如果函数值属性确实是方法,则通过方法定义创建它们。如果属性值与其所属对象(以下示例中的 obj
)及其兄弟方法密切相关,而与其周围作用域(示例中的 surroundingMethod()
)无关,则情况就是这样。
使用方法定义,属性值的 this
是方法调用的接收者(例如,如果方法调用是 obj.m(···)
,则为 obj
)。
例如,您可以按如下方式使用WHATWG 流 API
obj
是一个对象,其属性 start
、pull
和 cancel
是真正的方法。因此,这些方法可以使用 this
访问对象局部状态(* 行)并相互调用(** 行)。
12.3.4.2 属性为回调的对象
如果函数值属性是回调,则通过箭头函数创建它们。此类回调往往与其周围作用域(以下示例中的 surroundingMethod()
)密切相关,而与其存储的对象(示例中的 obj
)无关。
箭头函数的 this
是周围作用域的 this
(词法 this
)。箭头函数可以很好地用作回调,因为这是您通常希望回调(真正的、非方法的函数)的行为。回调不应该有自己的 this
来遮蔽周围作用域的 this
。
如果属性 start
、pull
和 cancel
是箭头函数,则它们会获取 surroundingMethod()
(它们的周围作用域)的 this
如果 * 行中的输出让您感到意外,请考虑以下代码
在方法 bar()
中,f
的行为应该很容易理解。o.p
的行为不太明显,但与 f
的行为相同。这两个箭头函数具有相同的周围词法作用域,即 bar()
。后一个箭头函数被对象字面量包围并不会改变这一点。
12.3.5 避免在 ES6 中使用 IIFE
本节提供有关在 ES6 中避免使用 IIFE 的技巧。
12.3.5.1 用块替换 IIFE
在 ES5 中,如果您想将变量保持在局部范围内,则必须使用 IIFE
在 ECMAScript 6 中,您可以简单地使用块和 let
或 const
声明
12.3.5.2 用模块替换 IIFE
在不通过库(如 RequireJS、browserify 或 webpack)使用模块的 ECMAScript 5 代码中,揭示模块模式很流行,并且基于 IIFE。它的优点是它清楚地分离了公共部分和私有部分
此模块模式生成一个全局变量,其使用方法如下
在 ECMAScript 6 中,模块 是内置的,这就是为什么采用它们的门槛很低的原因
此模块不会生成全局变量,其使用方法如下
在 ES6 中,仍然有一种情况下需要立即调用的函数:有时您只能通过一系列语句(而不是单个表达式)生成结果。如果要内联这些语句,则必须立即调用一个函数。在 ES6 中,您可以通过立即调用的箭头函数节省一些字符
请注意,您必须如所示添加括号(括号位于箭头函数周围,而不是整个函数调用周围)。详细信息在关于箭头函数的章节中进行了解释。
12.3.6 将类用作构造函数
在 ES5 中,构造函数是创建对象工厂的主流方式(但也还有许多其他技术,有些技术可以说更优雅)。在 ES6 中,类是实现构造函数的主流方式。一些框架支持它们作为自定义继承 API 的替代方案。
12.4 ES6 可调用实体详解
本节首先提供一个速查表,然后详细描述每个 ES6 可调用实体。
12.4.1 速查表:可调用实体
12.4.1.1 可调用实体的行为和结构
实体生成的值的特征
|
函数声明/函数表达式 |
箭头函数 |
类 |
方法 |
函数可调用 |
✔ |
✔ |
× |
✔ |
构造函数可调用 |
✔ |
× |
✔ |
× |
原型 |
F.p |
F.p |
SC |
F.p |
属性 prototype |
✔ |
× |
✔ |
× |
整个实体的特征
|
函数声明 |
函数表达式 |
箭头函数 |
类 |
方法 |
已提升 |
✔ |
|
|
× |
|
创建 window 属性。(1) |
✔ |
|
|
× |
|
内部名称 (2) |
× |
✔ |
|
✔ |
× |
实体主体的特征
|
函数声明 |
函数表达式 |
箭头函数 |
类 (3) |
方法 |
this |
✔ |
✔ |
词法 |
✔ |
✔ |
new.target |
✔ |
✔ |
词法 |
✔ |
✔ |
super.prop |
× |
× |
词法 |
✔ |
✔ |
super() |
× |
× |
× |
✔ |
× |
图例 - 表格单元格
- ✔ 存在,允许
- × 不存在,不允许
- 空单元格:不适用,不相关
- 词法:词法,从周围的词法作用域继承
-
F.p
:Function.prototype
- SC:派生类的超类,基类的
Function.prototype
。详细信息在关于类的章节中进行了解释。
图例 - 脚注
- (1) 有关哪些声明在全局对象上创建属性的规则在关于变量和作用域的章节中进行了解释。
- (2) 命名函数表达式和类的内部名称在关于类的章节中进行了解释。
- (3) 此列是关于类构造函数的主体。
生成器函数和方法呢? 它们的工作方式与其非生成器对应物类似,但有两个例外
- 生成器函数和方法具有原型
(GeneratorFunction).prototype
((GeneratorFunction)
是一个内部对象,请参见“迭代 API(包括生成器)中的继承”一节中的图表)。
- 您不能构造函数调用生成器函数。
12.4.1.2 this
的规则
|
函数调用 |
方法调用 |
new |
传统函数(严格模式) |
undefined |
接收器 |
实例 |
传统函数(非严格模式) |
window |
接收器 |
实例 |
生成器函数(严格模式) |
undefined |
接收器 |
TypeError |
生成器函数(非严格模式) |
window |
接收器 |
TypeError |
方法(严格模式) |
undefined |
接收器 |
TypeError |
方法(非严格模式) |
window |
接收器 |
TypeError |
生成器方法(严格模式) |
undefined |
接收器 |
TypeError |
生成器方法(非严格模式) |
window |
接收器 |
TypeError |
箭头函数(严格模式和非严格模式) |
词法 |
词法 |
TypeError |
类(隐式严格模式) |
TypeError |
TypeError |
SC 协议 |
图例 - 表格单元格
- 词法:从周围的词法作用域继承
- SC(子类化)协议:基类通过
this
接收新实例。派生类从其超类获取其实例。详细信息在关于类的章节中进行了解释。
12.4.2 传统函数
这些是您从 ES5 中了解到的函数。有两种方法可以创建它们
- 函数表达式
- 函数声明
this
的规则
- 函数调用:在严格模式函数中,
this
为 undefined
,在非严格模式下为全局对象。
- 方法调用:
this
是方法调用的接收器(或 call
/apply
的第一个参数)。
- 构造函数调用:
this
是新创建的实例。
12.4.3 生成器函数
生成器函数在关于生成器的章节中进行了解释。它们的语法与传统函数类似,但它们有一个额外的星号
- 生成器函数表达式
- 生成器函数声明
this
的规则如下。请注意,this
永远不会引用生成器对象。
- 函数/方法调用:
this
的处理方式与传统函数相同。此类调用的结果是生成器对象。
- 构造函数调用:您不能构造函数调用生成器函数。如果您这样做,则会抛出
TypeError
。
12.4.4 方法定义
方法定义可以出现在对象字面量中
以及类定义中
如您所见,您必须用逗号分隔对象字面量中的方法定义,但在类定义中它们之间没有分隔符。前者对于保持语法一致性是必要的,尤其是在 getter 和 setter 方面。
方法定义是唯一可以使用 super
引用超属性的地方。只有使用 super
的方法定义才会生成具有内部属性 [[HomeObject]]
的函数,这是该功能所必需的(详细信息在关于类的章节中进行了解释)。
规则
- 函数调用:如果您提取一个方法并将其作为函数调用,则其行为类似于传统函数。
- 方法调用:与传统函数的工作方式相同,但另外允许
super
。
- 构造函数调用:抛出
TypeError
。
在类定义中,名称为 constructor
的方法是特殊的,本章稍后将对此进行解释。
12.4.5 生成器方法定义
生成器方法在关于生成器的章节中进行了解释。它们的语法与方法定义类似,但它们有一个额外的星号
规则
- 调用生成器方法将返回一个生成器对象。
- 您可以像在普通方法定义中一样使用
this
和 super
。
12.4.6 箭头函数
箭头函数在它们自己的章节中进行了解释
以下变量在箭头函数内部是*词法*的(从周围的作用域中获取)
arguments
super
this
new.target
规则
- 函数调用:词法
this
等。
- 方法调用:您可以将箭头函数用作方法,但它们的
this
继续是词法的,并且不引用方法调用的接收器。
- 构造函数调用:生成
TypeError
。
12.4.7 类
类在它们自己的章节中进行了解释。
方法 constructor
很特殊,因为它“成为”了类。也就是说,类与构造函数非常相似
规则
- 函数/方法调用:类不能作为函数或方法调用(原因在关于类的章节中进行了解释)。
- 构造函数调用:遵循支持子类化的协议。在基类中,会创建一个实例,并且
this
引用该实例。派生类从其超类接收其实例,这就是为什么它需要在访问 this
之前调用 super()
的原因。
12.5 ES5 和 ES6 中的分派方法调用和直接方法调用
在 JavaScript 中有两种方法调用方法
- 动态分派 (
arr.slice(1)
):在 arr
的原型链中搜索属性 slice
。使用设置为 arr
的 this
调用其结果。
- 直接调用 (
Array.prototype.slice.call(arr, 1)
):直接调用 slice
,并将 this
设置为 arr
(call()
的第一个参数)。
本节解释了这两种方法的工作原理,以及为什么您在 ECMAScript 6 中很少直接调用方法。在我们开始之前,让我们回顾一下我们对原型链的了解。
12.5.1 背景:原型链
请记住,JavaScript 中的每个对象实际上都是一个由一个或多个对象组成的链。第一个对象从后面的对象继承属性。例如,数组 ['a', 'b']
的原型链如下所示
- 实例,包含元素
'a'
和 'b'
-
Array.prototype
,Array
构造函数提供的属性
-
Object.prototype
,Object
构造函数提供的属性
-
null
(链的末端,因此不是它的真正成员)
您可以通过 Object.getPrototypeOf()
检查链
“较早”对象中的属性会覆盖“较晚”对象中的属性。例如,Array.prototype
提供了 toString()
方法的特定于数组的版本,覆盖了 Object.prototype.toString()
。
12.5.2 分派方法调用
如果您查看方法调用 arr.toString()
,您会发现它实际上执行了两个步骤
- 分派:在
arr
的原型链中,检索名称为 toString
的第一个属性的值。
- 调用:调用该值,并将隐式参数
this
设置为方法调用的*接收器* arr
。
您可以使用函数的 call()
方法使这两个步骤显式
12.5.3 直接方法调用
在 JavaScript 中有两种方法进行直接方法调用
Function.prototype.call(thisValue, arg0?, arg1?, ···)
Function.prototype.apply(thisValue, argArray)
方法 call
和方法 apply
都在函数上调用。它们与普通函数调用的不同之处在于,您可以为 this
指定一个值。call
通过单个参数提供方法调用的参数,apply
通过数组提供它们。
使用分派方法调用,接收器扮演两个角色:它用于查找方法,并且它是一个隐式参数。第一个角色的一个问题是,如果要调用方法,则该方法必须位于对象的原型链中。使用直接方法调用,该方法可以来自任何地方。这允许您从另一个对象借用方法。例如,您可以借用 Object.prototype.toString
,从而将 toString
的原始、未覆盖的实现应用于数组 arr
toString()
的数组版本会产生不同的结果
适用于各种对象(而不仅仅是“它们的”构造函数的实例)的方法称为*泛型*。*Speaking JavaScript* 具有所有泛型方法的列表。该列表包括大多数数组方法和 Object.prototype
的所有方法(它们必须适用于所有对象,因此隐式地是泛型的)。
12.5.4 直接方法调用的用例
本节涵盖直接方法调用的用例。每次,我都会先描述 ES5 中的用例,然后说明它在 ES6 中如何变化(在 ES6 中,你很少需要直接方法调用)。
12.5.4.1 ES5:通过数组向方法提供参数
有些函数接受多个值,但每个参数只接受一个值。如果你想通过数组传递值怎么办?
例如,push()
允许你以破坏性的方式将多个值追加到数组中
但你不能以破坏性的方式追加整个数组。你可以使用 apply()
来解决这个限制
类似地,Math.max()
和 Math.min()
只对单个值有效
使用 apply()
,你可以将它们用于数组
12.5.4.2 ES6:扩展运算符 (...
) 在很大程度上取代了 apply()
仅因为你想将数组转换为参数而通过 apply()
进行直接方法调用很笨拙,这就是 ECMAScript 6 为此提供扩展运算符 (...
) 的原因。它甚至在调度方法调用中也提供了此功能。
另一个例子
此外,扩展运算符也适用于 new
运算符
请注意,apply()
不能与 new
一起使用 - 上述功能只能通过 ECMAScript 5 中的复杂解决方法 来实现。
12.5.4.3 ES5:将类数组对象转换为数组
JavaScript 中的一些对象是*类数组的*,它们几乎是数组,但没有任何数组方法。让我们看两个例子。
首先,函数的特殊变量 arguments
是类数组的。它具有 length
属性和对元素的索引访问。
但 arguments
不是 Array
的实例,并且没有 map()
方法。
其次,DOM 方法 document.querySelectorAll()
返回 NodeList
的实例。
因此,对于许多复杂的操作,你需要先将类数组对象转换为数组。这是通过 Array.prototype.slice()
实现的。此方法将其接收器的元素复制到一个新数组中
如果你直接调用 slice()
,你可以将 NodeList
转换为数组
你可以将 arguments
转换为数组
12.5.4.4 ES6:类数组对象不再那么麻烦
一方面,ECMAScript 6 有 Array.from()
,这是一种将类数组对象转换为数组的更简单方法
另一方面,你将不再需要类数组的 arguments
,因为 ECMAScript 6 有*剩余参数*(通过三个点声明)
12.5.4.5 ES5:安全地使用 hasOwnProperty()
obj.hasOwnProperty('prop')
告诉你 obj
是否具有*自身*(非继承)属性 prop
。
但是,如果 Object.prototype.hasOwnProperty
被覆盖,则通过调度调用 hasOwnProperty
可能会停止正常工作。
如果 Object.prototype
不在对象的原型链中,则 hasOwnProperty
也可能无法通过调度使用。
在这两种情况下,解决方案都是直接调用 hasOwnProperty
12.5.4.6 ES6:对 hasOwnProperty()
的需求减少
hasOwnProperty()
主要用于通过对象实现映射。幸运的是,ECMAScript 6 有一个内置的 Map
数据结构,这意味着你将减少对 hasOwnProperty()
的需求。
12.5.5 Object.prototype
和 Array.prototype
的缩写
你可以通过空对象字面量(其原型是 Object.prototype
)访问 Object.prototype
的方法。例如,以下两个直接方法调用是等效的
同样的技巧也适用于 Array.prototype
这种模式已经变得非常流行。它不像较长的版本那样清楚地反映作者的意图,但它更简洁。 速度方面,两个版本之间没有太大区别。
12.6 函数的 name
属性
函数的 name
属性包含函数的名称
此属性对于调试(其值显示在堆栈跟踪中)和一些元编程任务(按名称选择函数等)非常有用。
在 ECMAScript 6 之前,大多数引擎已经支持此属性。在 ES6 中,它成为语言标准的一部分,并且经常自动填充。
12.6.1 为函数提供名称的构造
以下部分描述了如何为各种编程构造自动设置 name
。
12.6.1.1 变量声明和赋值
如果函数是通过变量声明创建的,则它们会获取名称
但即使使用普通的赋值,name
也会被正确设置
关于名称,箭头函数类似于匿名函数表达式
从现在开始,每当你看到一个匿名函数表达式时,你可以假设箭头函数的工作方式相同。
12.6.1.2 默认值
如果函数是默认值,则它从其变量或参数获取其名称
12.6.1.3 命名函数定义
函数声明和函数表达式是*函数定义*。这种情况已经支持了很长时间:具有名称的函数定义会将其传递给 name
属性。
例如,函数声明
命名函数表达式的名称也会设置 name
属性。
因为它排在第一位,所以函数表达式的名称 baz
优先于其他名称(例如通过变量声明提供的名称 bar
)
但是,与 ES5 中一样,函数表达式的名称只是函数表达式内部的一个变量
12.6.1.4 对象字面量中的方法
如果函数是属性的值,则它从该属性获取其名称。无论它是通过方法定义(A 行)、传统的属性定义(B 行)、具有计算属性键的属性定义(C 行)还是属性值简写(D 行)来实现的,都没有关系。
getter 的名称以 'get'
为前缀,setter 的名称以 'set'
为前缀
12.6.1.5 类定义中的方法
类定义中方法的命名类似于对象字面量
Getter 和 setter 再次分别具有名称前缀 'get'
和 'set'
12.6.1.6 键为符号的方法
在 ES6 中,方法的键可以是符号。此类方法的 name
属性仍然是字符串
- 如果符号有描述,则方法的名称是方括号中的描述。
- 否则,方法的名称为空字符串 (
''
)。
12.6.1.7 类定义
请记住,类定义会创建函数。这些函数也正确设置了它们的属性 name
12.6.1.8 默认导出
以下所有语句都将 name
设置为 'default'
12.6.1.9 其他编程构造
- 生成器函数和生成器方法的命名方式与普通函数和方法相同。
-
new Function()
生成的函数的 name
为 'anonymous'
。 一个 webkit 错误 描述了为什么这在网络上是必要的。
-
func.bind()
生成的函数的 name
为 'bound '+func.name
12.6.2 注意事项
12.6.2.1 注意:函数的名称总是在创建时分配
函数名称总是在创建期间分配,并且以后永远不会更改。也就是说,JavaScript 引擎会检测前面提到的模式,并创建以正确名称开始其生命周期的函数。以下代码演示了由 functionFactory()
创建的函数的名称在 A 行中分配,并且不会被 B 行中的声明更改。
理论上,可以为每个赋值检查右侧是否计算结果为函数,以及该函数是否还没有名称。但这会导致严重的性能损失。
12.6.2.2 注意:压缩
函数名称可能会被压缩,这意味着它们通常会在压缩代码中更改。根据你的目的,你可能必须通过字符串(不会被压缩)来管理函数名称,或者你可能必须告诉压缩器哪些名称不要压缩。
12.6.3 更改函数的名称
这些是属性 name
的属性
该属性不可写意味着你不能通过赋值更改其值
但是,该属性是可配置的,这意味着你可以通过重新定义它来更改它
如果属性 name
已经存在,则你可以省略描述符属性 configurable
,因为缺少描述符属性意味着相应的属性不会更改。
如果属性 name
尚不存在,则描述符属性 configurable
确保 name
保持可配置(默认属性值均为 false
或 undefined
)。
12.6.4 规范中的函数属性 name
- 规范操作
SetFunctionName()
设置属性 name
。在规范中搜索其名称以了解它发生的位置。
- 匿名函数表达式没有属性
name
可以通过查看 它们的运行时语义 来看出
- 命名函数表达式的名称是通过
SetFunctionName()
设置的。该操作不会为匿名函数表达式调用。
- 函数声明的名称是在进入作用域时设置的(它们被提升了!)。
-
创建箭头函数时,也不会设置名称(不会调用
SetFunctionName()
)。
12.7 常见问题解答:可调用实体
12.7.1 如何确定函数是通过 new
调用的?
ES6 为子类化提供了一种新的协议,这在关于类的章节中有所解释。该协议的一部分是元属性 new.target
,它指的是构造函数调用链中的第一个元素(类似于超方法调用链中的 this
)。如果没有构造函数调用,它将是 undefined
。我们可以使用它来强制函数必须通过 new
调用,或者必须不能通过它调用。下面是一个后者的例子
在 ES5 中,通常是这样检查的