我们已经知道Javascript里的值都有对应的类型,函数始终特殊的值,它的类型是对象
name属性
函数对象包含一些可用的对象,例如name属性
function sayHi() {
alert("Hi");
}
alert(sayHi.name); // sayHi
显然,name属性返回函数的名字
有趣的是,函数名字的指配是灵活的,例如:
let sayHi = function() {
alert("Hi");
}
alert(sayHi.name); // sayHi (works!)
当函数名是默认值的时候也起作用,例如:
function f(sayHi = function() {}) {
alert(sayHi.name); // sayHi (works!)
}
f();
同样,对象的方法也有name属性,例如:
let user = {
sayHi() {
// ...
},
sayBye: function() {
// ...
}
}
alert(user.sayHi.name); // sayHi
alert(user.sayBye.name); // sayBye
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参数并不会被计算进去
自定义属性
除了函数的内置属性,我们可以添加自定义属性到函数中,例如添加counter属性来放回函数被调用次数:
function sayHi() {
alert("Hi");
// let's count how many times we run
sayHi.counter++;
}
sayHi.counter = 0; // initial value
sayHi(); // Hi
sayHi(); // Hi
alert( `Called ${sayHi.counter} times` ); // Called 2 times
这里需要注意的是,sayHi.counter=0来指定counter的值并不意味着counter是该函数的本地变量
函数的自定属性特性有时候可以用来当做闭包使用,例如:
function makeCounter() {
// instead of:
// let count = 0
function counter() {
return counter.count++;
};
counter.count = 0;
return counter;
}
let counter = makeCounter();
alert( counter() ); // 0
alert( counter() ); // 1
相比于闭包,如果count存在于外部变量中,那么外部代码将无法访问count,只有嵌套函数才可以修改它,例如:
function makeCounter() {
function counter() {
return counter.count++;
};
counter.count = 0;
return counter;
}
let counter = makeCounter();
counter.count = 10;
alert( counter() ); // 10
命名函数表达式
先看下面两个例子:
let sayHi = function(who) {
alert(`Hello, ${who}`);
};
现在我们为函数表达式加多一个名字,如下:
let sayHi = function func(who) {
alert(`Hello, ${who}`);
};
对于添加的函数名字,我们不能够直接在外部调用,否则会报错,例如:
let sayHi = function func(who) {
alert(`Hello, ${who}`);
};
sayHi("John"); // Hello, John
func("Jonh"); //error
这是因为命名函数表达式的名字(func)有以下两个特性:
(1)它只能在函数内部被引用或调用;
(2)在函数外部函数名是不可见的,如上边的例子;
那么使用命名函数表达式有什么好处呢?常见的作用是用于内部的自身调用,例如下面的例子:
let sayHi = function(who) {
if (who) {
alert(`Hello, ${who}`);
} else {
sayHi("Guest");
}
};
这里sayHi()函数在内部调用了自身,显然这没什么问题,但是考虑一下下面这种情况:
let sayHi = function(who) {
if (who) {
alert(`Hello, ${who}`);
} else {
sayHi("Guest"); // Error: sayHi is not a function
}
};
let welcome = sayHi;
sayHi = null;
welcome(); // Error, the nested sayHi call doesn't work any more!
很明显,sayHi的值如果发生变化的话,其内部的自身调用也要改变。为了解决这种情况,命名函数表达式的作用就很明显,如下:
let sayHi = function func(who) {
if (who) {
alert(`Hello, ${who}`);
} else {
func("Guest"); // Now all fine
}
};
let welcome = sayHi;
sayHi = null;
welcome(); // Hello, Guest (nested call works)
这里要注意一点是,函数“内部名”为命名函数表达式所有,普通的函数声明并不具备这样的语法特性。