作用域(Scope)
作用域产生于程序源代码中声明变量的区域,在程序编译阶段就确定了。帮助在程序执行阶段"查询并规定变量的可见性与可访问性。
作用域共两种工作模型:静态作用域、动态作用域。
Javascript运用的是静态作用域,函数作用域在声明时确定;Bash脚本运用的是动态作用域,函数作用域在调用时确定。
案例
let a = 1
function foo() {
console.log(a)
}
function bar() {
let a = 2
foo()
}
bar()
静态作用域: 函数作用域在声明时确定,foo函数内部不存在变量a,依据作用域链向上寻找到全局作用域中的变量a,输出结果1。
动态作用域: 函数作用域在调用时确定,foo函数内部不存在变量a,因foo在bar中调用寻找bar作用域内的变量a,输出结果2。
Javascript中可分为三种作用域:全局作用域、函数作用域、块级作用域。
全局作用域
程序中任何地方都有权访问全局作用域下的声明的变量。
注意: 多人协作下,定义在全局的变量容易引发命名冲突、污染全局的问题。为了避免污染有以下两种方式。
- 创建私有的命名空间,可一定程度的降低被污染的风险。
let object = {
name: '瑾行',
getName: function() {}
}
- 创建立即调用函数表达式(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 新增let和const命令,使声明的变量仅在当前函数内部或代码块中访问。
块级作用域有如下特点:
- 与
var相比,不存在变量提升。 - 不允许
重复定义。 - 全局定义
不绑定在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]]如图所示。
作用域链的最前端是当前环境的变量对象,末端是全局window环境的变量对象。
例子
let msg = 'hello,'
function say() {
let name = '瑾行'
return function hello() {
console.log(msg + name)
}
}
let hello = say()
hello() // hello, 瑾行
name 在Closure变量对象中,msg在 顶层Script变量对象中(因let 声明,未绑定在window的变量对象)。
作用域与执行上下文
作用域与执行上下文的概念经常被混为一谈。
作用域上文提到在程序编译阶段确定,但执行上下文是在代码执行阶段确定,是执行代码时所在环境的抽象概念,主要通过引擎创建执行上下文栈来管理执行上下文 。
执行上下文生命周期:创建阶段、执行阶段、回收阶段。
- 创建执行上下文,包含三个属性:变量对象、作用域链、this指向。
- 执行变量赋值、函数引用,执行其余代码。
- 垃圾回收。
执行上下文类型:全局执行上下文、函数执行上下文、Eval函数执行上下文。
全局上下文:当开始执行程序时,就会创建全局上下文压入栈中,结束程序,从栈中弹出。
函数执行上下文:当每个函数被调用时,都会创建一个对应的执行上下文,函数调用结束,从栈中弹出
Eval函数执行上下文:执行eval函数时创建。
举个🌰。
let a = 1
function foo() {
console.log(a)
}
function bar() {
let a = 2
foo()
}
bar()