js基础知识回顾(6)--函数对象

148 阅读4分钟

函数对象

在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教程 ,感兴趣的各位可以移步观看。