本章介绍如何在 ES6 中正确使用可调用实体(通过函数调用、方法调用等)。
super 进行的调用仅限于特定位置Object.prototype 和 Array.prototype 的缩写name 属性namenew 调用?在 ES5 中,单个构造函数(传统函数)扮演着三个角色
在 ES6 中,有更多的专业化。这三个职责现在按如下方式处理。就函数定义和类定义而言,定义可以是声明或表达式。
特别是对于回调,箭头函数很方便,因为它们不会遮蔽周围作用域的 this。
对于较长的回调和独立函数,传统函数是可以的。一些 API 使用 this 作为隐式参数。在这种情况下,您别无选择,只能使用传统函数。
请注意,我区分了
尽管它们的行为不同(稍后解释),但所有这些实体都是函数。例如
> typeof (() => {}) // arrow function
'function'
> typeof function* () {} // generator function
'function'
> typeof class {} // class
'function'
有些调用可以在任何地方进行,而另一些调用则仅限于特定位置。
在 ES6 中,可以在任何地方进行三种调用
func(3, 1)obj.method('abc')new Constr(8)super 进行的调用仅限于特定位置 可以通过 super 关键字进行两种调用;它们的使用仅限于特定位置
super.method('abc')super(8)constructor() 中可用。非方法函数和方法之间的区别在 ECMAScript 6 中变得更加明显。现在有针对这两种函数的特殊实体,以及只有它们才能做的事情
this(“词法 this”)。super 提供支持,以引用超级属性并进行超级方法调用。本节提供有关使用可调用实体的技巧:何时最好使用哪个实体;等等。
作为回调,箭头函数与传统函数相比有两个优点
this 是词法的,因此使用起来更安全。this 作为隐式参数 唉,一些 JavaScript API 使用 this 作为其回调的隐式参数,这会阻止您使用箭头函数。例如:B 行中的 this 是 A 行中函数的隐式参数。
beforeEach(function () { // (A)
this.addMatchers({ // (B)
toBeInRange: function (start, end) {
···
}
});
});
这种模式不太明确,并且会阻止您使用箭头函数。
这很容易修复,但需要更改 API
beforeEach(api => {
api.addMatchers({
toBeInRange(start, end) {
···
}
});
});
我们已将 API 从隐式参数 this 转换为显式参数 api。我喜欢这种明确性。
this 的值 在某些 API 中,可以使用其他方法获取 this 的值。例如,以下代码使用 this。
var $button = $('#myButton');
$button.on('click', function () {
this.classList.toggle('clicked');
});
但是也可以通过 event.target 访问事件的目标
var $button = $('#myButton');
$button.on('click', event => {
event.target.classList.toggle('clicked');
});
作为独立函数(相对于回调),我更喜欢函数声明
function foo(arg1, arg2) {
···
}
好处是
function 是一个优势——您希望构造函数脱颖而出。有一点需要注意:通常,您不需要在独立函数中使用 this。如果您使用它,您希望访问周围作用域的 this(例如,包含独立函数的方法)。唉,函数声明不允许您这样做——它们有自己的 this,它会遮蔽周围作用域的 this。因此,您可能希望 linter 警告您函数声明中的 this。
独立函数的另一种选择是将箭头函数分配给变量。避免了 this 的问题,因为它是词法的。
const foo = (arg1, arg2) => {
···
};
方法定义是创建使用 super 的方法的唯一方法。它们是对象字面量和类中的明显选择(它们是定义方法的唯一方法),但是如何将方法添加到现有对象中呢?例如
MyClass.prototype.foo = function (arg1, arg2) {
···
};
以下是在 ES6 中执行相同操作的快速方法(注意:Object.assign() 无法正确移动带有 super 的方法)。
Object.assign(MyClass.prototype, {
foo(arg1, arg2) {
···
}
});
有关更多信息和注意事项,请参阅有关 Object.assign() 的部分。
通常,应该通过方法定义创建函数值属性。但是,有时箭头函数是更好的选择。以下两个小节解释了何时使用哪种方法:前一种方法更适合于具有方法的对象,后一种方法更适合于具有回调的对象。
如果函数值属性确实是方法,则通过方法定义创建它们。如果属性值与其所属对象(以下示例中的 obj)及其兄弟方法密切相关,而与其周围作用域(示例中的 surroundingMethod())无关,则情况就是这样。
使用方法定义,属性值的 this 是方法调用的接收者(例如,如果方法调用是 obj.m(···),则为 obj)。
例如,您可以按如下方式使用WHATWG 流 API
const surroundingObject = {
surroundingMethod() {
const obj = {
data: 'abc',
start(controller) {
···
console.log(this.data); // abc (*)
this.pull(); // (**)
···
},
pull() {
···
},
cancel() {
···
},
};
const stream = new ReadableStream(obj);
},
};
obj 是一个对象,其属性 start、pull 和 cancel 是真正的方法。因此,这些方法可以使用 this 访问对象局部状态(* 行)并相互调用(** 行)。
如果函数值属性是回调,则通过箭头函数创建它们。此类回调往往与其周围作用域(以下示例中的 surroundingMethod())密切相关,而与其存储的对象(示例中的 obj)无关。
箭头函数的 this 是周围作用域的 this(词法 this)。箭头函数可以很好地用作回调,因为这是您通常希望回调(真正的、非方法的函数)的行为。回调不应该有自己的 this 来遮蔽周围作用域的 this。
如果属性 start、pull 和 cancel 是箭头函数,则它们会获取 surroundingMethod()(它们的周围作用域)的 this
const surroundingObject = {
surroundingData: 'xyz',
surroundingMethod() {
const obj = {
start: controller => {
···
console.log(this.surroundingData); // xyz (*)
···
},
pull: () => {
···
},
cancel: () => {
···
},
};
const stream = new ReadableStream(obj);
},
};
const stream = new ReadableStream();
如果 * 行中的输出让您感到意外,请考虑以下代码
const obj = {
foo: 123,
bar() {
const f = () => console.log(this.foo); // 123
const o = {
p: () => console.log(this.foo), // 123
};
},
}
在方法 bar() 中,f 的行为应该很容易理解。o.p 的行为不太明显,但与 f 的行为相同。这两个箭头函数具有相同的周围词法作用域,即 bar()。后一个箭头函数被对象字面量包围并不会改变这一点。
本节提供有关在 ES6 中避免使用 IIFE 的技巧。
在 ES5 中,如果您想将变量保持在局部范围内,则必须使用 IIFE
(function () { // open IIFE
var tmp = ···;
···
}()); // close IIFE
console.log(tmp); // ReferenceError
在 ECMAScript 6 中,您可以简单地使用块和 let 或 const 声明
{ // open block
let tmp = ···;
···
} // close block
console.log(tmp); // ReferenceError
在不通过库(如 RequireJS、browserify 或 webpack)使用模块的 ECMAScript 5 代码中,揭示模块模式很流行,并且基于 IIFE。它的优点是它清楚地分离了公共部分和私有部分
var my_module = (function () {
// Module-private variable:
var countInvocations = 0;
function myFunc(x) {
countInvocations++;
···
}
// Exported by module:
return {
myFunc: myFunc
};
}());
此模块模式生成一个全局变量,其使用方法如下
my_module.myFunc(33);
在 ECMAScript 6 中,模块 是内置的,这就是为什么采用它们的门槛很低的原因
// my_module.js
// Module-private variable:
let countInvocations = 0;
export function myFunc(x) {
countInvocations++;
···
}
此模块不会生成全局变量,其使用方法如下
import { myFunc } from 'my_module.js';
myFunc(33);
在 ES6 中,仍然有一种情况下需要立即调用的函数:有时您只能通过一系列语句(而不是单个表达式)生成结果。如果要内联这些语句,则必须立即调用一个函数。在 ES6 中,您可以通过立即调用的箭头函数节省一些字符
const SENTENCE = 'How are you?';
const REVERSED_SENTENCE = (() => {
// Iteration over the string gives us code points
// (better for reversal than characters)
const arr = [...SENTENCE];
arr.reverse();
return arr.join('');
})();
请注意,您必须如所示添加括号(括号位于箭头函数周围,而不是整个函数调用周围)。详细信息在关于箭头函数的章节中进行了解释。
在 ES5 中,构造函数是创建对象工厂的主流方式(但也还有许多其他技术,有些技术可以说更优雅)。在 ES6 中,类是实现构造函数的主流方式。一些框架支持它们作为自定义继承 API 的替代方案。
本节首先提供一个速查表,然后详细描述每个 ES6 可调用实体。
实体生成的值的特征
| 函数声明/函数表达式 | 箭头函数 | 类 | 方法 | |
|---|---|---|---|---|
| 函数可调用 | ✔ | ✔ | × | ✔ |
| 构造函数可调用 | ✔ | × | ✔ | × |
| 原型 | F.p |
F.p |
SC | F.p |
属性 prototype |
✔ | × | ✔ | × |
整个实体的特征
| 函数声明 | 函数表达式 | 箭头函数 | 类 | 方法 | |
|---|---|---|---|---|---|
| 已提升 | ✔ | × | |||
创建 window 属性。(1) |
✔ | × | |||
| 内部名称 (2) | × | ✔ | ✔ | × |
实体主体的特征
| 函数声明 | 函数表达式 | 箭头函数 | 类 (3) | 方法 | |
|---|---|---|---|---|---|
this |
✔ | ✔ | 词法 | ✔ | ✔ |
new.target |
✔ | ✔ | 词法 | ✔ | ✔ |
super.prop |
× | × | 词法 | ✔ | ✔ |
super() |
× | × | × | ✔ | × |
图例 - 表格单元格
F.p:Function.prototypeFunction.prototype。详细信息在关于类的章节中进行了解释。图例 - 脚注
生成器函数和方法呢? 它们的工作方式与其非生成器对应物类似,但有两个例外
(GeneratorFunction).prototype((GeneratorFunction) 是一个内部对象,请参见“迭代 API(包括生成器)中的继承”一节中的图表)。this 的规则 | 函数调用 | 方法调用 | new |
|
|---|---|---|---|
| 传统函数(严格模式) | undefined |
接收器 | 实例 |
| 传统函数(非严格模式) | window |
接收器 | 实例 |
| 生成器函数(严格模式) | undefined |
接收器 | TypeError |
| 生成器函数(非严格模式) | window |
接收器 | TypeError |
| 方法(严格模式) | undefined |
接收器 | TypeError |
| 方法(非严格模式) | window |
接收器 | TypeError |
| 生成器方法(严格模式) | undefined |
接收器 | TypeError |
| 生成器方法(非严格模式) | window |
接收器 | TypeError |
| 箭头函数(严格模式和非严格模式) | 词法 | 词法 | TypeError |
| 类(隐式严格模式) | TypeError |
TypeError |
SC 协议 |
图例 - 表格单元格
this 接收新实例。派生类从其超类获取其实例。详细信息在关于类的章节中进行了解释。这些是您从 ES5 中了解到的函数。有两种方法可以创建它们
const foo = function (x) { ··· };
function foo(x) { ··· }
this 的规则
this 为 undefined,在非严格模式下为全局对象。this 是方法调用的接收器(或 call/apply 的第一个参数)。this 是新创建的实例。生成器函数在关于生成器的章节中进行了解释。它们的语法与传统函数类似,但它们有一个额外的星号
const foo = function* (x) { ··· };
function* foo(x) { ··· }
this 的规则如下。请注意,this 永远不会引用生成器对象。
this 的处理方式与传统函数相同。此类调用的结果是生成器对象。TypeError。方法定义可以出现在对象字面量中
const obj = {
add(x, y) {
return x + y;
}, // comma is required
sub(x, y) {
return x - y;
}, // comma is optional
};
以及类定义中
class AddSub {
add(x, y) {
return x + y;
} // no comma
sub(x, y) {
return x - y;
} // no comma
}
如您所见,您必须用逗号分隔对象字面量中的方法定义,但在类定义中它们之间没有分隔符。前者对于保持语法一致性是必要的,尤其是在 getter 和 setter 方面。
方法定义是唯一可以使用 super 引用超属性的地方。只有使用 super 的方法定义才会生成具有内部属性 [[HomeObject]] 的函数,这是该功能所必需的(详细信息在关于类的章节中进行了解释)。
规则
super。TypeError。在类定义中,名称为 constructor 的方法是特殊的,本章稍后将对此进行解释。
生成器方法在关于生成器的章节中进行了解释。它们的语法与方法定义类似,但它们有一个额外的星号
const obj = {
* generatorMethod(···) {
···
},
};
class MyClass {
* generatorMethod(···) {
···
}
}
规则
this 和 super。箭头函数在它们自己的章节中进行了解释
const squares = [1,2,3].map(x => x * x);
以下变量在箭头函数内部是*词法*的(从周围的作用域中获取)
argumentssuperthisnew.target规则
this 等。this 继续是词法的,并且不引用方法调用的接收器。TypeError。类在它们自己的章节中进行了解释。
// Base class: no `extends`
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return `(${this.x}, ${this.y})`;
}
}
// This class is derived from `Point`
class ColorPoint extends Point {
constructor(x, y, color) {
super(x, y);
this.color = color;
}
toString() {
return super.toString() + ' in ' + this.color;
}
}
方法 constructor 很特殊,因为它“成为”了类。也就是说,类与构造函数非常相似
> Point.prototype.constructor === Point
true
规则
this 引用该实例。派生类从其超类接收其实例,这就是为什么它需要在访问 this 之前调用 super() 的原因。在 JavaScript 中有两种方法调用方法
arr.slice(1)):在 arr 的原型链中搜索属性 slice。使用设置为 arr 的 this 调用其结果。Array.prototype.slice.call(arr, 1)):直接调用 slice,并将 this 设置为 arr(call() 的第一个参数)。本节解释了这两种方法的工作原理,以及为什么您在 ECMAScript 6 中很少直接调用方法。在我们开始之前,让我们回顾一下我们对原型链的了解。
请记住,JavaScript 中的每个对象实际上都是一个由一个或多个对象组成的链。第一个对象从后面的对象继承属性。例如,数组 ['a', 'b'] 的原型链如下所示
'a' 和 'b'Array.prototype,Array 构造函数提供的属性Object.prototype,Object 构造函数提供的属性null(链的末端,因此不是它的真正成员)您可以通过 Object.getPrototypeOf() 检查链
> var arr = ['a', 'b'];
> var p = Object.getPrototypeOf;
> p(arr) === Array.prototype
true
> p(p(arr)) === Object.prototype
true
> p(p(p(arr)))
null
“较早”对象中的属性会覆盖“较晚”对象中的属性。例如,Array.prototype 提供了 toString() 方法的特定于数组的版本,覆盖了 Object.prototype.toString()。
> var arr = ['a', 'b'];
> Object.getOwnPropertyNames(Array.prototype)
[ 'toString', 'join', 'pop', ··· ]
> arr.toString()
'a,b'
如果您查看方法调用 arr.toString(),您会发现它实际上执行了两个步骤
arr 的原型链中,检索名称为 toString 的第一个属性的值。this 设置为方法调用的*接收器* arr。您可以使用函数的 call() 方法使这两个步骤显式
> var func = arr.toString; // dispatch
> func.call(arr) // direct call, providing a value for `this`
'a,b'
在 JavaScript 中有两种方法进行直接方法调用
Function.prototype.call(thisValue, arg0?, arg1?, ···)Function.prototype.apply(thisValue, argArray)方法 call 和方法 apply 都在函数上调用。它们与普通函数调用的不同之处在于,您可以为 this 指定一个值。call 通过单个参数提供方法调用的参数,apply 通过数组提供它们。
使用分派方法调用,接收器扮演两个角色:它用于查找方法,并且它是一个隐式参数。第一个角色的一个问题是,如果要调用方法,则该方法必须位于对象的原型链中。使用直接方法调用,该方法可以来自任何地方。这允许您从另一个对象借用方法。例如,您可以借用 Object.prototype.toString,从而将 toString 的原始、未覆盖的实现应用于数组 arr
> const arr = ['a','b','c'];
> Object.prototype.toString.call(arr)
'[object Array]'
toString() 的数组版本会产生不同的结果
> arr.toString() // dispatched
'a,b,c'
> Array.prototype.toString.call(arr); // direct
'a,b,c'
适用于各种对象(而不仅仅是“它们的”构造函数的实例)的方法称为*泛型*。*Speaking JavaScript* 具有所有泛型方法的列表。该列表包括大多数数组方法和 Object.prototype 的所有方法(它们必须适用于所有对象,因此隐式地是泛型的)。
本节涵盖直接方法调用的用例。每次,我都会先描述 ES5 中的用例,然后说明它在 ES6 中如何变化(在 ES6 中,你很少需要直接方法调用)。
有些函数接受多个值,但每个参数只接受一个值。如果你想通过数组传递值怎么办?
例如,push() 允许你以破坏性的方式将多个值追加到数组中
> var arr = ['a', 'b'];
> arr.push('c', 'd')
4
> arr
[ 'a', 'b', 'c', 'd' ]
但你不能以破坏性的方式追加整个数组。你可以使用 apply() 来解决这个限制
> var arr = ['a', 'b'];
> Array.prototype.push.apply(arr, ['c', 'd'])
4
> arr
[ 'a', 'b', 'c', 'd' ]
类似地,Math.max() 和 Math.min() 只对单个值有效
> Math.max(-1, 7, 2)
7
使用 apply(),你可以将它们用于数组
> Math.max.apply(null, [-1, 7, 2])
7
...) 在很大程度上取代了 apply() 仅因为你想将数组转换为参数而通过 apply() 进行直接方法调用很笨拙,这就是 ECMAScript 6 为此提供扩展运算符 (...) 的原因。它甚至在调度方法调用中也提供了此功能。
> Math.max(...[-1, 7, 2])
7
另一个例子
> const arr = ['a', 'b'];
> arr.push(...['c', 'd'])
4
> arr
[ 'a', 'b', 'c', 'd' ]
此外,扩展运算符也适用于 new 运算符
> new Date(...[2011, 11, 24])
Sat Dec 24 2011 00:00:00 GMT+0100 (CET)
请注意,apply() 不能与 new 一起使用 - 上述功能只能通过 ECMAScript 5 中的复杂解决方法 来实现。
JavaScript 中的一些对象是*类数组的*,它们几乎是数组,但没有任何数组方法。让我们看两个例子。
首先,函数的特殊变量 arguments 是类数组的。它具有 length 属性和对元素的索引访问。
> var args = function () { return arguments }('a', 'b');
> args.length
2
> args[0]
'a'
但 arguments 不是 Array 的实例,并且没有 map() 方法。
> args instanceof Array
false
> args.map
undefined
其次,DOM 方法 document.querySelectorAll() 返回 NodeList 的实例。
> document.querySelectorAll('a[href]') instanceof NodeList
true
> document.querySelectorAll('a[href]').map // no Array methods!
undefined
因此,对于许多复杂的操作,你需要先将类数组对象转换为数组。这是通过 Array.prototype.slice() 实现的。此方法将其接收器的元素复制到一个新数组中
> var arr = ['a', 'b'];
> arr.slice()
[ 'a', 'b' ]
> arr.slice() === arr
false
如果你直接调用 slice(),你可以将 NodeList 转换为数组
var domLinks = document.querySelectorAll('a[href]');
var links = Array.prototype.slice.call(domLinks);
links.map(function (link) {
return link.href;
});
你可以将 arguments 转换为数组
function format(pattern) {
// params start at arguments[1], skipping `pattern`
var params = Array.prototype.slice.call(arguments, 1);
return params;
}
console.log(format('a', 'b', 'c')); // ['b', 'c']
一方面,ECMAScript 6 有 Array.from(),这是一种将类数组对象转换为数组的更简单方法
const domLinks = document.querySelectorAll('a[href]');
const links = Array.from(domLinks);
links.map(link => link.href);
另一方面,你将不再需要类数组的 arguments,因为 ECMAScript 6 有*剩余参数*(通过三个点声明)
function format(pattern, ...params) {
return params;
}
console.log(format('a', 'b', 'c')); // ['b', 'c']
hasOwnProperty() obj.hasOwnProperty('prop') 告诉你 obj 是否具有*自身*(非继承)属性 prop。
> var obj = { prop: 123 };
> obj.hasOwnProperty('prop')
true
> 'toString' in obj // inherited
true
> obj.hasOwnProperty('toString') // own
false
但是,如果 Object.prototype.hasOwnProperty 被覆盖,则通过调度调用 hasOwnProperty 可能会停止正常工作。
> var obj1 = { hasOwnProperty: 123 };
> obj1.hasOwnProperty('toString')
TypeError: Property 'hasOwnProperty' is not a function
如果 Object.prototype 不在对象的原型链中,则 hasOwnProperty 也可能无法通过调度使用。
> var obj2 = Object.create(null);
> obj2.hasOwnProperty('toString')
TypeError: Object has no method 'hasOwnProperty'
在这两种情况下,解决方案都是直接调用 hasOwnProperty
> var obj1 = { hasOwnProperty: 123 };
> Object.prototype.hasOwnProperty.call(obj1, 'hasOwnProperty')
true
> var obj2 = Object.create(null);
> Object.prototype.hasOwnProperty.call(obj2, 'toString')
false
hasOwnProperty() 的需求减少 hasOwnProperty() 主要用于通过对象实现映射。幸运的是,ECMAScript 6 有一个内置的 Map 数据结构,这意味着你将减少对 hasOwnProperty() 的需求。
Object.prototype 和 Array.prototype 的缩写 你可以通过空对象字面量(其原型是 Object.prototype)访问 Object.prototype 的方法。例如,以下两个直接方法调用是等效的
Object.prototype.hasOwnProperty.call(obj, 'propKey')
{}.hasOwnProperty.call(obj, 'propKey')
同样的技巧也适用于 Array.prototype
Array.prototype.slice.call(arguments)
[].slice.call(arguments)
这种模式已经变得非常流行。它不像较长的版本那样清楚地反映作者的意图,但它更简洁。 速度方面,两个版本之间没有太大区别。
name 属性 函数的 name 属性包含函数的名称
> function foo() {}
> foo.name
'foo'
此属性对于调试(其值显示在堆栈跟踪中)和一些元编程任务(按名称选择函数等)非常有用。
在 ECMAScript 6 之前,大多数引擎已经支持此属性。在 ES6 中,它成为语言标准的一部分,并且经常自动填充。
以下部分描述了如何为各种编程构造自动设置 name。
如果函数是通过变量声明创建的,则它们会获取名称
let func1 = function () {};
console.log(func1.name); // func1
const func2 = function () {};
console.log(func2.name); // func2
var func3 = function () {};
console.log(func3.name); // func3
但即使使用普通的赋值,name 也会被正确设置
let func4;
func4 = function () {};
console.log(func4.name); // func4
var func5;
func5 = function () {};
console.log(func5.name); // func5
关于名称,箭头函数类似于匿名函数表达式
const func = () => {};
console.log(func.name); // func
从现在开始,每当你看到一个匿名函数表达式时,你可以假设箭头函数的工作方式相同。
如果函数是默认值,则它从其变量或参数获取其名称
let [func1 = function () {}] = [];
console.log(func1.name); // func1
let { f2: func2 = function () {} } = {};
console.log(func2.name); // func2
function g(func3 = function () {}) {
return func3.name;
}
console.log(g()); // func3
函数声明和函数表达式是*函数定义*。这种情况已经支持了很长时间:具有名称的函数定义会将其传递给 name 属性。
例如,函数声明
function foo() {}
console.log(foo.name); // foo
命名函数表达式的名称也会设置 name 属性。
const bar = function baz() {};
console.log(bar.name); // baz
因为它排在第一位,所以函数表达式的名称 baz 优先于其他名称(例如通过变量声明提供的名称 bar)
但是,与 ES5 中一样,函数表达式的名称只是函数表达式内部的一个变量
const bar = function baz() {
console.log(baz.name); // baz
};
bar();
console.log(baz); // ReferenceError
如果函数是属性的值,则它从该属性获取其名称。无论它是通过方法定义(A 行)、传统的属性定义(B 行)、具有计算属性键的属性定义(C 行)还是属性值简写(D 行)来实现的,都没有关系。
function func() {}
let obj = {
m1() {}, // (A)
m2: function () {}, // (B)
['m' + '3']: function () {}, // (C)
func, // (D)
};
console.log(obj.m1.name); // m1
console.log(obj.m2.name); // m2
console.log(obj.m3.name); // m3
console.log(obj.func.name); // func
getter 的名称以 'get' 为前缀,setter 的名称以 'set' 为前缀
let obj = {
get foo() {},
set bar(value) {},
};
let getter = Object.getOwnPropertyDescriptor(obj, 'foo').get;
console.log(getter.name); // 'get foo'
let setter = Object.getOwnPropertyDescriptor(obj, 'bar').set;
console.log(setter.name); // 'set bar'
类定义中方法的命名类似于对象字面量
class C {
m1() {}
['m' + '2']() {} // computed property key
static classMethod() {}
}
console.log(C.prototype.m1.name); // m1
console.log(new C().m1.name); // m1
console.log(C.prototype.m2.name); // m2
console.log(C.classMethod.name); // classMethod
Getter 和 setter 再次分别具有名称前缀 'get' 和 'set'
class C {
get foo() {}
set bar(value) {}
}
let getter = Object.getOwnPropertyDescriptor(C.prototype, 'foo').get;
console.log(getter.name); // 'get foo'
let setter = Object.getOwnPropertyDescriptor(C.prototype, 'bar').set;
console.log(setter.name); // 'set bar'
在 ES6 中,方法的键可以是符号。此类方法的 name 属性仍然是字符串
'')。const key1 = Symbol('description');
const key2 = Symbol();
let obj = {
[key1]() {},
[key2]() {},
};
console.log(obj[key1].name); // '[description]'
console.log(obj[key2].name); // ''
请记住,类定义会创建函数。这些函数也正确设置了它们的属性 name
class Foo {}
console.log(Foo.name); // Foo
const Bar = class {};
console.log(Bar.name); // Bar
以下所有语句都将 name 设置为 'default'
export default function () {}
export default (function () {});
export default class {}
export default (class {});
export default () => {};
new Function() 生成的函数的 name 为 'anonymous'。 一个 webkit 错误 描述了为什么这在网络上是必要的。func.bind() 生成的函数的 name 为 'bound '+func.name function foo(x) {
return x
}
const bound = foo.bind(undefined, 123);
console.log(bound.name); // 'bound foo'
函数名称总是在创建期间分配,并且以后永远不会更改。也就是说,JavaScript 引擎会检测前面提到的模式,并创建以正确名称开始其生命周期的函数。以下代码演示了由 functionFactory() 创建的函数的名称在 A 行中分配,并且不会被 B 行中的声明更改。
function functionFactory() {
return function () {}; // (A)
}
const foo = functionFactory(); // (B)
console.log(foo.name.length); // 0 (anonymous)
理论上,可以为每个赋值检查右侧是否计算结果为函数,以及该函数是否还没有名称。但这会导致严重的性能损失。
函数名称可能会被压缩,这意味着它们通常会在压缩代码中更改。根据你的目的,你可能必须通过字符串(不会被压缩)来管理函数名称,或者你可能必须告诉压缩器哪些名称不要压缩。
这些是属性 name 的属性
> let func = function () {}
> Object.getOwnPropertyDescriptor(func, 'name')
{ value: 'func',
writable: false,
enumerable: false,
configurable: true }
该属性不可写意味着你不能通过赋值更改其值
> func.name = 'foo';
> func.name
'func'
但是,该属性是可配置的,这意味着你可以通过重新定义它来更改它
> Object.defineProperty(func, 'name', {value: 'foo', configurable: true});
> func.name
'foo'
如果属性 name 已经存在,则你可以省略描述符属性 configurable,因为缺少描述符属性意味着相应的属性不会更改。
如果属性 name 尚不存在,则描述符属性 configurable 确保 name 保持可配置(默认属性值均为 false 或 undefined)。
name SetFunctionName() 设置属性 name。在规范中搜索其名称以了解它发生的位置。'get' 和 'set')Function.prototype.bind()(前缀 'bound')name 可以通过查看 它们的运行时语义 来看出SetFunctionName() 设置的。该操作不会为匿名函数表达式调用。SetFunctionName())。new 调用的? ES6 为子类化提供了一种新的协议,这在关于类的章节中有所解释。该协议的一部分是元属性 new.target,它指的是构造函数调用链中的第一个元素(类似于超方法调用链中的 this)。如果没有构造函数调用,它将是 undefined。我们可以使用它来强制函数必须通过 new 调用,或者必须不能通过它调用。下面是一个后者的例子
function realFunction() {
if (new.target !== undefined) {
throw new Error('Can’t be invoked via `new`');
}
···
}
在 ES5 中,通常是这样检查的
function realFunction() {
"use strict";
if (this !== undefined) {
throw new Error('Can’t be invoked via `new`');
}
···
}