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. iOS 线程间的通信 (GCD)

    1.从网络上 下载一张图片将它显示在view上 - (void)imageDownload { dispatch_async(dispatch_get_global_queue(DISPATCH_QU ...

  2. Introduction to Microsoft Dynamics 365 licensing

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

  3. web安全攻防----环境搭建篇

    1.安装虚拟机vMware. 2.在虚拟机上安装kali系统. *Kali为linux操作系统的一个发行版. 3.安装Xshell *Xshell是一个强大的安全终端模拟软件,它支持SSH1, SSH ...

  4. android SharedPreferences介绍

    SharedPreferences是Android平台上一个轻量级的存储类,主要是保存一些常用的配置比如窗口状态,一般在Activity中重载窗口状态onSaveInstanceState保存一般使用 ...

  5. React Native知识2-Text组件

    Text用于显示文本的React组件,并且它也支持嵌套.样式,以及触摸处理.在下面的例子里,嵌套的标题和正文文字会继承来自styles.baseText的fontFamily字体样式,不过标题上还附加 ...

  6. 敏捷开发与jira

    项目背景 项目是基于一套公司自主研发的平台做企业信息化的项目管理业务,经过两个里程碑的交付,已经在客户现场使用,每次版本都能按期交付,延迟较少,客户满意度也高. 项目开发过程采用的敏捷的方法,用类Sc ...

  7. ORACLE查看补丁出现“OPatch failed with error code 1”

    案例场景:               在Oracle Linux Server release 5.7上安装完ORACLE 10g后,顺便将PSR(Patch Set Release)p681018 ...

  8. 【转】JVM运行原理及JVM中的Stack和Heap的实现过程

    来自: http://blog.csdn.net//u011067360/article/details/46047521 Java语言写的源程序通过Java编译器,编译成与平台无关的‘字节码程序’( ...

  9. 2016款MACBOOK PRO触控条版 安装WIN10初体验 及 无奈退货记

    新的2016MBP终于发布了,作为把苹果电脑装WIN使用的人,等候很久之后,终于可以行动了. 黄山松 (Tom Huang) 发表于博客园http://www.cnblogs.com/tomview/ ...

  10. beautifulSoup(1)

    import re from bs4 import BeautifulSoupdoc = ['<html><head><title>Page title</t ...