参考博客:
前端基础进阶(三):变量对象详解
js闭包其实不难,你须要的只是了解什么时候使用它前端
代码在被调用时,会建立一个执行上下文,执行上下文能够理解为当前代码的执行环境,它会造成一个做用域,咱们通常讨论全局做用域和函数做用域两种状况。
执行上下文有生命周期,分别为上下文建立阶段,在该阶段会肯定变量对象、做用域链和this指向,建立结束后会进入执行阶段,完成相关赋值与调用。缓存
执行上下文在建立阶段吧会产生变量对象,变量对象存储了当前做用域中的变量参数和函数,当建立完成后,代码开始执行,变量被赋予相应的值,变量对象就被激活为活动变量。 变量对象的建立,依次经历了如下几个过程。bash
第3条的含义是,首先,函数声明的优先级要比变量高,会声明提早到变量前面,对于函数与变量同名的状况,同名的变量就不会被赋值undefined。固然这条只适用于建立阶段阶段,当代码开始执行时,赋值就按照前后顺序来。闭包
// 不一样名状况
console.log(a) // f a(){}
console.log(b) // undefined
function a(){}
var b=10
console.log(b) // 10
// 上述代码的编译执行顺序为:
(1) 函数声明提早
function a(){}
(2) 变量声明提早并赋值undefined
var b = undefined
(3) 编译完成执行代码
function a(){}
var b=undefined
console.log(a) // f a(){}
console.log(b) // undefined
b=10 // 执行时被赋值10
console.log(b) // 10
// 同名状况
console.log(a) // f a(){}
function a(){
}
var a=10
console.log(a) // 10
// 这段代码编译执行的顺序为:
(1) 函数声明提早
function a(){}
(2) 变量由于同名,因此跳过赋值undefined
(3) 编译完成,执行代码
function a(){}
console.log(a) // f a(){}
a=10 // 执行时被赋值10
console.log(a) // 10
复制代码
首先要明确的是,做用域在代码定义时就产生了,不管该段代码在哪里调用,做用域都不会改变。那代码什么时候定义?我理解的是当所处的环境被调用时,如一个函数嵌套一个函数,只有当外部函数被调用时,内部函数才被定义。
由于函数存在嵌套关系,会造成一个做用域链,做用域链是由当前环境与上层环境的一系列变量对象组成,它保证了当前执行环境对符合访问权限的变量和函数的有序访问。app
var a = 10;
var fn = null;
function outer() { // 做用域链为 本身->全局
console.log(a) // 10
console.log(b) // undefined
var b = 20;
console.log(b) // 20
function inner() {
console.log(b) // 20
console.log(c) // 报错
}
fn = inner;
}
function other() {
var c = 30;
fn();
}
outer(); // 10 undefined 20
other(); // 20,报错,找不到c
复制代码
首先,是全局代码执行,进入函数调用栈,生成全局执行上下文,分为两个阶段,首先是建立阶段,函数声明提高,变量提高并赋值undefined,由于outer和other被定义了,因此建立了各自的做用域链,分别为各自的做用域到全局做用域;建立完成后开始执行,变量被赋值,开始调用函数outer。
调用outer时,outer进入函数调用栈,开始生成outer的执行上下文,也分为两个阶段,建立阶段,变量提高,函数定义,产生变量对象放到本身的做用域中,此时inner被定义,建立了本身的做用域链,为 inner的做用域到outer的做用域再到全局做用域;outer的执行上下文建立完成后开始执行相应代码,依次输出10 unndefined 20;而后执行结束,弹出函数调用栈。
接着调用other,也是一样的步骤,注意执行到fn时,由于fn被赋值inner,做用域跟inner同样,而inner的做用域链在定义的时候就肯定了,不管在哪里执行都不会改变,因此此时inner的做用域链也只有本身、outer和全局的,并不能访问到other中的变量,因此找不到c。函数
其实上述代码就是闭包的场景,outer虽然调用结束了,可是由于做用域链的缘由,并不会被摧毁回收,inner仍然能够访问到做用域链上的变量。
简单来讲,闭包就是外部函数能够访问某个函数内部变量的机制,因此造成闭包的必要条件就是,函数嵌套函数,同时将内部函数返回出来。性能
(1)点赞功能,每一个点赞按钮点击加1,同时互不影响ui
function add(){
var count=0;
return function(){
return count++
}
}
// 给按钮绑定不一样的点击事件
for(i in n){
var fn=add()
btn[i].onclick=fn;
}
复制代码
(2)数据缓存.假若有一个计算乘积的函数,mult函数接收一些number类型的参数,并返回乘积结果。为了提升函数性能,咱们增长缓存机制,将以前计算过的结果缓存起来,下次遇到一样的参数,就能够直接返回结果,而不须要参与运算。这里,存放缓存结果的变量不须要暴露给外界,而且须要在函数运行结束后,仍然保存,因此能够采用闭包。this
var mult = (function(){
var cache = {};
var calculate = function() {
var a = 1;
for(var i = 0, len = arguments.length; i < len; i++) {
a = a * arguments[i];
}
return a;
}
return function() {
var args = Array.prototype.join.call(arguments, ',');
if(args in cache) {
return cache[args];
}
return cache[args] = calculate.apply(null, arguments);
}
}())
复制代码
感受闭包仍是须要多写案例才能真正弄得清楚,为何这么设计,会持续更新。spa