1.原始值与引用值
- 原始值:普通数据类型,在栈区存放的是数据本身。
- 引用值:复杂数据类型,在堆区存放数据,在栈区存放的是指向堆区存放数据的地址。
- 用const和let生名变量的区别,前者声明后变量不再修改,后者可以修改,这里说的可否修改,指的是变量的地址是否会改变。
- 对变量进行赋值操作,简单数据类型的原始值会被复制到新变量的位置,此时内存里会有一份值相同而内存不同的变量。而复杂数据类型则是让被赋值变量的地址指向原始值的地址。
- 给函数传递参数和赋值操作是一样的,只不过因为简单数据类型赋值时二者地址不同,因而在函数里面对形参(a是形参,b是传给形参的实参)进行操作,并不会影响到实参。
2.执行上下文
- 全局执行上下文(window),全局上下文是最后一个上下文并且全局上下文会在程序退出前被销毁。在函数被调用的时候,函数的执行上下文会被推向上下文栈中,等函数执行完毕后弹出。当上下文的代码在执行的时候,就存在作用域链,他决定各级上下文访问变量和函数的顺序,即从当前作用域往后找。 (全局上下文没有arguments对象)
- 执行上下文由词法环境(环境记录器(声明型环境记录(声明的变量和函数)和变量型环境记录(除了存放声明的函数和变量还有全局变量(window),函数的话有arguments(保存参数索引与参数之间的映射和arguments的长度))),指向外部的环境引用(有了它才有作用域链),this绑定)和变量环境组成。
- let ,const 声明的变量存放在词法环境,var声明的存放在变量环境。
- var会有作用域提升而let,const会有暂时性死区,是因为在创建执行上下文时,var声明的变量在环境记录中的值是undefined,let,const声明的变量在环境记录中的值是uninitialized,内部会进行一个判断,不是undefined就报错。
3.垃圾回收机制
1.标记清理和引用计数
- 标记清理:被上下文引用的会有被引用的标记,不被引用的会有不被引用的标记,当进行内存清理的时候就把这些都清理掉。
- 引用计数:只要被引用就在值上加1,但是假如出现了循环引用,那么就永远不会被清理,此时需要我们手动让变量指向null,切断联系。
- 其中特别要避免意外声明的全局变量,即没有使用任何关键字直接声明变量(解释器会当成window.xxx处理,而window一直存在,那对该变量的引用也一直存在,故而内存得不到释放),容易造成内存泄漏,通过关键字对变量进行声明,会在程序执行完毕或者程序退出时离开上下文,释放内存。
2.隐藏类和删除操作
- 隐藏类:我们都知道javascript是一种动态语言,对于动态语言来说,由于类型的不确定性,在方法调用的时候,语言引擎每次都需要动态查询,这就很消耗性能(大多数javascript引擎会采用哈希表的方式来存取属性和寻找方法)。为了加快对属性和方法的查找,v8引擎引入了隐藏类的机制。每当属性增减就会创建新的隐藏类或者查找之前的隐藏类,每个隐藏类都会记录属性在内存中的偏移位置,从而下次再调用的时候能快速定位。而共享同一个隐藏类性能会更高一些。
- 删除操作:删除操作容易引发问题就在于隐藏类。看下面的例子:
a1与a2共享同一个隐藏类
而此时如果调用了delete进行删除操作
delete a1.author
对于关于a1的查找,此时就又重新创建了隐藏类(因为原先有对于author的查找,现在没有了,所以要重建),但是由于是同一个实例,只不过后来新建的隐藏类也没有查找author的方式而已,而这个时候就存在了两份一样的隐藏类。最好的做法就是让不需要的属性指向null。