【每日笔记 · 1】调用栈经典题解

181 阅读3分钟

1.变量声明

console.log(foo); // ƒ foo(){ console.log("foo");}
function foo(){
    console.log("foo");
}
var foo = 1;

因为在执行上下文时,首先会处理函数声明,其次会处理变量声明,如果变量名称跟已经声明的形式参数或函数相同,则变量声明不会干扰已经存在的这类属性。

2. 连续赋值

var a = {n: 1};
var b = a;
a.x = a = {n: 2};

a.x 	// 这时 a.x 的值是多少  undefined
b.x 	// 这时 b.x 的值是多少  {n:1,x:{n:2}}

这个需要结合JS引擎对堆内存和栈内存的管理来理解,即可一目了然。

var a = {n: 1}; // 声明一个变量a,并为其赋值一个对象

var b = a;  //  创建一个变量b,为其赋值对象a。在栈内存中,a与b是不同的,是两个变量,
            //  但是他们的指针是相同的,指向同一个堆。

a.x = a = {n: 2};

先获取等号左侧的a.x,但a.x并不存在,于是JS为(堆内存中的)对象创建一个新成员x,这个成员的初始值为undefined,

(这也是为什么直接引用一个未定义的变量会报错,但是直接引用一个对象的不存在的成员时,会返回undefined.)

创建完成后,目标指针已经指向了这个新成员x,并会先挂起,单等等号右侧的内容有结果了,便完成赋值。

接着执行赋值语句的右侧,发现a={n:2}是个简单的赋值操作,于是a的新值等于了{n:2}。

这里特别注意,这个a已经不是开头的那个a,而是一个全新的a,这个新a指针已经不是指向原来的值的那个堆内存,而是分配了一个新的堆内存。但是原来旧的堆内存因为还有b在占用,所以并未被回收。

然后,将这个新的对象a的堆内存指针,赋值给了刚才挂起的新成员x,此时,对象成员x便等于了新的对象a。

所以,现在b={n:1,x:{n:2}};a={n:2};a===b.x(true,注意对象的相等,不是值的相等,而是引用的相等,也就是说,相等表示指针是指向同一个堆内存。)

console.log(a.x); // a.x当然没有了,但是因为对象a存在,所以JS不会报错,a.x等于undefined
console.log(b);

总结:

关键点一:a.x即完成了x的声明,其值为undefined。

关键点二:对象成员等待赋值时,锁定的赋值目标是成员,而非对象。

关键点三:对象重新赋值时,并非是修改原堆内存的值,而是重新分配堆内存,栈内存中的指针会做相应修改。(如果原堆内存有多个栈内存指向它,由于引用还存在,原堆内存的数据不会消失。如果堆内存再无其它引用,则会被JS的垃圾回收机制回收。对象的成员对象也一样。PS:引用类型应该都如此)

参考:

https://www.zhihu.com/question/41220520/answer/151955851