一、作用域与作用域链
作用域是指 js 变量使用时所存在的一个区域,分为全局作用域(window)和局部作用域(function、setTimeout...等都会产生局部作用域)。当局部作用域变量名与全局作用域变量名重复时,局部变量会覆盖全局变量。
在局部作用域使用变量时,如果在自己作用域找不到对应变量,则会往上一级作用域查找,直到全局作用域,如果全局作用域无此变量则会报 undefined。相反,全局作用域中无法使用局部作用域中的变量。
window.a = 1
function(){
// 输出 1,虽然局部没有 a 变量,但是 全局中有。
console.log(a)
var b = 2
}
// 报错,全局中无法使用局部变量。
console.log(b)
上面这种一层层向外查询变量的过程叫做查询作用域链。而这种一层层局部作用域直到全局作用域的结构被称为作用域链。
// 全局作用域,声明了一个全局变量 a
var a = 100
// 函数会生成局部作用域
function acs(){
// 在此局部作用域中声明一个局部变量 b
var b = 50
// 输出:100, 50
console.log(a, b) // 执行过程:在此作用域查找变量 a,
// 找不到-->往上一级作用域找-->在全局找到,使用全局作用域中的a
// 在此作用域查找变量 b,查找到了,使用此局部变量的 b
}()
// 输出:b is not defined
console.log(a, b)
二、闭包(Closure)
1. 闭包是什么?
闭包是指在函数外部调用函数内部的局部变量,且在调用后局部变量不会被浏览器立即回收,会一直存在的一种私有变量。再简单点说就是函数返回函数。
红宝书中的描写:闭包是指有权访问另一个函数作用域中的变量的函数。
其实闭包就是返回一个函数,且这个函数对局部变量存在引用形成的包含关系就是闭包。
其实就是创建一个不会被 GC 回收的局部变量。也正因如此,闭包才会有内存泄漏的风险,需要在每次使用完后立刻清除。
闭包的形成:当前环境中存在指向父级作用域的引用。
2. 闭包的写法
// 使用自执行函数形成闭包
var add = function(){
let sum = 0
return function operation(){
return sum = sum ? sum + 1 : 1
}
}()
// 输出:1
add()
// 输出:2
add()
// 输出:3
add()
// 输出:4
add()
// 清除闭包,删除私有变量
add = null
// 输出:null
console.log(add)
// 输出:add is not function
add()
3. 闭包的作用
使用闭包的目的――隐藏变量,间接访问一个变量,在定义函数的词法作用域外,调用函数。
闭包通常在回调函数、私有属性、函数柯里化中使用。
4. 使用闭包实现多个图片点赞功能
使用闭包完成,多图点赞单独点赞功能,且每个 input 的点赞数量互不干扰。在这个例子中利用闭包声明了 5 个新的独立词法作用域。
闭包实现多图点赞
-
-
-
-
-
-
5. 使用闭包保护私有属性
创建一个计数器函数,在里面定义一个私有属性,这里通过闭包保护它不会被直接修改。
可以看见在这个例子中我们并没有直接操作 privatelyCounter,而是通过 makeCounter 主动暴露的方法来操作计数器中的 privateCounter。
// 创建一个计数器
const makeCounter = function(){
// 创建私有变量
var privatelyCounter = 0
// 输出私有变量
function console(){
return privatelyCounter
}
// 更改计数器方法
function change(num){
privatelyCounter += num
}
// 暴露公有方法
return {
CounterAdd(num){
change(num)
},
CounterSub(num){
change(num)
},
CounterLog(){
return console()
}
}
}
// 声明两计数器
const counter1 = makeCounter()
const counter2 = makeCounter()
counter1.CounterAdd(1)
counter1.CounterAdd(1)
counter2.CounterSub(-1)
counter2.CounterSub(-1)
// 输出:2
console.log(counter1.CounterLog())
// 输出:-2
console.log(counter2.CounterLog())
参考视频讲解:进入学习
三、使用闭包实现函数柯里化
所谓函数柯里化就是将一个多参函数转为单参函数。
// 正常求自增方法
function numAdd(x, y){
return x + y
}
console.log(numAdd(1, 2))
// 使用闭包实现柯里化
function numAddCurry(x){
return function(y){
return x + y
}
}
// 先声明一个变量拿到自增方法
const curry = numAddCurry(1)
// 在调用这个变量进行自增,输出:3
console.log(curry(2))
// 亦或者直接调用自增方法传入两个参数,输出也是:3
console.log(numAddCurry(1)(2))