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. Introduction to Microsoft Dynamics 365 licensing

    Microsoft Dynamics 365 will be released on November 1. In preparation for that, Scott Guthrie hosted ...

  2. SuperMap iServer 扩展服务及扩展服务提供者范例

    一.扩展服务实例 1.将iserver-extend1下的listener.java打成jar包2.复制到D:\SuperMap-iServer\webapps\iserver\WEB-INF\lib ...

  3. iOS中的交换空间(swap space)

    看来是没有交换空间,原因是闪存和SSD硬盘相比,速度很慢,也有电源管理的原因. the NAND flash is not designed to be used as swap. It is dam ...

  4. C++实现Ping

    这是一个老话题了,但是我刚学会... 我们的目的是实现这么个东西: 之所以用红框框一下是因为,从baidu.com到123.125.114.144的过程是DNS解析,我们暂时先实现ping的部分. 基 ...

  5. 深度技术GHOST WIN7系统32.64位j极速安装版 V2016年

    系统来自系统妈:http://www.xitongma.com 深度技术GHOST win7系统64位j极速安装版 V2016年3月 系统概述 深度技术ghost win7系统64位j极速安装版  版 ...

  6. 基于Ruby的Watir-WebDriver自动化测试方案

    Watir-WebDriver       —— 软件测试的自动化时代 QQ群:160409929 自动化测试方案书 系统架构 该自动化测试框架分三个模块:Test用例.Control控制层.Tool ...

  7. 【转载】latch: cache buffers chains

    本文转自惜分飞的博客,博客原文地址:www.xifenfei.com/1109.html,支持原创,分享知识! 当一个数据块读入sga区,相应的buffer header会被放置到hash列表上,我们 ...

  8. MongoDB学习笔记~自己封装的Curd操作(按需更新的先决条件)

    回到目录 我们上一讲中介绍了大叔封装的Mongo仓储,其中介绍了几个不错的curd操作,而对于按需更新内部子对象,它是有条件的,即你的子对象不能为null,也就是说,我们在建立主对象时,应该为子对象赋 ...

  9. 在MySQL中,如何计算一组数据的中位数?

    要得到一组数据的中位数(例如某个地区或某家公司的收入中位数),我们首先要将这一任务细分为3个小任务: 将数据排序,并给每一行数据给出其在所有数据中的排名. 找出中位数的排名数字. 找出中间排名对应的值 ...

  10. linux svn 服务端搭建

    环境是centos6.x. 关于团队对代码管理,相信大部分人习惯于svn.不过我个人比较喜欢git的.这个blog git 常用命令 就是介绍git的基本用法.现部署svn服务端方式如下: 1. 用y ...