第四章、变量、作用域和内存
4.1 基本类型值与引用类型值
- ECMAScript变量可以包含两种不同类型的数据:原始值和引用值。
- 原始值(primitive value)就是最简单的数据,
- 引用值(reference value)则是由多个值构成的对象。
原始值 | 引用值 |
---|---|
Undefined、Null、Boolean、Number、String和Symbol | object |
保存原始值的变量是按值(by value)访问的,操作的就是存储在变量中的实际值 | 保存引用值的变量是按引用(by reference)访问的。操作的是对该对象的引用(reference)而非实际的对象本身 |
原始值不能有属性,尽管尝试给原始值添加属性不会报错 | 可以随时添加、修改和删除其属性和方法 |
直接赋值时,原始值会被复制到新变量的位置 | 在把引用值从一个变量赋给另一个变量时,存储在变量中的值也会被复制到新变量所在的位置 |
- JS中字符串并不是引用类型。
- Symbol(符号)是ECMAScript 6新增的。
- Object是一种无序键值对的集合。
4.1.1 动态属性
- 原始类型的初始化可以只使用原始字面量形式。如果使用的是new关键字,则JavaScript会创建一个Object类型的实例,但其行为类似原始值。
let name1 = 'name1';
let name2 = new String('name2');
name1.age = 16;
name2.age = 16;
console.log(typeof name1,name1.age); // string undefined
console.log(typeof name2,name2.age); // object 16
4.1.2 复制值
4.1.3 传递参数
- ECMAScript中所有函数的参数都是按值传递的。这意味着函数外的值会被复制到函数内部的参数中,就像从一个变量复制到另一个变量一样
- 基本类型,传递的参数中在函数中,相当于函数中的局部变量参数副本。
- 引用类型,相当于浅拷贝到函数中,此时操作修改引用类型参数,会修改原本的值。
// 在函数内部,obj和person都指向同一个对象。
function setName(obj){
obj.name = 'test1'
console.log("obj.name",obj.name) // test1
}
let person = new Object()
setName(person)
console.log("person.name",person.name) // test1
- 第二个例子
function setName(obj){
obj.name = 'test1'; // 这里我理解为 obj 与 person 指向同一对象,相当于person浅拷贝一份到obje
obj = new Object(); // 这里 obj 指向了新的对象
obj.name = "test2222"; // 从这里开始操作新对象
console.log("obj.name",obj.name); // test2222
// obj 本地对象在函数执行结束时就被销毁了
}
let person = new Object();
setName(person);
console.log("person.name",person.name) ; // test1
这表明函数中参数的值改变之后,原始的引用仍然没变。当obj在函数内部被重写时,它变成了一个指向本地对象的指针。而那个本地对象在函数执行结束时就被销毁了。
4.1.4 确定类型
- 使用typeof判断类型
typeof操作符最适合用来判断一个变量是否为原始类型。更确切地说,它是判断一个变量是否为字符串、数值、布尔值或undefined的最好方式
let a = 'aaaa';
let b = true;
let c = 22;
let u ;
let n = null;
let o = new Object();
console.log(typeof a); // string
console.log(typeof b); // boolean
console.log(typeof c); // number
console.log(typeof u); // undefined
console.log(typeof n); // object
console.log(typeof o); // object
- 使用 instanceof 判断类型
- 我们通常不关心一个值是不是对象,而是想知道它是什么类型的对象
- ECMAScript提供了instanceof操作符
result = variable instanceof constructor
let obej = new Object();
let arr = new Array();
let reg = /59/
console.log( obej instanceof Object); // true
console.log( arr instanceof Array); // true
console.log( reg instanceof RegExp); // true
- 按照定义,所有引用值都是Object的实例,因此通过instanceof操作符检测任何引用值和Object构造函数都会返回true
- 类似地,如果用instanceof检测原始值,则始终会返回false,因为原始值不是对象。
4.2 执行上下文与作用域(没看懂-_-)
变量或函数的上下文决定了它们可以访问哪些数据,以及它们的行为。每个上下文都有一个关联的变量对象(variable object),而这个上下文中定义的所有变量和函数都存在于这个对象上。虽然无法通过代码访问变量对象,但后台处理数据会用到它。
全局上下文是最外层的上下文。根据ECMAScript实现的宿主环境,表示全局上下文的对象可能不一样。在浏览器中,全局上下文就是我们常说的window对象
上下文在其所有代码都执行完毕后会被销毁,包括定义在它上面的所有变量和函数(全局上下文在应用程序退出前才会被销毁,比如关闭网页或退出浏览器)。
- 暂时理解为作用域一样的东西
- 代码执行时的标识符解析是通过沿作用域链逐级搜索标识符名称完成的。搜索过程始终从作用域链的最前端开始,然后逐级往后,直到找到标识符。
- 内部上下文可以通过作用域链访问外部上下文中的一切,但外部上下文无法访问内部上下文中的任何东西
上下文之间的连接是线性的、有序的。每个上下文都可以到上一级上下文中去搜索变量和函数,但任何上下文都不能到下一级上下文中去搜索- 注意 函数参数被认为是当前上下文中的变量,因此也跟上下文中的其他变量遵循相同的访问规则。
4.2.1 作用域链增强
- 虽然执行上下文主要有全局上下文和函数上下文两种(eval()调用内部存在第三种上下文),但有其他方式来增强作用域链。
- 1、try/catch语句的catch块
- 2、with语句
- 对with语句来说,会向作用域链前端添加指定的对象;
- 对catch语句而言,则会创建一个新的变量对象,这个变量对象会包含要抛出的错误对象的声明。
4.4 小结
- JavaScript变量可以保存两种类型的值:原始值和引用值。原始值可能是以下6种原始数据类型之一:Undefined、Null、Boolean、Number、String和Symbol。
原始值和引用值有以下特点。
1、 原始值大小固定,因此保存在栈内存上。
2、 从一个变量到另一个变量复制原始值会创建该值的第二个副本。
3、 引用值是对象,存储在堆内存上。
4、 包含引用值的变量实际上只包含指向相应对象的一个指针,而不是对象本身。
5、从一个变量到另一个变量复制引用值只会复制指针,因此结果是两个变量都指向同一个对象。
6、 typeof操作符可以确定值的原始类型,而instanceof操作符用于确保值的引用类型。
- 任何变量(不管包含的是原始值还是引用值)都存在于某个执行上下文中(也称为作用域)。这个上下文(作用域)决定了变量的生命周期,以及它们可以访问代码的哪些部分。
执行上下文可以总结如下。
1、执行上下文分全局上下文、函数上下文和块级上下文。
2、 代码执行流每进入一个新上下文,都会创建一个作用域链,用于搜索变量和函数。
3、函数或块的局部上下文不仅可以访问自己作用域内的变量,而且也可以访问任何包含上下文乃至全局上下文中的变量。
4、全局上下文只能访问全局上下文中的变量和函数,不能直接访问局部上下文中的任何数据。
5、变量的执行上下文用于确定什么时候释放内存。
- JavaScript是使用垃圾回收的编程语言,开发者不需要操心内存分配和回收。
JavaScript的垃圾回收程序可以总结如下。
1、 离开作用域的值会被自动标记为可回收,然后在垃圾回收期间被删除。
2、 主流的垃圾回收算法是标记清理,即先给当前不使用的值加上标记,再回来回收它们的内存。
3、 引用计数是另一种垃圾回收策略,需要记录值被引用了多少次。JavaScript引擎不再使用这种算法,但某些旧版本的IE仍然会受这种算法的影响,原因是JavaScript会访问非原生JavaScript对象(如DOM元素)。
4、 引用计数在代码中存在循环引用时会出现问题。
5、 解除变量的引用不仅可以消除循环引用,而且对垃圾回收也有帮助。为促进内存回收,全局对象、全局对象的属性和循环引用都应该在不需要时解除引用。