yxfanxiao / yxfanxiao.github.io

My Blog Space
3 stars 0 forks source link

我对js执行环境、作用域链、闭包的理解 #1

Open yxfanxiao opened 8 years ago

yxfanxiao commented 8 years ago

/* 2015-9-21 update */ 看到一篇讲的很好的 https://cnodejs.org/topic/5599521293cb46f578f0a57e

// 修改了很多次,最后把我个人目前的理解通俗地记录下来,有错请留言指正!

函数第一次被调用分为2个阶段,函数创建和函数激活。之后被调用就只进行函数激活。 当某个函数被第一次调用时,先进行函数创建。 首先介绍执行环境(也称执行上下文)的概念:指的是js在执行过程中可以取到哪些变量、函数。由于js是单线程的,当进入一个新的函数时,会产生一个新的上下文(context),被压入js的上下文堆栈中,等到函数执行完毕后弹出。

上下文的结构是:

  • 变量对象 (Variable object) [vars, function declarations (,arguments)]
  • 作用域链(Scope chain) [Variable object, all parent scopes]

变量对象:是一个与执行上下文相关联的特殊对象。 全局变量自身就是变量对象,只有全局上下文的变量对象才能间接地引用变量(浏览器中window,node中global且 VO === this === global)。 但是函数的变量对象无法直接引用VO,并且它扮演的角色叫作活动对象(Activation object且VO ===AO)。当函数被激活时,arguments和formal parameters are added。

作用域链:先把parent scopes 赋值给function的一个内部变量[[scope]],然后把被arguments和形参绑定后的 VO|AO 压入Scope Chain最前端,之后是按顺序的 parent scopes。(保证了取参和取函数的顺序) 之后,当函数执行完成时,执行环境中的作用域链销毁。若没有取消对函数的引用,AO活动对象作用域链(AO+[[scope]])会一直存在内存之中。
// 这里我困扰了很久,求指正。PS:书(js高级程序设计第三版P.180 11行)上写的是:只有AO存在 // 内存之中。 // 现在的理解是:函数的[[scope]]是在函数创建时就赋给它的,属于函数的静态属性,而作用域链 // 是由AO+[[scope]]组成的,这里的[[scope]]是类似指针的引用,因此不算占用内存。但是我更偏 // 向于作用域链留在内存之中。

当再次激活已创建过的函数时,取函数的[[scope]],然后根据新的arguments和形参生成新的AO。

注:判断this指向的方法:(按顺序判断)

  • New 指向新对象
  • Bind 指向绑定对象
  • Apply、Call 指向绑定对象
  • 对象的方法引用 指向该对象
  • 剩下的则 指向全局

this指向, 举个栗子:

var obj = {
  a: 1,
  b: function () {
      return function () {
        return this;                          // 由第五条,这里this指向全局
      }
    },
  c: function () {
    return this;                               // 由第四条,c是object obj 的方法,所以this指向obj
  }
}
console.log(obj.b()());
console.log(obj.c());

作用域链的顺序, 举个栗子:

!function foo () {
  function bar () {
    return 1;
  }
  !function test () {
    console.log(bar());                // 2.作用域链的活动顺序,保证了函数寻找变量或方法的顺序
    function bar () {
      return 2;
    }
  }();
}();

来看一段闭包,

function scope (num) {             // 在这里只会先声明有scope这个函数
  return function () {
      console.log(num);
  };
}
var create = scope(1);             // 执行scope()
var result = create();

当匿名函数从scope()中返回时,它的作用域链被初始化为包含了scope()函数的活动对象和全局变量对象,也就可以访问全局和scope()函数中的变量。并且,当scope()执行完毕,其执行环境的作用域链就被销毁,但是活动对象不会被销毁,依然存在内存中,直到匿名函数被销毁后,scope()的活动对象才会被销毁。 // create 作用域链 // 0: closure AO; // 1:scope() AO // 2: global VO

function scope (num) {             // 在这里只会先声明有scope这个函数
  return function () {
      console.log(num);
  };
}
var create = scope(1);             // 执行scope()
var result = create();
create = null;         // 解除指向匿名函数的引用,就会销毁匿名函数的作用域链,从而销毁scope()的作用域链,释放内存中的活动变量。

完。 这是我学习过程中的笔记,若有错误,请指正。 我的个人理解不保证对,所以万一被转载,对误人子弟不负责任。 参考资料: JavaScript高级程序设计(第三版)

yxfanxiao commented 8 years ago

想起了之前遇到的一题,现在豁然开朗:


!function () {
  for (var i = 0; i < 5; i++) {
    !function () {
      setTimeout(function () {
        console.log(i)                                // 5 5 5 5 5
      }, 1000)
    }()
  }
}()

!function () {
  for (var i = 0; i < 5; i++) {
    var a = i;
    !function () {
      setTimeout(function () {
        console.log(a)                                // 4 4 4 4 4
      }, 1000)
    }()
  }
}()

!function () {
  for (var i = 0; i < 5; i++) {
    !function (i) {
      setTimeout(function () {
        console.log(i)                                // 0 1 2 3 4        
      }, 1000)
    }(i)
  }
}()

我执行了这么一段代码:


!function () {
  for (var i = 0; i < 5000000000000; i++) {
    !function () {
      setTimeout(function () {
        console.log(i)
      }, 1000)
    }()
  }
}()

结果如图: 没过一会,node监测到内存占用过高,就停止了程序。

FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - process out of memory

我的理解是:异步的setTimeout,导致了所有匿名函数都是被引用状态,所有AO保存在内存之中,因此内存占用飙升。