作用域定义、作用域链、静态/动态作用域、变量
表象解释:javascript采用词法作用域
原理解释:创建执行上下文环境(js 引擎工作过程)
目录
答:作用域就是变量和常量的起效范围,只能在该范围内访问该变量
答:按作用范围,分为:全局作用域
、函数作用域
、块级作用域(es6)
按作用域链调用顺序,分为:词法作用域(静态作用域)
、动态作用域
,javascript采用词法作用域
答:之所以叫词法作用域,词,单词,就是 代码编写
的时候确定的作用域,动态作用域,就是在 代码运行
的时候确定。词法作用域,是依据代码扫描时生成的执行上下文 + 自身 outer 指向的外部作用域。动态作用域,是依据调用栈的执行上下文顺序来决定的。
也就是说,词法作用域不会按调用栈的顺序来读取变量,而动态作用域是按调用栈的顺序来读取的。
/* 例1. 词法作用域 */
var value = 1;
function foo() {console.log(value);}
function bar() {var value = 2;foo();}
bar();
例子分析:最终输出1,因为javascript采用词法作用域,函数的作用域在编码创建的时候就确定了,运行foo,先查找foo函数本身有没有value,foo本身没有,沿着作用域链向上层查找,找到的上级为value=1。如果采用动态作用域,那么作用域链上级的value就应该是2了
答:作用域链为一个数组栈,包含了调用栈中的相关执行环境对象的引用。
因为操作作用域链只可能在函数中操作,所以这里说的作用域链就是函数的作用域链,也就是函数变量所在的执行上下文的作用域链属性
关键词:scope
、执行上下文
、调用栈
创建步骤:在函数执行前创建,执行后销毁,类似钩子函数执行时期
scope
(浏览器知道,但是不能直接输出,通过打断点可以看到),它的值为一个指针数组,保存祖辈执行上下文的变量对象(不是赋值,是保存对象,拥有访问权限而已,因为祖辈变量值可以改变)。在浏览器中打断点可以看到 scope 对象详情
scope = [
local: 自身执行上下文对象的引用,
closure:闭包对象,也就是其他执行上下文对象的引用,
global:全局执行上下文对象的引用
]
而 scope 和 callstack 是同级展示,2者之间存在引用关系,作用域链与调用栈的对应关系(例子采用 深入js-执行上下文中的例子)
闭包注意点:如果内部访问的是全局执行上下文对象内变量,则不属于闭包数据;只有访问局部执行上下文对象属性,才叫做闭包引用。这点由作用域链决定。
总结:通过本次词法作用域学习,明白了js深入的一点,需要明白js代码解释运行的详细过程。(变量提升、创建作用域链、执行上下文也就成了常事)
答:原则上是不可更改的,作用域链保存的就是执行上下文相关的变量信息,但是通过eval、with可以改变。eval在函数执行的时候可以动态添加变量,这里就修改了 AO
,所以就修改了作用域链。with就更明显了,直接修改包含代码的作用域
延伸with
var obj = {
x: 10,
foo: function () {
with (this) {
var x = 20;
var y = 30;
console.log(y);//30
}
}
};
obj.foo();
console.log(obj.x);//20
console.log(obj.y);//undefined
with修改内部代码的作用域,多是操作对象。
with降低性能:js引擎通常会对我们写的js代码做静态分析,增加标识符,让我们代码运行的更快;但是不会对我们写的with里面的js代码做静态分析,不会得到优化,如果with里面执行的逻辑较多,那么就会造成严重性能浪费。
注意:使用with,js引擎只是不做优化,通常的变量提升还是会有的
存在位置:函数内部
和 {}之间
功能起效:只有在块级作用域中声明 let
、 const
才能体现它的功效
let特性:
说明:尽量避免在块级作用域内使用函数声明,因为不同浏览器对其处理方式不同;最好使用函数表达式,即采用let赋值方式 const: 保存的是它内存地址对应的值不变,如果是对象,它内存地址对应的值也是一个地址,所以使用const声明的对象,可以修改对象的属性值的
1.变量提升
使用 var
会有变量声明提升,使用 let | const
不会变量提升,而是会将其放入临时死区
var value = "global";
(function() {
console.log(value);
let value = 'local';
}());
{
console.log(value);
const value = 'local';
};
结果:都不会打印 global , 都会报错:Uncaught ReferenceError: value is not defined
分析:如果 let 和 const 换成 var,都会输出 undefined
;js引擎在扫描代码发现生命变量时,遇到 var ,就将其提升到作用域顶部,遇到 const | let
,则将它们放入暂时性死区。
当访问暂时性死区的变量的时候,就会报错,只有执行过变脸声明语句后,才会从暂时性死区中移除。这里的 console.log(value)
语句,首先会去访问本作用域内的变量,会访问到暂时性死区。
// 暂时性死区例子1
typeof x; // ReferenceError
let x;
// 暂时性死区例子2
let x = x; // ReferenceError
2.重复声明
使用 var
可以重复声明,使用 let | const
重复声明会报错 Uncaught SyntaxError: Identifier 'value' has already been declared
3.绑定全局作用域
var value = 1;
console.log(window.value); // 1
let value = 1;
console.log(window.value); // undefined
for (let i = 0; i < 3; i++) {
let i = 'abc';
console.log(i);
}
console.log(i);
// abc
// abc
// abc
// Uncaught ReferenceError: i is not defined
为什么能成功输出3次 abc
呢?为什么最后一个就报错呢?
表面原因:是不是在 for 循环的 ()
中,存在一个块级作用域,for 循环的 {}
相当于它的子块级作用域?
解答:let有单独的作用域。在ecmascript标准中规定,let声明在循环内部的行为,跟 var声明不同,会为let存在的for循环创建一个隐藏的作用域。
var funcs = [];
for (let i = 0; i < 3; i++) {
funcs[i] = function () {
console.log(i);
};
}
funcs[0](); // 0
为什么使用let代替var就能正确输出i的值了呢?
答:在for循环中,每次循环,为let创建一个单独的隐藏作用域,每次循环内部函数创建时,其scope
属性包含了该单独作用域i的引用,具有访问权限。如果是var,则其相当于全局变量var,循环内部函数创建时,其scope
属性包含的是用全局变量i的引用,当循环结束,执行 funcs[0]()
的时候,访问的是全局变量i, 此时i的值已经是3了。
if (false) {
let value = 1;
}
console.log(value); // Uncaught ReferenceError: value is not defined
使用babel编译之后
if (false) {
var _value = 1;
}
console.log(value); // Uncaught ReferenceError: value is not defined
另一个例子
var funcs = [];
for (let i = 0; i < 10; i++) {
funcs[i] = function () {
console.log(i);
};
}
funcs[0](); // 0
babel怎么处理的呢?既然标准为let创建了一个隐藏的作用域,那么我就模拟创建一个作用域,babel很聪明
var funcs = [];
var _loop = function _loop(i) {
funcs[i] = function () {
console.log(i);
};
};
for (var i = 0; i < 10; i++) {
_loop(i);
}
funcs[0](); // 0
每次循环,也为其创建一个单独的函数作用域,用以临时保存i的值
var、function、let、const、import、class
因为在不同环境(浏览器、node、webWorker)中,顶层对象不同
var getGlobal = function () {
if (typeof self !== 'undefined') { return self; }
if (typeof window !== 'undefined') { return window; }
if (typeof global !== 'undefined') { return global; }
throw new Error('unable to locate global object');
};
相关文章:深入js-执行上下文