所谓作用域

69 阅读2分钟

JS 令人诟病的特性之一便是变量提升,它存在变量覆盖、变量污染的缺陷。

块级作用域

JS 早期的设计并不支持块级作用域,ES6 后的块级作用域是通过执行上下文中词法环境的栈结构来实现的,而变量提升是通过变量环境来实现。

a,c 变量环境,b,d 词法环境

function foo(){ 
    var a = 1 
    let b = 2 
    { 
        let b = 3 
        var c = 4 
        let d = 5 
        console.log(a) 
        console.log(b) 
    } 
    console.log(b) 
    console.log(c) 
    console.log(d) 
}
foo()

TDZ

let myname= 'Tony' 
{ 
    console.log(myname) 
    let myname= 'Mark' 
}

image.png

log 时应该是 undefined,但是因为规范的暂停性死区限制,会报引用异常。

词法作用域

function bar() { 
    console.log(myName) 
} 
function foo() { 
    var myName = "lilei" 
    bar() 
} 
var myName = "hanmeimei" 
foo()

image.png

词法作用域决定 outer 作用域链的指向,而函数在定义时就决定了词法作用域指向,与函数在哪被调用无关。也就是说,词法作用域体现了代码的结构。

闭包

简单的说,闭包是返回的内部函数引用了外部函数的变量,根据词法作用域的规则,内部函数可以访问外部函数的变量。所以即使函数的执行上下文销毁了,依然可以通过返回的内部函数访问该变量。此时访问的就是闭包中的变量。JS 引擎的访问顺序是:

当前执行上下文–>函数闭包–>全局执行上下文

this

const bar = {
    myName:"this",
    printName: function () {
        console.log(myName)
    }    
}

let myName = "that"
bar.printName()

与其他语言不同,根据词法作用域规则,这里输出的是 that,但这很不符合编码直觉。所以 JS 又有了 this 机制。

和执行上下文类似,this 分

  • 全局执行上下文中的 this
  • 函数中的 this
  • eval中的 this

箭头函数

对于嵌套函数而言,this 是不会继承外层函数的,这当然不符合编程直觉。

var myObj = {
  name : "Tony", 
  showThis: function(){
    console.log(this)
    function bar(){console.log(this)}
    bar()
  }
}
myObj.showThis()

bar 函数的 thiswindow 对象,而不是 myObj,要解决这个问题,可以使用 that 变量保存 this,这样就把 this机制转化成作用域机制。另一个办法是使用箭头函数:

let myObj = {
  name : "Tony", 
  showThis: function(){
    console.log(this)
    let bar = () => {console.log(this)}
    bar()
  }
}
myObj.showThis()

因为箭头函数没有自己的执行上下文,也就没有自己的 this。所以会继承会继承外层函数(全局)执行上下文中的 this。