作用域

Wenn 于 2022-09-20 发布

作用域面试

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);

内存管理

内存的挂历都有如下的生命周期:

  1. 分配申请内存;在内存中创建空间

  2. 使用分配的内存;在创建的内存空间中存放数据
  3. 当内存不再使用时,进行释放

js 会在定义变量时为我们分配内存

Js 在对于基本数据类型时,对内存的分配直接在栈空间中进行分配;

JS 在对于复杂数据类型是,对内存的分配在对空间中开辟一块空间,并将该 指针(地址)返回变量引用;

image-20220921105829295

垃圾回收(Garbage Collection, GC)