三、理解返回函数的函数
在第一个示例中,函数 addTwo 返回一个数值。请记住,函数可以返回任何内容。让我们看一个返回函数的函数的示例,因为这对理解闭包来说很重要。
let val = 7
function createAdder() {
function addNumbers(a, b) {
let ret = a + b
return ret
}
return addNumbers
}
let adder = createAdder()
let sum = adder(val, 8)
console.log('example of function returning a function: ', sum)
让我们来逐步分析它的执行过程:
1、第 1 行,我们在全局执行上下文中声明一个变量 val,并将数值 7 赋给该变量。
2、第 2-8 行,我们在全局执行上下文中声明了一个名为 createAdder 的变量,并为其分配了一个函数定义。第 3 至 7 行是这个函数的定义,这个时候我们并没有跳进那个函数,只是将函数定义存储到该变量(createAdder)中。
3、第 9 行,我们在全局执行上下文中声明一个名为 adder 的新变量,并暂时赋值为 undefined。
4、第 9 行,我们看到了括号 ()。我们需要执行或调用函数。我们在全局执行上下文的内存中查找名为 createAdder 的变量,它是在第 2 步中创建的。找到它,然后调用它。
5、第 2 行,调用一个函数,创建了一个新的本地执行上下文。我们可以在新的执行上下文中创建局部变量,引擎将新上下文添加到调用栈。这个函数没有参数,所以直接进入它的函数体。
6、3-6 行,我们有一个新的函数声明,我们在本地执行上下文中创建变量 addNumbers,这很重要。addNumbers 仅在本地执行上下文中存在,我们将函数定义存储在名为 addNumbers 的局部变量中。
7、第 7 行,我们返回变量 addNumbers 的内容。引擎查找名为 addNumbers 的变量,这是一个函数定义。一个函数可以返回任何东西,包括函数定义,所以我们返回 addNumbers 的定义。第 4 行和第 5 行的括号之间的任何内容构成了函数定义,我们还从调用栈中删除了本地执行上下文。
8、在 return 语句之后,本地执行上下文被销毁,addNumbers 变量也不复存在,但函数定义仍然存在,它从函数返回并赋给变量 adder,这是我们在第 3 步中创建的变量。
9、第 10 行,我们在全局执行上下文中定义了一个新的变量 sum,临时赋值 undefined。
10、接下来我们需要执行一个函数,哪个函数?在名为 adder 的变量中定义的函数。我们在全局执行环境中查找它,这是一个带两个参数的函数。
11、我们先拿到两个参数,这样就可以调用函数并将正确的参数传给它。第一个是变量 val,在步骤 1 中定义的,它代表数值 7,第二个是数值 8。
12、现在我们要执行这个函数,函数体是在第 3-5 行定义的。创建一个新的本地执行上下文。在本地上下文中,创建了两个新变量:a 和 b。它们分别被赋值为 7 和 8,因为它们是在上一步传给函数的参数。
1)第 4 行,在本地执行上下文中声明一个新变量 ret。
2)第 4 行,执行加法运算,其中我们变量 a 的内容和变量 b 的内容相加,再将相加的结果(15)赋给变量 ret。
3)从函数返回变量 ret,本地执行上下文被销毁,并从调用栈中删除,变量 a、b 和 ret 不再存在。
4)返回的值被赋给我们在步骤 9 中定义的 sum 变量。
5)我们将 sum 的值打印到控制台。
正如预期的那样,控制台将打印出 15。我想说明几点:首先,函数定义可以存储在变量中,函数定义在被调用之前对程序是不可见的。其次,每次调用函数时,就会临时创建一个本地执行上下文,当函数执行结束时,执行上下文就被销毁。函数在遇到 return 语句或结束括号}时执行结束。
函数生命周期
直接上图,点击图片放大查看。要记住函数对象、作用域链对象、执行环境(EC)和活动对象(AO)这几个东西都啥时候出现,啥时候消失。
例子实战
看下面这个函数,函数对象的地址仅作标识,不代表真实的地址。
var getNum;//------------------------1
function getCounter() { // ----------2
var n = 1;
var inner = function () { return n++; }
return inner;
}
getNum = getCounter();//------------3
console.log(getNum()); //1 ---------4
console.log(getNum()); //2 ---------5
程序运行到2的时候: