函数对象
在js中函数就是对象,可以简单的理解为函数是可被调用的‘行为对象’。不仅可以调用它,还可以把它当做对象来增加/删除属性,按引用传值等
name属性
函数对象包含一些便于使用的属性,比如一个函数的名字可以通过name来访问
function sayHi(){
alert('hi')
}
alert(asyHi.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属性
它返回函数入参的个数,
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')
sayHi.count++
}
sayHi,count = 0
sayHi() //hi
sayHi() //hi
alert( `Called ${sayHi.counter} times` ); // Called 2 times
注意
被赋值给函数的属性,比如 sayHi.counter = 0,不会 在函数内定义一个局部变量 counter。换句话说,属性 counter 和变量 let counter 是毫不相关的两个东西。
我们可以把函数当作对象,在它里面存储属性,但是这对它的执行没有任何影响。变量不是函数属性,反之亦然。它们之间是平行的。
函数属性有时会用来替代闭包。 例如:改造一个闭包 (使用count记录调用次数)
function makeCounter(){
//不需要定义局部变量
// let count = 0
function counter(){
return counter.count++
}
counter.count = 0
return counter
}
let counter = makeCounter
alert(counter()) //1
alert(counter()) //2
现在 count 被直接存储在函数里,而不是它外部的词法环境。
那么它和闭包谁好谁赖?
两者最大的不同就是如果 count 的值位于外层(函数)变量中,那么外部的代码无法访问到它,只有嵌套的函数可以修改它。而如果它是绑定到函数的,那么就很容易:
function makeCounter(){
//不需要定义局部变量
// let count = 0
function counter(){
return counter.count++
}
counter.count = 0
return counter
}
let counter = makeCounter
//在此处可直接更改count
counter.count = 10;
alert(counter()) //1
alert(counter()) //2
所以,选择哪种实现方式取决于我们的需求是什么。
命名函数表达式 NFE、
命名函数表达式(NFE,Named Function Expression),指带有名字的函数表达式的术语。 例如,让我们写一个普通的函数表达式:
let sayHi = function(who) { alert(`Hello, ${who}`); };
然后给它加一个名字:
let sayHi = function func(who) { alert(`Hello, ${who}`); };
我们为函数加了‘func’首先请注意,它仍然是一个函数表达式。在 function 后面加一个名字 "func" 没有使它成为一个函数声明,因为它仍然是作为赋值表达式中的一部分被创建的。
添加这个名字当然也没有打破任何东西。
函数依然可以通过 sayHi() 来调用
关于名字 func 有两个特殊的地方,这就是添加它的原因:
- 它允许函数在内部引用自己。
- 它在函数外是不可见的。
例如,下面的函数
sayHi会在没有入参who时,以"Guest"为入参调用自己:
let sayHi = function func(who){
if(who){
alert(`hellow ${who}`}
}else {
func('Guest') // 使用 func 再次调用函数自身*
}
}
sayHi() // hellow,Guest
// 但这不工作:
func(); // Error, func is not defined(在函数外不可见)
我们为什么使用 func 呢?为什么不直接使用 sayHi 进行嵌套调用?
问题在于 sayHi 的值可能会被函数外部的代码改变。如果该函数被赋值给另外一个变量(译注:也就是原变量被修改),那么函数就会开始报错:
let sayHi = function func(who){
if(who){
alert(`hellow ${who}`}
}else {
sayHi('Guest') // Error: sayHi is not a function
}
}
let welcome = sayHi
sayHi = null
welcome() // Error,嵌套调用 sayHi 不再有效!
发生这种情况是因为该函数从它的外部词法环境获取 sayHi。没有局部的 sayHi 了,所以使用外部变量。而当调用时,外部的 sayHi 是 null。
我们给函数表达式添加的可选的名字,正是用来解决这类问题的。
let sayHi = function func(who){
if(who){
alert(`hellow ${who}`}
}else {
func('Guest') // 现在一切正常
}
}
let welcome = sayHi
sayHi = null
welcome() // Error,嵌套调用 sayHi 不再有效!
现在它可以正常运行了,因为名字 func 是函数局部域的。它不是从外部获取的(而且它对外部也是不可见的)。规范确保它只会引用当前函数。
外部代码仍然有该函数的 sayHi 或 welcome 变量。而且 func 是一个“内部函数名”,可用于函数在自身内部进行自调用。
函数声明没有这个东西
这里所讲的“内部名”特性只针对函数表达式,而不是函数声明。对于函数声明,没有用来添加“内部”名的语法。
有时,当我们需要一个可靠的内部名时,这就成为了你把函数声明重写成函数表达式的理由了。
活学活用
//写一个函数 `sum`,它有这样的功能:
sum(1)(2) == 3; // 1 + 2
sum(1)(2)(3) == 6; // 1 + 2 + 3
sum(5)(-1)(2) == 6
sum(6)(-1)(-2)(-3) == 0
sum(0)(1)(2)(3)(4)(5) == 15
解题的思路如下:1、为了使sum可以链式调用sum返回的结果必须是函数 2、要将每次求和的值做保存 3、返回的函数 被用于和 == 比较时 必须转换为数字 函数是对象 所以会按照对象-->原始类型 进行转换 (在基础知识回顾 数据类型章节)
function sum(a){
let currentSum = a;
function f(b){
currentSum += b
return f
}
//也可使用toString valueOf 方法来定义返回的的值
f[Symbol.toPrimitive] = function(){
return currentSum
}
return f
}
alert( sum(1)(2) ); // 3
alert( sum(5)(-1)(2) ); // 6
alert( sum(6)(-1)(-2)(-3) ); // 0
alert( sum(0)(1)(2)(3)(4)(5) ); // 15
请注意 `sum` 函数只工作一次,它返回了函数 `f`。
然后,接下来的每一次子调用,f 都会把自己的参数加到求和 currentSum 上,然后 f 自身。
在 f 的最后一行没有递归。
这个 f 会被用于下一次调用,然后再次返回自己,按照需要重复。然后,当它被用做数字或字符串时 —— Symbol.toPrimitive 返回 currentSum。我们也可以使用 toStringalueOf 来实现转换。
素材资料来自 现代 JavaScript教程 ,感兴趣的各位可以移步观看。