诡异的 javascript 变量

诡异例子:

function DelayExe() {

  var a = 10;

  setTimeout( function Print() { console.log(a); },  1000  );

}

 

以C++的观点来看:一旦函数DelayExe()执行完毕,变量 a 就不复存在,函数 Print() 根本引用不到变量a,必然抛出异常。

但是,在 js 中,在 DelayExe() 执行完毕 1000ms 后执行 Print() 函数,Print() 函数居然能正确引用 a。是不是很诡异?

  

概念和原理:

举个例子:

一个64位整型变量,在 C++ 程序中,只占8字节。

 一个64位整型变量,在 js 中,占有以下空间:

M1:8字节,表达数值

M2:引用计数器,若是 5,表示有 5 个函数引用该变量。

 

每当一个函数引用该变量时,js 引擎就递增变量的引用计数器

每当一个函数用完该变量后,js 引擎就递减变量的引用计数器。若变量的引用计数器递减为 0,说明已没有函数用到该变量,该变量才会被删除。

 

由此可见:

C1:js 中的变量,不仅包含数值,还包含引用计数器。

C2:变量是否存在,取决于其引用计数器的值。

C3:js 引擎根据引用计数器的值决定是否删除变量,是否从内存中清除变量。

 

实际用例:

function DelayExe() {

  var a = 10;

  setTimeout( function Print() { console.log(a); },  1000  );

}

 

以C++的观点来看:

变量 a 是函数DelayExe() 的局部变量,函数 DelayExe() 执行完毕,变量 a 就被删除,不再占用存储空间。

 

但是,js 的情况截然不同:

S1:执行函数 DelayExe()

S2:执行到语句 setTimeout() 时,做如下处理:

S2.1:在内存中分配一块空间,存储函数 Print(),

S2.2:由于函数 Print() 引用了变量 a,递增变量 a 的引用计数器

S2.3:安排函数 Print() 在 1000ms 后被放入任务队列调度执行

S3:函数 DelayExe() 执行完毕。由于此时变量 a 的引用计数器 > 0,所以 js 引擎不将其不删除,变量 a 依然存在。

S4:1000ms 后执行函数 Print(),执行完毕后,函数 Print() 不再引用变量 a,js 引擎递减变量 a 的引用计数器。此时,变量 a 的引用计数器递减为 0,不再被任何函数引用,于是 js 引擎才将其从内存中删除。

 

结论:

C1:js 变量的存在性,与变量的定义位置无关。

C2:js 变量是否存在,是否占用存储空间,取决于有没有被函数引用。

C2.1:若没有被任何函数引用,变量就被删除。

C2.2:若至少被一个函数引用,变量就存在。

C3:若一个变量被一个函数引用,但这个函数一直未被调用执行,那么这个变量就一直存在。

posted on 2022-01-05 10:53  micemik  阅读(168)  评论(0编辑  收藏  举报

导航