概念
闭包(closure)指有权访问另一个函数作用域中变量的函数
— Javacript高级程序设计 p309
简单理解,一个作用域可以访问另一个函数内部的私有变量
// 其中 test就是一个闭包
function fn(){
var num = 10
function test () { console.log(num) }
}
作用
保护作用
在上下文中会有一些私有的变量AO(XXX),这些私有变量和外界的变量不会冲突
(互不影响)
应用
团队协作开发
中,为防止全局变量的冲突污染,建议每个开发者,把自己的代码放到一个闭包中(立即执行函数即可)保护起来封装一个插件或类库
等,防止自己定义的变量和方法与用户定义的冲突,需要把所写的代码放到一个闭包中,例如Jquery
保存作用
某些情况下,上下文中的某些内容被为外界占用
后,当前上下文并不会出栈销毁
,这样开一把上下文中的一些信息存储起来
应用
1.在某些需求下,经常需要形成一个闭包,存储一些值(且不能销毁),供后面的程序运行使用,例如 惰性函数
,柯里化函数(bind)
,compose
函数等
优缺点
优点:保护和保存代码不受污染
缺点:会产生不销毁的上下文,导致栈/堆内存消耗过大
,也会导致内存泄露
(该内存空间使用完毕之后未回收),影响页面的运行性能
循环输出问题
for(var i = 1; i <= 5; i ++){
setTimeout(function timer(){
console.log(i) // 6 6 6 6 6 6
}, 0)
}
原因
- 1.var 属于
函数作用域
,在for循环属于块级作用域,存在变量提升机制, var 命令实际上只会执行一次
- 2.因为setTimeout为
宏任务
,由于JS中单线程eventLoop
机制,在主线程同步任务执行完后才去执行宏任务,因此循环结束后setTimeout中的回调才依次执行,但输出i的时候当前作用域没有,往上一级再找,发现了i,此时循环已经结束,i变成了6。
// 可以理解为
for (var i = 0; i <= 5; i++) {}
console.log('哦吼~'); // 主线程结束
// 进入事件队列执行, 此时的 i 已经是6了
setTimeout(function () {
console.log(i)
})
setTimeout(function () {
console.log(i)
})
setTimeout(function () {
console.log(i)
})
setTimeout(function () {
console.log(i)
})
setTimeout(function () {
console.log(i)
})
解决方法
- 1.利用IIFE(立即执行函数)
for(var i = 1;i <= 5;i++){
(function(j){
setTimeout(function timer(){
console.log(j)
}, 0)
})(i)
}
- 2.定时器的第三个参数, 作为timer函数的第一个函数参数
for(var i=1;i<=5;i++){
setTimeout(function timer(j){
console.log(j)
}, 0, i)
}
- 3.使用ES6中的let,let属于
块级作用域
for(let i = 1; i <= 5; i++){
setTimeout(function timer(){
console.log(i)
},0)
}
// i = 1
{
setTimeout(function timer(){
console.log(1)
},0)
}
// i = 2
{
setTimeout(function timer(){
console.log(2)
},0)
}
// i = 3
...
命名空间
概念
给每个对象的堆内存
起一个变量名,这个变量就是’命名空间’
作用
- 避免变量,函数名的冲突
var a = 1
var utils = { 方法... }
- 单例设计模式 (
闭包
)
各板块暴露到全局只有一个变量,避免全局变量的污染,实现了闭包之间的方法公用性
var utils = (function(){
var num = 10
function test(){console.log(num)}
//函数...
return {
test // 相当于 test:test
// 方法...
}
}())
utils.test() // 直接用命名空间 utils 调用其中方法
- 基于 window.xxx = 方法 ,暴露到全局
这种方法暴露到全局对象GO上,也可能导致方法之间的冲突
柯里化函数
概念
柯里化(Currying):把接受多个参数的参数变换成接受一个单一参数(最初函数的第一个参数)
的函数,并且返回接受余下的参数且返回结果的新函数的技术
作用
参数复用,提前返回和延迟执行
例题
- 实现 fn(11).add(1).mine(2).take(1).divide(8).res()
函数实现计算器
const fn = (num) => {
return {
add: (n) => fn(num + n),
mine: (n) => fn(num - n),
take: (n) => fn(num * n),
divide: (n) => fn(num / n),
res: () => num
}
}
- 实现 add(1)(2)(3)(4)
function add(a, b, c, d) {
return [...arguments].reduce((sum, x) => sum + x)
}
function currying(fn){
let len = fn.length
let args = []
return function HOC(...newArg){
args = [...args,...newArg]
if(args.length <len){
return HOC
}else{
return fn.apply(null,args.slice(0,len))
}
}
}
let addCurry = currying(add)
let res = addCurry(1)(2)(3)(4)
- 实现 add(1)(2)(3)(4)…(100)()
function add(...args) {
return args.reduce((sum, x) => sum + x)
}
function currying(fn) {
let args = []
return function HOC(...newArg) {
if (newArg.length) {
args = [...args, ...newArg]
return HOC
} else {
return fn.apply(null, args)
}
}
}
let addCurry = currying(add)
let res = addCurry(1)(2)(3)(4)(5)()
console.log(res)