资讯专栏INFORMATION COLUMN

《你不知道的JS》读书笔记---作用域及闭包

denson / 3305人阅读

摘要:注此读书笔记只记录本人原先不太理解的内容经过阅读你不知道的后的理解。作用域及闭包基础,代码运行的幕后工作者引擎及编译器。

注:此读书笔记只记录本人原先不太理解的内容经过阅读《你不知道的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的时候,访问了上一级的作用域,同时将这个作用域保存在了作用域链中吗?

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/90400.html

相关文章

  • 你不知道JavaScript上卷之作用域与闭包·读书笔记

    摘要:的分句会创建一个块作用域,其声明的变量仅在中有效。而闭包的神奇作用是阻止此事发生。依然持有对该作用域的引用,而这个引用就叫做闭包。当然,无论使用何种方式对函数类型的值进行传递,当函数在别处被调用时都可以观察到闭包。 date: 16.12.8 Thursday 第一章 作用域是什么 LHS:赋值操作的目标是谁? 比如: a = 2; RHS:谁是赋值操作的源头? 比如: conso...

    Raaabbit 评论0 收藏0
  • 你不知道JS读书笔记闭包在循环中应用

    摘要:闭包在循环中的应用延迟函数的回调会在循环结束时才执行事实上,当定时器运行时即使没给迭代中执行的是多有的回调函数依然是在循环结束后才会被执行,因此会每次输出一个出来。 闭包在循环中的应用 延迟函数的回调会在循环结束时才执行;事实上,当定时器运行时即使没给迭代中执行的是 setTime(..., 0),多有的回调函数依然是在循环结束后才会被执行,因此会每次输出一个6出来。 for(var...

    weapon 评论0 收藏0
  • 读书笔记-你不知道JavaScript(上)

    摘要:比如程序会被分解为解析语法分析将词法单元流转换成一个由元素逐级嵌套所组成的代表了程序语法接口的书,又称抽象语法树。代码生成将抽象语法树转换为机器能够识别的指令。 showImg(https://segmentfault.com/img/remote/1460000009682106?w=640&h=280); 本文首发在我的个人博客:http://muyunyun.cn/ 《你不知道的...

    jzzlee 评论0 收藏0
  • 你不知道JavaScript》 (上) 阅读摘要

    摘要:但是如果非全局的变量如果被遮蔽了,无论如何都无法被访问到。但是如果引擎在代码中找到,就会完全不做任何优化。结构的分句中具有块级作用域。第四章提升编译器函数声明会被提升,而函数表达式不会被提升。 本书属于基础类书籍,会有比较多的基础知识,所以这里仅记录平常不怎么容易注意到的知识点,不会全记,供大家和自己翻阅; 上中下三本的读书笔记: 《你不知道的JavaScript》 (上) 读书笔记...

    FingerLiu 评论0 收藏0
  • 你不知道javascript》笔记_作用域与闭包

    摘要:建筑的顶层代表全局作用域。实际的块级作用域远不止如此块级作用域函数作用域早期盛行的立即执行函数就是为了形成块级作用域,不污染全局。这便是闭包的特点吧经典面试题下面的代码输出内容答案个如何处理能够输出闭包方式方式下一篇你不知道的笔记 下一篇:《你不知道的javascript》笔记_this 写在前面 这一系列的笔记是在《javascript高级程序设计》读书笔记系列的升华版本,旨在将零碎...

    galaxy_robot 评论0 收藏0

发表评论

0条评论

denson

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<