闭包容许函数访问并操做函数外部的变量,只要变量或函数存在于声明函数时的做用域内,闭包就能够访问这些变量和函数javascript
//全局闭包 不明显
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();
复制代码
当在外部函数中声明内部函数时,不只定义了函数的声明,还建立了一个闭包java
该闭包不只包含了函数的声明,还包含了 函数声明时该做用域中的全部变量数组
闭包建立了被定义时的做用域内的变量和函数的安全气泡安全
每个经过闭包访问变量的函数 都具备一个做用域链,做用域链包含闭包的所有信息。闭包
:star: 闭包全部的信息都存储在内存中,直到 JavaScript 引擎确保这些信息再也不使用或页面卸载时,才会清理。函数
私有变量 是对外部隐藏的对象属性post
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: 在构造器中隐藏变量,使其在外部做用域中不可访问,可是可在闭包内部进行访问动画
//在 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");
//经过在函数内部定义变量,并基于闭包,使得在计时器的回调函数中能够访问这些变量,每一个动画都可以得到属于本身的"气泡"中的私有变量
复制代码
闭包内的函数 不只能够在闭包建立时能够访问这些变量,并且能够在闭包函数执行时,更改这些变量的值。ui
闭包不是在建立的那一时刻的快照,而是一个真实的状态封装,只要闭包存在,就能够对变量进行修改。this
JavaScript 有两种类型的代码:一种是全局代码,一种是函数代码
上下文分为两种:全局执行上下文和函数执行上下文
全局执行上下文只有一个,当 JavaScript 程序开始执行时就已经建立了全局上下文;而函数执行上下文在每次 调用 函数时,就会建立一个新的。
JavaScript 是单线程的:一旦发生函数调用,当前的执行上下文必须中止执行,并建立新的函数执行上下文来执行函数。当函数执行完成后,将函数执行上下文销毁,并从新回到发生调用时的执行上下文中。
栈 💉的活塞
//建立执行上下文
function skulk(ninja){
report(ninja + " skulking");
}
function report(message){
console.log(message);
}
skulk("Kuma");
skulk("Yoshi");
复制代码
能够经过 JavaScript 调试器中查看,在 JavaScript 调试器中能够看到对应的调用栈(call stack)。
词法环境 是 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]]属性与建立时的环境进行关联。
3个关键字定义变量:var let const
不一样之处:可变性、词法环境
const 不可变,let var 可变
声明时须要初始化,一旦声明完成以后,其值不可更改。 => 指向不可更改
const firstConst = "samurai";
firstConst = "ninja"; //报错
const secondConst = {};
secondConst.weapon = "wakizashi"; //不报错
const thirdConst = [];
thirdConst.push("Yoshi"); //不报错
复制代码
若是 const 的值是 静态变量,则不容许从新赋值;若是 const 的值是对象或者是数组类型,则能够对其增长新元素,可是不能重写。其实不可变的是引用,而不是值。
var 一组,let 和 const 一组
关键字 var :变量是在距离最近的 函数内部 或是在 全局词法环境 中定义的。忽略块级做用域
var 声明的变量实际上老是在 距离最近的函数内 或 全局词法环境中 注册的,不关注块级做用域
let 与 const 直接在最近的词法环境中定义变量(能够是块级做用域内、循环内、函数内或全局环境内)。咱们能够用 let 和 const 定义 块级别、函数级别、全局级别的变量。
词法做用域又叫静态做用域,由于js的做用域在词法解析阶段就肯定了
动态做用域:区别于静态做用域,即在函数调用时才决定做用域
JavaScript 代码的执行 分两个阶段:
一旦建立了新的词法环境,就会执行第一阶段。在第一阶段,没有执行代码,而是JavaScript引擎会访问并注册在当前词法环境中所声明的变量和函数。变量和函数声明提高
第二阶段的执行取决于变量的类型(let var const 函数声明)以及环境类型(全局环境、函数环境或块级做用域)
1.若是是建立一个函数环境,那么建立形参及函数参数的默认值。若是是非函数环境,将跳过此步骤。
2.若是是建立全局或函数环境,就扫描当前代码进行函数声明(不会扫描其余函数的函数体),可是不会扫描函数表达式或箭头函数。对于所找到的函数声明,将建立函数,并绑定到当前环境与函数名相同的标识符上。若该标识符已经存在,那么该标识符的值将被重写。若是是块级做用域,将跳过此步骤。
3.扫描当前代码进行变量声明。在函数或全局环境中,找到全部当前函数以及其余函数以外经过 var 声明的变量,并找到全部在其余函数或代码块以外经过 let 或 const 定义的变量。在块级环境中,仅查找当前块中经过 let 或 const 定义的变量。对于所查找到的变量,若该标识符不存在,进行注册并将其初始化为 undefined。若该标识符已经存在,将保留其值。
若函数是函数声明进行定义的,则能够在函数声明以前访问函数。
若函数是函数表达式或箭头函数进行定义的,则不会在以前访问到函数
变量的声明会提高至函数顶部,函数的声明会提高至全局代码顶部。
其实,变量和函数的声明并无实际发生移动,只是在代码执行以前,先在词法环境中进行注册。
闭包能够访问建立函数时所在做用域内的所有变量
//经过函数访问私有变量,而不经过对象访问
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 中没有真正的私有对象属性,可是能够经过闭包实现一种可接受的“私有”变量的方案