JavaScript闭包是个啥?

定义

函数与对其状态即词法环境(lexical environment)的引用共同构成闭包(closure)。也就是说,闭包可以让你从内部函数访问外部函数作用域。在JavaScript,函数在每次创建时生成闭包。
通俗的说就是:定义在一个函数内部的函数,并且该被函数访问外部函数作用域
只有调用外部函数时才会生成新闭包,不然就是同一个闭包

作用域链

对变量进行访问时,最先在自己的作用域中查找,如果找到则停止查找,否则向向一级作用域中查找,直到全局作用域中。如果还是没有找到就会报错。
例子:

// var myName = 'global'
function fn(){
	// var myName = 'fn'
	function fn1(){
		// var myName = 'fn1' 
		console.log(myName)
	}
	fn1()
}
fn()

在上面的代码里依次从下面向上注释,输出结果依次是fn1,fn,global,Uncaught ReferenceError: myName is not defined,最后在全局作用域里都没找到就会报错;

词法作用域

词法作用域:指函数在定义(或声明)它们的作用域里运行,而不是在执行(或调用)它们的作用里运行;看个例子

var name = 'fn1_name'
function fn1(){
console.log(name)
}
function fn2(){
	var name = 'fn2_name'
	fn1()
}
fn2()// fn1_name

fn1是在全局中声明的,因此fn1是在全局作用域中运行的,然后根据作用域链查找name,fn1函数中没有向向一级作用域查找。最终在全局中找到它为fn1_name

闭包

总所周知,一个函数运行完毕以后,该函数作用域里的所有变量都会被垃圾机制回收;而闭包就是延长了作用域链;
闭包例子:

  1. 函数作为返回值返回
function fn(){
var myName = 'fn'
	return function bar(){
		console.log(myName)
	}
}
var f = fn() //bar函数的引用赋值给变量f
f() //调用b函数 输出fn

上面的函数就是闭包的一种,var f = fn()产生了闭包,延长myName的作用域链,导致fn函数运行完毕后,myName得不到释放;
在这里插入图片描述
我们可以打个断点就清楚,闭包产生Closure,记着只有引用了外部函数的变量才会产生闭包。还有上面的Call Stack代表了调用栈,现在是bar,是函数名,如果该函数为匿名函数则为anonymous,如果点到下面的anonymous,就是匿名的全局,断点就会跑到37行;
例子2:

function makeAdder(x) {
  return function(y) {
    return x + y;
  };
}

var add5 = makeAdder(5); //返回匿名函数,x对外部函数有引用
var add10 = makeAdder(10);

console.log(add5(2));  // 7 相当于 function(2){return 5+2}
console.log(add10(2)); // 12

例子3:

for(var i=0;i<3;i++){
btn[i].onclick = function(){
		console.log(i) //输出都是3
	}
}

上面的代码运行了3次也就注册了3个按钮的点击事件,但是只有通过点击按钮才会执行函数,所以那时已经变为3了。
利用闭包:

for(var i=0;i<3;i++){
	(function(j){
		btn[i].onclick = function(){
			console.log(j)
		}
	})(i)
}

这样操作的话,产生了3个闭包也保存了它们的值。当然我们开发时一般都是使用属性保存索引,ES6中的let块作用域也可以解决该问题。

用闭包模拟私有方法

var Counter = (function() {
var privateCounter = 0;
  function changeBy(val) {
    privateCounter += val;
  }
  return {
    increment: function() {
      changeBy(1);
    },
    decrement: function() {
      changeBy(-1);
    },
    value: function() {
      return privateCounter;
    }
  }   
})();

console.log(Counter.value()); /* logs 0 */
Counter.increment();
Counter.increment();
console.log(Counter.value()); /* logs 2 */
Counter.decrement();
console.log(Counter.value()); /* logs 1 */

上面的代码,对象的privateCounter只能通过暴露的方法修改,从而有了私有属性或者方法;模块化应该的思想应该就是这样,像JQuery,向外暴露一些方法。

试题

var name = 'The window'
var obj = {
	name:'my obj',
	sayName:function(){
		return function(){
			return this.name
		}
	}
}
alert(obj.sayName()())  //The window
alert(obj.sayName().call(obj)) //my obj

上述代码输出The window,因为obj.sayName()()的调用对象是window ,所以this指向The window;第二个是修改this的指向为obj;

var name = 'The window'
var obj = {
	name:'my obj',
	sayName:function(){
		var that = this
		return function(){
			return that.name
		}
	}
}
alert(obj.sayName()()) //my obj

这个是把this的指向的引用赋值给that,然后是闭包,内部函数引用了外部函数的变量;

function fun(n,o){
console.log(o)
	return{
		fun:function(m){
			return fun(m,n)
		}
	}
}
var a = fun(0);//返回一个对象{fun:function(){}} 输出undefined,产生一个闭包
a.fun(1);//向function(m)传1,n引用为0,输出0
a.fun(2);//向function(m)传2,n引用为0,输出0
a.fun(3)//向function(m)传3,n引用为0,输出0
var b = fun(0).fun(1).fun(2).fun(3)
//返回一个对象{fun:function(){}} 输出undefined,产生闭包
//第二次再次调用了外部函数又产生一个闭包,输出0
//第三次再次调用了外部函数又产生一个闭包,输出1
//第四次再次调用了外部函数又产生一个闭包,输出2

var c = fun(0).fun(1);
//在第二个闭包的基础上开始,不再生成新闭包,使用先输出undefined,0
c.fun(2);//输出1
c.fun(3)//输出1

上述代码,注意有没有生成新的闭包;

总结

闭包,我们可能都用过,但是也没有刻意的去使用它,都是潜意识的。但是闭包可能会造成内存泄漏,所以要谨慎使用。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章