Javascript 中的作用域与作用域链

2017-05-22  本文已影响24人  另一只小白

一、作用域(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)中,ifwhilefor等代码块不能形成独立的作用域。因此,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此时的ain1内定义的a,值为2
in2()中,作用域链为in2 -> add -> window,所以a + b此时的awindow内定义的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
因此,此时in2with语句块内的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"

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"

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"

引用自理解 Javascript 中的作用域与作用域链以及JS核心系列:浅谈函数的作用域两篇文章

上一篇下一篇

猜你喜欢

热点阅读