- 什么是闭包? 有什么作用
- 闭包的形成
在高级程序设计3中对于闭包的定义是这样的
<blockquote>有权访问另一个函数作用域中的变量的函数。</blockquote>
在Wikipedia中的定于是这样的
<blockquote>引用了自由变量的函数。</blockquote>
一开始,我对这样的抽象概念很模糊,但是当我理解了闭包之后,我又认为这样的定义是简介而有力的,如果从理解闭包的定义来说,可以认为JS中的所有函数都是闭包,因为每个函数都能访问到全局作用域的自由变量
- 闭包的形成
var a = 1
function fn() {
console.log(a)
}
fn()
在这个例子中,fn可以访问了全局变量a,这个函数就叫做闭包,当然,这只是从理解的角度来说,实际上这样的全局作用域下的闭包是没意义的,因为全局的变量对象不会被销毁。在函数内部的闭包可以在chrome的开发者工具看到
要理解闭包的和闭包的作用,就要从作用域链的形成开始,通过下面的例子来说明
var a =2
function fn() {
var a = 1
function fn2() {
console.log(a)
}
fn2()
}
fn()
因为js是词法作用域,当函数fn声明的时候,就会预先包含全局作用域链,然后放在fn.Scope中,[[Scope]]是一个只有语言内部才能访问的属性,当函数fn被调用的调用的时候,会创建一个执行环境,执行环境里面有两个东西,一个是变量对象在函数内部又叫活动对象,一个是作用域链,作用域链的形成就是复制fn.Scope里面的变量对象,然后推入当前的活动对象。当fn执行完的时候,本来fn执行环境的变量对象和作用域链都会销毁,但是现在只有作用域链销毁了,变量对象却没有销毁,因为内部的函数fn2的作用域链在引用这fn的变量对象。因为fn2的作用域链里面包含fn的变量对象,所以可以访问到fn的变量,所以也就形成了闭包。
- 闭包的缺点
1. 在IE9之前因为使用不同的垃圾收集机制会导致循环引用会造成内存泄漏
2. 闭包会携带包含函数的作用域,所以会比其他函数占用更多内存,过度使用闭包会造成内存占用过多
- 闭包的用法
其实保存变量现场,封装私有变量都是对闭包特性的利用,并不是闭包的定义,不要混淆
1. 保存变量现场,主要利用了js函数传递参数的方式是按值传递
!function() {
var arr = []
for (var i = 0; i < 5; i++) {
arr[i] = function () {
alert(i)
}
}
arr[1]()//5
}()
本来的打算应该数字的每一项都是一个函数,可以打印出每一项的索引,但是因为所访问的i是外部函数的,当循环完以后,i已经累加到5了,所以无论多少输出当然是5,我们可以利用js函数传递是按值传递的方式来改造这个数组
!function() {
var arr = []
for (var i = 0; i < 5; i++) {
arr[i] = function (n) {
return function () {
alert(n)
}
}(i)
}
arr[1]()//5
}()
改造后的函数输出就是1,主要利用按值传递参数和闭包
2. 封装私有变量
有时候我们常常需要隐藏一些数据,而仅仅暴露一些接口供外部使用,起到封装的效果,这时候就可以利用闭包
var Car = (function () {
var speed = 0
return {
setSpeed: function (s) {
speed = s
},
getSpeed: function () {
return speed
}
}
})()
Car.setSpeed(30)
Car.getSpeed()
这就封装了一个汽车对象,其中是speed私有的变量,只能通过我们提供的接口setSpeed和getSpeed来访问到
- setTimeout 0 有什么作用
setTimeout表示的是超时调用,setTimeout可以接受多个参数,表示过延迟多长时间再执行回调函数- 第一个参数表示回调函数,可以是一个字符串或者是一个函数名,推荐使用函数名,因为字符串会导致一定的损耗和安全问题
- 第二个参数表示延迟的时间,单位是ms毫秒,因为js是单线程的解释器,所以一次只能执行一段代码,为了控制要执行的代码,就有一个任务队列,这些任务就会按照他们添加到任务队列的顺序执行,延迟的时间表示再过多少ms才把当前任务添加到任务队列,如果队列前面没有任务,当前任务就会立即执行,如果队列前面有任务,就要等到前面的任务执行完之后再执行
- 第三个和后面的参数表示参数回调函数的参数
setTimeout(f,0)表示的就是尽快的执行函数f,他的作用可以调整时间的发生顺序,例如在开发中,某个事件发生在子元素,然后冒泡到父元素,即子元素的回调函数会比父元素的回调函数先执行,通过setTimeout(f,0)我们就可以让子元素的回调函数排在队列后面,从而让父元素的回调函数先执行
参考:
定时器-阮一峰