js基础-作用域和闭包

你不知道的JavaScript!

发现自己越来越喜欢js的这门神奇的语言了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
var obj = {
name: 'dottie',
sayName: function() {
// console.log(this);
console.log('name', this.name);
}
};
var name = 'daejong';
obj.sayName(); // name dottie
setTimeout(obj.sayName, 0);

if (true) {
var aa = 'ddaa';
}
console.log(aa); // ddaa 因为js的if不是块级作用域。 es6可以使用let


// 提升
a = 20;
var a;
console.log(a); // 20 因为var a这个声明语句会提升到该词法作用域的顶部, 故这里能正常执行

foo(); // foo hello foo 虽然函数是在他下面定义的,但是这里也可以正常执行。
// 函数声明和变量声明会在编译阶段提升到它自己所在词法作用域的最上面。
// 函数声明(函数表达式不会提升)会先提升。变量后提升。 然后赋值语句会在原地等待执行阶段被执行。
function foo() {
console.log('foo', 'hello foo');
}

// IIFE: immediately invoked function expression立即执行函数表达式
(function timer1() {
console.log('ddd');
console.log(timer1); // [Function: timer1] timer1为IIFE 立即执行函数表达式. timer1只能在timer1函数中被访问。
})();
// console.log(timer1); // timer1 is not defined

// 闭包导致的问题
// for(var i = 0; i < 10; i++) {
// setTimeout(function timer() {
// console.log(i); // 都是输出 10
// }, i * 1000);
// }
// console.log(i); // 10 因为for中var i的词法作用域是在for的外面。故这里可以访问到

// 注意这里的timer是函数表达式。 而不是函数声明(直接是function f() {} 前面没有括号)。
// timer访问了自己所在词法作用域的变量i,并且在自己词法作用域之外执行(在setTimeout函数内部被执行)

// ? 闭包
// 函数访问自己所在词法作用域内的变量,并且该函数在自己所在词法作用域之外被执行。就会产生闭包.
// 例子。函数Bar在自己所在词法作用域内对Foo中的i形成了一个闭包.
// 虽然Foo函数被执行了。但是由于Bar没被执行,还引用这i。故Foo函数的内部作用域不会被释放。
function Foo() {
var i = 123;
function Bar() {
console.log(i);
}
return Bar;
}
var bar = Foo();
bar(); // 123


// 解决闭包
// for(var i = 0; i < 10; i++) {
// setTimeout(function timer() {
// console.log(i); // 都是输出 10
// }, i * 1000);
// }
// console.log(i); // 10 因为for中var i的词法作用域是在for的外面。故这里可以访问到

// ? 分析
/**
* 1. 首先明确i的词法作用域。是在定义该过程的外面。可以认为是全局作用域。
* 2. 每次for循环的i都是同一个共享的全局 i
* 3. timer函数访问自己所在词法作用域中 i,
* 4. 并且timer是在自己所在词法作用域外(setTimeout函数中)被执行
* 5. 故这里产生了一个闭包. 所以在timer函数没被执行之前。由于timer函数内部引用外部的 i, 故外部的i不会被释放。
* 6. 故 每次for循环都是在操作外部全局的 i,所以最终for循环执行完后,再去执行timer函数后,自然i的值就是 10了。
* 7. 本质就是这10个timer函数引用的都是同一个 i, 这也是解决问题的关键。
*/

// 解决办法1
// 用es6中let。在每次for循环中产生一个块级作用域的变量。
/**
for(let i = 0; i < 10; i++) {
// for循环每次都会产生一个let i。而且这个i只在for的{}体内中可以使用。
// 注意这里不能将 i 赋值给 j. 因为for循环{}不是块级作用域
// 这里的 var j 会自动提升为外部全局的作用域. 那么 j 在循环结束就是 j = 9。
// 这里的错误语句: var j = i;
setTimeout(function timer() {
console.log(i); // 虽然也有闭包的存在。但是这10个timer函数引用的是10个不同的i。 故不会导致错误。
}, i * 1000);
};
// console.log(i); // i is not defined
*/

// 解决办法2
// 通过IIFE。产生一个块级作用域
/**
for(var i = 0; i < 10; i++) {
(function inner() {
// 每一次for循环执行。都是执行inner这个IIFE函数. 每次执行都会产生一个新的 var j.
// 由于函数是有一个块级作用域, 故可以保证每次for执行时timer中的j都是不同的引用。
var j = i;
setTimeout(function timer() {
console.log(j);
}, j * 1000);
})();
}
*/
// 改进解决办法2
for(var i = 0; i < 10; i++) {
(function inner(j) {
// 每一次for循环执行。都是执行inner这个IIFE函数. 每次执行都会产生一个新的 var j.
// 由于函数是有一个块级作用域, 故可以保证每次for执行时timer中的j都是不同的引用。
setTimeout(function timer() {
console.log(j);
}, j * 1000);
})(i);
}

//总结---神奇的闭包
/**
* 明确下: 闭包是一个好东西。闭包无处不在。至于闭包产生的问题。要理解为什么会产生该问题。
* 1. 什么是闭包: 当函数可以记住并且访问所在词法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行。
* 2. 为什么会产生闭包: 闭包是基于词法作用域书写代码时所产生的自然结果。
* 3. 闭包会产生的经典问题。见上面。如何解决。本质是搞清楚词法作用域。
* 4. 闭包无处不在: 如 setTimeout,setInterval中 以及jquery中事件的绑定, ajax请求的回调,
* 只要使用了回调函数就是在使用闭包
* 如:
* sayHello('dottie', function callback(err, data) {
* console.log(data);
* })
*/