诡异的 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:若一个变量被一个函数引用,但这个函数一直未被调用执行,那么这个变量就一直存在。