作用域面试
var n = 100;
function foo(){
console.log(n)
}
function bar(){
var n =200
foo()
}
bar()
// 输出n=100
/*当代码开始编译时,
*首先在GlobalObject创建n,此时n=undefined;后解析foo以及bar函数,
*此时仅在go中创建foo以及bar,仅指向内存中的地址,并不赋值
*
*当代码开始执行时,此时为n赋值100
*当执行bar时,此时执行上下文创建Ao,此时bar的ao指向自身ao+ParentScope(go)并在ao中创建n,此时n为undefined,并赋值为200
*后执行foo时创建ao,此时foo的ao指向go,go中已经有n为100,则输出n=100
*/
function foo(){
console.log(n);
var n = 200;
console.log(n)
}
var n = 100;
foo();
//首先输出undefinded,后输出200
/*当代码开始编译时,
*首先解析foo,此时创建foo并指向内存中的地址,后创建n=undefined
*
*当代码开始执行时,
*此时将n=100赋值为100
*创建Ao,此时ao的作用域指向ao+go,首先执行第一行,此时在ao中创建n=undefined
*因为在中间声明了n,预先解析n
*并输出n=undefined,后将ao中的n赋值200,并输出
*/
var n = 100;
function foo(){
n = 200
}
foo()
console.log(n); //200
/*当代码开始编译时
*创建n=undefined,后创建foo,指向内存中的地址
*
*当代码开始执行时,
*为n赋值100
*创建函数执行上下文,并在函数执行上下文中创建ao,此时ao=ao+go,ao为空,则直接指向go,即ao=go
*此时go中存在n=100,执行n=200时,将200重新赋值为200
*最后输出n
*/
var n = 100;
function foo1(){
console.log(n); //100
}
function foo2(){
var n = 200;
console.log(n); //200
foo1();
}
foo2();
console.log(n); //100
/*当代码开始编译时,
*此时创建执行上下文栈,在执行上下文栈中创建vo,此时vo指向go,go中创建n=undefined,
*创建foo1,并在内存开辟空间并指向该地址,创建foo2,在内存中开辟空间并指向该地址;
*
*当代码开始执行时,
*go存在n,将100赋值给n
*foo2执行,在执行上下文栈中创建FEC(函数执行上下文),并在FEC中创建vo,此时vo等同于ao+go,在ao中创建n,并赋值200,第一个输出200;
*后执行foo1,此时在自行上下文栈中创建新的FEC,并在该FEC中创建vo,此时foo1的vo指向go,此时在go中存在n=100,此时输出100
*该时,foo1执行完毕,栈出销毁;当foo1执行完毕,则foo2执行完毕,栈出销毁
*最后输出go中的n=100
*/
var n = 100;
function foo(){
console.log(n); // undefined
return;
var a = 200;
}
foo();
/*当代码开始编译时,
*首先创建执行上下文栈,并在执行上下文栈中创建GEC(全局执行上下文),此时GEC中创建vo,此时vo指向go,创建n=undefined,创建foo,并在堆内存中开辟空间,指向该地址
*
*当代码开始执行时,
*此时将100赋值给go中的n,n=100;
*执行foo,创建FEC,并在FEC中创建VO,此时vo中存在变量,则vo指向ao+go,
*ao中创建n,赋值n=undefined
*输出n时,先从ao中找到n,此时n=undefined
*输出n
*/
补充
function foo(){
m = 100;
}
foo()
console.log(m); //100
//当go以及ao中未声明而直接赋值时,会在默认在go中创建m
function foo(){
var m = 100;
}
foo()
consoel.log(m); //ReferenceError: m is not defined
//当代码开始编译时在ECStack中创建foo,并在堆中开辟空间,指向该地址
//当代码开始执行时,首先在ECS中创建FEC,并在FEC中创建vo,此时vo中存在变量,
//则创建m=undefined,并将100赋值给m,,此时foo执行完毕,栈出销毁
//执行console.log时go中并不存在m,报错
function foo(){
var a = b = 10;
}
foo();
console.log(b); //10
console.log(a); //ReferenceError: a is not defined
//执行上文的代码时,将会转换为以下代码
function foo(){
b = 10; //b在go与ao中都未声明,默认在go中创建b,并将10赋值给b
var a = b; //a存在于FEC中的ao中,当函数执行完毕则栈出销毁
}
foo();
cosnole.log(b);
console.log(a);
内存管理
内存的挂历都有如下的生命周期:
-
分配申请内存;在内存中创建空间
- 使用分配的内存;在创建的内存空间中存放数据
- 当内存不再使用时,进行释放
js 会在定义
变量时为我们分配内存
Js 在对于基本数据类型时,对内存的分配直接在栈空间中进行分配;
JS 在对于复杂数据类型是,对内存的分配在对空间中开辟一块空间,并将该 指针(地址)返回变量引用;
垃圾回收(Garbage Collection, GC)
-
引用计数-清除
内存空间中创建内存后,只要有人引用它,则将他的引用计数(retain count)加一,直到当
retain count
为 0 时,才会回收该空间;这人中方法有弊端,当出现循环引用时,会出现内存泄漏var obj1 = {friend:obj2}; var obj2 = {friend:obj1}; //此时obj1与obj2的retain count都为1,并且永远不会被垃圾回收
-
标记清除
设置一个根对象 go,从根对象开始访问引用,依次向下,如果访问不到时,则标记为不可达,即清除