javascript忍者秘籍-第五章 闭包和作用域
栏目: JavaScript · 发布时间: 6年前
内容简介:闭包允许函数访问并操作函数外部的变量,只要变量或函数存在于声明函数时的作用域内,闭包就可以访问这些变量和函数当在外部函数中声明内部函数时,不仅定义了函数的声明,还创建了一个闭包该闭包不仅包含了函数的声明,还包含了 函数声明时该作用域中的所有变量
闭包允许函数访问并操作函数外部的变量,只要变量或函数存在于声明函数时的作用域内,闭包就可以访问这些变量和函数
//全局闭包 不明显 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忍者秘籍-第五章 闭包和作用域》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Open Data Structures
Pat Morin / AU Press / 2013-6 / USD 29.66
Offered as an introduction to the field of data structures and algorithms, Open Data Structures covers the implementation and analysis of data structures for sequences (lists), queues, priority queues......一起来看看 《Open Data Structures》 这本书的介绍吧!