4.1 基本类型和引用类型的值

JavaScript变量是松散类型的,它只是保存特定值的一个名字而已。

ECMAScript变量包含两种数据类型的值:基本类型值和引用类型值。基本类型值指的是简单的数据段,而引用类型值指那些由多个值构成的对象。

在将一个值赋给变量时,解析器必须确定这个值是基本类型值还是引用类型值。基本数据类型是按值访问的,因为可以操作保存在变量中的实际的值;而引用类型的值是保存在内存中的对象,JavaScript不允许直接访问内存中的位置,所以在操作对象时,实际上是在操作对象的引用而不是实际的对象。因此,引用类型的值是按引用访问的。

定义基本类型值和引用类型值的方式是类似的:创建一个变量并为该变量赋值。但是对值可以进行的操作却不同,只能对引用类型值动态添加、改变和删除属性和方法。

复制不同类型变量值的时候,也是不同的。
对于基本类型值,创建了新值然后复制到新变量。对于引用类型值,也创建了新值然后复制到新变量。不同的是,这个值实际上是一个“指针”,而这个指针指向存储在堆中的一个对象。两个变量实际上将引用同一个对象。因此,改变其中一个变量,就会影响到另一个变量的使用。

ECMAScript中所有函数的参数都是按值传递的。也就是说,把函数外的值复制给函数内部的参数。
基本类型值的传递如同基本类型变量的复制一样,而引用类型值的传递,则如同引用类型变量的复制一样。

在传递引用类型的值时,会把这个值在内存中的地址复制给一个局部变量,因此,这个局部变量的变化会反映在函数的外部。

示例:函数传递引用类型值

 function setName(obj){
obj.name = "Nicholas";
}
var person = new Object();
setName(person);
alert(person.name); // "Nicholas"

分析:创建了一个对象,并将其保存在了变量person中。然后,这个变量被传递到setName()函数中之后就被复制给了obj。obj和person引用的是同一个对象。

有的开发人员错误地认为:在局部作用域内修改的对象会在全局作用域中反映出来,就说明参数是按引用传递的。下面的例子是为了证明对象是按值传递的。

示例2:证明对象是按值传递的

 function setName(obj){
obj.name = "Nicholas";
obj = new Object();
obj.name = "Greg";
}
var person = new Object();
setName(person);
alert(person.name); // "Nicholas"

分析:如果person是按引用传递的,那么person就会自动被修改为指向新的对象,其name属性为"Greg"。但是,当接下来再访问person.name时,显示的值仍然是"Nicholas"。这说明,即使在函数内部修改了参数的值,但原始的引用仍然保持未变。

检测变量是不是一个基本数据类型,typeof操作符是最佳的工具。

 var s = "Nicholas";
var b = true;
var i = 22;
var u;
var n = null;
var o = new Object;
alert(typeof s); // string
alert(typeof b); // boolean
alert(typeof i); // number
alert(typeof u); // undefined
alert(typeof n); // object
alert(typeof o); // object

但是在检测引用类型的值时,这个操作符的用处不大。通常,我们并不想知道某个值是对象,而是想知道它是什么类型的对象。为此,ECMAScript提供的instanceof操作符。其语法为:

 result = variable instanceof constructor

4.2 执行环境及作用域

执行环境(execution context)是 JavaScript 中最为重要的一个概念。每个执行环境都有一个与之关联的变量对象(variable object),环境中定义的所有变量和函数都保存在这个对象中。

执行环境有两种:全局环境和局部环境(函数)。
全局环境是最外围的一个执行环境。在 Web 浏览器中,全局执行环境被认为是 window 对象。每个函数都有自己的执行环境。

当代码在一个环境中执行时,会创建变量对象的一个作用域链(scope chain)。作用域链的用途,是保证对执行环境中所有变量和函数的有序访问。
作用域链的前端,始终是当前代码所在环境的变量对象。作用域链中的下一个变量对象来自包含(外部)环境,而再下一个变量对象则来自下一个包含环境。这样,一直延续到全局执行环境。作用域链的最后始终是全局执行环境的变量对象。

标识符解析是沿着作用域链一级一级搜索标识符的过程。搜索过程始终从作用域链的前端开始,然后逐级地向后回溯,直至找到标识符为止(如果找不到标识符,通常会导致错误发生)。

示例3:

 var color = "blue";

 function changeColor(){
if(color == "blue"){
color = "red";
} else {
color = "blue";
}
} changeColor();
alert("Color is now " + color);

分析:函数changeColor()包含两个变量对象:它自己的变量对象和全局环境的变量对象。

内部环境可以通过作用域链访问所有的外部环境,但外部环境不能访问内部环境中的任何变量和函数。这些环境之间的联系是线性、有次序的。每个环境都可以向上搜索作用域链,以查询变量和函数名。但任何环境都不能通过向下搜索作用域链而进入另一个执行环境。

虽然只有两种执行环境——全局和局部(函数),但有两个语句可以延长作用域链:try-catch语句的catch块和with语句。这两个语句都会在作用域链前端临时添加一个变量对象。
对with语句来说,会将指定的对象添加到作用域链中。对于catch语句来说,会创建一个新的变量对象,其中包含的是被抛出的错误对象的声明。

示例4:

 function builderUrl(){
var qs = "Hello world!"; with(location){
var url = href + qs;
} return url;
}

分析:with语句在作用域链的前端添加了一个变量对象,此变量对象中包含了location对象的所有属性和方法。当在with语句中引用变量href时(实际引用的是location.href),可以在当前执行环境的变量对象中找到。当引用变量qs时,该变量在函数环境的变量对象中。而在with语句内部定义的变量url,则是函数执行环境的一部分。

JavaScript 没有块作用域。使用for语句时要牢记这一点,for语句创建的变量i即使在for循环执行结束后,也依旧会存在于循环外部的执行环境中。

使用var声明的变量会自动被添加到最接近的环境中。在函数内部,最接近的环境是函数局部环境;在with语句内部,最接近的环境是函数局部环境。
如果初始化变量时没有用var声明,该变量就被添加到全局环境。

在JavaScript中,不声明而直接初始化变量是一个常见的错误做法,这很可能会导致意外。建议在初始化变量之前,一定要先声明。

查询标识符
当在某个环境中为了读取或写入而引用一个标识符时,必须通过搜索来确定该标识符实际代表什么。搜索过程从作用域链的前端开始,向上逐级查询与给定名字匹配的标识符。如果找到了该标识符,搜索过程停止,变量就绪。如果没有找到,则继续沿作用域链向上搜索。搜索过程将一直追溯到全局环境的变量对象。如果在全局环境也没有找到这个标识符,则意味着该变量尚未声明。

4.3 垃圾收集

JavaScript具有自动垃圾收集机制。执行环境会负责管理代码执行过程中使用的内存。
垃圾收集机制的原理很简单:找出那些不再使用继续使用的变量,然后释放其占用的内存。垃圾收集器需要定期执行该操作。

函数中的局部变量,在函数执行完后,就没有存在的必要了,所以垃圾收集器很容易判断是否回收该变量。但对于其他情况就不那么容易判断了。

JavaScript中最常用的垃圾收集方式是标记清除(mark-and-sweep)。目前,IE、Firefox、Opera、Chrome、和Safari和JavaScript实现使用的都是标记清除式的垃圾收集策略(或类似的策略)。

标记清除策略:当变量进入环境时,就将这个变量标记为“进入环境”。而当变量离开环境时,则将其标记为“离开环境”。

可以使用任何方式来标记变量。比如通过记录某个位的状态,或者使用一个“进入环境”的变量列表及一个“离开环境”的变量列表来跟踪哪些变量发生了变化。

垃圾收集器运行时会给内存中所有变量加上标记;然后,为环境中的变量和被环境引用的变量去除标记。之后,剩余的变量被认为是要删除的变量。最后,垃圾收集器完成内存清除工作。

另一种不太常见的垃圾收集策略叫做引用记数(reference counting)。引用计数的含义是跟踪记录每个值被引用的次数。但这种方式存在一个“循环引用”的严重问题。

Netscape Navigator 3.0 是最早使用引用计数策略的浏览器,因为“循环引用”的问题,Netscape Navigator 4.0中放弃了引用记数方式,转而采用标记清除方式。

IE的BOM和DOM中的对象就是使用C++以COM对象的形式实现的。而COM对象的垃圾收集机制采用的就是引用记数策略。因此,只要在IE中涉及COM对象,就会存在循环引用的问题。
IE9把BOM和DOM对象都转换成了真正的JavaScript对象。这样,就消除了常见的内存泄漏现象。

确定垃圾收集的时间间隔是一个非常重要的问题。会影响JavaScript的性能。

JavaScript在进行内存管理及垃圾收集时面临的问题与一般程序有点不同,主要就是分配给Web浏览器的可用内存数量通常要比分配给桌面应用程序的少。这样做的是为了安全考虑,防止运行JavaScript的网页耗尽系统内存而导致系统崩溃。

因此,要确保占用最少的内存可以让页面获得更好的性能。而优化内存的最好的方式,就是只保存必要数据。一旦数据不再有用,最好通过将其值设置为null的方式来释放其引用,这个方法叫解除引用。解除引用的作用在于让值脱离执行环境,以便垃圾收集器下次运行时将其回收。
解除引用适用于大多数全局变量和全局对象的属性。局部变量会在它们离开执行环境时自动被解除引用。

示例5:

 function createPerson(name){
var localPerson = new Object();
localPerson.name = name;
return localPerson;
} var globalPerson = createPerson("Nicholas"); globalPerson = null

分析:localPerson在createPerson()函数执行完毕后就离开了其执行环境,因此无需我们显式地去为它解除引用。但对于全局变量globalPerson,则需要我们在不使用它的时候手动为它解除引用。

《JavaScript高级程序设计》 - 读书笔记 - 第4章 变量、作用域和内存问题的更多相关文章

  1. Javascript高级程序设计读书笔记(第二章)

    第二章  在HTML中使用Javascript 2.1<script>元素 延迟脚本(defer = "defer")表明脚本在执行时不会影响页面的构造,脚本会被延迟到 ...

  2. JavaScript高级程序设计学习笔记第四章--变量、作用域和内存问题

    1.变量可能包含两种不同数据类型的值:基本类型值和引用类型值. 基本类型值指的是简单的数据段,而引用类型值指那些可能由多个值构成的对象. 2.变量复制 如果从一个变量向另一个变量复制基本类型的值,会在 ...

  3. javascript高级程序设计读书笔记

    第2章  在html中使用javascript 一般都会把js引用文件放在</body>前面,而不是放在<head>里, 目的是最后读取js文件以提高网页载入速度. 引用js文 ...

  4. javascript高级程序设计读书笔记-事件(一)

    读书笔记,写的很乱   事件处理程序   事件处理程序分为三种: 1.html事件2. DOM0级,3,DOM2级别  没有DOM1 同样的事件 DOM0会顶掉html事件   因为他们都是属性  而 ...

  5. JavaScript高级程序设计 读书笔记

    第一章 JavaScript 简介 第二章 Html中使用JavaScript 第三章 基本概念 第四章 变量,作用域,内存 第五章 引用类型 第六章 面向对象 第七章 函数表达式 第八章 BOM 第 ...

  6. JavaScript高级程序设计-读书笔记(1)

    第1章 JavaScript简介 JavaScript是一种专为与网页交互而设计的脚本语言,由下列三个不同的部分组成: l        ECMAScript:提供核心语言功能: l        文 ...

  7. javascript高级程序设计 读书笔记1

    第二章  在HTML中使用JS 加载JS有三种:行内,head头部和外部链接JS   最好使用外部链接<script src="example.js" ></sc ...

  8. Javascript高级程序设计读书笔记(第六章)

    第6章  面向对象的程序设计 6.2 创建对象 创建某个类的实例,必须使用new操作符调用构造函数会经历以下四个步骤: 创建一个新对象: 将构造函数的作用域赋给新对象: 执行构造函数中的代码: 返回新 ...

  9. JavaScript高级程序设计 读书笔记 第一章

    JavaScript是一种专门为与网页交互而设计的脚本语言 JavaScript实现 ECMAscript---核心 DOM---文档对象模型 BOM---浏览器对象模型

  10. Javascript高级程序设计读书笔记(第10章 DOM)

    第10章 DOM 10.1  节点层次 每个节点都有一个nodeType属性,用于表明节点的类型.任何节点类型必是下面中的一个: Node.Element_NODE(1); NODE.ATTRIBUT ...

随机推荐

  1. php每天一题:怎么在不使用第三个变量的情况下交换两个变量的值

    $a = 'php'; $b = 'my'; list($a,$b) = array($b,$a); echo $a,$b; 很简单,大家试一下是不是交换了!

  2. UIPickerView的使用(一)

    简介:UIPickerView是一个选择器控件,它比UIDatePicker更加通用,它可以生成单列的选择器,也可生成多列的选择器,而且开发者完全可以自定义选择项的外观,因此用法非常灵活.UIPick ...

  3. 安装Portal for ArcGIS时如何正确配置HTTPS证书

    SSL协议位于TCP/IP协议与各种应用层协议之间,为数据通讯提供安全支持.SSL协议可分为两层: SSL记录协议(SSL Record Protocol):它建立在可靠的传输协议(如TCP)之上,为 ...

  4. VMware VirtualBox共存时桥接注意

    今天在VMware必须桥接的一个虚拟机上需要连接其他机器时,遇到总是连接不到的情况. 具体现象: HOST机器可以ping A机器 VMWare Guest机器无法ping A机器,也无法ping H ...

  5. IOS开发基础知识--碎片10

    1:如何给表格单元列增加选择时的背影效果 if (cell == nil) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCel ...

  6. UIWebView保存网页中的图片(转载)

    现在H5混合原生开发的方式越来越流行,也就要用到UIWebView控件.在开发过程中,我们可能会遇到一个需求,要求我们保存网页上的图片,当用户点击图片的时候,就可以让用户选择是否下载图片. 在系统自带 ...

  7. 基于Ruby的watir-webdriver自动化测试方案与实施(五)

    接着基于Ruby的watir-webdriver自动化测试方案与实施(四) http://www.cnblogs.com/Javame/p/4164570.html 继续 ... ... 关于特殊控件 ...

  8. SqlServer-- NULL空值处理

    数据库中,一个列如果没有指定值,那么值就为null,数据库中的null表示"不知道",而不是表示没有.因此select null+1结果是null,因为"不知道" ...

  9. chrome 浏览器的预提取资源机制导致的一个请求发送两次的问题以及ClientAbortException异常

    调查一个 pdf 打印报错: ExceptionConverter: org.apache.catalina.connector.ClientAbortException: java.net.Sock ...

  10. android html.fromHtml 用例

    private String content = "<b>标题</b><br>" + "内容<br>": mCo ...