文章目录

  • 作用域、作用域链
  • 作用域
  • 作用域链
  • 循环中的作用域
  • 自由变量、闭包
  • 自由变量
  • 闭包的定义、表现、应用
  • 如何确定在闭包中获取正确的变量
  • 总结



作用域、作用域链

作用域

编程语言中存储、访问、修改变量当中的值是一项基本能力、存储变量、访问变量必须按照一定的规则,这套规则就是作用域。JavaScript中的作用域可分为三种:全局作用域、函数作用域、块作用域

  • 全局作用域

在任何函数之外的顶层作用域,全局可使用,如windows对象,document对象, 全局变量在全局作用域、函数作用域和块作用域里都可以获取到。

// 全局作用域
    var temp = 'A'; 

    // 函数作用域
    function showTemp() {
      console.log(temp);
    }

    console.log(temp) // A
    showTemp(); // A

    // 块作用域
    {
      temp = 'B'
    }
    console.log(temp) // B
  • 函数作用域

函数中定义的作用域,只能在当前函数中使用

// 函数作用域
    function showTemp() {
      var temp = 'A'
      console.log(temp);
    }
   
    showTemp(); // A
    console.log(temp) // 报错: temp not defined
  • 块作用域
  • ES6 新增的两个用于声明变量的新关键词 letconst。这两个关键字定义的变量如果处于大括号 { } 中,大括号中的变量就形成了一个块作用域。
  • if/while/for 等的大括号{ }里也形成了一个块作用域。
{
      let temp = 'A'
    }
    {
      console.log(temp) // 报错: temp not defined
    }    
    console.log(temp) // 报错: temp not defined

作用域链

实际工程中,通常会使用多种作用域。当在当前作用域中无法找到目标变量时,就会向上级作用域寻找,这一层层向上的过程称为 作用域链

const A = 1;
    
     function printSum(A) {
      const B = 2
      console.log(A + B)
     } 
    
     printSum(A) // 3

上面是一个简单的示例,printSum函数中找到了需要的变量 B ,但是找不到变量 A ,于是沿着 作用域链 找到了 全局作用域 的目标变量A。

循环中的作用域

for (var i = 0; i < 5; i++) {
            setTimeout(function () {
                console.log(i);
            }, 1000);
        }

上面是一道经典的面试题目,最终会输出五个5,根据作用域的理论,在function中找不到变量i,当向上层寻找时,for循环里的i早已经执行到 i=5, 函数在延迟执行后取到的i只会是5。要实现预计的0-4打印效果,可以在 setTimeout 外面再套一层函数,或者在循环中使用let:

var print = function (i) {
            setTimeout(function () {
                console.log(i);
            }, 1000);
        };

        for (var i = 0; i < 5; i++) {
            print(i); // 会去print函数的作用域去寻找变量 i
        }
        // 0 1 2 3 4

        for (let i = 0; i < 5; i++) {
            setTimeout(function () {
                console.log(i);
            }, 1000);
        }
        // 0 1 2 3 4

自由变量、闭包

自由变量

  • 定义:某变量 a 在作用域 A 中被使用,却没有在该作用域中被定义,需要沿作用域链寻找,则对于作用域A来说,a是一个自由变量
  • 自由变量确定:沿作用域链向上级作用域一层层寻找,直到找到;若在全局作用域都没找到,会报错:xxx is not defined

闭包的定义、表现、应用

如果一个函数引用了 自由变量,即该函数使用了某变量,但它既 不是 函数参数、也不是函数内部定义的变量,则该函数就叫 闭包

闭包通常有两种表现:函数作为返回值函数作为参数被传递

  • 函数作为返回值
function closure() {
    const a = 100
    // 返回值函数
    return function () {  
        console.log(a)  // 100
      }
	}   
    // 把返回值函数赋给fn
    const fn1 = closure()  
    fn1()
  • 函数作为参数被传递
function print(fn2){  
   
      fn2()            //函数参数执行
   }
   const b = 100
   function fn2(){      //函数参数定义
      console.log(b)
   }
   print(fn2) //100

通过上述描述其实可以看到:闭包是作用域应用的一种特殊表现形式。那么,闭包到底有什么作用呢?一句话:闭包可以使变量仅在对象内部生效,无法从外部触及,只提供API,从而保护数据

举例一: 闭包隐藏数据,不能直接修改数据

function cache() {
           const data = {}    // data是在函数cache作用域中被定义的,全局中未定义
           return {
               set: function (key, val) {
                   data[key] = val
               },
               get: function (key) {
                   return data[key]
               }
           }
       }

       const data = cache()
       data.set('name', 'jackeroo')
       const data_name = data.get('name')
       console.log(data_name) // jackeroo
       console.log(data.name) // undefiend 无法直接获取name属性

举例二: 创建一个User对象,能够调取它的login方法获取用户名和密码,也能够直接访问该用户的用户名,但是不能取到该用户的密码

const User = function () {
           let _password;
           return class User {
               constructor(name, password) {
                   this.userName = name;
                   _password = password;
               }

               login() {
                   console.log(`使用账号:${this.userName}, 密码:${_password}进行登录`)
               }
           }
       }()

       const theUser =  new User('jackeroo', 123)
       
       theUser.login() // 使用账号:jackeroo, 密码:123进行登录
       console.log(theUser.userName) // jackeroo
       console.log(theUser.password, theUser._password) // undefined undefined

如何确定在闭包中获取正确的变量

⭐在函数定义的地方向上级作用域查找,注意是函数定义的地方而不在函数执行处

// 示例1
function create(){
    const a = 100
    return function (){  // 函数的定义处
        console.log(a) 
    }
}
const a = 200
const fn1 = create()  // 函数执行处
fn1()//100

// 示例2
function print(fn2){  
    const b = 200
    fn2()            //函数执行处
}
const b = 100
function fn2(){      //函数定义处
    console.log(b)
}
print(fn2) // 100

// 示例3
const c = 1;
function test(){
    a = 2;
    return function(){ // 函数定义处
        console.log(a);
    }
    var a = 3; // 变量提升 => var a = 2
}
test()(); // 2

通过以上三个例子再次强调:闭包/所有自由变量的查找是在函数定义的地方向上级作用域查找,而不是在函数执行的地方!!!

总结

作用域、作用域链

  • 作用域
  • 作用域链

自由变量、闭包

  • 自由变量
  • 闭包的定义、表现、应用

如何确定在闭包中获取正确的变量

闭包中的自由变量的查找是在函数定义的地方向上级作用域查找