1. 原始值与引用值
JavaScript变量可以包含两种不同类型的数据:原始值(Number,String,Boolean,Symbol,Null,Undefined)和引用值(Object)。
原始值变量按值访问,我们操作的是储存在变量中的实际值;引用值按引用访问,是保存在内存中的对象的引用。
1.1 复制值
原始值类型的变量在复制时是拷贝值,两个变量没有任何关系;
引用值类型的变量在复制时也是拷贝值,但是因为值是一个指针,所以两个变量会指向同一个对象,在一个变量上操作可能会影响到另一个变量;
1.2 传递参数
JavaScript中函数的参数都是按值传递的,函数外的值会被复制到函数内部的参数中,就像是复制值一样,包括原始类型和引用类型。
1.3 确定类型
原始值类型:typeof ;
引用值类型:variable instanceof constructor;instanceof用于原始值的时候衡为false
2. 执行上下文与作用域
执行上下文
变量或函数的执行上下文(简称“上下文”)决定了它们可以访问哪些数据以及它们的行为。
每个上下文都有一个关联的变量对象,这个上下文中定义的所有变量和函数都存在这个对象上;虽然无法用代码直接访问这个对象,但是后台处理数据会用到它。
全局上下文是最外层的上下文,在浏览器宿主环境中,全局上下文是window对象,所有通过var定义的全局变量和函数都会成为window对象的属性和方法。
上下文在其所有代码被执行完后会被销毁,包括定义在它上面的变量和函数。
每个函数调用都有自己的上下文,当代码执行流程进入函数时,函数的上下文会被压入到上下文栈中,当函数执行完,上下文栈会弹出该函数的上下文,将控制权返还给之前的上下文。
作用域链
上下文中的代码在执行的时候,会创建变量对象的一个作用域链,这个作用域链决定了代码在访问变量和函数时的顺序。
代码正在执行的上下文的变量对象始终位于作用域链的最前端,如果上下文是函数,那么其活动对象用作变量对象,活动对象上最开始只有一个变量arguments;作用域链的下一个变量对象来自包含上下文,在下一个来自下一个包含上下文,直到全局上下文。
代码执行时的标识符解析沿着作用域链逐级往后,内部的上下文可以通过作用域链访问外部上下文中的一切,外部的不能访问内部的。
2.1 作用域链增强
某些语句会在作用域链的前端添加一个上下文,这个上下文会在代码执行结束后被删除。
- with语句
- try/catch的catch块
2.2 变量声明
使用var的函数作用域声明
使用var声明变量时,变量会被自动添加到最近的上下文,在函数中最近的就是函数上下文(with语句中最近的也是函数上下文);如果变量没有被声明就直接初始化,就会被添加到全局上下文;
var声明会被拿到函数或全局上下文的顶部,这个现象就是变量提升(hoisting);
使用let/const的块级作用域声明
块级作用域由最近的一个{}界定;
在同一个块中不能重复使用let声明同一个变量;
严格来讲,let/const也会被变量提升,但是会出现“暂时性死区”,导致不能在声明前使用变量,但是内部变量的存在会屏蔽对外层上下文的访问;
var value = 20;
(function () {
console.log(name); // undefined
console.log(value); // Uncaught ReferenceError: Cannot access 'value' before initialization
var name = 'local value';
let value = 21;
})();
用let声明的变量value之所以会报错,是因为let/const造成的“暂时性死区”的问题;用let/const声明的变量,在包含它们的上下文被创建时就会被创建,但是,只有在变量的词法绑定已经被求值运算后(赋值),才能够被访问;所以严格来讲,let/const也会和var一样被变量提升,在赋值语句之前访问会报错;
在本题中,由于let value = 21;一句,在function的上下文中,value已经在上下文创建之初存在了,所以它的存在屏蔽了对外层value的访问,然而,由于let的暂时性死区的限制,它虽然存在但是无法被访问,所以这里会报错;
用const声明的变量在声明时就必须被初始化,原始类型的const变量不能修改;引用类型的const变量只是不能修改引用值,被引用的对象是可以修改内容的;
如果想整个对象都不能被修改,可以使用obj2 = Object.freeze(obj1)
3. 垃圾回收
垃圾回收机制有:
- 标记清理
- 引用计数
引用计数会遇到循环引用的问题;
垃圾回收程序周期性运行,如果运行过于频繁会导致性能严重下降;
内存管理
将内存占用量保存在一个较小的值可以让页面的性能更好,为此,在代码执行时尽量只保存必要的数据,如果数据不再需要,就把它置为null从而释放其引用(引用解除);
减少对象的先创建再补充的动态赋值,在构造函数里一次性声明所有属性,这样可以减少隐藏类的创建;