闭包
概念:闭包是指有权访问另一个函数作用域中的变量的函数。
前提概念:
当某个函数被调用时,会创建一个执行环境及相应的作用域链。然后,使用arguments和其他命名参数的值来初始化函数的活动对象。但在作用域链中,外部函数的活动对象始终处于第二位,外部函数的外部函数的活动处于第三位,直至到作用域链终点的全局执行环境。
function compare(value1,value2){
if(value1 < value2){
return -1
}else if(){
return -1
}else{
return 0
}
}
var result = compare(5,10)
在上面的代码中,调用compare()函数时,会创建一个包含compares,value1和value2的活动对象。全局执行环境的变量对象(包含result和compare)在compare()函数的执行环境的作用域链中会放在第二位。
无论什么时候在函数中访问一个变量时,就会从作用域链中搜索具有相应名字的变量。一般来讲,当函数执行完毕后,局部活动对象就会被销毁,内存中仅保存全局作用域。但是闭包中又会有所不同。闭包中函数的作用域链会包括匿名函数的活动对象,外部函数的活动对象,以及全局变量对象。外部函数执行完之后,里面的变量也不会被销毁,因为匿名函数的作用域链还在引用该活动对象。直到这个匿名函数被销毁。
function createFunctions(){
var result = new Array()
for(var i = 0; i < 10; i++){
result[i] = function(){
return i
}
}
return result
}
createFunctions()//[ƒ, ƒ, ƒ, ƒ, ƒ, ƒ, ƒ, ƒ, ƒ, ƒ]
createFunctions()[0]()//10
let arr = createFunctions()
for(let j = 0;j < arr.length;j++){
console.log(arr[j]())//10,10,10,10,10,...
}
上面代码就向我们解释了闭包的副作用,即闭包中只能取得包含函数中任何变量的最后一个值,闭包保存的是整个变量,而不是某个特殊的变量。createFunctions()
函数执行后,返回的是一个函数数组 [f,f,f,f,f,f,f,f..]
,每个函数数组都保存了变量i 的最终值10。
function createFunctions(){
var result = new Array()
for(var i = 0;i < 10;i++){
result[i] = function(num){
return function(){
return num
}
}(i)
}
return result
}
let arr1 = createFunctions()
arr1[1]()
//1
arr1[2]()
//2
arr1[0]()
//0
关于this对象
在闭包中使用this对象也可能会导致一些问题。我们知道,this对象是在运行时基于函数的执行环境绑定的:在全局函数中,this相当与window,而当函数被作为某个对象的方法调用时,this等于那个对象。不过匿名函数的执行环境具有全局性,因此其this对象通常指向window。 但有时候由于编写闭包的方式不同,这一点可能不会那么明显。
var name = "The window"
var obj = {
name:'pp',
getName:function(){
return function(){
return this.name
}
}
}
//这个getName中的闭包
obj.getName()()//"The window"
上面代码是讲匿名函数中的this是指向全局变量window/global的。
改写上面的代码:
var name = "the window"
var obj = {
name:"pp",
getName:function(){
var that = this
return function(){
return that.name
}
}
}
obj.getName()()
//"pp"
解释:在返回的闭包函数的外部把this值取到,然后在内部时还会引用该变量,使得外部的函数不销毁,并且顺利取到对象的name属性。
块级作用域
包含在函数中的变量自身创建了一个块级作用域,函数外部访问不到它。
function outputNumbers(count){
for(var i = 0;i < count;i++){
console.log(i)
}
console.log(i)
}
outputNumbers(3)
//0
//1
//2
//3
console.log(i)//Uncaught ReferenceError: i is not defined
function outputNumbers(count){
for(var i = 0;i < count;i++){
console.log(i)
}
var i //即使再申明一次也不会改变它的值
console.log(i)
}
outputNumbers(2)//0 1 2
用块级作用域(通常称为私有作用域)的匿名函数的语法如下所示:
(function(){
//这里是块级作用域
})()
无论什么地方,只要需要一些变量,就可以使用私有作用域:
function outputNumbers(count){
(function(){
//变量i是匿名函数的私有变量,因为在私有作用域中声明的
for(var i=0 ; i < count; i++){
alert(i)
}
})()
alert(i)//导致一个错误
}
这种技术经常在全局作用域中被用在函数外部,从而限制向全局作用域中添加过多的变量和函数。一般来说,我们应该尽量少向全局作用域中添加变量和函数。
eg:
(function(){
var now = new Date()
if(now.getMonth() == 0&&now.getDate() == -1){
alert("happy new year")
}
})()
私有变量
任何在函数中定义的变量,都可以认为是私有变量,因为不能在函数的外部访问这些变量。私有变包括函数的参数,局部变量和在函数内部定义的其他函数。
建立一个特权方法(构造函数模式):
function MyObject(){
var privateVariable = 10
function privateFunction(){
return false
}
//特权方法
this.publicMethod = function (){
privateVariable++
return privateFunction()
}
}
静态私有变量
通过私有作用域中定义私有变量或函数,同样也可以创建特权方法,其基本模式如下所示:
(function(){
//私有变量和私有函数
var privateVariable = 10
function privateFunction(){
return false
}
//构造函数:没有var关键字,是一个全局变量,外部可以取到,注意函数声明只能在这个闭包内创建一个局部函数,所以我们使用
//函数表达式,且不带var关键字声明,使得其可以被外部取到
MyObject = function(){
}
//实现共有的/特权方法
MyObject.prototype.publicMethod = function(){
privateVariable++
return privateFunction()
}
})()
模块模式
前面的模式是用于为自定义类型创建私有变量和特权方法的。而道格拉斯所说的模块测是为单例模式擦混噶斤私有变量和特权方法。
所谓单例,指的就是只有一个实例的对象。
var singleton = function(){
//私有变量和私有函数
var privateVariable = 10
function privateFunction(){
return false
}
//创建对象
var object = new CustomType()
//添加共有属性和方法
object.publicProperty = true
object.publicMethod = function(){
privateVariable++
return privateFunction()
}
return object
}
由于创建闭包必须维护额外的作用域,所以过度使用他们可能会占有大量的内存。