变量作用域 scope 和作用域链 scope chain
var 变量的作用域和变量提升
作用域链
ES6 中的 let 和 const
this
全局上下文 global context
函数上下文 function context
补充:箭头函数、严格模式下的 this
闭包 closure
变量作用域 scope 和作用域链 scope chain
var 变量的作用域和变量提升
JavaScript 有两种作用域:全局和局部。
JavaScript 中,在函数体内 var 声明的变量是函数级作用域,是局部变量,在本函数体内可以访问,而且是在函数体内任意位置可以访问。
// JS 代码
function test() {
console.log(val);
var val = 'this is val';
console.log(val);
func();
function func() {
for (var i = 0; i < 5; i++) {
}
console.log('i: ', i);
console.log('this is func');
}
}
test();上述代码结果是:
undefined this is val
i: 5this is funcJavaScript 解析器预解析代码的时候, test 函数作如下解析:
function test() { // 变量提升, 缺省值是 undefined
var val; // 函数声明提升
function func() { // 变量提升
var i;
for (i = 0, i < 5, i++) {
} console.log('i: ', i);
console.log('this is func');
}
console.log(val); // 变量赋值
val = 'this is val';
console.log(val);
func();
}所以第一次 console.log(val) 时候并不会抛异常, 因为此时变量 val 是被声明过的,值是 undefined。
如果声明变量时不加 var 直接
val = 1;, 那么 val 是全局变量。
作用域链
作用域链包含了执行环境有权访问的所有变量和访问顺序。
作为单线程语言的 JavaScript,初始化代码时会创建一个全局上下文,每一次函数调用都会创建一个执行上下文,执行上下文及包含关系:
变量对象
变量
函数声明
参数(arguments)
作用域链
有权访问的变量和访问顺序(本作用域变量和所有父作用域变量)。即函数内部属性
scope: 本函数有权访问的[变量、对象、函数]的集合
this 值
如下代码:
function func_1() {
var val_1 = 1;
// 抛异常: ReferenceError: val_2 is not defined
console.log(val_1, val_2);
function func_2() {
var val_2 = 2; // 输出:1 2
console.log(val_1, val_2);
}
func_2();
}
func_1();简言之, func_1 不能访问 func_2 中声明的变量, func_2 可以访问 func_1 中声明的变量。
当在作用域内访问一个变量 x 时,JavaScript 的查找顺序是这样的:
当前作用域
var x的定义 => 2.x形参 => 3. 函数自身名称是否是x=> 4. 上级作用域从 1 开始查找
ES6 中的 let 和 const
ES6 的 let 和 const 实现了块级所用域的变量声明方式,使用 let 和 const 声明变量能有效避免由于变量提升导致的变量污染的问题。
用 let 和 const 声明的变量作用域是代码块,这个设计比较符合大多数人的思维方式。(代码块简单来说就是 {} 大括号包着的区域)
function test() {
if (true) {
var a = 'a';
let b = 'b';
}
// 输出: a
console.log(a);
// 抛异常:ReferenceError: b is not defined
console.log(b);
}
test();关于 const 的作用有必要正确理解:
MDN 的例子很赞,这里直接拷过来看:
简言之: this 总是指向调用该函数的对象。
全局上下文 global context
// 在浏览器中console.log(this === window); // true函数上下文 function context
在函数中访问 this 时, this 指向调用该函数的对象。
1)全局对象
// 全局变量val = 1;function test() {
console.log(this.val);
}
test(); // 1上例中,调用 test 函数的对象并不是一个自己声明的函数或对象,此时 this 默认值为全局对象。
2) 调用对象
var testObj = {
val: 1,
getVal: function() {
var val = 2;
return this.val;
}
};
console.log(testObj.getVal()); // 1上述代码运行输出 1, 顺藤摸瓜,getVal() 函数的调用者是 testObj 对象, 按照 this 指向调用该函数的对象 的原则,getVal() 中的 this 指向 testObj 对象, testObj 对象的 val 值是 1.
3) 构造函数
'use strict';
function testFunc(val) {
this.a = val;
this.b = 'bb';
}
var testInstance = new testFunc('aa');
console.log(testInstance.a); // aa
console.log(testInstance.b); // bb当一个函数的调用者是构造函数(new 出来的对象), this 指向新构造出来的对象 testInstance
4) call and apply
function testFunc(val) {
this.a = val;
this.b = 'bb';
}
function execFunc() {
var a = 'exec aa';
var b = 'exec bb';
console.log(this.a, this.b);
}
var testInstance = new testFunc('aa');
execFunc.call(testInstance); // aa bb
execFunc.apply(testInstance); // aa bb通过 call apply 函数将 execFunc 的 this 值指向 testInstance 对象的 this 值。
注意: 以
fun.apply() // or call为例 call apply 的第一个参数是func 函数运行时的 this 值(第一个参数的解释版本真的多)。
补充:箭头函数、严格模式下的 this
1) ES6 中的箭头函数 arrow function
GLOBAL.a = 'global aa';var testObj = {
a: 'aa',
getValArrowFuc: function() {
var val = (() => this.a);
return val();
},
getVal: function() {
var self = this;
var val = function() {
return self.a;
};
return val();
},
getValGlobal: function() {
var val = function() {
return this.a;
};
return val();
}
};
console.log(testObj.getValArrowFuc()); // aa
console.log(testObj.getVal()); // aa
console.log(testObj.getValGlobal()); // global aa箭头函数中的 this 值,就是词法作用域的 this 值。
2) 严格模式下的 this
对于一个开启严格模式的函数,指定的 this 不再被封装为对象,而且如果没有指定 this 的话它值是 undefined.
'use strict';/**
* from MDN
*/function fun() { return this; }
console.log(fun() === undefined); // true
console.log(fun.call(2) === 2); // true
console.log(fun.apply(null) === null); // true
console.log(fun.call(undefined) === undefined); // true
console.log(fun.bind(true)() === true); // true注: 以上所有不考虑 Eval
闭包 closure
闭包的构成:
函数
创建该函数的环境,环境由闭包创建时在作用域中的任何局部变量组成
自执行函数表达式写法:
var test = (function() {
var val = 0;
var add = function(num) {
val += num;
return val;
};
return add;
})();
console.log(test(3)); // 3
console.log(test(4)); // 7个人感觉这个写法可读性更好:
var test = function() {
var val = 0;
var add = function(num) {
val += num;
return val;
};
return add;
};
/**
* 此处 instance 是一个闭包。
* 由 add 函数, 和创建 add 函数时的环境(变量 val)组成
*/
var instance = test();
console.log(instance(3)); // 3
console.log(instance(4)); // 7以下代码:
function test() {
for (var i = 0; i < 5; i++) {
setTimeout(() => {
console.log(i);
}, 10);
}
}
test();输出是:
5
5
5
5
5这里变量 i 的作用域是 test 函数作用域,也就是说 console.log(i) 中的 i 是 test 函数作用域下的同一个变量。
setTimeout 中的函数被执行时,for 遍历已完成并且 i 被赋值为 5.
利用闭包:
function test() {
for (var i = 0; i < 5; i++) {
(function (val) {
setTimeout(() => {
console.log(val);
}, 10);
})(i);
}
}
test();则会输出:
0
1
2
3
4这里我们将 i 赋值成一个局部变量,可在闭包内访问(每次循环创建一个闭包, i 作为该闭包作用域下的局部变量,不跟随外层 i 的值改变)。
闭包对性能有负面影响(尤其是内存占用),如果不需要使用,则不使用。
本文深入探讨JavaScript的变量作用域、作用域链、闭包原理及其对性能的影响,包括var、let、const的差异,this关键字的行为,以及ES6引入的箭头函数如何改变this的绑定。
362

被折叠的 条评论
为什么被折叠?



