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(); 复制代码
当在外部函数中声明内部函数时,不仅定义了函数的声明,还创建了一个闭包
该闭包不仅包含了函数的声明,还包含了 函数声明时该作用域中的所有变量
闭包创建了被定义时的作用域内的变量和函数的安全气泡
每一个通过闭包访问变量的函数 都具有一个作用域链,作用域链包含闭包的全部信息。
: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
回调函数
//在 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"); //通过在函数内部定义变量,并基于闭包,使得在计时器的回调函数中可以访问这些变量,每个动画都能够获得属于自己的"气泡"中的私有变量 复制代码
闭包内的函数 不仅可以在闭包创建时可以访问这些变量,而且可以在闭包函数执行时,更改这些变量的值。
闭包不是在创建的那一时刻的快照,而是一个真实的状态封装,只要闭包存在,就可以对变量进行修改。
5.3 执行上下文跟踪代码
JavaScript 有两种类型的代码:一种是全局代码,一种是函数代码
上下文分为两种:全局执行上下文和函数执行上下文
全局执行上下文只有一个,当 JavaScript 程序开始执行时就已经创建了全局上下文;而函数执行上下文在每次 调用 函数时,就会创建一个新的。
JavaScript 是单线程的:一旦发生函数调用,当前的执行上下文必须停止执行,并创建新的函数执行上下文来执行函数。当函数执行完成后,将函数执行上下文销毁,并重新回到发生调用时的执行上下文中。
栈:syringe:的活塞
//创建执行上下文 function skulk(ninja){ report(ninja + " skulking"); } function report(message){ console.log(message); } skulk("Kuma"); skulk("Yoshi"); 复制代码
可以通过 JavaScript 调试器中查看,在 JavaScript 调试器中可以看到对应的调用栈(call stack)。
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(); 复制代码
: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 定义 块级别、函数级别、全局级别的变量。
词法环境注册标识符
词法作用域又叫静态作用域,因为js的作用域在词法解析阶段就确定了
动态作用域:区别于静态作用域,即在函数调用时才决定作用域
JavaScript 代码的执行 分两个阶段:
一旦创建了新的词法环境,就会执行第一阶段。在第一阶段,没有执行代码,而是JavaScript引擎会访问并注册在当前词法环境中所声明的变量和函数。 变量和函数声明提升
第二阶段的执行取决于变量的类型(let var const 函数声明)以及环境类型(全局环境、函数环境或块级作用域)
1.如果是创建一个函数环境,那么创建形参及函数参数的默认值。如果是非函数环境,将跳过此步骤。
2.如果是创建全局或函数环境,就扫描当前代码进行函数声明(不会扫描其他函数的函数体),但是不会扫描函数表达式或箭头函数。对于所找到的函数声明,将创建函数,并绑定到当前环境与函数名相同的标识符上。若该标识符已经存在,那么该标识符的值将被重写。如果是块级作用域,将跳过此步骤。
3.扫描当前代码进行变量声明。在函数或全局环境中,找到所有当前函数以及其他函数之外通过 var 声明的变量,并找到所有在其他函数或代码块之外通过 let 或 const 定义的变量。在块级环境中,仅查找当前块中通过 let 或 const 定义的变量。对于所查找到的变量,若该标识符不存在,进行注册并将其初始化为 undefined。若该标识符已经存在,将保留其值。
若函数是函数声明进行定义的,则可以在函数声明之前访问函数。
若函数是函数表达式或箭头函数进行定义的,则不会在之前访问到函数
变量的声明会提升至函数顶部,函数的声明会提升至全局代码顶部。
其实,变量和函数的声明并没有实际发生移动,只是在代码执行之前,先在词法环境中进行注册。
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忍者秘籍-第五章 闭包和作用域》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。