JavaScript 的变量只是在特定时间用于保存特定值的一个名字而已,不限制变量保存的数据类型,变量的值及其数据类型能够在脚本的生命周期内改变;这就是一般所说的有趣、强大但又容易出问题的“灵活性”。前端
ECMAScript 变量可能包含两种不一样数据类型的值:基本类型值和引用类型值。基本类型值指的是简单的数据段;引用类型值指的是那些可能由多个值构成的对象。算法
变量被赋值时,解析器必须肯定要赋的值是基本类型值仍是引用类型值。扩展:这也是由于须要开辟不一样的内存空间,由于基本类型值保存在内存的栈区,而引用类型值保存在内存的堆区(堆比栈大,栈比堆快)。浏览器
五种基本数据类型是按值访问的,由于能够操做保存在变量中的实际值。闭包
引用类型值是保存在内存中的对象。与其余语言不一样,JavaScript 不容许直接复制保存在内存中的对象,在复制保存着对象的某个变量时,实际上复制的是对象的引用(内存地址)而非对象自己。函数
引用类型的值能够为其进行属性和方法的添加、删除和改变,而基本类型的值是不能添加属性的,更谈不到属性的删除和改变了。性能
变量之间值的复制,若是是基本类型的值,会在变量对象上建立一个新值,而后把这个值复制到为新变量分配的位置上;原值与新值之间是彻底独立的,新值只是原值的一个副本,两个变量能够参与任何操做而不会互相影响。spa
当变量之间复制的是引用类型的值时,与基本类型值的复制过程是同样的,而不一样的是变量中存储的是指向存储在堆中的一个对象的指针;完成复制后,两个变量拥有指向同一对象的指针,因此,改变其中一个变量,就会影响另外一个变量。例如:指针
var obj1 = new Object(); var obj2 = obj1; obj1.name = 'hawk'; alert(obj2.name); //两个变量里值是指针,它们都指向同一个对象,因此结果必然也是hawk;
ECMAScript 中全部函数的参数都是按值传递的。书中所说的“困惑”,我的理解是:因为引用类型值保存的是指向对象的“指针”,而在函数内部修改参数引用函数外对象的属性会反应到这个对象上,会让人误觉得参数也会按引用传递。code
参数传递与变量复制,二者的机制是相同的;值的副本复制给了参数,而参数能够看作是函数内部的局部变量。对象
对象的传参比较特殊,例如:
var person = new Object(); person.name = 'Lucy'; function setName(obj) { obj.name = 'Bob'; //在函数内部将传入对象的name属性赋值为Bob; } setName(person); alert(person.name); //Bob,函数外部的对象name属性发生了变化; function setAge(obj) { obj.age = 19; //在函数内部为传入对象添加并设置age属性; obj = new Object(); //这时修改了参数的值,也就是修改了参数存储的指针; obj.age = 99; //再次更改age属性; } setAge(person); alert(person.age); //19,函数内部修改了参数存储的指针后并未影响外部对象;
上面的例子中,函数内部重写了 obj,这个变量的应用已是一个局部对象了,这个局部对象会在函数执行完毕后当即被销毁。
检测一个变量是否为基本类型,可使用 typeof 操做符;可是当要检测的变量是 null 或者是一个对象时,typeof 没法作出区分,都会返回 "object"。
因为 typeof 操做符没法准确检测引用类型的值,因此用处不大,若是要检测某个值是什么类型的对象,须要使用 instanceof 操做符。
若是变量是给定引用类型(根据原型链识别)的实例,那么 instanceof 操做符便返回 turn;若是变量是基本数据类型,会直接返回 false,由于基本类型不是对象。
全部引用类型的值都是 Object 类型的实例,因此检测一个引用类型值和 Object 构造函数时,instanceof 操做符会始终返回 turn。
执行环境(execution context)是 JavsScript 中最为重要的一个概念。我的理解,“执行环境”就是通常所说的“做用域”。执行环境定义了变量或函数有权访问的其余数据,决定了它们各自的行为。
每一个执行环境都有一个与之关联的变量对象(variable object),环境中定义的全部变量和函数都保存在这个对象中;我的理解,函数是一个执行环境,那么函数内部的变量(包括参数)或函数就至关于它的属性和方法。
全局执行环境是最外围的一个执行环境。根据 ECMAScript 实现所在的宿主环境不一样,表示执行环境的对象也不同,在 Web 浏览器中,全局执行环境是 window 对象,全部的全局变量和函数都是 window 对象的属性和方法。
某个执行环境中的全部代码执行完毕后,该环境被销毁,里面保存的全部变量和函数也随之销毁;全局执行环境直到网页关闭才会销毁。
当代码在一个环境中执行时,会建立变量对象的一个做用域链(scope chain),它用来保证对执行环境有权访问的全部变量和函数的有序访问。说白了就像地图同样,按图索骥。
做用域链的前端,始终都是当前执行代码所在环境的变量对象;这里的“前端”,能够理解为“起始点”。若是这个环境是函数,则将其活动对象(activation object)做为变量对象。
全局执行环境的变量对象始终都是做用域链中的最后一个对象。我的理解,例如函数中的变量访问,先从函数内部找,若是内部没有,则向上一层的环境中找,一直追溯到全局执行环境,直至找到,若是最终都没有找到,那么就会出现编译错误。
前面所说的变量追溯就是标识符解析的一种,搜索过程始终从做用域链的前端开始。
变量和函数访问只能经过做用域链在执行环境内和向上级(父执行环境)访问,而不能访问下一级中的变量和函数,若是要访问下一级中的变量和函数,可使用闭包。
执行环境的类型有全局和局部(函数)两种,但有其余办法来延长做用域。做用域链的延长有两种状况,try-catch 语句的 catch 块,以及 with 语句。
这两个语句都会在做用域的前端添加一个变量对象,例如 with 语句,会将括号中制定的对象添加到做用域链中;而 catch 语句会建立一个新的变量对象,其中包含的是被抛出的错误对象的声明。
在 JavaScript 中,没有块级做用域;就是说,不像其余类 C 的语言中,使用花括号封闭的代码块就有本身的做用域(执行环境),从而根据条件来定义变量。
没有块级做用域,使得在 if、for 等语句中的变量,在语句执行完成后,仍然能够在语句外部访问到这些变量。
1. 声明变量
使用 var 声明的变量会被自动添加到最近的环境中,若是没有使用 var 声明,该变量会被自动添加到全局环境中。
实际开发中,初始化变量以前必定要先声明;在严格模式下,初始化未经声明的变量会致使错误。
2. 查询标识符
标识符的查询过程从做用域链的前端开始,向上逐级查找。找到后,查找结束,变量就绪;没有找到,便一直向上查找到全局环境,若是到全局环境还没找到,说明该变量还没有声明。
JavaScript 具备自动垃圾回收机制,执行环境会自动找出再也不继续使用的变量,而后释放其占用的内存;为此,垃圾收集器会按照固定时间间隔(或代码预约的间隔)周期性释放内存。
变量或函数的生命周期,指的是变量或函数从声明到其在执行环境中使用完毕后被垃圾回收销毁的过程。
JavaScript 中经常使用的垃圾收集方式是标记清除(mark-and-sweep)。当变量进入环境时,就将该变量标记为“进入环境”;当变量离开环境时,则将其标记为“离开环境”。垃圾收集器按照标记进行垃圾收集策略。
另外一种不太常见的策略叫引用计数(reference counting)。它的含义是追踪记录每一个值被引用的次数。
当声明了一个变量并将一个引用类型的值赋给该变量时,则该值引用次数为 1;若是同一值又被赋给另外一变量,则该值引用次数加 1;若是包含对该值引用的变量又取得了另外一个值,则该值引用次数减 1。当次数为 0 时,则会被回收。
循环引用致使引用次数的策略出现了问题。循环引用指的是对象 A 中包含一个指向对象 B 的指针,而对象 B 中也包含一个指向对象 A 的指针;也就是二者在相互引用。这致使它们的应用次数永远不为 0。
为了不循环引用形成的内存泄露,最好实在不使用这些对象的时候手工断开,好比给不使用的对象会变量赋值 null。
垃圾收集器的时间间隔策略的不合理,会致使性能问题,例如 IE6 浏览器的垃圾收集器。
在有的浏览器中能够出发垃圾收集过程,例如 IE 中调用 window.CollectGarbage() 方法,在 Opera7 及更高版本中调用 window.opera.collect() 方法。但在实际开发中不建议手动出发垃圾收集过程。
虽然具有垃圾收集机制的 JavaScript 通常没必要操心内存管理的问题,可是分配给 Web 浏览器的内存有限。
为了用最少的内存得到更好的性能,在开发中,只为执行中的代码保存必要数据,一旦数据不在有用,最好将其设置为 null 来释放其引用,这就叫作解除引用(dereferencing)。
解除引用不会当即释放值占用的内存,可是会在垃圾收集器下一轮的运行时将其回收。
● JavaScript 变量能够用来保存两种类型的值:基本类型和引用类型。基本类型的值源自 5 种基本数据类型。基本类型值和引用类型值具备如下特色:
◎ 基本类型值在内存中占据固定大小的空间,所以被保存在栈内存中;
◎ 从一个变量向另外一个变量复制基本类型的值,会建立这个值的一个副本;
◎ 引用类型的值是对象,保存在堆内存中;
◎ 包含引用类型值的变量实际上包含的并非对象自己,而是一个指向该对象的指针;
◎ 从一个变量向另外一个变量复制引用类型的值,复制的是指针,所以两个变量最终指向同一个对象;
◎ 肯定一个值是哪一种基本类型可使用 typeof 操做符,而肯定一个值是哪一种引用类型可使用 instanceof 操做符。
● 全部变量都存在于一个执行环境中,这个环境决定了变量的生命周期,以及哪部分代码能够访问其中的变量。关于执行环境的总结以下:
◎ 执行环境分为全局执行环境和函数执行环境;
◎ 每次进入一个新执行环境,都会建立一个用于搜索变量和函数的做用域链;
◎ 函数的局部环境不只有权访问函数做用域中的变量,还有权访问其包含(父)环境,乃至全局环境;
◎ 全局环境只能访问在全局环境中定义的变量和函数,而不能直接访问局部环境中的任何数据(除了没有使用 var 声明的变量);
◎ 变量的执行环境有助于肯定应该什么时候释放内存。
● JavaScript 是一门具备自动垃圾回收机制的语言,开发中没必要关心内存的分配和回收问题,关于垃圾回收的总结以下:
◎ 离开做用域的值将被自动标记为能够回收,所以将在垃圾收集期间被删除;
◎ “标记清除”是目前主流的垃圾收集算法,这种算法的思想是给当前不使用的值加上标记,而后再回收其内存;
◎ 另外一种垃圾手机算法叫“引用计数”,它的思想是跟踪记录全部值被引用的次数。目前已不使用,可是 IE 中访问非原生 JavaScript 对象(如 DOM 元素)时,这种算法仍然会致使问题;
◎ 当代码中存在循环引用时,“引用计数”算法就会致使问题;
◎ 解除变量的引用不只有助于消除循环引用,还能有效回收内存,应该及时解除不在使用的全局对象、全局对象属性以及循环引用变量的引用。
(完)