Javascript 中的作用域与作用域链
一、作用域(scope)
所谓作用域就是:变量在声明它们的函数体以及这个函数体嵌套的任意函数体内都是有定义的。
作用域的范围有全局作用域、函数作用域、块级作用域。
- 全局作用域:属于全局作用域的代码在任何地方都能访问到;
- 函数作用域:在定义该变量 / 函数的函数体内可以访问到;
- 块级作用域:在定义该变量 / 函数的语句块中可以访问到。
function scope(){
var foo = "global";
if(window.getComputedStyle){
var a = "I'm if";
console.log("if:"+foo); //if:global
}
while(1){
var b = "I'm while";
console.log("while:"+foo);//while:global
break;
}
!function (){
var c = "I'm function";
console.log("function:"+foo);//function:global
}();
console.log(
foo,//global
a, // I'm if
b, // I'm while
c // c is not defined
);
}
scope();
scope
函数中定义的foo
变量,除过自身可以访问以外,还可以在if
语句、while
语句和内嵌的匿名函数中访问。 因此,foo
的作用域就是scope
函数体。
在javascript(ES5)
中,if
、while
、for
等代码块不能形成独立的作用域。因此,javascript
中没有块级作用域,只有函数作用域。
while(1) {
let foooo = "baaar";
break;
}
console.log(foooo); // Uncaught ReferenceError: foooo is not defined;
在es6
中可以使用let
关键字定义变量,使变量拥有块级作用域
function func2() {
y = 1;
console.log(y);
}
func2();
console.log(y); // 1
在作用域内的变量没有被var
声明,则自动提升为全局作用域。
console.log(upper); // undefined
var upper = 3;
因为js
存在“变量提升”的特性,所以在未使用严格模式('use strict')
的情况下,在某一作用域内定义的变量可以在任何位置被调用而不出错,哪怕是在其被声明之前。
二、作用域链(scope chain)
所谓作用域链就是:一个函数体中嵌套了多层函数体,并在不同的函数体中定义了同一变量, 当其中一个函数访问这个变量时,便会形成一条作用域链(scope chain)。
当某对象被调用时,js
会依此按照
当前作用域 -> 次级作用域 -> 次次级作用域 -> ... -> 全局作用域
来查找并使用变量。此即为作用域链。
a = 1;
function add() {
var b = 3;
function in1() {
var a = 2;
console.log(a + b); // 5
}
function in2() {
console.log(a + b); // 4
}
in1();
in2();
}
add();
在in1()
中,作用域链为in1 -> add -> window
,所以a + b
此时的a
为in1
内定义的a
,值为2
;
在in2()
中,作用域链为in2 -> add -> window
,所以a + b
此时的a
为window
内定义的a
,值为1
。
在未使用严格模式时,可以使用with
关键字临时扩展作用域。
a = 1;
function add() {
var b = 3;
function in1() {
var a = 2;
console.log(a + b); // 5
}
function in2(obj) {
with(obj) {
console.log(a + b); // 12
}
}
var obj = {a: 9};
in1();
in2(obj);
}
add();
在in2()
中,因为with
关键字的原因,with
内的对象被添加到作用域链的头部,形成了如下作用域链:
obj -> in2 -> add -> window
因此,此时in2
内with
语句块内的a
值为9
。
可以看出,在运行期上下文中,访问全局变量的速度是最慢的。因为全局作用域总是处在作用域链的最末端。
所以,在编写代码的时候,尽可能少使用全局变量,多使用局部变量。
三、 This关键字
var obj = {
x: 1,
log: function() {
console.log(this.x);
}
};
obj.log(); // 1
var func3 = obj.log; //function() {
console.log(this.x);
}
func3(); // undefined
this
是函数中才有的对象。在一个函数中 (es5),this
总是指向调用该函数的对象,总是在运行时才能确定this的值以及其指向。
window.name = "func4";
function func4() {
console.log(this.name);
}
func4(); // func4;
var obj = {name:'obj'};
func4.call(obj); //obj
直接调用函数的话,函数体内的this
默认指向window
。
func4.call(obj)
是把func4()
放在obj
对象上执行,相当于obj.func4()
,此时func4
中的this
就是obj
,所以输出的是"obj"
- code1
var foo = "window";
var obj = {
foo : "obj",
getFoo : function(){
return function(){
return this.foo;
};
}
};
var f = obj.getFoo();
f(); //window
obj.getFoo()(); //window
执行 var f = obj.getFoo()
返回的是一个匿名函数,相当于:
var f = function(){
return this.foo;
}
f()
相当于window.f()
, 因此f
中的this
指向的是window
对象,this.foo
相当于window.foo
, 所以f()
返回"window"
- code2
var foo = "window";
var obj = {
foo : "obj",
getFoo : function(){
var that = this;
return function(){
return that.foo;
};
}
};
var f = obj.getFoo();
f(); //obj
执行var f = obj.getFoo()
同样返回匿名函数,即:
var f = function(){
return that.foo;
}
唯一不同的是f
中的this
变成了that
, 要知道that
是哪个对象之前,先确定f
的作用域链:f->getFoo->window
并在该链条上查找that
,此时可以发现that
指代的是getFoo
中的this
, getFoo
中的this
指向其运行时的调用者,从var f = obj.getFoo()
可知此时this
指向的是obj
对象,因此that.foo
就相当于obj.foo
,所以f()
返回"obj"