根据上篇关于 新手秒懂 - 高逼格解释变量提高 的文章中说明了,在生成执行上下文的建立阶段,生成变量对象后会创建做用域链。那咱们接下里就看看做用域和做用域链究竟是个啥子玩意。javascript
做用域是一套规则, 用于肯定在何处以及如何查找变量(标识符)。(说白了就是你写代码的那块旮旯里,来肯定你以后怎么查找变量,简单粗暴。。)前端
javascript
采用的是静态做用域)简单的例子表述一下:java
var value = 1;
function foo() {
console.log(value);
}
function bar() {
var value = 2;
foo();
}
bar(); // 1
复制代码
我将以最简单的大白话告诉您发生了啥: foo
函数执行 -> 查询value值(没有) -> 向上查找(var value = 1
), so, 打印 1
。es6
静态做用域,只看定义时的位置,就像你跟别人作了邻居,哪天你老婆吵架了跑出去了,不在家里。你只要去外面找就好了,别人家就算也有老婆,但确定不是你要找的老婆啊对不对??函数
try/catch
结构在 catch
分句中具备块做用域。在 ES6 中引入了 let/const
关键字( var 关键字的表亲), 用来在任意代码块中声明变量。 if(..) { let a = 2; }
会声明一个劫持了 if
的 { .. }
块的变量,而且将变量添加到这个块 中。以下例所示:var foo = true;
if (foo) {
let bar = foo * 2;
bar = something( bar );
console.log( bar );
}
console.log( bar ); // ReferenceError
复制代码
IIFE
之前刚入门的时候被人问到一个问题:请问,当即执行函数表达式的做用是什么??
post
白痴的我居然把匿名函数和 IIFE(当即执行函数表达式)
认为是同一个东西。ui
IIFE
: 最多见的用法其实就是使用了匿名函数表达式并最后加入()
,让它当即执行。var a = 2;
(function () {
var a = 3;
console.log( a ); // 3
// 匿名函数表达式内及是块级做用域
})();
console.log( a ); // 2
复制代码
而它的做用主要包括几点:spa
当查找变量的时候,会先从当前上下文的变量对象中查找,若是没有找到,就会从父级(词法层面上的父级)执行上下文的变量对象中查找,一直找到全局上下文的变量对象,也就是全局对象。这样由多个执行上下文的变量对象构成的链表就叫作做用域链。code
形象一点就是:对象
做用域链就像一栋楼,当前做用域在一楼,全局做用域在顶楼,就是一直往上找你要用的变量。
而编译器的查找方式有两种:
// console.log(a) --> VM130:1 Uncaught ReferenceError: a is not defined
ReferenceError
异常(严格模式下)。 // a = 1
由于javascript
是静态做用域,函数的做用域在函数定义的时候就决定了。
这是由于函数有一个内部属性 [[scope]],当函数建立的时候,就会保存全部父变量对象到其中,你能够理解 [[scope]] 就是全部父变量对象的层级链,可是注意:[[scope]] 并不表明完整的做用域链!(意思就是在函数建立时就能够拿到父级的变量对象VO
)
文字比较难理解不要紧,我们以一个例子说明
function foo() {
function bar() {
...
}
}
复制代码
在函数建立时, 各自的[[scope]]为:
foo.[[scope]] = [
globalContext.VO
];
bar.[[scope]] = [
fooContext.AO,
globalContext.VO
]; // 父级对象的 AO/VO(表示变量对象),俺的上篇文章提到过
复制代码
而以后函数激活, 变量对象就会添加到做用链的前端
做用域链 = [AO].concat([[Scope]]);
复制代码
下面咱们结合示例具体说说实现过程:
var scope = "global scope";
function checkscope(){
var scope2 = 'local scope';
return scope2;
}
checkscope();
复制代码
执行过程以下:
1.checkscope 函数被建立,保存做用域链到 内部属性 [[scope]]
checkscope.[[scope]] = [
globalContext.VO // 建立时就能够获取父变量对象(静态做用域)
];
复制代码
2.执行 checkscope 函数,建立 checkscope 函数执行上下文,checkscope 函数执行上下文被压入执行上下文栈
执行上下文栈:当执行一个函数的时候,就会建立一个执行上下文,而且压入执行上下文栈,当函数执行完毕的时候,就会将函数的执行上下文从栈中弹出。
ECStack = [ // 执行上下文栈
checkscopeContext, // checkscope上下文
globalContext // 全局上下文
];
复制代码
3.checkscope 函数启动(不执行函数内的请求)。开始作准备工做,第一步:复制函数[[scope]]属性建立做用域链
checkscopeContext = {
做用域链: checkscope.[[scope]], //上面提到的建立时生成的[[scope]]
}
复制代码
4.第二步:用 arguments 建立活动对象,随后初始化活动对象,加入形参、函数声明、变量声明(生成变量对象的的几个过程)
checkscopeContext = {
VO: { // 变量对象
arguments: {
length: 0
},
scope2: undefined
}
}
复制代码
5.第三步:将活动对象压入 checkscope 做用域链顶端
checkscopeContext = {
VO: {
arguments: {
length: 0
},
scope2: undefined
},
做用域链: [VO, [[Scope]]]
}
复制代码
6.准备工做作完,开始执行函数,随着函数的执行,修改 AO (活动变量,变量对象的执行阶段)的属性值
checkscopeContext = {
AO: {
arguments: {
length: 0
},
scope2: 'local scope' // 函数执行后 scope2 获取到值
},
做用域链: [AO, [[Scope]]]
}
复制代码
7.查找到 scope2 的值,返回后函数执行完毕,函数上下文从执行上下文栈中弹出
ECStack = [
globalContext
];
复制代码
这篇主要分享的是做用域相关知识,感受大体了解就差很少了,写的都是我本身的浅薄理解,有错误的地方欢迎指出,对于变量对象不了解的小伙伴请参照个人上篇文章 新手秒懂 - 高逼格解释变量提高,仍是一句话,努力,奋斗💪💪
《你不知道的JavaScript(上卷)》