原始值与引用值

动态属性

原始值类型的初始化可以只使用原始字面量形式。如果使用的是 new 关键字,则 JavaScript 会创建一个 Object 类型的实例,但其行为类似原始值

1
2
3
4
5
6
7
8
let name1 = 'haha'
let name2 = new String('hehe')
name1.age = 25
name2.age = 26
console.log(name1.age) // undefined
console.log(name2.age) // 26
console.log(typeof name1) // string
console.log(typeof name2) // object

执行上下文与作用域

变量对象( variable object )

每一个上下文都有一个关联的变量对象,而这个上下文中定义的所有变量和函数都存在于这个对象上

作用域链( scope chain )
活动对象( activation object )

作用域链增强

某些语句会导致在作用域链前端临时添加一个上下文,这个上下文在代码执行后会被删除。通常在两种情况下会出现这个现象:

  • try / catch 语句的 catch 块
  • with 语句

这两种情况下,都会在作用域链前端添加一个变量对象。对 with 语句来说,会向作用域链前端添加指定的对象;对 catch 语句而言,则会创建一个新的变量对象,这个变量对象会包含要抛出的错误对象的声明

1
2
3
4
5
6
7
function buildUrl(){
let qs = "?debug=true"
with(location){
let url = href + qs
}
return url
}

这里 with 语句将 location 对象作为上下文,因此 location 会被添加到作用域链前端。当 with 中的代码引用变量 href 时,实际上引用的是 location.href,也就是自己变量对象的属性;而 qs 则是定义在 buildUrl() 中的那个变量,它定义在函数上下文的变量对象上。with 块中使用 let 声明的变量,被限制在块级作用域,buildUrl() 中没有定义,所以执行到 return 行会报错,如果换成 var 则可以正常执行

变量声明

var 声明会被拿到函数或全局作用域的顶部,位于作用域中所有代码之前,这个现象叫做“提升”(hoisting)

if块、while块、function块,甚至连单独的块也是 let 声明变量的作用域

1
2
3
4
5
6
// 这不是对象字面量,而是一个独立的块
// JavaScript 解释器会根据其中内容识别出它来
{
let a;
}
console.log(a) // ReferenceError: a is not defined

使用 const 声明的变量必须同时初始化为某个值

如果想让整个对象都不能修改,可以使用 Object.freeze(),这样再给属性赋值时虽然不会报错,但会静默失败

垃圾回收

标记清理( mark-and-sweep )

垃圾回收程序运行的时候,会标记内存中存储的所有变量,然后,它会将所有在上下文中的变量,以及被在上下文中的变量引用的变量的标记去掉。在此之后再被加上标记的变量就是待删除的了,原因是任何在上下文中的变量都访问不到它们了,最后垃圾回收程序做一次内存清理,销毁带标记的所有值并收回他们的内存

到了2008年,IE、Firefox、Opera、Chrome 和 Safari 都在自己的 JavaScript 实现中采用标记清理(或其变体),只是在运行垃圾回收的频率上有所差异

引用计数( reference counting )

对每个值都记录它被引用的次数。声明变量并给它赋一个引用值时,这个值的引用数位1,如果同一个值又被赋给另一个变量,那么引用数加1。类似的,如果保存对该值引用的变量被其他值给覆盖了,那么引用数减1。当一个值的引用数为0时,就说明没办法再访问到这个值了,因此可以安全地回收其内存了。垃圾回收程序下次运行的时候就会释放引用数为0的值的内存

内存管理

解除引用:如果数据不再必要,那么把它设置为 null,从而释放其引用(这个建议最适合全局变量和全局对象的属性)
1、通过 const 和 let 声明提升性能
const 和 let 都以块为作用域(而非函数),所以相比于使用 var,使用这两个新关键字可能会更早的让垃圾回收程序介入,尽早回收应该回收的内存
2、隐藏类和删除操作
同一个构造函数创建的两个实例,共用一个隐藏类,如果其中一个实例进行动态属性赋值操作,会导致两个实例对应两个不同的隐藏类。所以,应该避免“先创建再补充”( ready-fire-aim )式的动态属性赋值,并在构造函数中一次性声明所有属性

1
2
3
4
5
6
7
8
9
10
11
12
13
function Article(){
this.title = 'Hello World'
}
let a1 = new Article()
let a2 = new Article()
a2.author = 'Jack' // 动态属性赋值

function Article(author){
this.title = 'Hello World'
this.name = author
}
let a1 = new Article()
let a2 = new Article('Jack')

使用 delete 关键字会导致生成相同的隐藏类片段,最佳实践是把不想要的属性设置为 null