变量、作用域与内存
-
原始值 按值访问的,操作的即是存储在变量中的实际值
-
引用值 由多个值构成的保存在内存中的对象,实际中操作的是对该对象的引用而非实际的对象本身,保存引用值得变量是按引用访问的
-
动态属性 引用值可以随时增删改查其属性以及方法,而原始值不能有属性,给原始值添加属性不会报错但是最终无法使用
let person = new Object() person.name = "Nicholas" console.log(person.name) //"Nicholas" let name = "Nicholas" name.age = 27 //不报错 console.log(name.age) //undefined
-
复制值 通过原始值赋值到另一个变量时,原始值会被复制到新变量的位置,两个变量可以独立使用,互不干扰;把引用值从一个变量赋给另一个变量时,存储在变量中的值也会给复制到新变量所在的位置,其中复制的值实际上是一个指针,复制后两个变量实际上指向同一个对象,因此一个对象上面的变化会在另一个对象上面反应出来
-
传递参数 ES中所有函数的参数都是按值传递的,即函数外的值会复制到函数内部的参数中。对引用值传递参数时,函数内部的改变会映射到函数之外的引用值
-
确定类型 typeof是判断一个变量是否未字符串、数值、布尔值或undefined的最好方式,若值是对象或null,则返回“object”。typeof对原始值作用较大,而对引用值用处小,因此使用instanceof。若变量是给定引用类型(由其原型链决定)的实例,则instanceof操作将返回true
console.log(person instanceof Object)
若instanceof检测到原始值,将返回false,因此原始值不是对象。typeof检测函数时返回“function”
-
执行上下文与作用域 变量或函数的上下文决定了其可访问的数据以及行为,每个上下文都有一个关联的变量对象,在这个上下文中定义的所有变量和函数都存在于这个对象上。
-
arguments对象 该对象为特殊对象,开发者无需明确指出参数名,就能访问。arguments对象不是数组,而是一个类数组对象。
function test(a,b,c) { console.log(arguments) console.log(test.length) console.log(arguments.callee.length) } test(1,2,3,4) /** * Arguments(4)[1,2,3,4,callee: ƒ, Symbol(Symbol.iterator): ƒ] * 3 * 3 */
其中arguments代表的是函数实参的个数,fn.length代表函数形参的个数,arguments.callee指向函数本身。
…args剩余参数与arguments对象的区别
- 剩余参数只包含没有对应的形参的实参,arguments对象则包含了传给函数的所有实参
- arguments对象不是真正的数组,而是一个类数组,剩余参数则是真正的Array实例,即可以在剩余参数上直接使用所有关于数组二点方法,如sort、map、forEach或pop等
- arguments有一些附加的属性,如callee
-
变量对象、活动对象 表明了函数执行上下文中的变量对象和活动对象是同一个对象,只是存在于不同的生命周期之中。在进入执行阶段之前,变量对象中的属性都不可访问;进入执行阶段之后,变量对象变成活动对象,里面的属性可以被访问了,然后执行代码
-
js的提升机制
var str = 'global' function test() { console.log(str) var str = 'location' console.log(str) } function test2() { console.log(str) } test() test2() /** * undefined * location * global */
全局上下文是最外层的上下文,不同宿主环境的表示全局上下文的对象不同,浏览器中全局上下文对象即为window对象。所有通过var定义的全局变量和函数都会成为window对象的属性和方法。使用let和const的顶级声明不会定义在全局上下文中,但在作用域链上解析效果相同。每个函数都有自己的上下文,上下文代码在执行时,会创建变量对象的一个作用域链,这个作用域链决定了各级上下文中的代码在访问变量和函数时的顺序。若上下文是函数,则其活动对象用作变量对象。
-
-
作用域链增强 某些语句会导致在作用域链前端临时添加一个上下文,这个上下文在代码执行后就会被删除,该变量对象在代码执行时会被调用
-
let与var的另一个区别在于同一个作用域内不能声明两次。let适合在循环中声明迭代变量,使用var声明的迭代变量会泄露到循环外部。
-
若想使整个对象都不能修改,可以使用Object.freeze(),这样给属性赋值时不会报错,但会静默失败
const o = Object.freeze({}) o.name = 'Jake' console.log(o.name) //undefined
-
在寻找标识符的时候,若没有找到变量名,则继续沿作用域链搜索,而作用域链中的对象也有一个原型链,因此搜索可能涉及到每个对象的原型链
-
垃圾回收 执行环境负责在代码执行时管理内存,通过自动内存管理实现内存分配和闲置资源回收。确定哪个变量不会再使用,然后释放其占用的内存,其过程是周期性的。
-
标记清理 当变量进入上下文,比如在函数内部声明一个变量,则该变量会被加上存在于上下文的标记。逻辑上,存在于上下文的变量不应该被释放内存。垃圾回收时会标记内存中存储的所有变量,然后将所有在上下文的变量以及被上下文引用的变量标记去掉,此时剩下的带标记的变量即为待删除的。
-
引用计数 记录每个值被引用的次数,声明变量并给其赋予引用值时,则引用数为1,当同一个值被赋予另一个变量,则引用数加1,当保存对该值的引用的变量被其他值给覆盖了,则引用数减1,当引用数为0时,即无法再访问到该值,可以收回内存。存在循环引用问题,即对象A有一个指针指向对象B,对象B也有指针指向对象A
-
内存管理 内存限制不仅影响变量分配,也影响调用栈以及能够同时在一个线程中执行的语句数量。若数据不再必要,则将其设置为null,从而释放其引用,这叫做解除引用
function createPerson(name){ let localPerson = new Object() localPerson.name = name return localPerson } let globalPerson = createPerson("Nicholas") //解除引用 globalPerson = null
解除对一个值的引用并不会自动导致相关内存被回收,关键在于确保相关的值已经不在上下文中,在下次垃圾回收时会被回收。
-
对象池 在初始化的某一时刻,可以创建对象池,用来管理一组可回收的对象。应用程序可以向该对象池请求一个对象、设置其属性、使用它,在操作完成之后将其还给对象池。
-