前端系统课程 - 26. JavaScript 函数

声明函数

  • 函数声明的方式有:

    • 使用 function 操作符声明函数,这种方式又分为具名函数、匿名函数和函数表达式。

    • 使用全局方法 Function()

    • 使用 λ(拉姆达)表达式,又称为“箭头函数”(ES6 新增),这种函数都是匿名函数;当函数内只有一个 return 语句时,花括号和 return 关键字都可以(要一并)省略;函数参数只有一个时,包裹参数的括号可以省略;例如:

      (x, y) => {return x + y} 可简写为 (x, y) => x + y

      (x) => {return x * 2} 可简写为 x => x * 2

  • 匿名函数无法直接使用,需要赋值为一个变量,或者将其转换为表达式。

  • 如果将一个具名函数赋值给一个变量,那么它的函数名会变更为变量名。

  • 函数有一个 name 属性,如果是匿名函数,这个值是空字符串;如果是具名函数,这个值是函数声明时的名称,即使将这个函数赋值给一个变量,也不会改变,但是如果此时使用这个名字访问函数,是无法访问的;而匿名函数赋值给一个变量后,这个值会变成变量名,实际上此时,匿名函数已成为具名函数,name 值也就遵循具名函数的特性。

  • 关于函数的 name 属性,还有一个特殊情况:使用 new Function() 方式声明的函数,它的 name 属性值是字符串 'anonymous',意思是“匿名的”,也无法通过赋值操作改变,但又无法使用这个名字调用这个函数……

函数的本质

  • 函数是一段可以反复调用的代码块;函数还能接收输入的参数,不同的参数会返回不同的值。

  • 要了解函数的本质,要先了解 eval() 这个全局方法,它可以将传入的字符串,计算后当作脚本代码来执行。

  • 函数的调用,实际上是在调用函数的 call() 方法,其内部有一个类似 eval() 的方法将函数体解析为脚本代码后运行;直接使用函数名后加括号的调用方法,实际上是 JavaScript 的一种语法糖,真正的“硬核(hard cored)” 方法是函数调用自己的 call() 方法。

  • 从前面的内容可以看出,函数是一个可以执行代码的对象。

  • 函数原型包含的三个重要方法:call()apply()bind()

谈谈 this 和 arguments

  • 函数的 call() 方法中的第一个参数,可以用 this 得到,其他的参数,可以用 arguments 得到,前面说过,arguments 是一个伪数组。

  • 在普通模式(相对于严格模式)下,this 的值如果是 undefined,JavaScript 会将其自动引用为 window 对象(全局对象),这是一个“潜规则”。

  • JavaScript 中的 newthis 都是要模拟 Java 语言,但是画虎不成反类其犬……

调用栈 Call Stack

  • 函数调用分为:普通调用、嵌套调用和递归调用(自己嵌套调用自己)。

  • 函数被调用时,都会在调用栈里记录一个位置;嵌套调用也一样,当调用并完成相应的运算后,会跳回之前记录的位置,并将记录抹去,再继续往下运行,直至函数结束调用。

  • 当函数未运行完成时,它会暂时保存在调用栈里,这个叫做“压栈”;递归调用和嵌套调用都会压栈。

  • 当压栈超过系统提供的栈的最大值时,运行就会报错,称为“栈溢出(Stack Overflow)”。

作用域

  • 当遇到一些关于作用域的问题,一定不要忘记变量声明提前这回事。

  • 作用域按照语法树,遵循就近原则。

  • 作用域与变量的关系,主要理解的是变量在哪个作用域,变量是哪一个变量,而不是变量是哪一个变量的值,因为变量的值是可变的。

  • 理解了作用域和变量的关系,以及变量与值的关系,就能理解为什么在循环为一个菜单列表添加事件后,触发事件时打印出的下标,都是列表的长度而不是每个列表项对应的下标;因为当你触发事件的时候,循环已经完成并停止,此时的循环下标,已经是列表的长度了,虽然下标还是那个下标,但是值已经发生了改变。

闭包

  • 如果一个函数使用了它范围外的变量,那么(这个函数加这个变量)就叫做闭包。

课后拾遗

  • 在严格模式下('use strict'),this 值如果是基本类型,那么打印时便是这个值,而普通模式下,打印时是这个值包装的对象。

  • 语句都会返回 undefined

  • 多用硬核方式调用函数,fn.call(undefined, arg1, arg2...),就会理解每个函数都有自己的 this