javascript 函数作用域和作用域链

260 阅读4分钟

作用域

所谓作用域:变量在声明它们的函数体以及这个函数体嵌套的函数体内都是有定义的

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函数体
  • if语句、while语句和内嵌的匿名函数中定义的变量a,b,c可以在scope函数中访问,是由于在javascript中,if、while、for 等代码块不能形成独立的作用域。因此,javascript中没有块级作用域,只有函数作用域

但是,在JS中有一种特殊情况:如果一个变量没有使用var声明,window便拥有了该属性,因此这个变量的作用域不属于某一个函数体,而是window对象。

function varscope(){
    foo = "I'm in function";
    console.log(foo);//I'm in function
}
varscope();
console.log(window.foo); //I'm in function

作用域链

所谓作用域链:当函数体嵌套了多层函数,并在函数体内定义了同一变量,当其中一个函数访问这个变量时,便会根据执行上下文栈形成一条作用域链

foo = "window";
function first(){
    var foo = "first";
    function second(){
       var foo = "second";
       console.log(foo);
    }
    function third(){
       console.log(foo);
    }
    second(); //second
    third();  //first
}
first();
  • **当执行second时,JS引擎会将second的作用域放置链表的头部,其次是first的作用域,最后是window对象,于是会形成如下作用域链:second->first->window, 此时,JS引擎沿着该作用域链查找变量foo, 查到的是 second
  • 当执行third时,third形成的作用域链:third->first->window, 因此查到的是:frist**

作用域链延长(with/catch)

with 和 catch语句主要用来临时扩展作用域链,将语句中传递的变量对象添加到作用域的头部。语句结束后,原作用域链恢复正常。

//with语句
foo = "window";
function first(){
    varfoo = "first";
    function second(){
       varfoo = "second";
       console.log(foo);
    }
    function third(obj){
       console.log(foo); //first
       with (obj){
           console.log(foo); //obj
       }
       console.log(foo); //first
    }
    var obj = {foo:'obj'};
    third(obj);
}
first();
 
//catch语句
vare = new Error('a');
try{
    throw new Error('b');
} catch(e) {
    console.log(e.message); //b
}
 console.log(e.message); //a
  • with语句:在执行third()时,传递了一个obj对象,obj中有属性foo,在执行with语句时,JS引擎将obj放置在了原链表的头部,于是形成的作用域链如下: obj->third->first->window,此时查找到的 foo 就是 obj 中的 foo ,因此输出的是 obj
  • 而在with之前和之后,都是沿着原来的链表进行查找,从而说明在with语句结束后,作用域链已恢复正常。
  • catch语句:try中的代码捕获到错误以后,会把异常对象推入一个可变对象并置于用域的头部,在catch代码块内部,函数的所有局部变量将会被放在第二个作用域对象中,catch中的代码执行完,会立即销毁当前作用域。

this 关键字

在一个函数中,this总是指向当前函数的所有者对象,this总是在运行时才能确定其具体的指向, 也才能知道它的调用对象。

window.name = "window";
function f(){
    console.log(this.name);
}
f();//window
 
var obj = {name:'obj'};

在执行f()时,此时f()的调用者是window对象,因此输出 window

f.call(obj) 是把f()放在obj对象上执行,相当于obj.f(),此时f 中的this就是obj,所以输出的是 obj

实战应用

demo1

var num = 101;
var counter = {
    num : 0,
    increase : function() {
        return function() {
            return ++this.num;
        };
    }
};
var fn = counter.increase();
fn(); //101

demo2

var num = 101;
var counter = {
    num : 0,
    increase : function() {
        var _this = this;
        return function() {
            return ++_this.num;
        };
    }
};
var fn = counter.increase();
fn(); //1
两个案例执行counter.increase()函数结果都是返回一个匿名函数:

fn = function(){
      return ++this.num;
}

fn = function(){
      return ++_this.num;
}

demo1:
      this指向调用window.fn()的对象window,this指向window
      this.num = window.num

demo2:
      唯一不同的是fn中的this变成了_this, 要知道_this是哪个对象之前,先确定fn的作用域链:fn->increase->window并在该链条上查找_this
      查找到 _this指向increase中的this,this指向调用increase的对象counter
      _this.num = counter.num

参考文章: https://www.cnblogs.com/onepixel/p/5036369.html