二、变量、作用域与内存
相比于其他语言,JavaScript中的变量可谓独树一帜。正如ECMA-262所规定的,JavaScript变量是松散类型的,而且变量不过就是特定时间点一个特定值的名称而已。
1、原始值和引用值
原始值包括6种:Undefined
、Null
、Boolean
、Number
、String
和Symbol
。引用值则是由多个值构成的对象。
保存原始值的变量是按值(by value)访问的,,保存引用值的变量是按引用(by reference)访问的。
动态属性
引用值,可以随时添加、修改和删除其属性和方法。
复制
除了存储方式不同,原始值和引用值在通过变量复制时也有所不同。
在通过变量把一个原始值赋值到另一个变量时,原始值会被复制到新变量的位置。在把引用值从一个变量赋给另一个变量时,里复制的值实际上是一个指针。
传递参数
JavaScript函数中所有函数的参数都是按值传递的。如果是原始值,那么就跟原始值变量的复制一样,如果是引用值,那么就跟引用值变量的复制一
// 按值传递的实例
function setName(obj) {
obj.name = "Nicholas";
obj = new Object();
obj.name = "Greg";
}
let person = new Object();
setName(person);
console.log(person.name); // "Nicholas"
在上述例子中,person被复制一份传递给 setName函数的obj参数,由于两者是同一个引用,所以obj的name属性变化会影响person对象,而在函数中对obj重新设置为一个新对象,则不会改变原始的引用,所以person对象不受影响。
确定类型
typeof操作符最适合用来判断一个变量是否为原始类型
instanceof操作符适用于检测任何引用值和对象。
//typeof
let s = "Nicholas";
let b = true;
let i = 22;
let u;
let n = null;
let o = new Object();
console.log(typeof s); // string
console.log(typeof i); // number
console.log(typeof b); // boolean
console.log(typeof u); // undefined
console.log(typeof n); // object
console.log(typeof o); // object
//instance
console.log(person instanceof Object); // 变量person是Object吗?
console.log(colors instanceof Array); // 变量colors是Array吗?
console.log(pattern instanceof RegExp); // 变量pattern是RegExp吗?
2、执行上下文和作用域
执行上下文的概念在JavaScript中是颇为重要的。每个上下文都有一个关联的变量对象(variable object),而这个上下文中定义的所有变量和函数都存在于这个对象上。
每个函数调用都有自己的上下文。当代码执行流进入函数时,函数的上下文被推到一个上下文栈上。在函数执行完之后,上下文栈会弹出该函数上下文,将控制权返还给之前的执行上下文。ECMAScript程序的执行流就是通过这个上下文栈进行控制的。
作用域链 上下文中的代码在执行的时候,会创建变量对象的一个作用域链。这个作用域链决定了各级上下文中的代码在访问变量和函数时的顺序。代码正在执行的上下文的变量对象始终位于作用域链的最前端。
代码执行时的标识符解析是通过沿作用域链逐级搜索标识符名称完成的。搜索过程始终从作用域链的最前端开始,然后逐级往后,直到找到标识符。
作用域链增强
虽然执行上下文主要有全局上下文和函数上下文两种,但有其他方式来增强作用域链。
- try/catch语句的catch块
- with语句
小结
JavaScript变量可以保存两种类型的值:原始值和引用值。原始值可能是以下6种原始数据类型之一:
Undefined、Null、Boolean、Number、String和Symbol。原始值和引用值有以下特点。
- 原始值大小固定,因此保存在栈内存上。
- 从一个变量到另一个变量复制原始值会创建该值的第二个副本。
- 引用值是对象,存储在堆内存上。
- 包含引用值的变量实际上只包含指向相应对象的一个指针,而不是对象本身。
- 从一个变量到另一个变量复制引用值只会复制指针,因此结果是两个变量都指向同一个对象。
- typeof操作符可以确定值的原始类型,而instanceof操作符用于确保值的引用类型。
任何变量(不管包含的是原始值还是引用值)都存在于某个执行上下文中(也称为作用域)。这个上下文(作用域)决定了变量的生命周期,以及它们可以访问代码的哪些部分。执行上下文可以总结如下。
- 执行上下文分全局上下文、函数上下文和块级上下文。
- 代码执行流每进入一个新上下文,都会创建一个作用域链,用于搜索变量和函数。
- 函数或块的局部上下文不仅可以访问自己作用域内的变量,而且也可以访问任何包含上下文乃至全局上下文中的变量。
- 全局上下文只能访问全局上下文中的变量和函数,不能直接访问局部上下文中的任何数据。
- 变量的执行上下文用于确定什么时候释放内存。