变量、作用域与内存

91 阅读5分钟

原始值与引用值

ECMAScript 变量可以包含两种不同类型的数据:原始值和引用值。

  1. 原始值:就是最简单的数据,保存原始值的变量是按值访问的。Undefined、Null、Boolean、Number、String 和 Symbol 。
  2. 引用值:则是由多个值构成的对象,保存在内存中的对象。 ECMAScript 不允许直接访问内存位置。

动态属性

引用值可以随时添加、修改和删除其属性和方法。

let person = new Object();
person.name = 'lisi';
console.log(preson.name); // lisi

复制值

  1. 原始值:在通过变量把一个原始值赋值到另一个变量时,原始值会被复制到新变量的位置。
  2. 引用值:存储在变量中的值也会被复制到新变量所在的位置。区别在于,这里复制的值实际上是一个指针,它指向存储在堆内存中的对象。操作完成后,两个变量实际上指向同一个对象,因此一个对象上面的变化会在另一个对象上反映出来。
let obj1 = new Object();
let obj2 = obj1;
obj1.name = 'lisi';
console.log(obj2.name); // lisi

在这个例子中,变量 obj1 保存了一个新对象的实例。然后,这个值被复制到 obj2,此时两个变 量都指向了同一个对象。在给 obj1 创建属性 name 并赋值后,通过 obj2 也可以访问这个属性,因为它们都指向同一个对象。

传递参数

ECMAScript 中所以函数的参数都是按值传递的。

在按值传递参数时,值会被复制到一个局部变量。

在按引用传递参数时,值在内存中的位置会被保存在一个局部变量,这意味着对本地变量的修改会反映到函数外部。

function setName(obj) {
  obj.name = 'lisi';
}
let person = new Object();
setName(person);
console.log(person.name); // lisi
function setName(obj) {
  obj.name = 'lisi';
  obj = new Object();
  obj.name = 'zhangsan';
}
let person = new Object();
setName(person);
console.log(person.name); // lisi

这两个例子的区别: 在函数中将 obj 重新定义为一个有着不同 name 的新对象。如果 person 是按引用传递 第二个例子 person.name 应该打印 zhangsan,但是不是。

这表明函数中参数的值改变之后,原始的引用仍然没变。当 obj 在函数内部被重写时,它变成了一个指向本地对象的指针。而那个本地对象在函数执行结束时就被销毁了。

确定类型

  1. typeof 操作符最适合判断是否为原始类型(Undefined、String、Boolean、Number ),如果值是对象或null。typeof 返回 'Object'。
let s = 'hello'
console.log(typeof s); // string
  1. instanceof 操作符,如果变量是给定引用类型(由其原型链决定)的实例,则 instanceof 操作符返回 true。
let colors = ['red', 'black'];
console.log(colors instanceof Array); // true
console.log(colors instanceof Object); // true

所有引用值都是 Object 的实例,因此通过 instanceof 操作符检测任何引用值和 Object 构造函数都会返回 true。类似地,如果用 instanceof 检测原始值,则始终会返回 false, 因为原始值不是对象。

执行上下文与作用域

作用域链增强

  1. try/catch 语句的 catch 块。
  2. with 语句。

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

变量声明

使用 var 的函数作用域声明

在使用 var 声明变量时,变量会被自动添加到最接近的上下文。在函数中,最接近的上下文就是函 数的局部上下文。在 with 语句中,最接近的上下文也是函数上下文。如果变量未经声明就被初始化了, 那么它就会自动被添加到全局上下文。

使用 let 的块级作用域声明

ES6 新增的 let 关键字跟 var 很相似,但它的作用域是块级的,这也是 JavaScript 中的新概念。块 级作用域由最近的一对包含花括号{}界定。换句话说,if 块、while 块、function 块,甚至连单独 的块也是 let 声明变量的作用域。

let在同一作用域内不能声明两次。

使用 const 的常量声明

除了 let,ES6 同时还增加了 const 关键字。使用 const 声明的变量必须同时初始化为某个值。 一经声明,在其生命周期的任何时候都不能再重新赋予新值。

const 声明只应用到顶级原语或者对象。换句话说,赋值为对象的 const 变量不能再被重新赋值 为其他引用值,但对象的键则不受限制。

标识符查找

当在特定上下文中为读取或写入而引用一个标识符时,必须通过搜索确定这个标识符表示什么。搜 索开始于作用域链前端,以给定的名称搜索对应的标识符。如果在局部上下文中找到该标识符,则搜索 停止,变量确定;如果没有找到变量名,则继续沿作用域链搜索。(注意,作用域链中的对象也有一个 原型链,因此搜索可能涉及每个对象的原型链。)这个过程一直持续到搜索至全局上下文的变量对象。 如果仍然没有找到标识符,则说明其未声明。