# 函数表达式
# 闭包
# 1. 引入
- JS中变量用用域的范畴来界定的话分为全局变量以及局部变量。
- 在函数的局部环境中,由于作用域链的机制,可以直接访问外部作用域的变量。 但是反之无法访问。
- 如果想要从外部访问函数内部的变量,就有了闭包。
# 2. 解决方案
- 定义两个函数,形成嵌套关系,外部函数将内部函数return出来,这样,外部作用域中,就可以直接访问内部函数。
# 3. 示例
function outer() {
let outNum = 1;
// 闭包
function inner(num1, num2) {
let sum = num1 + num2;
return sum;
}
return inner;
}
let result = outer()(1, 2);
console.log(result);
//3
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
注意:
- 函数内部嵌套函数,并且将内部函数return出来,形成闭包。
- 外部环境保持对inner函数的引用,所以inner函数一直存在于内存中,无法被垃圾回收机制清除,消耗内存,影响网页性能,在IE中可能会引起内存泄漏。
# 4. 意义
- 闭包作用:延长变量的声明周期。
- 闭包缺点:消耗内存,在IE中可能会引起内存泄漏。
# 递归
函数内部调用自身形成递归。
# 1. 递归的应用
- 使用递归替换迭代
//需求:封装一个函数,用于计算x的n次方
//1. 法一:迭代思想
const fn1 = (x, n) => {
let result = 1;
for (let i = 1; i <= n; i++) {
result *= x
}
return result;
}
console.log(fn1(2, 3));
//法二:递归思想
const fn2 = (x, n) => {
return n == 1 ? x : (x * fn2(x, n - 1));
}
console.log(fn2(2, 2));
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
可见,使用递归可以优化代码结构。
- 使用递归遍历
//需求:封装一个函数,实现对象的深拷贝
var obj1 = {
name: 'jack',
age: 18,
car: {
color: 'black',
bland: 'benz'
}
}
const copy = (obj) => {
if (!(obj instanceof Object)) return console.error('The params in this function must be the type of Object');
const newObj = {};
for (let key in obj) {
// 递归遍历
newObj[key] = obj[key] instanceof Object ? copy(obj[key]) : obj[key];
}
return newObj;
}
copy(obj1);
console.log(obj1);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 执行上下文与堆栈
# 1. 执行上下文
- 执行上下文为内部的数据结构
- 存储着执行流所在当前环境下的所有变量、this、控制流所在位置(执行到第几行)以及其他内部细节。
- 分类:
- 全局执行上下文(只有一个)
- 函数执行上下文(多个)
- eval()执行上下文
注意:每调用一次函数,都会产生一个新的函数执行上下文数据结构来存储该函数的内部信息。
# 2. 栈
- 也叫调用栈,是一种先进后出的数据结构。
- 当多个函数进行嵌套调用时,调用栈内部会按一定顺序存储这些函数对应的执行上下文,产生压栈动作。
- 栈顶的函数执行完后,会将该函数对应的执行上下文弹出,栈顶执行上下文实现切换。
# 3. 示例
下面有一个递归函数:
const fn2 = (x, n) => {
return n == 1 ? x : (x * fn2(x, n - 1));
}
console.log(fn2(2, 3));
1
2
3
4
2
3
4
执行机制
首先第一次调用函数fn,其执行上下文Context进栈,此时递归深度为一。
接下来进行第二次调用fn,发生如下事:
- 由于第一次调用fn未执行完,所以未弹出。
- 第二次执行fn,其执行上下文压栈(这两个上下文不同,每调用一次函数,都会产生一个新的执行上下文数据结构来存储该函数的内部信息)
以此类推,当第三次调用函数执行完后,开始将栈顶的执行上下文弹出。
弹出后,栈顶更新,由于执行上下文记录了函数的信息,比如上次没执行完就被压栈而停止执行时在哪一行代码,图上用
at line 12
来大概表示这个意思。