回顾
对象的属性名可以是基本数据类型,不能是引用数据类型,如果是引用数据类型,会转换为字符串。属性值可以是任意数据类型
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属性,但并不是函数执行时的实参集合。