JavaScript作用域和闭包
在javascript中,如果对作用域和闭包弄不清楚,写代码就会出很多问题,今天对作用域和闭包做一个总结。
作用域
作用域主要分为全局作用域和局部作用域,其中局部作用域分为函数作用域和块级作用域。
全局作用域
如果你在大括号({})或者函数的外面定义了一个变量,那么它就是一个全局的变量,它的作用域就是全局作用域。
let a = 1
function fun1 () {
console.log(a) // 结果:1
function fun2 () {
console.log(a) // 结果:1
}
fun2()
}
fun1()
console.log(a) // 结果:1
变量a就是定义在最外层,它就能在全局任意地方被使用。
值得注意的是,在同一级作用域中,使用let或const声明变量的时候第二个同名会报错,而使用var声明变量的时候,会覆盖前面的变量;
局部作用域
如果你在函数或者大括号({})内定义的变量,就是局部作用域的变量,它能够在该级作用域级任意下级作用域中使用。
function fun1() {
let a = 100
console.log(a) // 结果: 100
function fun2 () {
console.log(a) // 结果:100
}
fun2()
}
fun1()
console.log(a) // 结果: a is not defined
a只能在fun1函数内部包括内部函数中使用,一旦出了fun1的范围就无法使用该变量了。
自由变量的查找
一个变量在当前作用域没有定义却被使用了,就是自由变量。它的执行规则是怎样的呢?
自由变量的查找是向上级作用域,一层一层以此寻找,直至找到为止。如果全局作用域都没有找到,则报错xx is not defined。
let a = 100
function fun1 () {
let a1 = 2
function fun2 () {
let a2 = 3
let a = 0
function fun3 () {
let a3 = 4
a++
console.log(a + a1 + a2 + a3) // 结果: 10
}
fun3()
}
fun2()
}
fun1()
console.log(a) // 结果: 100
正如上述代码所示,在fun3函数内,a和a1、 a2都没有定义,但被使用了,在执行的时候,它会往上级作用域中查找,从而找到它们的值。值得注意的一点的是,在全局作用域和fun2的函数中我们都定义了a,但是在fun3中使用的fun2中定义的值,外面的使用的全局作用域的值,也就是说,它往上级查找的时候,只要查找到就会停止查找,会就近使用。作用域间也不会互相干扰。(它们里面存在的变量提升和函数提升可以查看我的另一篇博客有专门的总结)
闭包
闭包是作用域应用的特殊情况,主要有两种表现:(1)函数作为参数被传递。(2)函数作为返回值被返回。
/**
* 函数作为返回值
*/
function create () {
const a1 = 100
return function () {
console.log(a1)
}
}
const fn = create()
const a1 = 200
fn() // 结果: 100
/**
* 函数作为参数
*/
function print (fn) {
const a2 = 300
fn()
}
const a2 = 400
function fn1 () {
console.log(a2)
}
print(fn1) // 结果: 400
上面代码演示了函数的两种表现,值得注意的是:在闭包中,自由变量的查找,是在函数定义的地方,向上级作用域查找,不是在执行的地方!
闭包的实际应用场景
(1)隐藏数据, 如做一个简单的cache工具
(2)函数防抖与节流
(补充)this在不同场景中的取值
首先我们要知道this是在执行过程中确定它的值的,那么在作用域和闭包不同的场景下如何取值呢?
(1) 当作普通函数被调用时,this是当前的window对象
function fn1 () {
console.log(this)
}
fn1() // 结果: window
(2) 在使用call、apply或者 bind的情况下,this是指定的对象
function fn1 () {
console.log(this)
}
fn1.call({x: 100}) // 结果: {x: 100}
(3) 作为对象方法被调用时,this是当前对象
class People {
constructor (name) {
this.name = name
}
sayHi() {
console.log(this) // 结果:当前对象
}
}
(4) 在class的方法中被调用时,this是当前对象(第3点和第4点其实一样)
class People {
constructor (name) {
this.name = name
}
sayHi() {
console.log(this)
}
}
// 实例化并使用
const p = new People('一个人')
p.sayHi() // 结果: p 对象
(5) 在箭头函数中使用,this指向的是上层对象,箭头函数本身并不会指定this
class People {
constructor (name) {
this.name = name
}
test1() {
setTimeout(function() {
console.log(this) // 结果:window; 原因:这个是由setTimeout触发的执行,所以this就是window
})
}
test2() {
setTimeout(() => {
console.log(this) // 结果:window; 原因:这个是虽然是由setTimeout触发的执行,但是箭头函数的this指向的是上级作用域的this,也就是当前对象
})
}
}