1 作用域和闭包

1.1 理解作用域

理解作用域

变量的赋值操作会执行两个动作,首先编译器会在当前作用域中声明一个变量(如果没有声明过),然后在运行时引擎会作作用域中查找该变量,如果能找到就会对它赋值。

当变量出现在赋值操作的左侧时进行LHS查询,右侧进行RHS查询

RHS查询与简单地查找某个变量的值别无二致。即赋值的源头
LHS查询则是试图找到变量的容器本身,从而可对其赋值。即赋值目标

作用域嵌套

引擎从当前的执行作用域开始查找变量,如找不到,向上查找。当抵达最外层全局作用域时,无论找到还是没找到,都会停止。

不成功的RHS引用会导致抛出ReferenceError 异常。不成功的LHS引用会导致自动隐式地创建一个全局变量(非严格模式下),该变量使用LHS引用的目标作为标识符,或者抛出ReferenceError异常(严格模式下)

1.2 词法作用域

词法阶段

词法作用域是由你在写代码时将变量和块作用域写在哪里来决定的,因此当词法分析器处理代码时会保持作用域不变。

image.png
  1. 包含着整个全局作用域,其中只有一个标识符 foo
  2. 包含着foo所创建的作用域,其中有三个标识符: a bar b
  3. 包含着bar所创建的作用域,其中只有一个标识符:c

作用域气泡由其对应的作用域块代码写在哪里决定,它们是逐级包含的。

无论函数在哪里被调用,也无论它如何被调用,它的词法作用域都只由函数被声明是所处的位置决定。

1.3 函数作用域和块作用域

函数中的作用域

函数作用域的含义是指,属于这个函数的全部变量都可在整个函数的范围内使用及复印。

隐藏内部实现

可把变量和函数包裹在一个函数的作用域中,然后用这个作用域来"隐藏"它们。即最小授权或最小暴露原则。

“隐藏”作用域中的变量和函数所带来的另一个好处,是可避免同名标识符之间的冲突。

函数作用域

我们已知道,在任意代码片段外加包装函数,可将内部的变量和函数“隐藏”起来。如下:

var a = 2;
function foo() {
    var a = 3;
    console.log(a); //3
}
foo();
console.log(a); //2

但是并不理想,如果函数不需要函数名,并且能自动运行,这将会更加理想。

var a = 2;
(function foo(){
    var a = 3;
    console.log(a); //3
})();
console.log(a); //2

包装函数的声明以 (function...而不是以function...开始。函数会被当作函数表达式而不是一个函数声明来处理。

区分函数声明和表达式最简单的方法是看 funciton关键字出现在声明中的位置。如果function是声明中的第一个词,就是函数声明,否则就是一个函数表达式。

函数声明和函数表达式之间最重要的区别是它们的名称标识符将会绑定的何处。

第一个片段中foo被绑定在所在作用域中,可直接通过foo()来调用调。
第二个片段中foo被绑定在函数表达式自身的函数中而不是所在作用域中。

换句话说(function foo(){...})作为函数表达式意味着foo只能在...处被访问,外部作用域则不行。

匿名和具名
 setTimeout( function() {
    console.log("I waited 1 second!");
}, 1000)

这是匿名函数表达式
始终给函数表达式命名是一个最佳实践。

IIFE
var a = 2;
(function IIFE(global){
    var a = 3;
    console.log( a );  // 3
    console.log(global.a);  // 2
})(window);

console.log(a);  // 2

块作用域

try/catch

try/catch的catch分句会创建一个块作用域

let

let 关键字可将变量绑定到所在的任意作用域中(通常是{..}内部)。

1.4 提升

变量和函数声明会被提升,但是函数表达式不会被提升。
函数声明会优先被提升,然后才是变量

应尽可能避免在块内部声明函数。

1.5 作用域闭包

当函数可记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行。

function foo(){
    var a = 2;
    function bar() {
        console.log(a);  //2
    }
}
foo();

这是闭包吗?
技术上来讲,也许是。根据上面定义,确切地说并不是。
bar() 对 a 的引用的方法是词法作用域查找规则,而这些规则只是闭包的一部分

function foo() {
    var a = 2;
    function bar() {
        console.log(a);
    }
    return bar;
}
var baz = foo();
baz(); // 2  这就是闭包

bar()可被正常执行,这个例子中,它在自己定义的词法作用域以外的地方执行。

在foo() 执行后,通常会期待foo()的整个内部作用域都被销毁。而闭包的“神奇”之处正是可阻止这件事情的发生。事实上内部作用域依然存在,因此没有被回收。谁在使用这个内部作用域?原来是bar()本身在使用。

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

无论通过何种手段将内部函数传递到所在词法作用域以外,它都会持有对原始定义作用域的引用,无论在何处执行这个函数都会使用闭包。

现在我懂了

在定时器,事件监听器、Ajax请求、跨窗口通信、Web Workers 或者任何其他的异步(或同步)任务中,只要使用了回调函数,实际上就是在使用闭包!

循环和闭包

for ( var i = 1; i <=5; i++) {
    setTimeout(function timer(){
        console.log( i );
    }, i * 1000);
}

正常情况下,预期是分别输出数字1~5。每秒一次
但是,会每秒一次输出五次6

这是为什么?

首先6从哪来的呢?这个循环的终止条件是 i 不再 <=5。 条件首次成立时i 是6。因此,输出显示的是循环结束时 i 的最终值。

缺陷是我们试图假设循环中的每个迭代在运行时会给自己“捕获”一个i的副本。但是根据作用域的工作原理,实际情况是尽管循环中的五个函数是在各个迭代中分别定义的,但是它们都被封闭在一个共享的全局作用域中,因此实际上只有一个 i。

每个函数需要自己的变量,用来在每个迭代中储存 i 的值。

for(var i=1; i<=5; i++) {
    (function(j){
        setTimeout( function timer() {
            console.log( j );
        }, j * 1000);
    })( i );
}
重返块作用域

let 声明,可用来劫持块作用域,并且在这个块作用域中声明一个变量。

for(let i = 1; i<=5; i++){
    setTimeout( function timer(){
        console.log( i );
    }, i * 1000)
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容