javascript忍者秘籍-第五章 闭包和作用域

栏目: JavaScript · 发布时间: 5年前

内容简介:闭包允许函数访问并操作函数外部的变量,只要变量或函数存在于声明函数时的作用域内,闭包就可以访问这些变量和函数当在外部函数中声明内部函数时,不仅定义了函数的声明,还创建了一个闭包该闭包不仅包含了函数的声明,还包含了 函数声明时该作用域中的所有变量

闭包允许函数访问并操作函数外部的变量,只要变量或函数存在于声明函数时的作用域内,闭包就可以访问这些变量和函数

//全局闭包 不明显
var outerValue = "ninja";
function outerFunction(){
    outerValue === "ninja";  //true
}
outerFunction();
复制代码
//闭包例子
var outerValue = "samurai";
var later;

function outerFunction(){
    var innerValue = "ninja";
    
    function innerFunction(){
        outerValue === 'samurai';  //true
        innerValue === 'ninja';    //true
    }
    later = innerFunction();
}
outerFunction();
later();
复制代码

当在外部函数中声明内部函数时,不仅定义了函数的声明,还创建了一个闭包

该闭包不仅包含了函数的声明,还包含了 函数声明时该作用域中的所有变量

闭包创建了被定义时的作用域内的变量和函数的安全气泡

javascript忍者秘籍-第五章 闭包和作用域

每一个通过闭包访问变量的函数 都具有一个作用域链,作用域链包含闭包的全部信息。

:star: 闭包所有的信息都存储在内存中,直到 JavaScript 引擎确保这些信息不再使用或页面卸载时,才会清理。

5.2 使用闭包

封装私有变量

私有变量 是对外部隐藏的对象属性

function Ninja(){
    var feints = 0;
    this.getFeints = function(){
        return feints;
    }
    this.feint = function(){
        feints++;
    }
}

var ninja1 = new Ninja();
ninja1.feint();

ninja1.feints  //无法直接从外部访问属性
ninja1.getFeints() == 1  //true 可以通过方法来访问

var ninja2 = new Ninja();  //创建一个新的对象实例,新对象实例作为上下文 this指向新的对象实例
ninja2.getFeints() == 0  //true 新实例有自己的私有变量
复制代码

:zap: 在构造器中隐藏变量,使其在外部作用域中不可访问,但是可在闭包内部进行访问

  • 通过变量 ninja , 对象实例是可见的
  • 因为 feint 方法在闭包内部,因此可以访问变量 feints
  • 在闭包外部,无法访问变量 feints
javascript忍者秘籍-第五章 闭包和作用域

回调函数

//在 interval 的回调函数中使用闭包
//回调函数中 this 指向变了
function animateIt(elementId){
    var elem = document.getElementById(elementId);
    var tick = 0;
    var timer = setInterval(function(){
        if(tick < 100){
            elem.style.left = elem.style.top = tick + 'px';
            tick++;
        }else{
            clearInterval(timer);
            tick === 100;  //true
        }
    },10);
}
animateIt("box1");

//通过在函数内部定义变量,并基于闭包,使得在计时器的回调函数中可以访问这些变量,每个动画都能够获得属于自己的"气泡"中的私有变量
复制代码

闭包内的函数 不仅可以在闭包创建时可以访问这些变量,而且可以在闭包函数执行时,更改这些变量的值。

闭包不是在创建的那一时刻的快照,而是一个真实的状态封装,只要闭包存在,就可以对变量进行修改。

javascript忍者秘籍-第五章 闭包和作用域

5.3 执行上下文跟踪代码

JavaScript 有两种类型的代码:一种是全局代码,一种是函数代码

上下文分为两种:全局执行上下文和函数执行上下文

全局执行上下文只有一个,当 JavaScript 程序开始执行时就已经创建了全局上下文;而函数执行上下文在每次 调用 函数时,就会创建一个新的。

JavaScript 是单线程的:一旦发生函数调用,当前的执行上下文必须停止执行,并创建新的函数执行上下文来执行函数。当函数执行完成后,将函数执行上下文销毁,并重新回到发生调用时的执行上下文中。

栈:syringe:的活塞

//创建执行上下文
function skulk(ninja){
    report(ninja + " skulking");
}
function report(message){
    console.log(message);
}
skulk("Kuma");
skulk("Yoshi");
复制代码
javascript忍者秘籍-第五章 闭包和作用域

可以通过 JavaScript 调试器中查看,在 JavaScript 调试器中可以看到对应的调用栈(call stack)。

javascript忍者秘籍-第五章 闭包和作用域

5.4 使用词法环境跟踪变量的作用域

词法环境 是 JavaScript 引擎内部用来跟踪标识符与特定变量之间的映射关系

词法环境 是 JavaScript 作用域的内部实现机制,称为 作用域 (scopes)

代码嵌套

词法环境主要基于代码嵌套,通过代码嵌套可以实现代码结构包含另一代码结构

在作用域范围内,每次执行代码时,代码结构都获得与之关联的词法环境。

内部代码结构可以访问外部代码结构中定义的变量。

代码嵌套与词法环境

每个执行上下文都有一个与之关联的词法环境,词法环境中包含了在上下文中定义的标识符映射表。=> 作用域链

在特定的执行上下文中,我们的程序不仅直接访问词法环境中定义的局部变量,而且还会访问外部环境中定义的变量。

无论何时创建函数,都会创建一个与之相关联的词法环境,并存储在名为 [[Environment]] 的内部属性上。两个中括号表示是内部属性。

函数都有词法环境

var ninja = "Muneyoshi";

function skulk(){
    var action = "Skulking";
    
    function report(){
        var intro = "Aha!";
        assert(intro === "Aha!","Local");
        assert(action === "Skulking","Outer");
        assert(ninja === "Muneyoshi","Global");
    }
    report();
}
skulk();
复制代码
javascript忍者秘籍-第五章 闭包和作用域

:question: 为什么不直接跟踪整个执行上下文搜索标识符,而是通过词法环境来跟踪呢?

JavaScript 函数可以作为任意对象进行传递,定义函数时的环境与调用函数的环境往往是不同的(闭包)

:zap: 无论何时调用函数,都会创建一个新的执行环境,被推入执行上下文栈。此外,还会创建一个与之关联的词法环境。外部环境与新建的词法环境,JavaScript 引擎将调用函数的内置[[Environment]]属性与创建时的环境进行关联。

5.5 理解 JavaScript 的变量类型

3个关键字定义变量:var let const

不同之处:可变性、词法环境

vs 不可变

const 不可变,let var 可变

声明时需要初始化,一旦声明完成之后,其值不可更改。 => 指向不可更改

const firstConst = "samurai";
firstConst = "ninja";  //报错

const secondConst = {};
secondConst.weapon = "wakizashi";  //不报错

const thirdConst = [];
thirdConst.push("Yoshi");  //不报错
复制代码

如果 const 的值是 静态变量,则不允许重新赋值;如果 const 的值是对象或者是数组类型,则可以对其增加新元素,但是不能重写。其实不可变的是引用,而不是值。

vs 词法环境

var 一组,let 和 const 一组

关键字 var :变量是在距离最近的 函数内部 或是在 全局词法环境 中定义的。忽略块级作用域

var 声明的变量实际上总是在 距离最近的函数内 或 全局词法环境中 注册的, 不关注块级作用域

let 与 const 直接在最近的词法环境中定义变量(可以是 块级作用域内循环内函数内或全局环境内 )。我们可以用 let 和 const 定义 块级别、函数级别、全局级别的变量。

javascript忍者秘籍-第五章 闭包和作用域
javascript忍者秘籍-第五章 闭包和作用域

词法环境注册标识符

词法作用域又叫静态作用域,因为js的作用域在词法解析阶段就确定了

动态作用域:区别于静态作用域,即在函数调用时才决定作用域

JavaScript 代码的执行 分两个阶段:

一旦创建了新的词法环境,就会执行第一阶段。在第一阶段,没有执行代码,而是JavaScript引擎会访问并注册在当前词法环境中所声明的变量和函数。 变量和函数声明提升

第二阶段的执行取决于变量的类型(let var const 函数声明)以及环境类型(全局环境、函数环境或块级作用域)

1.如果是创建一个函数环境,那么创建形参及函数参数的默认值。如果是非函数环境,将跳过此步骤。

2.如果是创建全局或函数环境,就扫描当前代码进行函数声明(不会扫描其他函数的函数体),但是不会扫描函数表达式或箭头函数。对于所找到的函数声明,将创建函数,并绑定到当前环境与函数名相同的标识符上。若该标识符已经存在,那么该标识符的值将被重写。如果是块级作用域,将跳过此步骤。

3.扫描当前代码进行变量声明。在函数或全局环境中,找到所有当前函数以及其他函数之外通过 var 声明的变量,并找到所有在其他函数或代码块之外通过 let 或 const 定义的变量。在块级环境中,仅查找当前块中通过 let 或 const 定义的变量。对于所查找到的变量,若该标识符不存在,进行注册并将其初始化为 undefined。若该标识符已经存在,将保留其值。

javascript忍者秘籍-第五章 闭包和作用域

若函数是函数声明进行定义的,则可以在函数声明之前访问函数。

若函数是函数表达式或箭头函数进行定义的,则不会在之前访问到函数

变量的声明会提升至函数顶部,函数的声明会提升至全局代码顶部。

其实,变量和函数的声明并没有实际发生移动,只是在代码执行之前,先在词法环境中进行注册。

5.6 闭包的工作原理

闭包可以访问创建函数时所在作用域内的全部变量

//通过函数访问私有变量,而不通过对象访问
function Ninja(){
    var feints = 0;
    this.getFeints = function(){
        return feints;
    }
    this.feint = function(){
        feints++;
    };
}
var ninja1 = new Ninja();
ninja1.feint();

var imposter = {};
imposter.getFeints = ninja1.getFeints;
imposter.getFeints() == 1   //true
复制代码

JavaScript 中没有真正的私有对象属性,但是可以通过闭包实现一种可接受的“私有”变量的方案


以上所述就是小编给大家介绍的《javascript忍者秘籍-第五章 闭包和作用域》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

计算几何

计算几何

邓俊辉 / 清华大学出版社 / 2009-6 / 49.00元

《计算几何:算法与应用(第3版)》的前4章对几何算法进行了讨论,包括几何求交、三角剖分、线性规划等,其中涉及的随机算法也是《计算几何:算法与应用(第3版)》的一个鲜明特点。第5章至第10章介绍了多种几何结构,包括几何查找、kd树、区域树、梯形图、Voronoi图、排列、Delaunay三角剖分、区间树、优先查找树以及线段树等。第11章至第16章结合实际问题,继续讨论了若干几何算法及其数据结构,包括......一起来看看 《计算几何》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具