定义函数

定义函数有两种方式:函数声明和函数表达式

函数声明

  1. function functionName(arg0, arg1, arg2) {
  2. // 函数体
  3. }

函数声明的关键特点是函数声明提升,即函数声明会在代码执行之前获得定义。这意味着函数声明 可以出现在调用它的代码之后

  1. sayHi();
  2. function sayHi() {
  3. console.log("Hi!");
  4. } // 这个例子不会抛出错误,因为 JavaScript 引擎会先读取函数声明,然后再执行代码。

函数表达式

  1. let functionName = function(arg0, arg1, arg2) {
  2. // 函数体
  3. };

函数表达式看起来就像一个普通的变量定义和赋值,即创建一个函数再把它赋值给一个变量 functionName。这样创建的函数叫作匿名函数(anonymous funtion),因为 function 关键字后面没有 标识符。(匿名函数有也时候也被称为兰姆达函数)。未赋值给其他变量的匿名函数的 name 属性是空字 符串。

  1. sayHi(); // Error! function doesn't exist yet
  2. let sayHi = function() {
  3. console.log("Hi!");
  4. }; // 函数表达式跟 JavaScript 中的其他表达式一样,需要先赋值再使用

闭包

闭包是指那些引用另外一个函数作用域中变量的函数
变量对象:全局上下文中的变量,它会在代码执行期间始终存在,
活动对象:函数局部上下文中的变量,只有函数执行期间存在

  1. function compare(value1, value2) {
  2. if (value1 < value2) {
  3. return -1;
  4. } else if (value1 > value2) {
  5. return 1;
  6. } else {
  7. return 0;
  8. }
  9. }
  10. let result = compare(5, 10);
  • 在定义compare函数时,就会为它创建作用域链,预装载全局变量对象,并保存在内部的[[Scope]]中,函数的作用域在函数定义的时候就决定了。
  • 在调用compare时,会创建相应的上下文,然后通过复制函数的[[Scope]]来创建其作用域链。接着会创建函数的活动对象(用作变量对象)并将其推入作用域链的前端
  • AO:活动对象
  • 对于每个执行上下文,都有三个重要属性:
    • 变量对象(Variable object,VO)
    • 作用域链(Scope chain)
    • this
  • 作用域链:在《JavaScript深入之变量对象》中讲到,当查找变量的时候,会先从当前上下文的变量对象中查找,如果没有找到,就会从父级(词法层面上的父级)执行上下文的变量对象中查找,一直找到全局上下文的变量对象,也就是全局对象。这样由多个执行上下文的变量对象构成的链表就叫做作用域链

image.png
在这个例子中,这意味着 compare() 函数执行上下文的作用域链中有两个变量对象:局部变量对象和全局变量对象。作用域链其实是一个包含指针的列表,每个指针分别指向一个变量对象,但物理上并不会包含相应的对象。

函数内部的代码在访问变量时,就会使用给定的名称从作用域链中查找变量。函数执行完毕后,局 部活动对象会被销毁,内存中就只剩下全局作用域。不过,闭包就不一样了。