1

注:此读书笔记只记录本人原先不太理解的内容经过阅读《你不知道的JS》后的理解。
作用域及闭包基础,JS代码运行的幕后工作者:引擎及编译器。引擎负责JS程序的编译及执行,编译器负责词法分析和代码生成。那么作用域就像一个容器,引擎及编译器都从这里提取东西。
如 var a = 2 这句简单的代码,声明一个变量a,同时给他赋值为2.
背后运行的过程,编译器阶段:
进行词法分析,将证据代码切分成 var, a, =, 2 并逐一分析是否有特定作用,词法分析的过程中遇到var a 会询问作用域在当前作用域下是否有a这个变量,若有,则忽略声明,继续编译,若无,就要求作用域在当前作用域声明该变量。 而后生成 var a = 2的代码。
引擎阶段:运行var a =2 时 首先访问当前作用域是否有a 的变量,有就赋值为2,没有就向上查询。若最终能找到该变量则使用该变量并给它赋值为2,若没有就会抛出一个异常。
以上就是其背后的运行过程。

块作用域:
JS语言中似乎是没有相对其他语言相关的块作用域的说法即{}框定的内容不算一个封闭的作用域,简单的例子:

if(true){
    var b = "this is in the block"
}

console.log(b) //this is in the block

显然b变量在if的语句块外获取到了,有如下几种做法可以限定作用域:
let, IIFE(立即执行函数表达式)[相当于变成了函数作用域], with
但是用with的时候需要注意:
简单的例子

var ob1 = {
    a : 1
}

var ob2 = {
    b : 2
}

with(ob1) {
    a = 2
}
with(ob2) {
    a = 1
}
ob1.a // 2
ob2.a //undefined
a // 1





发现在ob2里面还是无法找到a的属性,同时全局变量多了个a变量赋值为了1,这里的原理是在ob2的作用域中LHS寻找a变量,并没有找到,则会创建一个属于全局作用域的a变量(非严格模式)[这是硬知识,不知道其这样实现的原理是什么]。所以可以在全局环境中找到a这个变量,其值为1.原理跟下述的例子类似:

function foo(){
    var a = 1
    b = a // 这里函数作用域中b变量是未声明的,编译器在进行LHS查询时由于未能在所有的作用域中找到b‘
          //,(非严格模式下)会自动声明一个全局变量
    something
    return something
}

就像上述的例子,只不过是换了个对象而已,由ob2换成了foo函数对象。

闭包
闭包是一个极其有意思的现象,官方的解释是这样的,函数在其所在的作用域外被调用,同时该函数访问了其作用域中的某些变量,这就形成了一个闭包。

一个简单的例子:

function closure(){
    var a = "here is closure"
    function out(){
        console.log(a)
    }
    return out
}

var test = closure()
a = "here is out of closure"
test() // "here is closure"

这里输出的是"here is closure",分析下代码:

var test = closure() //  var test = function(){
                     //             console.log(a)
                     //  }
a = "here is out of closure"
test()

理论上来说,按照作用域的概念,这里应该输出的是上一句的a变量,这里test的函数无法访问到closure内部的变量的。这就是闭包的作用,我的理解是,他会将作用域给锁定,内存中原函数的内部的变量并不会被回收。因此在函数在其作用域外被调用,还是能使用其作用域中的变量a.

利用闭包这个特性,JS还有很多有意思的东西:
下面这个比较通俗点的例子,廖学峰大佬的JS教程中的例子:

   function count() {
        var arr = [];
        for (var i=1; i<=3; i++) {
            arr.push(function () {
                return i * i;
            });
        }
        return arr;
    }
    
    var array = count()
    var f1 = array[0]
    var f2 = array[1]
    var f3 = array[2]
    f1()  // 16
    f2()  // 16
    f3()  // 16
    
    

预期的输出应该是1, 4, 9,但实际的结果是全都是16,原因就在于JS的for循环没有块作用域这个属性,数组添加元素的内容是一个函数,而函数并非立即调用,他们都共享一个作用域,而作用域中的变量i只有一个,其最终的结果就是4,所以所有的输出都是16。

要解决这个问题,可以用快作用域的方法,let, IIFE将i在迭代时的作用域锁定。

 function count() {
        var arr = [];
        for (var i=1; i<=3; i++) {
            (function(){
                var j = i  //利用IIFE时需要声明变量接受变量i,否则IIFE中的作用域为空,向上查询,使用                
                          //的仍然是i,而全局共享的i依旧取最终的结果
                arr.push(function () {
                             return j * j;
                        });
            })()
        }
        return arr;
    }
    
    var array = count()
    var f1 = array[0]
    var f2 = array[1]
    var f3 = array[2]
    f1()  // 1
    f2()  // 4
    f3()  // 9

这里的上下两个例子都是闭包的例子,不同的是,第一个闭包的访问的作用域是函数count的内部作用域共享一个变量i,取最后的值,而下一个例子,闭包访问的作用域是一个IIFE的内部作用域,为变量j。而IIFE是类似一个函数作用域,j是不会随着i变化而变化,这里执行了3次IIFE,每次的IIFE对应的变量j是不同的,不会相互影响,所以达到了预期输出,如果IIFE中作用域为空,那么闭包访问的仍旧是count的作用域,因此依旧达不到期望的输出。

同理let也能达到预期的输出

 function count() {
        var arr = [];
        for (var i=1; i<=3; i++) {
                let j = i  
                arr.push(function () {
                             return j * j;
                        });
        }
        return arr;
    }
    
    var array = count()
    var f1 = array[0]
    var f2 = array[1]
    var f3 = array[2]
    f1()  // 1
    f2()  // 4
    f3()  // 9

而for循环结构有个特殊的地方,for 循环头部的 let 声明还会有一
个特殊的行为。这个行为指出变量在循环过程中不止被声明一次,每次迭代都会声明。随
后的每个迭代都会使用上一个迭代结束时的值来初始化这个变量。
所以还可以这样实现,使代码结构更为明朗:

function count() {
        var arr = [];
        for (let i=1; i<=3; i++) {
                arr.push(function () {
                             return i * i;
                        });
        }
        return arr;
    }
    
    var array = count()
    var f1 = array[0]
    var f2 = array[1]
    var f3 = array[2]
    f1()  // 1
    f2()  // 4
    f3()  // 9

以上就是对当初作用域不太理解的地方如今通过此书获得的认识,以上只是个人的理解,依旧存有疑问,如为什么在IIFE实现独立作用域的时候若不声明var j = i或获得的结果依旧不是期望的值,是因为闭包在当前作用域访问不到i的时候,访问了上一级的作用域,同时将这个作用域保存在了作用域链中吗?


jiejiewu
13 声望0 粉丝