第1章 javascript简介
js诞生于1995年。
ECMAScript规定语言的组成部分:
语法、类型、语句、关键字、保留字、操作符、对象
后来添加了:强类型变量、新语句和新数据结构、真正的类和经典继承。
ECMAScript 3.1 成为 ECMA-262第5版
ECMA-262第3版中定义的ECMAScript是各浏览器实现最多的一个版本。
DOM吧整个页面映射为一个多层节点结构。
BOM处理浏览器窗口和框架。
html5为<script>元素定义了async属性,与defer属性类似,都是延迟加载脚本。
html引入外部文件需要强调的优点:
可维护性:吧所有js文件都放在一个文件夹中,便于维护,集中编辑js代码。
可缓存:浏览器能根据具体的设置缓存链接的所有外部js文件,多个地方调用只需下载一次。
适应未来:通过外部文件包含js无需使用前面提到的xhtml或注释hack。
小结:
js是一种专为与网页交互而设计的脚本语言,由下列三个不同的部分组成:
核心(ECMAScript):由ECMA-262定义,提供核心语言功能。
文档对象模型(DOM):提供访问和操作网页内容的方法和接口。
浏览器对象模型(BOM):提供与浏览器交互的方法和接口。
当前五个主要浏览器:IE、Firefox、Chrome、Safari、Opera
第2章 在HTML中使用JavaScript
IE5.5引入了文档模式的概念,通过使用文档类型(DOCTYPE)切换实现的。
最初的两种文档模式是:混杂模式和标准模式。这两种模式主要影响CSS内容呈现,但在某些情况下也会影响到JS的解释执行。
小结:
把js插入到HTML页面中要使用<script>元素。使用这个元素可以吧js嵌入到html页面中,让脚本与标记混合咋一起:也可以包含外部的js文件。而我们需要注意的地方有:
在包含外部js文件时,必须将src属性设置为指向相应的url。而这个文件既可以是与包含他的页面位于同一个服务器上的文件,也可以是其他任何域中的文件。
所有<script>元素都会安装他们在页面中出现的先后顺序依次被解析。在不使用defer和async属性的情况下,只有在解析完前面<script>元素中的代码之后,才会开始解析后面的<script>元素中的代码。
由于浏览器会先解析完不使用defer属性的<script>元素中的代码,而后在解析后面的内容,所以一般应该把<script>元素放在页面最后,既主要内容后面,</body>标签前面。
使用defer属性可以让脚本在文档完全呈现之后再执行。延迟脚本总是按照指定他们的顺序执行。
使用async属性可以表示当前脚本不必等待其他脚本,也不必阻塞文档呈现。不能保证异步脚本按照他们在页面中出现的顺序执行。
另外,使用<noscript>元素可以指定在不支持脚本的浏览器中显示的替代内容。但在启动了脚本的情况下,浏览器不会显示<noscript>元素中的任何内容。
第3章 基本概念
ECMAScript的语法大量借鉴了C及其他类C语言(如Java和Perl)的语法。
ES中的一切(变量、函数名、操作符)都区分大小写。
标识符是指变量、函数、属性的名字,或函数的参数。标识符规则:
1、第一个字符必须是一个字母、下划线(_)或一个美元符号($);
2、其他字符可以是字母、下划线、美元符号或数字。
ES5引入了严格模式的概念,为了js定义了一种托尼盖的解析与执行模型。
严格模式在顶部添加 "use strict"; 也可以指定函数在严格模式下。
function() {
"use strict";
//函数体
}
ES的变量是松散类型的,用var操作符定义变量。省略了var操作符,变量就成了全局变量。
ES5的数据类型:undefined、null、boolean、number、string、object。
typeof检查给定变量的数据类型。 typeof null会返回object(null被认为是一个空的对象引用)
null值标示一个空对象指针。
undefined值是派生自null值的。
保存对象的变量还没有真正保存对象时,就应该明确地让该变量保存null值。
Boolean() 可以对任何数据类型的值调用,总会返回一个Boolean值。
NaN 即非数值是一个特殊的数值,与任何值都不相等,包括NaN本身。
数据转换
吧非数值转换为数值:Number()、parseIn()、parseFloat()。
Number()可以用于任何数据类型。另两个函数专用于吧字符串转换成数值。
ECMAScript中的字符串是不可变的,字符串一旦创建,他们的值就不能改变。要改变某个变量保存的字符串,首先要销毁原来的字符串,然后在用另一个包含新值的字符串填充该变量。
var s = "jvar";
s = "html";
第一句创建了一个string对象,并将其引用赋值给s,第二季创建了一个新的string对象,并将其引用赋值给s,赋值后的第一个string对象仍然存在,在不能访问,以为变量s现在指向了新的对象。
使用toString()吧一个值转换为一个字符串。
object类型
object的每个实力2都具有下列属性和方法:
constructor:保存着用于创建当前对象的函数。构造函数(constructor)就是object()。
hasOwnProperty:用于检查给定的属性在当前对象实例中是否存在。
isPrototypeOf(object):用于检查传入的对象是否是传入对象的原型。
propertyIsEnumerable:用于检查给定的属性是否能够使用for-in语句来枚举。
toLocaleString():返回对象的字符串表示,该字符串与执行环境的地球对应。
toString():返回对象的字符串标示。
valueOf():返回对象的字符串、数值或布尔值标示。通常与toString()方法的返回值相同。
ECMAScript中object是所有对象的基础,因此所有对象都具有这些基础的属性和方法。
操作符
一元操作符: + - ++ --
按位非操作符由一个波浪线(~)标示,执行结果是返回数字的反码。按位非是ES操作符中少数几个与二进制计算有关的操作符之一。
布尔操作符:
逻辑非操作符由一个叹号(!)标示,无论这个值是什么数据类型,这个操作符都会返回一个布尔值。逻辑非操作符首先会将它的操作数据转换为一个布尔值,然后再对其求反。
逻辑与操作符由两个和好(&&)标示。逻辑与操作可以应用于任何类型的操作数,而不仅仅是布尔值。
逻辑或操作符由两个竖线(||)标示,与逻辑与相似,有一个操作数不是布尔值,也不一定返回布尔值。
乘性操作符: * / %(求模)
关系操作符: > < >= <=
相等操作符: 相等和不相等(== !=) -- 先转换在比较
全等和不全等(=== !==) -- 仅比较不转换
条件操作符: ?:
逗号操作符:在一条语句中执行多个操作。逗号用于声明多个变量,也可以赋值。在赋值时,逗号操作符总会返回表达式中的最后一项。
语句
if - else
do - while
while (break立即退出循环,强制继续执行循环后面的语句)
for (continue也是立即退出循环,但退出循环后会从循环的顶部继续执行)
for - in 例:for( var i in txt ) console.log( i );
with是将代码的作用域放置到一个特定的对象中。严格模式下不允许使用with语句,将视为语法错误。
switch case default (break退出循环) switch语句在比较值时使用的是全等操作符
函数
ECMAScript中的函数使用function关键字来声明,后跟一组参数以及函数体。
通过函数可以封装任意多条语句,而且可以在任何地方、任何时候调用执行。
函数通过其函数名来调用,后面加上一对圆括号和参数(圆括号中的参数有多个可以用逗号隔开)。
ES中函数在定义时不必指定是否返回值。任何函数在任何时候都可以通过return+返回值。
参数
ES中参数在内容是用一个数字来标示的。函数的接受始终都是这个数组,而不关心数字中包含的那些参数(如果有参数的话)。在函数体内可以通过arguments对象来访问这个参数数组,获取传递给函数的每一个参数。
ES函数的一个重要特点:命名的参数只提供便利,点不是必需的。
arguments.length可以获知有多少个参数传递给了函数。
ES中的所有参数传递的都是值,不可能通过引用传递参数。
ES函数不能像传统意义上那样实现重载。定义了两个相同的函数,该名字只属于后定义的函数。
小结:
js的核心语言特性在ECMA-262中是以名为ECMAScript的伪语言的形式来定义的。
ECMAScript中包含了所有基本的语法、操作符、数据类型已经完成基本的技术任务所必须的对象,但没有对取得输入和产生输出的机制作出规定。理解ECMAScript及其纷繁复杂的各种细节,是理解其在web浏览器中实现--js的关键。目前大多数实现所遵循的都是ECMA-262第3版,但很多也已经着手开始实现第5版了。一下简要总计了ECMAScript中基本的要素。
ECMAScript中的基本数据类型包括undefined、null、boolean、number、string
与其他语言不同,ECMAScript没有为整数和浮点数值分别定义不同的数据类型,number类型可以用于便是所有数值。
ECMAScript中也有一种复杂的数据类型,即Object类型,该类型是这门语言中所有对象的基础类型。
严格模式为这门语言中容易出错的地方施加了限制。
ECMAScript提供了很多与C及其他类C语言中相同的基本操作符,包括算术操作符、布尔操作符、关系操作符、相等操作符及其赋值操作符等。
ECMAScript从其他语言中借鉴了很多流控制语句,例如if语句、for语句和switch语句等。ECMAScript中的函数和其他语言中的函数有诸多不同之处。
无须指定函数的返回值,因为任何ECMAScript函数都可以在任何时候返回任何值。
实际上,未指定返回值的函数返回的是一个特殊的undefined值。
ECMAScript中也没有函数签名的概念,因为其函数参数是一个包含零或多个值的数组的形式传递的。
可以向ECMAScript函数传递任意数量的参数,并且可以通过arguments对象来访问这些参数
由于不存在函数签名的特性,ECMAScript函数不能重载。
第4章 作用域和内存问题
ES变量包含两种不同数据类型的值:基本类型值和引用类型值。基本类型值指的是简单的数据段。引用类型值指呢些可能由多个值构成的对象。
基本数据类型:undefined、null、boolean、number、string。这5种基本数据类型是按值访问的,可以操作报错在变量中的实际的值。
引用类型的值是保存在内容中的对象。js不允许直接访问内存中的位置。也就是说不能直接操作对象的内存空间。在操作对象时,实际上是在操作对象的引用而不是实际的对象。为此,引用类型的值是按引用访问的。
我们不能给基本类型的值添加属性,尽管不会导致任何错误。
例:var name = "nice"; name.age = 27; alert(name.age); //undefined
当一个变量向另一个变量复制基本类型的值,会在变量对象上创建一个新的值,而后吧该值复制到为新变量分配的位置上。例: var num1 = 5; var num2 = num1; //num2的值是num1的一个副本。num1和num2这两个变量可以参与任何操作而不会互相影响。
当一个伴郎想另一个变量复制引用类型的值时,同样也会将保存在变量对象的值复制一份放到为新变量分配的空间中,不同的是,这个值的副本实际上是一个指针,指针指向存储在堆中的一个对象。复制操作结束后,两个对象实际上讲引用同一个对象。因此,改变其中一个变量,就会影响另一个变量。例:var obj1 = new Object(); var obj2 = obj1; obj1.name = "nice"; alert(obj2.name); //"nice"
执行环境及作用域
执行环境是js中最为重要的一个概念,执行环境定义了变量或函数有权访问的其他数据,决定了它们各自的行为。每个执行环境都有一个与之关联的变量对象(variable object),环境中定义的所有变量和函数都保存在这个对象中。虽然我们编写的代码无法访问这个对象,但解析器在处理数据时会在后台使用它。
在web浏览器中,全局执行环境被认为是window对象。(全局执行环境知道应用程序退出--例如关闭网页或浏览器--时才被销毁)。
当代码在一个环境中执行时,会创建变量对象的一个作用域链(scope chain)。作用域链的用途:是保证对执行环境有权访问的所有变量和函数的有序访问。作用域链的前端,始终都是当前执行的代码所在环境的变量对象。
作用域就是变量与函数的可访问范围,即作用域控制着变量与函数的可见性和生命周期。
js中变量的作用域有全局作用域和局部作用域
在js中函数也是对象,在js中一切都是对象。函数对象和其他对象一样拥有可以通过代码访问的属性和内部属性(Scope)。该内部属性包含了函数被创建的作用域中对象的集合,这个集合称为函数的作用域链。(this、window、document、add...)
内部环境可以通过作用域链访问所有的外部环境,但外部环境不能访问内部环境中的任何变量和函数。
垃圾收集
js具有自动垃圾收集机制,执行环境会复制管理代码执行过程中使用的内存。在编写js程序是,开发人员不用关心内存使用问题,所需内存的分配以及无用内存的回收完全实现了自动管理。(找出那些不在继续使用的变量,然后释放其占用的内存,为此,垃圾收集器会按照固定的时间间隔周期性地执行这一操作)。
一旦数据不在有用,最好将其值设为null来释放其引用 -- 这个做法叫解除引用。(这一做法适用于大多数全局变量和局部对象的属性,局部变量会在他们离开执行环境时自动被解除引用)
小结:
js变量可以用来保存两种类型的值:基本类型值和引用类型值。基本类型值源自5中基本数据类型:undefined、null、boolean、number、string。基本类型值和引用类型值具有以下特点:
基本类型值在内存中占据固定大小的空间,因此被保存在栈内存中。
从一个变量向另一个变量复制基本类型的值,会创建这个值的一个副本。
引用类型的值是对象,保存在堆内存中。
包含引用类型的变量实际上包含的并不是对象本身,而是一个指向该对象的指针。
从一个变量向另一个变量复制引用类型的值,复制的其实是指针,因此两个变量最终都指向同一个对象。
确定一个值是哪种基本类型可以使用typeof操作符,而确定一个值是哪种引用类型可以使用instanceof操作符。
所有变量(包括基本类型和引用类型)都汆子一个指向环境(也称为作用域)当中,这个执行环境决定了变量的生命周期,以及那一部分代码可以访问其中的变量,以下是关于执行环境的总结:
执行环境有全局执行环境(也称为全局环境)和函数执行环境之分;
每次进入以新执行环境,都会创建一个用于搜索变量和函数的作用域链;
函数的局部环境不仅有权访问函数作用域中的变量,而且有权访问其包含(父)环境,乃至全局环境;
全局环境只能访问在全局环境中定义的变量和函数,而不能直接访问局部环境中的任何数据。
变量的执行环境有助于确定应该何时释放内存。
第5章 引用类型
引用类型是一种数据结构,用于将数据和功能组织在一起。
var o = new Object();
对象是某个特定引用类型的实例。新对象是使用new操作符后跟一个构造函数来创建的。构造函数本身就是一个函数,该函数是出于创建新对象的目的而定义的。
创建Object实例的方式有两种。
1、 使用new操作符后跟Object构造函数。 var o = new Object(); o.name = "nice"; o.age = 29;
2、使用对象字面量表示法。var o = { name: "nice", age: 29 }; (最后一个属性后面添加逗号会在IE7及更早版本和Opera中导致报错)
访问对象属性时使用1、(.)点表示法 2、([""])方括号语法时,访问属性以字符串的形式放在方括号中。
Array类型
数字的length属性很有特点 -- 它不是只读的,通过设置这个属性,可以从数组的末尾移除项或向数组中添加新项。 var colors = ["red", "blue", "green"]; colors.lenght= 2; colors[2]; //undefined
分割字符串: join(",") 只接收一个参数,作为字符串的分隔符。
push() 方法可以接受任意数量的参数,吧他们逐个添加到数组的末尾,并返回修改后的数组长度。(添加)
pop() 方法则从数组末尾移除最后一项,减少数字的length值,人后返回移除的项。(移除)
shift() 方法能够移除数字中的第一个项并返回该项,同时数组长度减1。(移除)
unshift() 方法与shift() 用途相反,它能在数组前端添加任意个项并返回数组的长度。(添加)
从排序方法
reverse() 方法会反转数组项的顺序。
sort() 排序,会调用每个数组项的toString() 转型方法,比较得到字符串,以确定如何排序。即使数组中的每一项都是数值,sort() 方法比较的也是字符串。
操作方法
concat() 方法可以基于当前数组中的所有项创建一个新数组。
slice() 方法能够基于当前数组中的一个或多个项创建一个新数组。
slice(2)一个参数的情况下,会返回从该参数置顶位置开始到当前数组末尾的所有项。
slice(2, 6)两个参数,返回起始位置和结束位置之间的项--但不包括结束位置的项。
splice() 方法主要用途是向数组的中部插入项。
删除:可以删除任意数量的项,只需指定2个参数:要删除的第一项的位置和要删除的项数。例如:splice( 0 , 2 ) 会删除数组中的前两项。
插入:可以向置顶位置插入任意数量的项,只需提供3个参数:起始位置、0(要删除的项数)和要插入的项。如要插入多个项,可以传入第四、第五,以至任意多个项。例如:splice( 2, 0, "red", "green") 会从当前数组的位置2开始插入字符串“red”和“green”.
替换:可以向置顶位置插入任意数量的项,且同时删除任意数量的项,只需指定3个参数:起始位置、要删除的项数(>0)和要插入的任意数量的项。插入的项数不必与删除的项数相等。例如,splice( 2, 1, "red", "green") 会删除当前数组位置2的项,然后再从位置2开始插入字符串“red”和“green”。
splice() 方法始终都会返回一个数组,该数组中包含从原始数组中删除的项。(没有删除项,返回一个空数组)。
位置方法
indexOf() 和lastIndexOf() 方法都接收两个参数,要查找的项和(可选的)表示查找起点位置的索引。这两个方法返回要查找的项在数组中的位置,未找到返回-1。
indexOf() 方法从数组的开头向后查找。
lastIndexOf() 方法则从数组的末尾开始向前查找。
迭代方法
ES5为数组定义了5个迭代方法。每个方法都接收两个参数:运行的函数和(可选的)运行该函数作用域对象(影响this的值)。
every():对数组中的每一项运行给定函数,如果该函数对每一项都返回true,则返回true。
filter():对数组中的每一项运行给定函数,返回该函数会返回true项组成的数组。
forEach():对数组中每一项运行给定函数,这个方法没有返回值。
map():对数组中每一项运行给定函数,返回每次函数调用的结果组成的数组。
some():对数组中每一项运行给定函数,如果该函数对任一项返回true,则返回true。
Function类型
“ 函数是对象,函数名是指针 ”。因此函数名与包含对象指针的其他变量没有什么不同。
function sum() {} sum //函数名是对象指针,本身就是对象,可以作为值类使用。 sum() //调用函数
函数内部有两个特殊的对象:arguments和this。
arguments主要用途是保存函数参数,它有一个叫callee的属性。
arguments.callee 指向父级函数。
call() 和 apply() 方法在特定的作用域中调用函数,实际上等于设置函数体内this对象的值。他们的作用相同,区别在于接收参数的方式不同。使用他们来扩充作用域的最大好处,就是对象不需要与方法有任何耦合。
trim方法删除前置及后缀的所欲空格,然后返回结果。
字符串正则模式匹配方法:
match() //根据pattern(验证字段的模式)进行正则匹配,匹配到返回匹配结果,匹配不到返回null。
search() //根据pattern(验证字段的模式)进行正则匹配,匹配到返回索引,否则返回-1
replace() //根据pattern(验证字段的模式)进行正则匹配,吧匹配结果换完替换内容。
<input type="text" pattern="[a-z]{3}" /> // pattern 属性规定用于验证输入字段的模式。
eval() 方法解析器。通过eval()执行的代码被认为是包含该次调用的执行环境的一部分。
Math对象
Math对象包含的属性大都是数学计算中可能用到的一些特殊值。
Math.min(3, 9, 78) // 3 返回最小值。
Math.max(3, 9, 78) //78 返回最大值。
Math.ceil(25.9) //26 向上舍入。
Math.floor(25.9) //25 向下舍入。
Math.round(25.9) //25 执行标准舍入。
Math.random() 方法返回0-1之间一个随机数。
小结:
对象在JS中被称为引用类型的值,而且有一些内置的引用类型可以用来创建特定的对象,简要总结如下:
引用类型与传统面向对象程序设计中的类相似,但现实不同;
Object是一个基础类型,其他所有类型都是从Object继承了基本的行为。
Array类型是一组值的有序列表,同时还提供了操作和转换这些值的功能;
Date类型提供了有关日期和时间的信息,包括当前日期和时间以及相关的计算功能;
RegExp类型是ES支持正则表达式的一个接口,提供了最基本的和一些高级的正则表达式功能。
函数实际上是Function类型的实例,因此函数也是对象;而这一点正是js最有特色的地方。由于函数是对象,所以函数也拥有方法,可以用来增强其行为。
因为有了基本包装类型,所以JS中的基本类型值可以被当作对象来访问。三种基本包装类型分别是:Boolean、Number、String。以下是他们共同的特征:
每个包装类型都映射到同名的基本类型;
在读取模式下访问基本类型时,就会创建对应的基本包装类型的一个对象,从而方便了数据操作。
操作基本类型值的语句一经执行完毕,就会立即销毁新创建的包装对象。
在所有代码执行之前,作用域中已经存在的两个内置对象:Global好Math。在大多数ES实现中都不能直接访问Global对象;不过,web浏览器实现了继承该角色的window对象。全局变量和函数都是Global对象的属性。Math对象提供了很多属性和方法,用于辅助完成复杂的数学计算任务。
第6章 面向对象的程序设计
ECMA-262吧对象定义为:“无序属性的集合,其属性可以包含基本值、对象或者函数。”
ECMAScript中有两种属性:数据属性和访问器属性。
数据属性包含一个数据值的位置,在这个位置可以读取和写入值。
访问器属性不包含数据值;它们包含一对getter和setter函数(这两个函数不是必需的)。在读取访问器属性时调用getter,这个函数负责返回有效的值。在写入访问器属性时,会调用setter函数并传入新值,这个函数阀组决定如何处理数据。
var book = {
_year :2004 //_year前面的下划线是一种常用的标记,用于表示只能通过对象方法访问属性。
}
工厂模式
工厂模式是软件工程领域一种广为人知的设计模式,这种模式抽象了创建具体对象的过程。
在ECMAScript中无法创建类,开发人员就发明了一种函数,用函数来封装以特定接口创建对象的细节。
function createPerson( name, age, job ) {
var o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.sysName = function() { alert( this.name); };
return o;
}
var p1 = createPerson( "Nice", 29, "software engineer" );
var p2 = createPerson( "Greg", 27, "doctor" );
构造函数
按照惯例,构造函数始终都应该以一个大写字母开头,而非构造函数则应该以一个小写字母开头。
function Person( name, age, job ) {
this.name = name;
this.age = age;
this.job = job;
this.sayName = function() { alert( this.name ); }
}
var p1 = new Person( "Nice", 29, "software engineer" );
var p2 = new Person( "Greg", 27, "doctor" );
要创建Person的新实例,必须使用new操作符。以上这种方式调用构造函数实际上会经历4个步骤:
(1)创建一个新对象;
(2)将构造函数的作用域赋给新对象(因此this就指向了这个新对象);
(3)执行构造函数中的代码(为这个新对象添加属性);
(4)返回新对象。
任何函数,只要通过new操作符来调用,那它就可以作为构造函数。
原型模式
每一个函数都有一个prototype(原型)属性,这个属性是一个指针,执行一个对象。这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。
使用原型对象的好处是可以让所有对象实例共享它所包含的属性和方法。换句话说,不必再构造函数中定义对象实例的信息,而是可以将这些信息直接添加到原型对象中。
function Person() { }
Person.prototype.name = "Nice";
Person.prototype.sayName = function() { alert( this.name ) ; }
var p1 = new Person();
p1.name; //"Nice"
更简单的原型语法:用一个包含所有属性和方法的对象字面量来重写整个原型对象。
function Person() { }
Person.prototype = {
name: "Nice",
sayName: function() { alert( this.name); }
};
原型的动态性:实例中的指针仅指向原型,而不指向构造函数。
继承
ECMAScript只支持实现继承,而且其实现继承主要是依靠原型链来实现的。
原型链是利用原型让一个引用类型继承另一个引用类型的属性和方法。
小结:
ECMAScript支持面向对象(OO)编程,但不使用类或者接口。对象可以在代码执行过程中创建和增强,因此具有动态性而非严格定义的实体。在没有类的情况下,可以采用下列模式创建对象:
工厂模式:使用简单的函数创建对象,为对象添加属性和方法,然后返回对象。这个模式后来被构造函数模式所取代。
构造函数模式:可以创建自定义引用类型,可以像创建内置对象实例一样使用new操作符。不过,构造函数模式也有缺点,它的每个成员都无法得到复用,包括函数。由于函数可以不局限于任何对象(即与对象具有松散耦合的特点),因此没有理由不在多个对象间共享函数。
原型模式:使用构造函数的prototype属性来指定那些应该共享的属性和方法。组合使用构造函数模式和原型模式时,使用构造函数定义实例属性,而使用原型定义共享的属性和方法。
JS主要通过原型链实现继承。原型链的构建是通过将一个类型的实例赋值给另一个构造函数的原型实现的。这样,子类型就能够访问超类型的所有属性和方法,这一点与基于类的继承很相似。
原型链的问题是对象实例共享所有继承的属性和方法,因此不适宜单独使用。解决这个问题的技术是借用构造函数,即在子类型构造函数的内部调用超类型构造函数。这样就可以做到每个实例都具有自己的属性,同时还能保证只使用构造函数模式来定义类型。使用最多的继承模式是组合继承,这样模式使用原型链继承共享的属性和方法,而通过借用构造函数继承实例属性。
此外,还有下列可供选择的继承模式:
原型式继承:可以在不必预先定义构造函数的情况下实现继承,其本质是执行对给定对象的浅复制。而复制得到的副本还可以得到进一步改造。
寄生式继承:与原型式继承非常相似,也是基于某个对象或某些信息创建一个对象,然后增强对象,最后返回对象。为了解决组合继承模式由于多次调用超类型构造函数而导致的低效率问题,可以将这个模式与组合继承一起使用
寄生组合式继承:集寄生式继承和组合继承的优点与一身,是实现基于类型继承的最有效方式。
第7章 函数表达式
定义函数有两种:一种是函数声明,另一种就是函数表达式。
function functionName(arg1, arg1, arg2) { //函数体 }
函数声明有一个重要的特征是函数声明提升,意思是在执行代码之前会先读取函数声明。这就意味着可以吧函数声明放在调用它的语句后面。
sayHi();
function sayHi() {}
第二种创建函数的方式是使用函数表达式。
var functionName = function(arg1, arg1, arg2) { //函数体 }
这种情况下创建的函数叫做匿名函数,因为function关键字后面没有标识符。
函数表达式与其他表达式一样,在使用前必须先赋值。
闭包
闭包是指有权访问另一个函数作用域中的变量的函数。创建闭包的常见方式,就是在一个函数内部创建另一个函数。
个人理解的闭包:就是函数内部定义的内部函数可以访问到内部的变量,如果返回内部函数,依然可以访问到内部变量,返回的内部函数赋值给不同的变量,会创建不同的作用域链,互不影响。
当某个函数被调用时,会创建一个执行环境及相应的作用域链。
调用函数,在函数的执行环境而言,其作用域链中包含两个变量对象:本地活动对象和全局变量对象。
作用域链本质上是一个指向变量对象的指针列表,它只引用但不实际包含变量对象。
无论什么时候在函数中访问一个变量,就会从作用域中搜索具有相应名字的变量。一般来讲,当函数执行完毕后,局部活动对象就会被销毁,内存中仅保存全局作用域(全局执行环境的变量对象)。但闭包的情况有有所不同。
在另一个函数内部定义的函数会将包含函数(即外部函数)的活动对象添加到它的作用域链中。
由于闭包会携带包含它的函数的作用域,因此会比其他函数占用更多的内存。过度使用闭包可能会导致内存占用过多。
闭包只能取得包含函数中任何变量的最后一个值
垃圾回收
如果闭包的作用域链中保存着一个HTML元素,那么就意味着该元素将无法被销毁。在函数结束之前html元素设为null值解除对dom对象的引用,确保正常回收其占用的内存: element = null;
模仿块级作用域
块级作用域(通常称为私有作用域)的匿名函数的语法如下:
(function() {
//这里是块级作用域
})()
将函数声明包含在一对圆括号中,表示它实际上是一个函数表达式。而紧随其后的另一对圆括号会立即调用这个函数。
私有变量:包括函数的参数、局部变量和在函数内部定义的其他函数。
模块模式
模块模式则是为单例创建私有变量和特权方法。所谓单例,指的是只有一个实例的对象。按照惯例,js是以对象字面量的方式来创建单例对象的。
小结:
在js编程中,函数表达式是一种非常有用的技术。使用函数表达式可以无须对函数命名,从而实现动态编程。匿名函数,也称为拉姆达函数,是一种使用JS函数的强大方式。以下总结了函数表达式的特点:
函数表达式不同于函数声明。函数声明要求有名字,但函数表达不需要。没有名字的函数表达式也叫匿名函数。
在无法确定如何引用函数的情况下,递归函数就会变得比较复杂。
递归函数应该始终使用arguments.callee来递归地调用自身,不要使用函数名--函数名可能发生变化。
当在函数内部定义了其他函数时,就创建了闭包。闭包有权访问包含函数内部的所有变量,原理如下:
在后台执行环境中,闭包的作用域链包含着它自己的作用域、包含函数的作用域和全局作用域。
通常,函数的作用域及其所有变量都会在函数执行结束后被销毁。
但是,当函数返回了一个闭包时,这个函数的作用域将会一直在内存中保存到闭包不存在为止。
使用闭包可以在js中模仿块级作用域(js本身没有块级作用域的概念),要点如下:
创建并立即调用一个函数,这样既可以执行其中的代码,又不会在内存中留下对该函数的引用。
结果就是函数内部的所有变量都会被立即销毁--除非将某些变量赋值给了包含作用域(即外部作用域)中的变量。
闭包还可以用于在对象中创建私有变量,相关概念和要点如下:
即使js中没有正式的私有对象属性的概念,但可以使用闭包来实现公有方法,而通过公有方法可以访问在包含作用域中定义的变量。
有权访问私有变量的公有方法叫做特权方法。
可以使用构造函数模式、原型模式来实现自定义类型的特权方法,也可以使用模块模式、增强的模板模式来实现单例的特权方法。
js中的函数表达式和闭包都是极其有用的特性,利用它们可以实现很多功能。不过,因为创建闭包必须维护额外的作用域,所以过度使用他们可能会占用大量内存。
第8章 BOM
BOM的核心对象是window,它表示浏览器的一个实例。在浏览器中,window对象有双重角色,它既是通过js访问浏览器窗口的一个接口,有事ES规定的Global对象。这意味着在网页中定义的任何一个对象、变量和函数,都以window作为其Global对象,因此有权访问parseInt()等方法。
在全局作用域中声明的变量、函数都会变成window对象的属性和方法。
尝试访问未声明的变量会抛出错误,但是通过查询window对象,可以知道某个可能未声明的变量是否存在。
window.oldValue //返回undefined
如果页面中包含框架,则每个框架都拥有自己的window对象,并且报错在frames集合中。每个window对象都有一个name属性,其中包含框架的名称。
引用框架:window.frames["topFrame"] 或者 top.frames["topFrame"]
top对象始终指向最高(最外)层的框架。
与top相对的另一个window对象是parent。parent(父)对象始终指向当前框架的直接上层框架。但在没有框架的情况下,parent一定等于top(此时他们都等于window)。
window.open() 打开新窗口。
修改window对象位置的属性:screenLeft和screenTop,分别用于表示窗口相对于屏幕左边和上边的位置。Firefox则在screenX和screenY属性中国提供相同的窗口位置信息,safair和chrome也支持这两个属性。
var leftPos = (typeof window.screenLeft == "numer") ? window.screenLeft : window.screenX;
var topPos = (typeof window.screenTop == "numer") ? window.screenTop : window.screenY;
这个例子运用二元运算符先确定screenLeft和screenTop属性是否存在。
窗口大小
浏览器窗口大小提供了4个属性:innerWidth和innerHeight返回该容器中页面试图区的大小。
outerWidth、outerHeight返回浏览器窗口本身的尺寸。Chrome中这四个属性返回相同的值,即视口(viewport)大小而非浏览器窗口大小。
document.documentElement.clientWidth和clientHeight中保存了页面视口的信息。
resizeTo(width, height)方法调整浏览器窗口的新宽度和新高度。
resizeBy(width, height)方法可以调整新窗口与原窗口的宽度和高度之差。
这两个方法同样不适用于框架,只能对最外层的window对象使用。
window.open()打开新的浏览器窗口。
window.open("www.baidu.com", "height=600,width=400,top=10,left=10,resizable=yes"); //打开一个新的可以调整窗口大小的窗口,窗口初始化为600*400像素,并且距屏幕上沿河左边各10px。
window.close() //关闭通过window.open()打开的弹出窗口。
top.close() //弹出窗口可以在不经用户运行的情况下关闭自己。
间歇调用和超时调用
js是单线程语言,但它允许通过设置超时值和间歇时间值来调度代码在特定的时刻执行。前者是在指定的时间过后执行代码,后者则是每隔指定的时间就指向一次代码。
超时调用需要使用window对象的setTimeout()方法, 间歇调用的方法是setInterval()。
调用setTimeout()之后,该方法会返回一个数值ID,标示超时调用。
clearTimeout()方法来取消超时调用。
系统对话框
浏览器通过alert()、confirm()、prompt()方法调用系统对话框向用户显示消息。系统对话框与在浏览器中显示的网页没有关系,也不包含html。他们的外观有操作系统或浏览器设置决定。此外,通过这几个方法打开对话框都是同步和模态的。也就是说,显示这些对话框的时候代码会停止执行,关掉后代码会恢复执行。
location对象
location是最有用的BOM对象之一,它提供了与当前窗口中加载的文档有关的信息。还提供了一些导航功能。
location既是window对象的属性,也是document对象的属性。
location对象将url解析为独立的片段:
hash:返回url中#号后的所有字符
host:返回服务器名称和端口号
hostname:返回不带端口号的服务器名称
href:返回当前加载的完整的url。而location对象的toString()方法也返回这个值。
pathname:返回url中的目录和文件名
port:返回url中指定的端口号。
protocol:返回页面使用的协议
search:返回url的查询字符串,这个只付出以问号开头。
location对象改变浏览器的位置:(以下三种方法的效果完全一样,都打开新url并在浏览器生成一条历史记录)
location.assign("http://www.baidu.com");
window.location = "http://www.baidu.com";
location.href = "http://www.baidu.com";
每次修改location的属性(hash除外),页面都会以新URL重新加载。浏览器的历史记录就会生成一条新记录,用户可以通过单击“后退”按钮回到上一个页面。要禁用这种行为,可以使用replace()方法,这个方法只接收一个参数,跳转url但不会导致浏览器位置,不会生成历史记录,调用replace()方法后用户不能回到前一个页面。
reload()方法重新加载当前显示的页面。不传任何参数页面会以最有效的方法重新加载。reload()调用之后的代码代码可能不会执行,这取决于网络延迟或系统资料等因素,因此,最好将reload()放在代码的最后一行。
navigator对象
navigator对象现已成为识别客户浏览器的事实标准。
navigator.userAgent获取浏览器的用户代理字符串
检测插件
非IE浏览器可以使用plugins数组来达到这个目的。IE中检测插件的唯一方式就是使用专有的ActiveXObject类型。IE是以COM对象的方式实现插件的,而COM对象使用唯一标识符来标识。
鉴于检查这两种插件的方式差别太大,因此典型的做法是针对每个插件分别创建检查函数。
screen对象
scree对象用来表明客户端的能力,包括浏览器窗口外部的显示器信息。如宽高等。screen.width //屏幕宽度
history对象
history对象保存着用户上网的历史记录,从窗口打开的那一刻算起。因为history是window对象的属性。
history.go(-1) //后退一页 history.go(1) //前进一页 history.go(2) //前进两页
history.go("wrox.com") //跳到最近的wrox.com页
history.back() //后退一页 history.forward() //前进一页
小结
浏览器对象模型(BOM)以window对象为依托,表示浏览器窗口以及页面可见区域。同时,window对象还是ECMAScript中的Global对象,因而所有全局变量和函数都是它的属性,且所有原生的构造函数及其他函数也都存在于它的命名空间下。
BOM的组成部分:
在使用框架时,每个框架都有自己的window对象以及所有原生构造函数及其他函数的副本。每个框架都保存在frames集合中,可以通过位置或通过名称来访问。
有一些窗口指针,可以用来引用其他框架,包括父框架。
top对象始终指向最外围的框架,也就是整个浏览器窗口。
parent对象表示包含当前框架的框架,而self对象则回指window。
使用location对象可以通过编程方式来访问浏览器的导航系统。设置相应的属性,可以逐段或整体性地修改浏览器的url
调用replace()方法可以导航到一个新url,同时该url会替换浏览器记录中当前显示的页面。
navigator对象提供了与浏览器有关的信息,到底提供哪些信息,很大程度上取决于用户的浏览器;不过,也有一些公共的属性(如userAgent)存在于所有浏览器中。
BOM中还有两个对象:screen和history,但它们的功能有限。screen对象中保存着与客户端显示器有关的信息,这些信息一般只用于站点分析。history对象为访问浏览器的历史记录开了一个小缝隙,开发人员可以根据此判断历史记录的数量,也可以在历史记录中向后或向前导航到任意页面。
第9章 客户端检测
能力检测:检查某个属性是否存在:typeof object.sort == "function"; //检查sort是不是函数
用户代理检测:navigator.userAgent属性访问用户代理字符串。至今,基于WebKit的所有浏览器都将自己标识为Mozilla 5.0,确定浏览器是否基于WebKit要比确定它是不是Safari更有价值。
移动操作系统ios和Android默认的浏览器都基于webkit。
如Firefox、Camino和Netscape都使用相同版本的Gecko呈现引擎,编写脚本在浏览器中呈现主要检测的五大呈现引擎:IE、Gecko、WebKit、KHTML和Opera。
要正确地识别呈现引擎,关键是检测顺序要正确。
第一步是识别Opera,要识别Opera,必须得检测window.opera对象。
第二步检测的呈现引擎是WebKit,WebKit的用户代理字符串中的“AppleWebKit”是独一无二的。
第三步要检测的呈现引擎是KHTML.
第四步在排除了WebKit和KHTML之后,就可以准确的检测Gecko了
最后要的检测的呈现引擎是IE。
小结:
客户端检测是JavaScript开发中最具争议的一个话题。由于浏览器间存在差别,通常需要根据不同浏览器的能力分别编写不同的代码。有不少客户端检测方法,但下列是最经常使用的。
能力检测:在编写代码之前先检测特定浏览器的能力。例如,脚本在调查 某个函数之前,可能要先检测该函数是否存在。这种检测方法将开放人员从考虑具体的浏览器类型和版本中解放出来,让他们把注意力集中到相应的能力是否存在上。能力检测无法精确检测特定的浏览器和版本。
怪癖检测:怪癖实际上是浏览器实现中存在的bug,例如早期的WebKit中就存在一个怪癖,即它会在for - in循环中返回被隐藏的属性。怪癖检测通常涉及到运行一小段代码,然后确定浏览器是否存在某个怪癖。由于怪癖检测与能力检测相比效率更低,因此应该只在某个怪癖会干扰脚本运行的情况下使用。怪癖检测无法精确地检测特定的浏览器和版本。
用户代理检测:通过检测用户代理字符串来识别浏览器。用户代理字符串中包含大量与浏览器有关的信息,包括浏览器、平台、操作系统及浏览器版本。用户代理字符串有过一段相当长的发展历史,在此期间,浏览器提供商试图通过在用户代理字符串中添加一些欺骗性信息,欺骗网站相信自己的浏览器是另一种浏览器。用户代理检测需要特殊的技巧,特别是要注意Opera会隐瞒其用户代理字符串的情况。即便如此,通过用户代理字符串仍然能够检测出浏览器所用的呈现引擎以及所在的平台,包括移动设备和游戏设备。
在决定使用哪种客户端检测方法时,一般应优先考虑使用能力检测。怪癖检测是确定应该如何处理代码的第二选择。而用户代理检测则是客户端检测的最后一种方案,因为这种方法对用户代理字符串具有很强的依赖性。