JavaScript作用域学习笔记

时间:2021-10-06 14:06:41

文章部分实例和内容来自鸟哥的blogJavascript作用域原理

 

前端基础进阶(三):变量对象详解    波同学 

在JS中,作用域的概念和其他语言差不多,是JS中一个极为重要的概念。在每次调用一个函数的时候 ,就会进入一个函数内的作用域,当从函数返回以后,就返回调用前的作用域.

理解作用域,首先理解几个概念

变量对象:执行环境(execution context)定义所有的变量和函数都存在这个对象中。虽然我们编写的代码无法访问这个对象,但是解析器在处理数据时后台会使用它

var foo=10;
    function func(){};
    
    //因为是在全局作用域当中,so...
    Global VO={
        foo:10,
        func:<function>
    }

 变量对象的创建经历以下几个过程:

  1. 建立arguments对象,检查上下文中的参数,建立该对象下的属性和属性值。
  2. 检查当前上下文的函数声明
  3. 检查上下文变量声明,每找到一个变量声明,就在变量对象中以变量名建立一个属性,属性值为undefined。如果该变量名的属性已经存在,为了防止同名的函数被修改为undefined,则会直接跳过,原属性值不会被修改。

由上可以看出function的优先级比var优先级高

活动对象:在调用func的时候, 会创建一个活动对象(假设为aObj, 由JS引擎预编译时刻创建, 后面会介绍),并创建arguments属性

function foo(x,y){
        var z=30;
        function bar(){};
    }
    foo(10,20);
    //当执行到foo(10,20)时即会产生AO
    Activation Object={
        z:30,
        x:10,
        y:20,
        bar:<function>,
        arguments:{0:10,1:20,length:2}
    }

 

 注意到, 因为函数对象的[[scope]]属性是在定义一个函数的时候决定的, 而非调用的时候, 所以如下面的例子

var name = 'laruence';
     function echo() {
          alert(name);
     }
 
     function env() {
          var name = 'eve';
          echo();
     }
 
     env();

  结果

laruence

 可以看出一段函数被激活时有两个阶段,一个是创建阶段,一种执行阶段

                       1.生成变量对象                                 1.变量赋值

创建阶段 ==》2.建立作用域        =====》执行==》2.函数引用  ===》出栈 回收

                       3.确定this的指向                                3.执行代码

看下面梨字

// demo01
function test() {
    console.log(a);
    console.log(foo());
 
    var a = 1;
    function foo() {
        return 2;
    }
}
 
test();

  从上下文开始理解,全局作用域的运行test()时,test()的执行上下文开始创建。

创建过程
testEC = {
    // 变量对象
    VO: {},
    scopeChain: {},
    this: {}
}
 
// 因为本文暂时不详细解释作用域链和this,所以把变量对象专门提出来说明
 
// VO 为 Variable Object的缩写,即变量对象
VO = {
    arguments: {...},  //注:在浏览器的展示中,函数的参数可能并不是放在arguments对象中,这里为了方便理解,我做了这样的处理
    foo: <foo reference>  // 表示foo的地址引用
    a: undefined
}

  变量对象和活动对象是一种对象,只不过是处于执行上下文的不同生命周期。

// 执行阶段
VO ->  AO   // Active Object
AO = {
    arguments: {...},
    foo: <foo reference>,
    a: 1
}

  因此demo1变成了如下代码

function test() {
    function foo() {
        return 2;
    }
    var a;
    console.log(a);
    console.log(foo());
    a = 1;
}
 
test();

  实际的例子:

function factory(){
    var name="laruence";
    var intro=function(){
        console.log("I'm "+name);
    }
    return intro;
}
function app(para){
    var name=para;
    var func=factory();
    func();
}

app("eve");

  当调用app的时候, scope chain是由: {window活动对象(全局)}->{app的活动对象} 组成.

此时的scope chain如下:

[[scope chain]]=[
Active Object={
    this:window,
    arguments:{0:"eve",length:1}
    name:'eve'
    func:<function>
    para:"eve",
},
Global VO={
    this:window,
    app:<function>,
    window:<object>,
    document:<object>
}
]

  

  当调用进入factory的函数体的时候, 此时的factory的scope chain为:

[[scope chain]]={
Active Object={
    this:window,
    arguments:{},
    name:"laruence",
    intro:<function>,
},
Global Object(Variable Object)={
   this:window,
   factory:<function>,
   app:<function>,
   window:<object>,
   document:<object>
}
}

  在定义intro函数的时候,intro函数[[scope]]为:

[[scope chain]]={
Object={
    name:"laruence",
    intro:<function>,
    this:<factory>,     //注意这里的this指向
    arguments:{}
},
Gloabal Object={
    this:window,
    factory:<function>,
    document:<object>,
    window:<object>
}
}

  factory函数返回后,在app体内调用intro时,发生了标识符的解析,而此时的scope chain是:

[[scope chain]]={
  intro AO={
        <intro 活动对象>
  } ,
  Factory AO={
        name:"laruence",
        intro:<function>,
  },
  Global VO={
        this:window,
        factory:<function>,
        document:<obeject>,
        window:<object>
  }
}

  因为scope chain中,并不包含factory活动对象. 所以, name标识符解析的结果应该是factory活动对象中的name属性, 也就是’laruence’.