第4章 提升
-
var a = 2引擎会将这条语句分为var a和a = 2,一个是声明变量,编译阶段的任务,而第二个是赋值,执行阶段的任务。 - 提升是指无论作用域中的声明出现在什么地方,都会在编译阶段放到最前面首先进行处理。这个过程将变量和函数移动到给自的作用域最顶端,这个过程被称为”提升“
- 函数声明首先被提升,然后才是变量
- 函数表达式中的变量声明会被提升,但是赋值操作不会。
- 这段代码输出的是b,因为函数声明被提升了,并且被覆盖了
foo(); // "b"
var a = true;
if (a) {
function foo() { console.log("a"); }
}
else {
function foo() { console.log("b"); }
}
第5章 作用域闭包
闭包是前端很重要的概念,但是很少有人能分辨清楚什么是闭包,并且如何去使用闭包。很多人认为函数里嵌套函数就是闭包,我认为是不对的。这本书里很好的帮我理清楚了闭包到底是什么。
- 当函数可以记住并访问所在的词法作用域时,就产生了闭包。即使函数是在当前词法作用域之外执行的。
function foo() {
var a = 2;
function bar() {
console.log( a ); // 2
}
bar();
}
foo();
按照上面的定义,这是闭包吗?从技术上来说,函数bar具有一个涵盖foo作用域的闭包,但是我们无法从上面代码看得出闭包如何工作的。看下面这段代码:
function foo() {
var a = 2;
function bar() {
console.log( a );
}
return bar;
}
var baz = foo();
baz(); // 2 —— 朋友,这就是闭包的效果。
Baz返回的是bar函数,bar() 显然是调用的地方是在自己定义的词法作用域以外的地方执行。一般情况,foo执行结束后,通常会期待foo()的整个内部作用域都会被销毁,但是因为闭包的作用,bar()所声明的地方位置的隐私,它拥有foo的内部作用域的闭包,所以不会不销毁。bar的这个引用就叫做闭包。
- 无论何种方式的函数值传递,我们都可以看见闭包的作用
function foo() {
var a = 2;
function baz() {
console.log( a ); // 2
}
bar( baz );
}
function bar(fn) {
fn(); // 妈妈快看呀,这就是闭包!
}
var fn;
function foo() {
var a = 2;
function baz() {
console.log( a );
}
fn = baz; // 将 baz 分配给全局变量
}
function bar() {
fn(); // 妈妈快看呀,这就是闭包!
}
foo();
bar(); // 2
- setTimeout, 事件监听器,ajax请求,跨窗口通信,异步任务等,只要用到了回调函数,实际上就是在使用闭包。
循环和闭包
- 观察这段代码,执行结果:回调函数会在循环结束后执行,因为每次循环都会赋值上一次运行的值,继续运行。
for (var i=1; i<=5; i++) {
setTimeout( function timer() {
console.log( i );
}, i*1000 );
}
- 对比和上面代码区别
for (var i=1; i<=5; i++) {
(function() {
setTimeout( function timer() {
console.log( i );
}, i*1000 );
})();
}
IIFE 会通过声明并立即执行一个函数来创建作用域,虽然有了独立的作用域但是什么都没有,需要一个变量存储变量才行
for (var i=1; i<=5; i++) {
(function() {
var j = i;
setTimeout( function timer() {
console.log( j );
}, j*1000 );
})();
}
//改进后
for (var i=1; i<=5; i++) {
(function(j) {
setTimeout( function timer() {
console.log( j );
}, j*1000 );
})( i );
}
这里才是预期的效果,一段时间后输出循环的参数。
小结: 这部分还需要再多看几遍,有一种似懂非懂的感觉,还不能十分把握说清楚。