理解JavaScript作用域

作用域也就是变量对象能被其他对象访问的作用范围

主要包括以下几个概念:

1、全局作用域(Global Scope)与局部作用域(Local Scope)

2、静态(词法)作用域(Lexical Scope)与动态作用域(Dynamic Scope)

3、块级作用域(Block Scope)

4、作用域链(Scope Chain)

理解JavaScript作用域

现在来具体举例说明下这几个区别:

1、全局作用域,指在全局的范围都能访问到。例如:

let a = 1;

(function foo() {

b = 2;

}())

window.c = 3;

console.log(a, b, c);

其中a,b,c能被全局访问到。a定义在当前全局环境,b没有关键词声明也是全局,c定义在window全局对象下。

2、局部作用域,也就是函数作用域,只有在函数内部才能访问到

var a = 1;

(function foo() {

var a = 2;

var b = 2;

}())

console.log(a, typeof b);

从外部能访问到a,但是外部的a,值为1,而b就是undefined。

理解JavaScript作用域

局部作用域能访问全局的作用域,子函数能访问父函数的作用域,但父函数不能访问子函数的作用域。如果要想外部访问内部作用域,那就得利用闭包了,相当于利用函数复制了一个封闭的空间出来,供外部调用。

3、词法作用域,也就是作用域在变量定义时决定,范围在所定义的位置,因为JS解析引擎在提取代码进行词法分析生成语法树时就决定了。动态作用域则是程序在运行阶段动态来确定变量的作用域范围,也就是从调用方来查找,而不是定义的环境。JS的call与apply,bind可以动态地改变执行上下文,可以改变变量的执行上下文this,相当于修改了对象作用域的范围,但不是修改作用域。

理解JavaScript作用域

var a = 1;

var a = 2;

function foo() {

console.log(a);

}

function change() {

var a = 3;

foo();

}

change();

变量a在定义时的作用范围内,所以打印仍是2,foo里面a作用范围在定义时的foo局部作用域内。如果是动态作用域则打印3。一般编程语言都是静态作用域,例如C、Java、Python、JS等,但如shell脚本或velocity模板语言是动态作用域。

4、块级作用域,也就是变量作用范围在一个块内,一般是花括号标识。大多数语言都是块级作用域域。JS直到ES6推出了后,通过let关键字声明的变量作用域在块内。

{

var a = 1;

let b = 1;

}

console.log(a, b);

这里a将打印1,b就会报错,因为变量未定义而调用。

for (var i = 0; i < 5; i++) {

setTimeout(function() {

console.log(i);

}, 0);

}

这里会打印5次5,因为没有块级作用域,后面的覆盖了前面的,这里一般要用闭包才能打印出1到5来。

for (let i = 0; i < 5; i++) {

setTimeout(function() {

console.log(i);

}, 0);

}

而如果换成let,则打印1到5,虽然同样是超时打印,这是因为变量的作用域不同。至于let与var的其他不同,以及var带来的变量提升等问题,就不在这里赘述了。

5、作用域链,JS的代码在执行时会针对执行代码块(global, function和eval)创建执行上下文(Execution Context, 包括this, variable object,[[scope]]等),执行上下文中的变量对象会从子函数(是一个执行上下文)逐级往上到父函数(也是一个执行上下文)中查找该变量,一直到全局。函数通过一个[[scope]]属性供其他的其他对象来链接查找。这里注意不要将变量逐级往上查找与变量作用域本身混淆。变量逐级查找是基于作用域链向上查找该变量,而变量作用域是该变量的作用范围,这个范围是声明时所在的位置。同时也不要跟this混淆(this表示对象本身,执行上下文也会创建一个this,用来代表这个执行环境,但并不等于这个上下文),函数的this可以看作是函数的指针,指向函数的调用方,它可以被动态修改。

var a = 'a';

function outer() {

var b = 'b';

function child(b) {

a = '1';

b = '2';

function inner() {

console.log('inner:', 'a=' + a, 'b=' + b);

}

inner(b);

}

child(b);

console.log('outer:', 'a=' + a, 'b=' + b);

}

outer();

输出:

inner: a=1 b=2

outer: a=1 b=b

inner结果原因:a变量在inner里面没有,向上查找child,child里没有,再向上找到outer,依然没有,直到最外层找到a。a在执行child()时被重新重新赋值为1。b是向上在child作用域中找中。

outer结果原因:a在outer作用域下没有,向上才找到a,但a在执行child()时已经被赋值为1了,b因为传递的是值而非引用,因此child()执行,b被修改时并非修改的outer下的b,而是child(b)形参定义的b变量。

理解JavaScript作用域

以上就把作用域说完了,很简单吧。好了,来一个例子,如果你能说清原理,那就说明理解了。

function foo(b) {

var a = b;

return {

a: a,

change: function(b) {

console.log(a, b, this.a);

a = b + this.a;

},

output: function() {

console.log(a, this.a, b);

}

}

}

var x = foo(1);

console.log('change before:')

console.log(x.a); // 1;

x.output(); // 1, 1, 1

x.a = 3;

x.change(2); // 1, 2, 3

console.log('after change:');

console.log(x.a); // 3

x.output(); // 5, 3, 1

关于这个答案讲解可以放在以后的章节中。


分享到:


相關文章: