持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第5天,点击查看活动详情 我们已经知道,在 JavaScript 中,函数也是一个值。
而 JavaScript 中的每个值都有一种类型,那么函数是什么类型呢?
在 JavaScript 中,函数的类型是对象。
一个容易理解的方式是把函数想象成可被调用的“行为对象(action object)”。我们不仅可以调用它们,还能把它们当作对象来处理:增/删属性,按引用传递等。
属性 “name”
函数对象包含一些便于使用的属性。
比如,一个函数的名字可以通过属性 “name” 来访问:
function sayHi() {
alert("Hi");
}
alert(sayHi.name); // sayHi
更有趣的是,名称赋值的逻辑很智能。即使函数被创建时没有名字,名称赋值的逻辑也能给它赋予一个正确的名字,然后进行赋值:
let sayHi = function() {
alert("Hi");
};
alert(sayHi.name); // sayHi(有名字!)
当以默认值的方式完成了赋值时,它也有效:
function f(sayHi = function() {}) {
alert(sayHi.name); // sayHi(生效了!)
}
f();
规范中把这种特性叫做「上下文命名」。如果函数自己没有提供,那么在赋值中,会根据上下文来推测一个。
对象方法也有名字:
let user = {
sayHi() {
// ...
},
sayBye: function() {
// ...
}
}
alert(user.sayHi.name); // sayHi
alert(user.sayBye.name); // sayBye
这没有什么神奇的。有时会出现无法推测名字的情况。此时,属性 name 会是空,像这样:
// 函数是在数组中创建的
let arr = [function() {}];
alert( arr[0].name ); // <空字符串>
// 引擎无法设置正确的名字,所以没有值
而实际上,大多数函数都是有名字的。
属性 “length”
还有另一个内建属性 “length”,它返回函数入参的个数,比如:
function f1(a) {}
function f2(a, b) {}
function many(a, b, ...more) {}
alert(f1.length); // 1
alert(f2.length); // 2
alert(many.length); // 2
可以看到,rest 参数不参与计数。
属性 length 有时在操作其它函数的函数中用于做 内省/运行时检查(introspection)。
比如,下面的代码中函数 ask 接受一个询问答案的参数 question 和可能包含任意数量 handler 的参数 ...handlers。
当用户提供了自己的答案后,函数会调用那些 handlers。我们可以传入两种 handlers:
- 一种是无参函数,它仅在用户给出肯定回答时被调用。
- 一种是有参函数,它在两种情况都会被调用,并且返回一个答案。
为了正确地调用 handler,我们需要检查 handler.length 属性。
我们的想法是,我们用一个简单的无参数的 handler 语法来处理积极的回答(最常见的变体),但也要能够提供通用的 handler:
function ask(question, ...handlers) {
let isYes = confirm(question);
for(let handler of handlers) {
if (handler.length == 0) {
if (isYes) handler();
} else {
handler(isYes);
}
}
}
// 对于肯定的回答,两个 handler 都会被调用
// 对于否定的回答,只有第二个 handler 被调用
ask("Question?", () => alert('You said yes'), result => alert(result));
这就是所谓的 多态性 的一个例子 —— 根据参数的类型,或者根据在我们的具体情景下的 length 来做不同的处理。这种思想在 JavaScript 的库里有应用。
自定义属性
我们也可以添加我们自己的属性。
这里我们添加了 counter 属性,用来跟踪总的调用次数:
function sayHi() {
alert("Hi");
// 计算调用次数
sayHi.counter++;
}
sayHi.counter = 0; // 初始值
sayHi(); // Hi
sayHi(); // Hi
alert( `Called ${sayHi.counter} times` ); // Called 2 times
属性不是变量