阅读 468

作用域与作用域链

作用域(Scope)

作用域产生于程序源代码中声明变量的区域,在程序编译阶段就确定了。帮助在程序执行阶段"查询并规定变量的可见性与可访问性。

作用域共两种工作模型:静态作用域动态作用域
Javascript运用的是静态作用域,函数作用域在声明时确定;Bash脚本运用的是动态作用域,函数作用域在调用时确定

案例

let a = 1
function foo() {
  console.log(a)  
}
function bar() {
  let a = 2
  foo()
}
bar()
复制代码

静态作用域: 函数作用域在声明时确定,foo函数内部不存在变量a,依据作用域链向上寻找到全局作用域中的变量a,输出结果1
动态作用域: 函数作用域在调用时确定,foo函数内部不存在变量a,因foobar中调用寻找bar作用域内的变量a,输出结果2

Javascript中可分为三种作用域:全局作用域函数作用域块级作用域

全局作用域

程序中任何地方都有权访问全局作用域下的声明的变量。
注意: 多人协作下,定义在全局的变量容易引发命名冲突、污染全局的问题。为了避免污染有以下两种方式。

  1. 创建私有的命名空间,可一定程度的降低被污染的风险。
let object = {
    name: '瑾行',
    getName: function() {}
}
复制代码
  1. 创建立即调用函数表达式(IIFE),Jquery就是这么干的...👀,利用函数作用域私有化变量起到隔离变量的作用。
(function(obj) {
    var name = '瑾行'
    var getName = function() {}
})(window)

console.log(name) //输出为空 
console.log(getName) //输出为空
复制代码

函数作用域

函数内部定义的变量,外层作用域无权进行访问。当然,耍些手段可达到目的:闭包
例子:

function closure() {
    let name = '瑾行',
    return hello() {
        console.log('hello,' + name)
    }
}
let func = closure()
func() // hello,瑾行
复制代码

块级作用域

ES6 新增letconst命令,使声明的变量仅在当前函数内部或代码块中访问。
块级作用域有如下特点:

  1. var相比,不存在变量提升。
  2. 不允许重复定义
  3. 全局定义不绑定在window上

来看下,利用块级作用域解决的经典面试题

for(var i = 0; i < 5; i++) {
    setTimeout(() => {
        console.log(i) // 5个5
    })
}
// 将i绑定到for循环的块作用域
for(let i = 0; i < 5; i++) {
    setTimeout(() => {
        console.log(i) // 0,1,2,3,4                                                       
    })
}
复制代码

作用域链

上文提到作用域是在定义时就确定了。原因是函数有个内部属性[[scopes]],当函数创建时,会保存所有的父变量对象;当函数被调用时,关联当前函数的活动对象(初始化arguments)与定义时的所有父变量对象确定完整的作用域链。[[scopes]]如图所示。

image.png

作用域链的最前端是当前环境的变量对象,末端是全局window环境的变量对象

例子

let msg = 'hello,'
function say() {
    let name = '瑾行'
    return function hello() {
      console.log(msg + name) 
    }
} 
let hello = say()
hello() // hello, 瑾行
复制代码

image.png

name 在Closure变量对象中,msg在 顶层Script变量对象中(因let 声明,未绑定在window的变量对象)。

作用域与执行上下文

作用域与执行上下文的概念经常被混为一谈。
作用域上文提到在程序编译阶段确定,但执行上下文是在代码执行阶段确定,是执行代码时所在环境的抽象概念,主要通过引擎创建执行上下文栈来管理执行上下文 。

执行上下文生命周期:创建阶段执行阶段回收阶段

  1. 创建执行上下文,包含三个属性:变量对象、作用域链、this指向。
  2. 执行变量赋值、函数引用,执行其余代码。
  3. 垃圾回收。

执行上下文类型:全局执行上下文函数执行上下文Eval函数执行上下文
全局上下文:当开始执行程序时,就会创建全局上下文压入栈中,结束程序,从栈中弹出。
函数执行上下文:当每个函数被调用时,都会创建一个对应的执行上下文,函数调用结束,从栈中弹出
Eval函数执行上下文:执行eval函数时创建。

举个🌰。

let a = 1
function foo() {
  console.log(a)  
}
function bar() {
  let a = 2
  foo()
}
bar()
复制代码

未命名文件.png

参考

Javascript深入之作用域链
Javascript深入之执行上下文

文章分类
前端
文章标签