本章介绍如何在 ES6 中正确使用可调用实体(通过函数调用、方法调用等)。
super
进行的调用仅限于特定位置Object.prototype
和 Array.prototype
的缩写name
属性name
new
调用?在 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.prototype
Function.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
);
以下变量在箭头函数内部是*词法*的(从周围的作用域中获取)
arguments
super
this
new.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`'
);
}
···
}