1. 数据类型
1.1 基本类型和引用类型的值
在JavaScript中,变量的数据类型分为基本类型和引用类型。
基本类型指那些简单的数据段,包括Boolean、Number、String、Undefined、Null、Symbol,基本数据类型是直接按值访问的,可以操作保存在变量中的实际的值。
引用类型的值是保存在内存中的对象,JavaScript不能直接访问内存中的位置,而是通过指针将变量与内存中的对象联系起来。因此在操作对象时,实际上是在操作对象的引用。
1.2 栈内存和堆内存
为了更加深入的理解JS变量基本类型和引用类型的区别,我们还需要了解JS变量的值在内存中的存储方式。
1.2.1 栈内存
栈,是一种数据结构,有着先进后出、后进先出的特点。就像一个羽毛球筒,只有一个口(即是出口也是入口),最先进入的球只能最后拿出来:
如上图,入栈的顺序:1、2、3,出栈的顺序:3、2、1
1.2.2 栈内存中的数据存储
栈内存用来保存基本类型的变量或变量的指针,举个例子:
var num1 = 3;
变量 num1 在内存中的保存形式为:
对于基本类型的变量,当我们进行复制操作时,比如这个例子:
var num1 = 3;
vat num2 = num1;
此时JS会创建一个新值并将新值分配给新的变量,内存中的变量对象表示为:
变量 num2 得到的是一个全新的值,与变量 num1 中的值无关。
1.2.3 堆内存
堆内存与栈内存不同,对于变量值的保存没有需要遵循的规律。
1.2.4 堆内存中的数据存储
堆内存用来保存对象类型的变量值,对象的内容和大小也会随时变化。而此时变量对象中的变量保存的是一个指向堆内存中对象的指针,由于存在这种引用关系,对象也被称为引用类型。例:
var num1 = 3;
var obj = {
a: 1
}
当我们对对象类型的值进行复制操作时,实际上copy的是对象的指针,因此两个变量指向的是同一个对象,例:
var obj 1 = { a: 1 }
var obj2 = obj1;
在内存中表现为:
此时,修改变量 obj1 的属性等同于修改变量 obj2,例:
var obj1 = { a: 1 };
vat obj2 = obj1;
obj1.b = 2;
alert(obj2.b); // 2
1.3 包装对象
对于引用类型的值,我们可以为其添加属性和方法,也可以改变和删除其属性和方法:
var person = new Object();
person.name = 'Ian';
alert(person.name); // 'Ian'
但是,我们不能给基本类型的值添加属性,尽管这样做不会导致错误:
var name = 'Ian';
name.age = 29;
alert(name.age); // undefined
我们可能听过一种说法,基本类型的值是没有属性或方法的。可有意思的是,上述给基本类型的值添加属性的操作不会报错,我们还能读取某些基本类型的值,比如我们可以读到String类型值的length属性:
var name = 'Ian';
name.length; // 3
那么基本类型值的属性是从哪里来的呢?
其实,当我们操作基本类型值的属性时,JS会创建一个临时的包装对象,我们操作的实际上是这个包装对象的属性,而这个包装对象是用完立即销毁的。在上面的例子中,name.length 的 length 属性实际上来自包装对象。
var name = 'Ian';
name.length;
var nameObj = new String(name); // 包装对象
nameObj.length; // 3
由于包装对象用完立即销毁的特性,我们对基本类型值的属性修改都是“无效”的:
var name = 'Ian';
name.length = 4;
name.length; // 3
上面的例子中,当我们再次读取 name.length 时,实际上又创建了一个新的包装对象,读取的是新包装对象的 length 属性。
2. 执行环境
执行环境(也称环境)是JavaScript重要的概念,执行环境定义了变量或函数有权访问的数据。每个执行环境都有一个相关联的变量对象,这个变量对象中保存了当前执行环境中定义的所有变量和函数。
2.1 全局执行环境
JS代码执行时最外层的执行环境称为全局执行环境,由于宿主环境的不同,表示全局执行环境的对象也不同,在web浏览器中将window对象作为全局执行环境,全局的变量和函数都是作为window对象的属性和方法声明的。
2.2 执行环境栈
函数也有执行环境,当开始执行一个函数时,函数执行环境会被压入一个执行环境栈中,在函数执行性完成后,执行环境栈将函数环境弹出,将主导权交给上层环境。我们通过一段代码来展示执行环境栈的变化过程:
var outerName = 'Ian';
function changeName() {
var innerName = 'Jack';
outerName = innerName;
}
alert(outerName); // 'Jack'
在初始状态下,执行环境栈是这样的:
当开始执行函数 changeName 时,函数的环境被压入环境栈:
当函数 changeName 执行完成后,该环境被弹出环境栈并销毁(环境栈恢复到上一步的状态),保存在其中的变量和函数的定义也随之销毁。全局环境只到程序退出,在web浏览器中关闭网页或浏览器程序时才会销毁。
JS这种执行机制也引出了另一个重要的概念——作用域链。
2.3 作用域链
JS代码在进入一个新的执行环境时会创建一个作用域链(scope chain),用来规定对当前执行环境有权访问的变量或函数的访问顺序。作用域链是由执行环境的变量对象组成的,如果是函数环境就将函数的活动对象作为变量对象。
作用域链的最前端永远是当前执行环境的变量对象,上一级变量对象来自上一层执行环境,直到全局环境的变量对象。当我们访问一个变量时,会先在当前的变量对象中查找,如果找不到就会沿着作用域链逐级向上查找,直到返回变量的值或报错(变量未声明)。
下面我们通过一个例子来理解这种结构:
var outerName = 'Ian';
function changeName() {
var innerName = 'Jack';
outerName = innerName;
function alertName() {
alert(outerName); // 'Jack'
}
}
alert(innerName); // Uncaught ReferenceError: innerName is not defined
在上面这段代码中,函数可以访问到外部定义的变量 outerName,而在全局环境中不能访问函数内部定义的变量 innerName。当代码进入 alertName 执行环境时,作用域链如下图所示:
3. 小结
在JavaScript中,变量的数据类型分为基本类型和引用类型,他们具有以下特点:
- 基本类型的值大小固定保存在栈内存中,当复制基本类型的值时,会创建一个值的副本;
- 引用类型的值是对象,保存在堆内存中,而变量保存的是对象的引用,当复制引用类型的值时,复制的是对象的引用,两个变量都指向同一个对象;
- 全局执行环境和函数执行环境;
- 在进入一个新的执行环境时会创建一个作用域链(scope chain),用来规定对当前执行环境有权访问的变量或函数的访问顺序;
- 函数局部环境能访问父级(直到全局)环境的变量,而全局(父级)环境不能访问函数的局部变量;
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。