变量
JS中变量主要两种类型原始值和引用值。
原始值为String Undefined Null Boolean Number Symbol 六种,原始值是按值访问的,操作只是数据本身。
应用值是保存在内存里的对象,在操作对象的时候实际上是操作该对象的引用而不是数据本身。
以上都是已知基础知识,在读书的时候发现了一些没有实操过的特性,比如:
- 给原始值可以赋值子属性而不会报错,只是会无效
let name = 'a'
name.age = 27
console.log(name.age) // undefined 但不会报错,
- 初始化原始值的时候使用new,不会创建原始值,而是会创建一个Object。
let name = new String('a')
name.age = 27
console.log(name.age) // 27
复制引用值
这个知识点应该是变量里面最重要的了,基础要点就是原始值复制会创建这个值的第二个副本,而引用值复制则是复制指针,操作复制值就是操作引用值。即使是函数的传递参数使用的是引用值,外部的引用值也会改变.。 但是当传递参数中传递的是对象的时候,这却不是按引用传递,而是按值传递,很迷惑。“很多开发者错误的以为,当在局部作用域中修改对象而变化反映到全局的时候,就意味着参数是按引用传递...”震撼,我一直也是以为是因为按引用传递的原因,但是书里没有写为什么,只有个例子。
function setName(Obj){
Obj.name = 'aa'
Obj = new Object()
Obj.name = 'ddd'
}
let person = new Person()
setName(person)
console.log(person.name) // 'aa'
书里描述person.name!= 'ddd'的原因说明了他不是按引用传递,可是这不是因为它又重新创建了一个新的地址,此时Obj不再引用外部的Person的原因么?我认为这个例子并不能充分的证明他是按值传递(这是我的思考,也许不对)
确定类型:
typeof更适合检测原始值,instanceof检测引用类型。
typeof null == Object typeof Object == Object
instanceof 可以检查Object、Array、RegExp
执行上下文和作用域
这部分看的我头疼,书里用词非常拗口,经常需要口读才能明白啥意思...
任何变量都存在于某个执行上下文,即作用域,这个上下文决定了变量的生命周期,以及他们可以访问代码的那些部分。
上下文代码在执行中会创建作用域链,先从块级作用域开始到函数作用域,最后到全局作用域。代码执行的标识符解析是通过这个作用域链逐步搜索,当上级作用域找到了变量和函数就不会再搜索下级作用域,也就是说块级里有和全局的重名变量,全局的重名变量是不会被用到的,除非写限定性的符号才能拿到,比如window.a
try catch和with会有作用域链增强的效果,从来没有用过with,所以去搜了一下,第一句话就是不要用with哈哈哈
function f(foo, values) {
with (foo) {
console.log(values) // 当foo也有values属性,它输出的是foo.values,而不是f的values,会导致语义不明
}
}
catch会在作用域链前面创建一个新的变量对象,这个变量对象会包含要抛出的错误对象的声明,with会向作用域链前端添加想新的对象
变量声明
var声明的变量会被添加到最接近的上下文,在函数里声明的var变量最靠近的上下文是函数,使用var还会有变量提升的现象
console.log(name) // undefined 而不是报错
var name = 'aaa'
let const的作用域是块级作用域,和最近一个{} 绑定
- let 和var 另一个不同之处是不可以声明两次(我都不知道var可以声明两次而不报错,新知识√)
- let其实也会被提升,但是因为“暂时性死区”,所以不能提前使用let
- const变量不能重新被赋值,但是如果是对象的属性,还是可以修改的,如果完全不想对象被修改的话可以使用
Object.freeze(),这样赋值的时候不会报错,但是会静默失败!
垃圾声明
常见两种方式:标记清理和引用计数
- 标记清理:当变量进入上下文时候添加进入上下文的标记,离开上下文时候添加离开上下文的标记(标记方法有很多种),当上下文的任何变量都访问不到他们了,就会被垃圾回收程序做一次内存清理。
- 引用计数,顾名思义。这个方法最大的问题是循环引用,则计数永远不会被清空。
function problem(){
let a = new Object()
let b = new Object()
a.some = b
b.somePart = a
}
为避免循环引用的问题,可以手动将变量设置为null,切断关系,这些值就会被清除,内存也会被回收。
内存管理的一些办法
- 手动解除引用值,确保相关知识不在上下文中了
- 使用const和var可以尽快让垃圾回收程序介入
- V8Javascript会在解析后的机器码使用隐藏类,两个实例使用了相同的对象创建的,会共享相同的隐藏类,尽量不要在外部添加不同的属性,会创建不同的隐藏类,减少动态属性赋值,避免“先创建再补充”,在构造环境中声明所有变量,会带来潜在的性能提升。delete会破坏掉这一特性。
function Person( nameString){
this.name = nameString
}
let a1 = new Person()
let a2 = new Person('aaa2')
- 闭包引用外部变量容易导致内存泄露
- 浏览器决定垃圾回收的时间的标准之一就是对象更替的速度,少创建新的对象并让他超出自己的作用域。
- 创建一个对象池,专门管理一组可回收的对象,应用程序可以向对象池申请对象,操作完成后再返回给对象池,由于没发生对象初始化,垃圾回收检测就不会发现有对象更替,因此垃圾回收程序就不会频繁的执行。
有不足的地方欢迎指正~