【JS】对象及函数相关细节问题讨论,底层机制讲解

490 阅读5分钟

回顾

对象的属性名可以是基本数据类型,不能是引用数据类型,如果是引用数据类型,会转换为字符串。属性值可以是任意数据类型

var a={},b={n:"1"},c={m:"2"};
a[b]="哈哈";
a[c]="嘿嘿";
console.log(a[b]);
=> "嘿嘿"
注:b和c都是普通对象,转化为字符串都是"[object Object]"

经典面试题

var a ={n:1};
var b =a;
a.x = a ={n:2};
console.log(a.x);
console.log(b);
=> undefined   {n:1,x:{n:2}}

分析:这道题考察了引用数据类型的存储方式,以及连等赋值的运算顺序优先级。

总结

  • 引用数据类型是以堆内存的形式存储的,将堆内存的地址赋值给了变量
  • 赋值过程:先创建堆内存,后将堆内存地址赋值给变量
  • 连等赋值:一般是从右向左赋值的,但是要注意运算优先级的问题,成员访问的运算优先级更高,因此本题中要从左往右依次赋值

解题:因此,本题中a.x是成员访问,运算优先级更高,需要是先给a.x赋值,先将原本的{n:1}这个堆内存中添加x属性,属性值是{n:2}的堆内存地址,再将{n:2}的堆内存地址给a这个变量,而b指向的仍然是原本的堆内存地址,因此可以得出本题的答案。

而且我们还可以继续尝试看一下b.x ===a

console.log(b.x===a);
=> true

这就说明只开辟了一次堆内存,b.x 和 a 指向的是同一个堆内存

经典练习题

var x = [12,23];
function fn(y){
    y[0]=100;
    y=[100];
    y[1]=200;
    console.log(y);
}
fn(x);
console.log(x);
=>[100,200]   [100,23]

分析:本题涉及了引用数据类型赋值,传递的是空间地址,空间地址相同,则指向的是同一个堆内存,并且可以修改堆内存中的内容。

解题:数组和函数都是引用数据类型,创建时会开辟新的堆内存。数组[12,23]开辟了一个新的堆内存地址,并将地址给了变量 x。fn(x) 函数执行,将 x 的堆内存地址给了 y 这个形参,y 可以通过这个地址修改堆内存中的内容,y[0]=100 是将数组中的第一项修改为100,此时 y 这个数组变为了[100,23],同样的 x 也指向的这个堆内存,所以 x 指向的这个数组也变为了[100,23]。而y=[100] 是新创建了一个堆内存,将新的堆内存地址给了 y,并且通过成员访问y[1]=200 给这个新的数组添加了一项索引为 1 ,属性值为200 的键值对,此时 y =[100,200]。由此可以得到本题的答案。

总结

创建函数的底层机制:

1、开辟一个堆内存,有一个相应的空间地址

2、记录一下函数所属的作用域scope,函数在哪里创建的,所属的作用域scope就在哪里

3、把函数当成字符串存到堆内存里面,称为"代码字符串"

4、把空间地址给相应的变量

: 如果函数不执行,堆内存中存储的函数的代码仅仅是一些没有任何意义的字符串。

函数执行过程:

1、形成一个全新的可执行环境(私有作用域)EC(fn),目的是提供代码执行,然后进栈

2、在上下文中会有一个AO,主要用来存储私有变量的

3、在代码自上而下执行之前会做很多事情:

  • 初始化作用域链:scope-chain:[自己的EC]---[当前函数所在的EC]

  • 初始化arguments

  • 初始化this

  • 形参赋值

  • 变量提升

4、代码执行,将堆内存中存储的代码字符串执行

  • 在此过程中需要注意:

    作用域链查找机制:如果当前作用域中没有这个变量,就会按照作用域链一直向上查找,一直找到 EC(g) 为止,如果 EC(g) 中也没有,相当于给GO添加了一个新的属性

5、判断私有作用域 EC(fn) 是否销毁,也就是决定是否出栈:

一般情况下,如果函数的代码执行完毕,都会出栈,但是如果里面的东西被外面占用了,就不会出栈。全局的在打开页面的时候会形成一个执行栈,当前页面关闭的时候,这个栈就会被移除

注: 作用域链规定了变量的查找顺序;arguments 是实参集合,是一个类数组;形参赋值在变量提升之前,形参也是私有变量。

什么是私有变量

1、形参

2、在私有作用域EC(fn)中,带 var 或 function 、 let 、 const 声明的变量

关于实参集合arguments的一些思考

接下来我们进行以下代码的尝试:

function fn(){return this};

console.dir(fn);

=> fn 里面显示是有 arguments:null

那么这个可以证明fn在创建的时候就有arguments 了吗?

然后又进行了如下验证

function fn(){
console.log(arguments===fn.arguments);
}
fn();
=> fasle

这就证明了函数自身的arguments和函数执行产生的arguments不是同一个引用对象,函数创建的时候确实有arguments属性,但并不是函数执行时的实参集合。