《JavaScript高级程序设计》 - 读书笔记 - 第4章 变量、作用域和内存问题
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章 变量、作用域和内存问题的更多相关文章
- Javascript高级程序设计读书笔记(第二章)
第二章 在HTML中使用Javascript 2.1<script>元素 延迟脚本(defer = "defer")表明脚本在执行时不会影响页面的构造,脚本会被延迟到 ...
- JavaScript高级程序设计学习笔记第四章--变量、作用域和内存问题
1.变量可能包含两种不同数据类型的值:基本类型值和引用类型值. 基本类型值指的是简单的数据段,而引用类型值指那些可能由多个值构成的对象. 2.变量复制 如果从一个变量向另一个变量复制基本类型的值,会在 ...
- javascript高级程序设计读书笔记
第2章 在html中使用javascript 一般都会把js引用文件放在</body>前面,而不是放在<head>里, 目的是最后读取js文件以提高网页载入速度. 引用js文 ...
- javascript高级程序设计读书笔记-事件(一)
读书笔记,写的很乱 事件处理程序 事件处理程序分为三种: 1.html事件2. DOM0级,3,DOM2级别 没有DOM1 同样的事件 DOM0会顶掉html事件 因为他们都是属性 而 ...
- JavaScript高级程序设计 读书笔记
第一章 JavaScript 简介 第二章 Html中使用JavaScript 第三章 基本概念 第四章 变量,作用域,内存 第五章 引用类型 第六章 面向对象 第七章 函数表达式 第八章 BOM 第 ...
- JavaScript高级程序设计-读书笔记(1)
第1章 JavaScript简介 JavaScript是一种专为与网页交互而设计的脚本语言,由下列三个不同的部分组成: l ECMAScript:提供核心语言功能: l 文 ...
- javascript高级程序设计 读书笔记1
第二章 在HTML中使用JS 加载JS有三种:行内,head头部和外部链接JS 最好使用外部链接<script src="example.js" ></sc ...
- Javascript高级程序设计读书笔记(第六章)
第6章 面向对象的程序设计 6.2 创建对象 创建某个类的实例,必须使用new操作符调用构造函数会经历以下四个步骤: 创建一个新对象: 将构造函数的作用域赋给新对象: 执行构造函数中的代码: 返回新 ...
- JavaScript高级程序设计 读书笔记 第一章
JavaScript是一种专门为与网页交互而设计的脚本语言 JavaScript实现 ECMAscript---核心 DOM---文档对象模型 BOM---浏览器对象模型
- Javascript高级程序设计读书笔记(第10章 DOM)
第10章 DOM 10.1 节点层次 每个节点都有一个nodeType属性,用于表明节点的类型.任何节点类型必是下面中的一个: Node.Element_NODE(1); NODE.ATTRIBUT ...
随机推荐
- browserify学习总结
前言 在未接触browserify,虽然我知道它是一个前端构建工具,但还是有几个疑问: 1. browserify出现的日期? 2. 能构建哪些文件? 3. 附加的browserify代码体积是多大? ...
- 3D Banner(jQuery)
1.这是用面向对象的思想去动态生成banner的简易流程,用到一个javaScript框架jQuer: 2.将代码黏贴成html文件,直接用浏览器打开即可: 3.layer属于弹窗提示类插件,可能需要 ...
- jQuery iPresenter 3D图片旋转
在线实例 效果一 效果二 使用方法 <div class="htmleaf-container"> <div class="htmleaf-conten ...
- GPS数据读取与处理
GPS数据读取与处理 GPS模块简介 SiRF芯片在2004年发布的最新的第三代芯片SiRFstar III(GSW 3.0/3.1),使得民用GPS芯片在性能方面登上了一个顶峰,灵敏度比以前的产品大 ...
- IIS配置文件的XML格式不正确 applicationHost.config崩溃 恢复解决办法
当打开IIS管理器,或配置网站时提示错误:配置文件的XML格式不正确 且是applicationHost.config的问题,那么肯定是applicationHost.config被破坏,IIS就崩溃 ...
- xcode8打包ipa文件, application loader上传成功,但是iTunes Connect不显示构建版本
最近更新的Xcode8.今天提交新项目时.按照以往的流程走 Xcode 编译ipa文件.applicaiton loader提交成功 但是.iTunes connect构建版本不显示.非常疑惑.平时等 ...
- Linux常用命令:sed
本文记录的是自己在学习<Linux私房菜>中正则表达式的笔记. 关于行尾符$ 如果文件本身没有内容,比如使用touch新建的文件,那么$将会没有意义.例如下面操作: 先使用touch新建了 ...
- Android Studio连接真机没反应?
刚好遇到这个问题,在网上百度了一下,看到有人分享了引起该问题的几个原因: 手机设置问题.开USB调试 方法: 手机设置 - 开发人员选项 - USB调试 - 勾选 数据线问题. 有的数据线只能用来充电 ...
- CLR简介(一)
什么是通用语言运行时(CLR),简单来讲: CLR是一个支持多种编程语言及多语言互操作,完整的高级虚拟机. 有点拗口,而且不是很有启发性,但上面的文字是将又大又复杂的CLR的功能归类以便容易理解的第一 ...
- Guest Speaker on 2015 WinHEC Shenzhen 秋季大会
继今年3月份的WinHEC春季大会,秋季大会于11月10日-11日深圳如期举行.此次大会的主题是Windows 10 IoT和Microsoft Azure,云和端的无缝连接是微软物联网解决方案的典型 ...