JS作用域和闭包

80 阅读2分钟

作用域

  • 全局作用域
var num = 10
function fn() {
  // 函数中并未声明变量 num ,但能访问到全局变量 num
  console.log(num);
}
fn()  // 10
  • 函数作用域
function fn() {
  var num = 20
  console.log(num);
}
fn() // 20
console.log(num)  // 报错:num is not defined

/* 如果在函数内部没有使用关键字进行声明变量,此时变量也属于全局变量
function fn1() {
  a = 20
}
fn1()
console.log(a)  // 20
*/
  • 块级作用域(ES6) ES6新增块级作用域,在 {} 括号内使用let、const定义的变量、常量,具有块级作用域, {} 以外无法访问定义在块级作用域内的数据
if (true) {
  var num1 = 10;
  let num2 = 20
  const num3 = 30
}

console.log(num1);  // 10
console.log(num2);  // 报错
console.log(num3);  // 报错

经典面试题

// 页面上有10个按钮,通过for循环给每一个按钮绑定点击事件,在事件处理函数中打印当前按钮的索引

let btns = document.querySelectorAll('button')

for(var i = 0; i < btns.length; i++){
    btns[i].addEventListener('click',function(){
        console.log(i)
    })
}

// 此时无论点击哪个按钮输出都是 10

原因:因为 i 属于全局变量,当点击i时,此时 i 已经变为 10 了,相当于每一次循环都进行了 i = x,当i = 10时,此时跳出循环,此时就出现了覆盖问题,故每次输出都为10;

解决方案:

一、给每一个按钮添加自定义属性记录当前按钮的索引

for (var i = 0; i < btns.length; i++) {
    btns[i].setAttribute('data-index', i)
    btns[i].addEventListener('click', function () {
        console.log(this.getAttribute('data-index'))
    })
}

二、将 var 改为 let。原理:

for(let i = 0; i < btns.length; i++){
    btns[i].addEventListener('click',function(){
        console.log(i)
    })
}

三、使用闭包(后面说)

闭包

能够访问其他函数内部变量的函数,被称为 闭包函数

特性:

  • 函数嵌套函数
  • 函数内部可以引用外部的参数和变量
  • 参数和变量不会被垃圾回收机制回收
function fun(){
    var num = 10
    return function(){
        num++
        console.log(num)
    }
}
var fn = fun()
fn()	// 11
fn()	// 12
fn()	// 13
fn()	// 14

var fn1 = fun() // 每次执行都会开辟一块新的内存空间
fn1()  // 11

/*
1、这里是否形成闭包?
num变量 与 返回的这个函数 形成了闭包
2、为什么要使用函数嵌套?
因为需要局部变量,所以才把 num 放在一个函数里,如果不把 num 放在一个函数里,num 就是一个全局变量了,达不到形成闭包的条件
*/

闭包的优点:

  • 保护函数内的变量安全 ,实现封装,防止变量流入其他环境发生命名冲突(变量污染)
  • 在内存中维持一个变量,可以做缓存(但使用多了同时也是一项缺点,消耗内存) 闭包的缺点:
  • 闭包内被引用的私有变量不能被销毁,增大了内存消耗,造成内存泄漏(已经用不上的变量,但不会被JS垃圾回收机制回收,依然占居着内存空间,不能被再次利用起来)
/* 解决方案:使用完变量后手动为它赋值为null */
function fun(){
    var num = 10
    return function(){
        num++
        console.log(num)
    }
}
var fn = fun()
fn()
fn()

// 回收闭包----释放内存
fn = null

接上面的使用闭包打印每一个按钮的索引

for (var i = 0; i < lis.length; i++) {
    // 利用for循环创建了 btns.length 个立即执行函数
    // 立即执行函数也成为小闭包因为立即执行函数里面的任何一个函数都可以使用它的i这变量
    (function (index) {
        btns[index].addEventListener('click', function () {
            console.log(index);
        })
    })(i);
}