作用域的问题

console.log(a);

function test() {
   a = 1;
}
test(); // 报错 

第一个问题:
js是词法作用域不是动态作用域,也就是在代码写完时作用域就已经形成了,那么上面的那段代码在函数没有执行的时候在变量a没有声明的时候不是应该给window添加对应的这个属性名吗?这样无论函数是否一样会打印undefined的啊?

还有函数执行与否会影响打印结果,不就是变成动态作用域了吗?

同样还是第一个问题的引申,当函数内部的变量没有定义时,该变量是作为全局变量的,结合上面的案例我就更难理解了

function getProduct(num1, num2) {
    product = num1 * num2;
    return product;
}
var product = 10;
var number = getProduct(20, 20);
alert(product);//得到的弹出框是400

下面是第二个问题

    var num = 10;
    function test(){
      console.log(num);
    }
    
    function test1(){
      var num = 100;
      test();
    }
    
    test1(); // 10

第二个问题: test函数的执行环境是test1作用域,所以会优先找test1中的变量num,为什么会是window中的变量?

阅读 2.6k
4 个回答

很感激这么多朋友的回答,给我提供很多帮助,本人比较愚钝,还是不大明白所以又翻了一遍《javascript高级程序设计》这本书,感觉清晰了很多,所以我觉得我的回答还是比较中肯的,所以自问自答了,如果有不妥的地方欢迎指出

(根据javascript高级程序设计第四章)解析上面的代码

  1. js中存在全局执行环境和由函数形成的局部执行环境这两种,统称为执行环境(这里可以理解成作用域);
  2. 执行环境都会对应一个变量对象,包含当前环境的变量和函数(函数中的参数也作为函数执行环境的变量,即函数所在作用域的局部变量);
  3. 只有当函数执行时会形成作用域链,作用域链的前端始终是当前执行代码所在的执行环境对应的变量对象,往后是下一个(外部)变量对象,直到最外边的全局执行环境的变量对象(所谓的作用域链就是变量对象组成的一条线);
  4. 变量对象中变量的解析查找就是沿着作用域链一级一级查找;
  5. 如果执行环境中的变量没有用var声明,那么在函数执行时(这样才会形成作用域链)会沿着作用域链一级一级查找变量对象,如果没有找到则会在全局变量对象中声明该变量。

综上,上面的代码可以改写成下面这样

function test() {
   a = 1;
}
test();
console.log(a); // 只能放在函数执行后面才会打印出undefined

同样第二个问题,首先要明白一点,只有在函数内部定义的函数,其在执行时作用域链才会包括外部函数的活动对象,下面该问题中test函数不是在函数test1中定义的,所以当test执行时,作用域链只有当前活动对象和全局变量对象。

var num = 10;
function test(){
  console.log(num);
}

function test1(){
  var num = 100;
  test();
}

test1(); // 10

刚看你编辑了问题,我再补充一下

function getProduct(num1, num2) {
    product = num1 * num2;
    return product;
}
var product = 10;
var number = getProduct(20, 20);
alert(product);//得到的弹出框是400

变量提升,等同于:

var product = 10;
var getProduct = function(num1, num2) {
    product = num1 * num2;
    return product;
}
var number = getProduct(20, 20);
alert(product);//得到的弹出框是400

另外test的声明不在test1方法的代码块中({}),他们是在平行的作用域。


第一个问题:
变量提升

console.log(a);
function test() {
   a = 1;
}
test();

等同于:

var test = function() {
   a = 1;
}
console.log(a);//报错undefined
test();//window.a = 1

第二个问题:
test函数的执行环境并不是test1作用域,以下才是

 var num = 10;

    function test1(){
      var num = 100;
      function test(){
        console.log(num);
      }
      test();
    }
    
    test1(); // 100

OK,解答完毕

函数建立的时候会创建一个预先包含全局变量对象引用,保存在函数内部的[[Scope]]中,也就是VO;函数执行的时候会创建一个执行环境,并复制[[Scope]]中的VO作为当前执行环境的作用域链,然后把函数的AO推入作用域链的前端,由此构建出完整的作用域链。

  • 必须执行才能打印undefined 否则报错

函数在被调用的时候才会执行函数体中的代码 a = 1 是在test()被调用的时候才存在的,必须执行才能打印undefined 否则报错没毛病 另外只有var声明的变量才有变量提升,这里跟变量提升没有关系。

  • 为什么会是window中的变量?

因为test执行的时候只会创建自身的AO并推入作用域链的前端,AO中不存在num,之后会沿着作用域链向VO中查找num,因此会返回10。test1的AO并不在它的作用域链内,因此无法访问var num = 100这个变量。

var num = 10;
function test(){
    console.log(num)
};
function test1(){
    num = 100;
    test();
}
test();
// 100
function test(){
    console.log(num)
};
function test1(){
    var num = 100;
    test();
}
test();
// num is not defined

可以结合这两个例子思考一下

第一个变量提升,比较好理解。
第二个,可以认为test() 是一个闭包函数,创建test时,内部的num是var num = 10的一个引用,所以test1中的num无法覆盖它,这样就能理解了。

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题