1、JavaScript简介
JavaScript 是一门跨平台、面向对象的轻量级脚本语言。
JavaScript 内置了一些对象的标准库,如数组Array,日期Date,数学Math和包括运算符,流程控制符及声明方式等的一套核心语句。
JavaScript 的核心部分可以通过添加对象来扩展语言以适应不同用途。
1.1JavaScript 和 Java
JavaScript语言类似 Java,但没有Java的静态类型和强类型检查特性。与 Java 相比,Javascript 是一门形式自由的语言。你不必声明所有的变量,类和方法。你不必关心方法是否是 共有、私有或者受保护的,也不需要实现接口。无需显式指定变量、参数、方法返回值的数据类型。
JavaScript 拥有基于原型的对象模型提供的动态继承;也就是说,独立对象的继承是可以改变的。
JavaScript 支持匿名函数。函数也可以作为对象的属性执行。
JavaScript | Java |
面向对象。不区分对象类型。通过原型机制继承,任何对象的属性和方法均可以被动态添加。 | 基于类系统。分为类和实例,通过类层级的定义实现继承。不能动态增加对象或类的属性或方法。 |
变量类型不需要提前声明(动态类型)。 | 变量类型必须提前声明(静态类型)。 |
不能直接自动写入硬盘。 | 可以直接自动写入硬盘。 |
1.2JavaScript 和ECMAScript规范
JavaScript 的标准化组织是ECMA。标准化版本的 JavaScript 被称作 ECMAScript。
ECMAScript 规范并没有描述文档对象模型(DOM),该模型由 万维网联盟(W3C) 制定。DOM 定义了HTML文件对象被脚本操作的方法。
JavaScript支持ECMAScript规范中列出的所有功能。
1.3Firefox
Web 控制台(Web Console):最下面一行可以执行输入的命令行,面板上可以显示执行结果。
代码片段速记器(Scratchpad):一个拥有独立窗口的编辑器,可以用来编辑和在浏览器中执行 JavaScript,也可以将代码保存到本地磁盘,或者从本地载入,如果选择”显示”,代码片段速记器中的代码会在浏览器中执行,并在内容后面以注释的形式插入返回的结果。
2语法和数据类型
JavaScript 区分大小写,使用 Unicode 字符集。
2.1声明
JavaScript有三种声明:
1)var:声明一个变量,可选择将其初始化为一个值。
2)let:声明一个块作用域的局部变量,可选择将其初始化为一个值。
3)const:声明一个只读的常量。
2.2变量
2.2.1声明变量
可以用以下三种方式声明变量:
1) 使用关键词var,可以用来声明局部变量和全局变量。
2) 直接赋值,声明一个全局变量,并会在严格模式下产生一个ReferenceError。声明变量时不应该用这种方式。
3) 使用关键词let,可以用来声明块作用域的局部变量。
2.2.2变量求值
用 var 或 let 声明的且未赋初值的变量,值会被设定为undefined。
试图访问一个未声明的变量或者访问一个使用 let 声明的但未初始化的变量会导致一个ReferenceError异常被抛出。
可以使用 undefined 来判断变量是否已赋值。
undefined 值在布尔类型环境中会被当作 false,在数值类型环境中会被转换为 NaN。
空值 null 在数值类型环境中会被当作0,而在布尔类型环境中会被当作 false。
2.2.3变量的作用域
在所有函数之外声明的变量,叫做全局变量,因为它可被当前文档中的任何其他代码所访问。在函数内部声明的变量,叫做局部变量,因为它只能在该函数内部访问。
ECMAScript 6之前的JavaScript没有语句块作用域;相反,语句块中声明的变量将成为语句块所在代码段的局部变量。如果使用 ECMAScript 6 中的 let 声明,则声明的变量将具有块作用域。
2.2.4变量声明提升
JavaScript 变量的另一特别之处是,可以引用稍后声明的变量而不会引发异常。这一概念称为变量声明提升,即JavaScript 变量感觉上是被“提升”或移到了所有函数和语句之前。然而提升后的变量将返回 undefined 值,所以在使用或引用某个变量之后进行声明和初始化操作,这个被提升的引用仍将得到 undefined 值。
由于存在变量声明提升,一个函数中所有的var语句应尽可能地放在接近函数顶部的地方。这将大大提升程序代码的清晰度。
在 ECMAScript 2015 中,let(const)将不会提升变量到代码块的顶部。因此,在变量声明之前引用这个变量,将抛出错误ReferenceError。这个变量将从代码块一开始的时候就处在一个“暂时性死区”,直到这个变量被声明为止。
2.2.5函数提升
对于函数,只有函数声明会被提升到顶部,而不包括函数表达式。
2.2.6全局变量
全局变量实际上是全局对象的属性。在网页中,缺省的全局对象是window,所以可以用形如 window.variable的语法来设置和访问全局变量。因此,可以通过指定 window 或 frame 的名字,从一个 window 或 frame 访问另一个 window 或 frame 中声明的变量。例如,在文档里声明一个叫 phoneNumber 的变量,那么就可以在子框架里使用 parent.phoneNumber 来引用它。
2.3常量
常量不可以通过赋值改变其值,也不可以在脚本运行时重新声明。它必须被初始化为某个值。
常量的作用域规则与 let 块级作用域变量相同。若省略const关键字,则该标识符将被视为变量。
在同一作用域中,不能使用与变量名或函数名相同的名字来命名常量。
2.4数据结构和类型
2.4.1数据类型
JavaScript语言可以识别7 种不同类型的值:
Object对象 + 6种原型数据类型:
1)Boolean:布尔值,true 和 false。
2)null:注意 null 与Null、NULL不同。
3)undefined: 变量未定义时的属性。
4)Number:表示数字。
5)String:表示字符串。
6)Symbol:在 ECMAScript 6 中新添加的类型,它的实例是唯一且不可改变的。
注:Objects和functions 是本语言的其他两个基本要素。可以将对象视为存放值的命名容器,而将函数视为的应用程序能够执行的过程。
2.4.2数据类型的转换
JavaScript是一种动态类型语言。这意味着声明变量时可以不必指定数据类型,而数据类型会在脚本执行时根据需要自动转换。
在涉及加法运算符(+)的数字和字符串表达式中,JavaScript 会把数字值转换为字符串。在涉及其它运算符(如'-')时,则不会把数字变为字符串。
2.4.3字符串转换为数字:使用parseInt()和parseFloat()方法或使用单目加法运算符。
注:parseInt() 仅能够返回整数,所以使用它会丢失小数部分。另外,调用 parseInt() 时最好总是带上进制(radix) 参数,这个参数用于指定使用哪一种进制。
2.5字面量
字面量是由语法表达式定义的常量,是脚本中按字面意思给出的固定的值,而不是变量,在程序脚本运行中不可更改。
2.5.1数组字面量
封闭在方括号对([])中的包含有零个或多个表达式的列表,其中每个表达式代表数组的一个元素。
若在顶层(全局)脚本里用字面值创建数组,JavaScript语言将会在每次对包含该数组字面值的表达式求值时解释该数组。另一方面,在函数中使用的数组,将在每次调用函数时都会被创建一次。
数组字面值同时也是数组对象。
声明时,不必列举数组字面值中的所有元素。若在同一行中连写两个逗号(,),数组中就会产生一个没有被指定的元素,其初始值是undefined。如果在元素列表的尾部添加了一个逗号,它将会被忽略。但是,在自己写代码时:显式地将缺失的元素声明为undefined,将大大提高代码的清晰度和可维护性。
2.5.2布尔字面量
布尔类型有两种字面量:true和false。
不要混淆作为布尔对象的真和假与布尔类型的原始值true和false。布尔对象是原始布尔数据类型的一个包装器。
2.5.3浮点数字面量
浮点数字面值可以有以下的组成部分:
a 一个十进制整数,可以带正负号(即前缀“+”或“-”);
b 小数点(“.”);
c 小数部分,由一串十进制数表示;
d 指数部分,以“e”或“E”开头,后面跟着一个整数,可以有正负号(即前缀“+”或“-”)
浮点数字面量至少有一位数字,而且必须带小数点或者“e”(大写“E”也可)。
2.5.4整数
整数可以用十进制、十六进制、八进制以及二进制表示。
a. 十进制整数字面量由一串数字序列组成,且没有前缀0。
b. 八进制的整数以0(或0O、0o)开头(严格模式下,八进制整数字面量必须以0o或0O开头,而不能以0开头),只能包括数字0-7。
c. 十六进制整数以0x(或0X)开头,可以包含数字(0-9)和字母 a~f 或 A~F。
d. 二进制整数以0b(或0B)开头,只能包含数字0和1。
2.5.5对象字面量
对象字面值是封闭在花括号对({})中的一个对象的零个或多个"属性名-值"对的(元素)列表。不能在一条语句的开头就使用对象字面值,这将导致错误或产生超出预料的行为,因为此时左花括号({)会被认为是一个语句块的起始符号。
可以使用数字或字符串字面值作为属性的名字,或者在另一个字面值内嵌套上一个字面值。
对象属性名字可以是任意字符串,包括空串。如果对象属性名字不是合法的javascript标识符,它必须用""包裹。属性的名字不合法,那么便不能用.访问属性值,而是通过类数组标记("[]")访问和赋值。
2.5.6RegExp字面量
一个正则表达式是字符被斜线(译注:正斜杠“/”)围成的表达式。
2.5.7字符串字面量
字符串字面量是由双引号(")对或单引号(‘)括起来的零个或多个字符。字符串被限定在同种引号之间;也即,必须是成对单引号或成对双引号。
可以在字符串字面值上使用字符串对象的所有方法——JavaScript会自动将字符串字面值转换为一个临时字符串对象,调用该方法,然后废弃掉那个临时的字符串对象。
除非有特别需要使用字符串对象,否则,应当始终使用字符串字面值。
能在JavaScript的字符串中使用的特殊字符:(转义字符)
\0 | Null字节 |
\b | 退格符 |
\f | 换页符 |
\n | 换行符 |
\r | 回车符 |
\t | Tab (制表符) |
\v | 垂直制表符 |
\' | 单引号 |
\" | 双引号 |
\\ | 反斜杠字符 |
\XXX | 由从0到377最多三位八进制数XXX表示的 Latin-1 字符。例如,\251是版权符号的八进制序列 |
\uXXXX | 由四位十六进制数字XXXX表示的Unicode字符。例如,\ u00A9是版权符号的Unicode序列。见Unicode escape sequences (Unicode 转义字符) |
\u{XXXXX} | Unicode代码点 (code point) 转义字符。例如,\u{2F804} 相当于Unicode转义字符 \uD87E\uDC04的简写 |
注:严格模式下,不能使用八进制转义字符。
3流程控制和错误处理
3.1语句块
在ECMAScript 6标准之前,Javascript没有块作用域。如果在块的外部声明了一个变量,然后在块中声明了一个相同变量名的变量,并赋予不同的值。那么在程序执行中将会使用块中的值,这样做虽然是合法的,但是这不同于JAVA与C。
从 ECMAScript 6 开始,使用 let 定义的变量是块作用域的。
3.2条件判断语句
false等效值:false、undefined、null、0、NaN、空字符串””。
当传递给条件语句时,所有其他值,包括所有对象会被计算为 true 。
不要混淆原始的布尔值true和false 与 布尔对象的值true和false。布尔对象true和false在条件语句中会被计算为 true。
3.3循环语句
1) for语句
2) do...while语句
3) while语句
4) label语句
标签语句提供一种使同一程序的在另一处找到它的标识。例如,可以用一个标签来识别一个循环,并用break或continue语句来说明一个程序是否要中断这个循环或是继续执行。
label 的值可以是js的任何非保留字标识符。 用label 标识的语句可以是任何语句。
5) break语句
使用中断语句终止循环、开关或与标签语句连接。
中断语句的语法如下:
a. break;
用于终止在循环体或者switch的封闭内部。当使用没有带标签语句的中断语句(break)时,while,do-while,for或者switch封闭的内部语句将立即终止,并转移到后面的语句执行。
b. break label;
用于在特定的封闭标签语句。当使用带有标签语句的中断语句(break)时,将终止在特定的标签语句。
6) continue语句
连续语句用于重新开始 while, do-while, for语句,或者label语句。
连续语句的语法如下:
a. continue;
当使用没有带标签语句的连续语句时,将在当前的while,do-while或者for循环体封闭的内部语句中止执行,然后进入下一次循环继续执行。与break语句相比,连续语句不会把整个循环终止。在while循环里,将跳回条件判断;在for循环里,则跳回累计表达式。
b. continue label;
当使用带有标签语句的连续语句时,用于识别循环体中的标签语句。
注:for...in也反复执行语句,但它是用来操作对象的。
3.4对象操作语句
JavaScript 用 for...in, for each...in和 with语句来操作对象。
1)for...in语句
for...in语句迭代一个指定的变量去遍历这个对象的属性,每个属性,javascript 执行指定的语句。
虽然大多趋向于用for...in作为一种遍历数组(Array)元素的方式,因为除了遍历数组元素,for...in语句也遍历了用户自定义属性。如果修改了数组对象,比如添加了通用属性或者方法,for...in语句还会返回除了数字索引(index)外的自定义属性的名称(name)。因此还是用带有数字索引的传统for循环来遍历数组会比较好。
2)for each...in语句
它和for...in相似,但是取得的是对象属性的值,而不是属性。
3.5异常处理语句
可以用throw 语句抛出一个异常并且用try...catch 语句捕获处理它。
3.5.1异常类型
JavaScript可以抛出任意对象。然而,不是所有对象能产生相同的结果。尽管抛出数值或者字母串作为错误信息十分常见,但是通常用下列其中一种异常类型来创建目标更为高效:
a. ECMAScript exceptions
b. DOMException
c. nsIXPCException(XPConnect)
3.5.2抛出语句
使用throw语句抛出一个异常。当抛出异常,规定一个含有值的表达式要被抛出。
可以抛出任意表达式(如String、Boolean、Number型等)而不是特定一种类型的表达式。
可以在抛出异常时声明一个对象。那就可以在捕捉块中查询到对象的属性。
3.5.3捕获语句
try...catch 语句标记一块待尝试的语句,并规定一个以上的响应应该有一个异常被抛出。如果抛出一个异常,try...catch语句就捕获它。
try...catch 语句有一个包含一条或者多条语句的try代码块,0个或多个的catch代码块,catch代码块中的语句会在try代码块中抛出异常时执行。换句话说,如果在try代码块中的代码没有执行成功,那么希望将执行流程转入catch代码块。如果try代码块中的语句(或者try 代码块中调用的方法)一旦抛出了异常,那么执行流程会立即进入catch 代码块。如果try代码块没有抛出异常,catch代码块就会被跳过。finally 代码块总会紧跟在try和catch代码块之后执行,但会在try和catch代码块之后的其他代码之前执行。
捕捉块catch指定了一个标识符来存放抛出语句指定的值;可以用这个标识符来获取抛出的异常信息。在插入捕捉块时JavaScript创建这个标识符;标识符只存在于捕捉块的存续期间里;当捕捉块执行完成时,标识符不再可用。
终结块finally包含了在try和catch块完成后、下面接着try...catch的语句之前执行的语句。终结块无论是否抛出异常都会执行。如果抛出了一个异常,就算没有异常处理,终结块里的语句也会执行。如果终结块返回一个值,该值会是整个try-catch-finally流程的返回值,不管在try和catch块中语句返回了什么。
可以嵌套一个或多个try...catch语句。如果一个内部的try...catch语句没有捕捉块,将会启动匹配外部的try...catch语句的捕捉块。
3.5.4错误匹配对象
根据错误类型,也许可以用'name'和'message'获取更精炼的信息。'name'提供了常规的错误类(如 'DOMException' or 'Error'),而'message'通常提供了一条从错误对象转换成字符串的简明信息。
3.6Promises
从 ECMAScript 6 开始,JavaScript 增加了对Promise对象的支持,它允许对延时和异步操作流进行控制。
Promise 对象有以下几种状态:
1) pending (进行中): 初始的状态,即正在执行,不处于 fulfilled 或 rejected 状态。
2) fulfilled (已完成): 成功的完成了操作。
3) rejected (已失败): 失败,没有完成操作。
4) settled (已解决): Promise 处于 fulfilled 或 rejected 二者中的任意一个状态, 不会是 pending。
通过 XHR 加载图片:
使用Promise 和XMLHTTPRequest来加载图片。
4循环和迭代
JavaScript中提供了这些循环语句:
(1)for语句
(2)do.while语句
(3)while语句
(4)label语句
(5)break语句
(6)continue语句
(7)for...in语句
(8)for...of语句
for...of语句在可迭代的对象上创建了一个循环(包括Array,Map,Set,参数对象(arguments) 等等),对值的每一个独特的属性调用一个将被执行的自定义的和语句挂钩的迭代。
与 for...in 循环遍历的结果是数组元素的下标不同的是, for...of 遍历的结果是元素的值。
5函数
5.1定义函数
5.1.1函数声明
一个函数定义(也称为函数声明,或函数语句)由一系列的函数关键字组成, 依次为:
a. 函数的名称。
b. 函数参数列表,包围在括号( )中并由逗号( , )区隔。
c. 函数功能,包围在花括号{ }中,用于定义函数功能的一些JavaScript语句。
5.1.2函数表达式
函数也可以由函数表达式创建。这样的函数可以是匿名的;它不必有一个名称。
函数表达式也可以提供函数名,并且可以用于在函数内部代指其本身,或者在调试器堆栈跟踪中识别该函数。
当将函数作为参数传递给另一个函数时,函数表达式很方便。
5.2调用函数
其它的方式来调用函数:常见的一些情形是某些地方需要动态调用函数,或者函数的实参数量是变化的,或者调用函数的上下文需要指定为在运行时确定的特定对象。
5.3函数作用域
在函数内定义的变量不能在函数之外的任何地方访问,因为变量仅仅在该函数的域的内部有定义。相对应的,一个函数可以访问定义在其范围内的任何变量和函数。换言之,定义在全局域中的函数可以访问所有定义在全局域中的变量。在另一个函数中定义的函数也可以访问在其父函数中定义的所有变量和父函数有权访问的任何其他变量。
5.4作用域与函数堆栈
5.4.1递归(Recursion)
一个函数可以指向并调用自身。有三种方法可以达到这个目的:
a. 函数名
b. arguments.callee
c. 作用域下的一个指向该函数的变量名
5.4.2嵌套函数和闭包
可以在一个函数里面嵌套另外一个函数。嵌套(内部)函数对其容器(外部)函数是私有的。它自身也形成了一个闭包(closure)。一个闭包是一个可以自己拥有独立的环境与变量的的表达式(通常是函数)。
既然嵌套函数是一个闭包,就意味着一个嵌套函数可以”继承“容器函数的参数和变量。换句话说,内部函数包含外部函数的作用域。
总结:
a. 内部函数只可以在外部函数中访问。
b. 内部函数形成了一个闭包:它可以访问外部函数的参数和变量,但是外部函数却不能使用它的参数和变量。
5.4.3保存变量
一个闭包必须保存它可见作用域中所有参数和变量。因为每一次调用传入的参数都可能不同,每一次对外部函数的调用实际上重新创建了一遍这个闭包。只有当返回的 inside 没有再被引用时,内存才会被释放。
5.4.4多层嵌套函数
函数可以被多层嵌套。因此,闭包可以包含多个作用域;他们递归式的包含了所有包含它的函数作用域。这个称之为域链。
5.4.5命名冲突
当同一个闭包作用域下两个参数或者变量同名时,就会产生命名冲突。更近的作用域有更高的优先权,所以最近的优先级最高,最远的优先级最低。这就是作用域链。链的第一个元素就是最里面的作用域,最后一个元素便是最外层的作用域。
5.5闭包
闭包是JavaScript中最强大的特性之一。JavaScript允许函数嵌套,并且内部函数可以访问定义在外部函数中的所有变量和函数,以及外部函数能访问的所有变量和函数。但是,外部函数却不能够访问定义在内部函数中的变量和函数。这给内部函数的变量提供了一定的安全性。而且,当内部函数生存周期大于外部函数时,由于内部函数可以访问外部函数的作用域,定义在外部函数的变量和函数的生存周期就会大于外部函数本身。当内部函数以某一种方式被任何一个外部函数作用域访问时,一个闭包就产生了。
使用闭包时仍然要小心避免一些陷阱。如果一个闭包的函数用外部函数的变量名定义了同样的变量,那在外部函数域将再也无法指向该变量。
5.6使用arguments对象
函数的实际参数会被保存在一个类似数组的arguments对象中。在函数内,可以用arguments.length来获得实际传递给函数的参数的数量,用arguments对象通过arguments[i]
来取得每个参数。
注意: arguments 变量只是 ”类数组对象“,并不是一个数组。称其为类数组对象是说它有一个索引编号和Length属性。尽管如此,它并不拥有全部的Array对象的操作方法。
5.7函数参数
从ECMAScript 6开始,有两个新的类型的参数:默认参数和剩余参数。
5.7.1默认参数
在JavaScript中,函数参数的默认值是undefined。然而,在某些情况下设置不同的默认值是有用的。这时默认参数可以提供帮助。
通过在函数头给函数的参数赋值来设置默认值。
5.7.2剩余参数
剩余参数语法允许将不确定数量的参数表示为数组。使用剩余参数收集从第二个到最后参数。
5.8箭头函数
箭头函数表达式(也称胖箭头函数)相比函数表达式具有较短的语法并以词法的方式绑定 this。箭头函数总是匿名的。
箭头函数的标记符号:=>
有两个因素会影响引入箭头函数:
1)更简洁的函数:
参数=>返回值
2)this
在箭头函数出现之前,每一个新函数都重新定义了自己的this值(在严格模式下,一个新的对象在构造函数里是未定义的,以“对象方法”的方式调用的函数是上下文对象等)。以面向对象的编程风格,这样着实有点恼人。
5.9预定义函数
JavaScript语言有好些个顶级的内建函数:
1) eval()
eval()方法会对一串字符串形式的JavaScript代码字符求值。
2)uneval()
uneval()方法创建的一个Object的源代码的字符串表示。
3)isFinite()
isFinite()函数判断传入的值是否是有限的数值。 如果需要的话,其参数首先被转换为一个数值。
4) isNaN()
isNaN()函数判断一个值是否是NaN。注意: isNaN函数内部的强制转换规则十分有趣; 另一个可供选择的是ECMAScript 6 中定义Number.isNaN() , 或者使用 typeof 来判断数值类型。
5) parseFloat()
parseFloat() 函数解析字符串参数,并返回一个浮点数。
6) parseInt()
parseInt() 函数解析字符串参数,并返回指定的基数(基础数学中的数制)的整数。
7) decodeURI()
decodeURI() 函数对先前经过encodeURI函数或者其他类似方法编码过的字符串进行解码。
8) decodeURIComponent()
decodeURIComponent()方法对先前经过encodeURIComponent函数或者其他类似方法编码过的字符串进行解码。
9) encodeURI()
encodeURI()方法通过用以一个,两个,三个或四个转义序列表示字符的UTF-8编码替换统一资源标识符(URI)的某些字符来进行编码(每个字符对应四个转义序列,这四个序列组了两个”替代“字符)。
10) encodeURIComponent()
encodeURIComponent() 方法通过用以一个,两个,三个或四个转义序列表示字符的UTF-8编码替换统一资源标识符(URI)的每个字符来进行编码(每个字符对应四个转义序列,这四个序列组了两个”替代“字符)。
11) escape()
已废弃的 escape() 方法计算生成一个新的字符串,其中的某些字符已被替换为十六进制转义序列。使用encodeURI或者encodeURIComponent替代本方法。
12) unescape()
已废弃的 unescape() 方法计算生成一个新的字符串,其中的十六进制转义序列将被其表示的字符替换。上述的转义序列就像escape里介绍的一样。因为 unescape 已经废弃,建议使用decodeURI或者decodeURIComponent替代本方法。
6表达式和运算符
6.1运算符
---更复杂的赋值——解构赋值
一个能从数组或对象对应的数组结构或对象字面量里提取数据的 Javascript 表达式。
var foo = ["one", "two", "three"];
var [one, two, three] = foo;
6.1.1delete操作符
删除一个对象或一个对象的属性或者一个数组中某一个键值。
delete objectName;
delete objectName.property;
delete objectName[index];
delete property; //只在with声明的状态下是合法的, 从对象中删除一个属性。
能使用 delete 删除各种各样的隐式声明(implicity declared), 但是被var声明的除外。
删除数组中的元素时,这个元素仍然能寻址,但不在数组中了,可是数组的长度是不变的。
6.1.2typeof 操作符
1) typeof operand
2) typeof (operand)
operand 可为字符串、变量、关键词或对象,其类型(function、string、number、object、undefined、boolean等)将被返回,null将返回object。operand 两侧的括号为可选。
6.1.3void操作符
1) void (expression)
2) void expression
void运算符,表明一个运算没有返回值。expression是javaScript表达式,括号中的表达式是一个可选项,当然使用该方式是一种好的形式。
6.1.4in操作符
如果指定的属性在指定的对象中会返回true。
6.1.5instanceof操作符
如果对象是某种指定类型返回true。
6.2表达式
6.2.1this
使用this关键字来指代当前对象,通常,this指代的是方法中正在被调用的对象。
6.2.2new
可以使用new 创建一个自定义类型或者是预置类型的对象实例。
6.2.3super
super关键字可以用来调用一个对象父类的函数,它在用来调用一个类的父类的构造函数时非常有用。
7数字和日期
JavaScript没有特定的整型数据类型。除了能够表示浮点数,数字类型有三个符号值: +Infinity、-Infinity和 NaN。
7.1Number对象
内置的Number对象有一些数字化常量属性,如Number.MAX_VALUE、Number.MIN_VALUE、Number.POSITIVE_INFINITY、Number.NEGATIVE_INFINITY、Number.NaN。不能改变这些属性的值。
内置的Number对象有一些方法,如Number.parseFloat()、Number.parseInt()、Number.isFinite()、Number.isInteger()、Number.isNaN()。
内置的Number对象的原型有一些方法,如toExponential()、toFixed()、toPrecision()
7.2Math对象
Math对象的方法:abs()、sin()、cos()、tan()、asin()、acos()、atan()、atan2()、sinh()、cosh()、tanh()、asinh()、acosh()、atanh()、pow()、exp()、expm1()、log10()、log1p()、log2()、floor()、ceil()、min()、max()、random()、round()、fround()、trunc()、sqrt()、cbrt()、hypot()、sign()、clz32()、imul()
7.3Date对象
JavaScript没有日期数据类型。但是你可以在你的程序里使用 Date对象和其方法来处理日期和时间。Date对象有大量的设置、获取和操作日期的方法。 它并不含有任何属性。
JavaScript 处理日期数据类似于Java。这两种语言有许多一样的处理日期的方法,也都是以1970年1月1日00:00:00以来的毫秒数来储存数据类型的。
Date 对象的范围是相对距离 UTC 1970年1月1日 的前后 100,000,000 天。
使用new关键字创建一个日期对象,不使用 new 关键字来调用Date对象将会简单地将所提供的date对象转换为字符串表示形式。
1) 无参数 : 创建今天的日期和时间。
2) 一个符合以下格式的表示日期的字符串: "月 日, 年 时:分:秒.",如果省略时、分、秒,那么他们的值将被设置为0。
3) 一个年,月,日的整型值的集合。
4) 一个年,月,日,时,分,秒的集合。
处理日期时间的Date对象方法可分为以下几类:
1) "set" 方法:用于设置Date对象的日期和时间的值。
2) "get" 方法:用于获取Date对象的日期和时间的值。
3) "to" 方法:用于返回Date对象的字符串格式的值。
4) parse 和UTC 方法:用于解析Date字符串。
有个getDay方法可以返回星期,但是没有相应的setDay方法用了设置星期,因为星期是自动设置的。
上述方法用整数来代表以下这些值:
1) 秒,分: 0 至 59
2) 时: 0 至 23
3) 星期: 0 (周日) 至 6 (周六)
4) 日期:1 至 31
5) 月份: 0 (一月) to 11 (十二月)
6) 年份: 从1900开始的年数
8文本格式化
8.1字符串
JavaScript中的 String 类型用于表示文本型的数据. 它是由无符号整数值(16bit)作为元素而组成的集合. 字符串中的每个元素在字符串中占据一个位置. 第一个元素的index值是0, 下一个元素的index值是1, 以此类推. 字符串的长度就是字符串中所含的元素个数.你可以通过String字面值或者String对象两种方式创建一个字符串。
8.1.1String字面值
a. 可以使用单引号或双引号创建简单的字符串。
b. 可以使用转义序列来创建更复杂的字符串。
① 十六进制转义序列:\x之后的数值将被认为是一个16进制数。
② Unicode转义序列:Unicode转义序列在\u之后需要至少4个字符。
③ Unicode code point escapes:这是ECMAScript 6中的新特性. 有了Unicode code point escapes, 任何字符都可以用16进制数转义, 这使得通过Unicode转义表示大于0x10FFFF的字符成为可能. 使用简单的Unicode转义时通常需要分别写字符相应的两个部分(译注:大于0x10FFFF的字符需要拆分为相应的两个小于0x10FFFF的部分)来达到同样的效果。
String.fromCodePoint()
String.prototype.codePointAt()
8.1.2字符串对象
String对象是对原始string类型的封装。
可以在String字面值上使用String对象的任何方法—JavaScript自动把String字面值转换为一个临时的String对象,然后调用其相应方法,最后丢弃此临时对象。在String字面值上也可以使用String.length属性。
除非必要, 应该尽量使用String字面值
String对象的方法:
charAt、charCodeAt、codePointAt、indexOf、lastIndexOf、startsWith、endsWith、includes、concat、fromCharCode, fromCodePoint、split、slice 、substring、substr、match、replace、search、toLowerCase、toUpperCase、normalize、repeat、trim
8.1.3多行模板字符串
模板字符串是一种允许内嵌表达式的String字面值。可以用它实现多行字符串或者字符串内插等特性。
模板字符串使用反勾号 (` `)包裹内容而不是单引号或双引号。模板字符串可以包含占位符。占位符用美元符号和花括号标识 (${expression})。
a. 多行
console.log(`string text line 1
string text line 2`);
b. 嵌入表达式
var a = 5, b = 10;
console.log(`Fifteen is ${a + b} and\nnot ${2 * a + b}.`);
8.2国际化
1) 日期和时间格式化:DateTimeFormat对象
2) 数字格式化:NumberFormat对象
3) 定序(字符串比较和排序):Collator对象
9正则表达式
在JavaScript中,正则表达式也是对象。这种模式可以被用于 RegExp 的 exec和test方法以及String的match、replace、search 和 split 方法。
9.1创建一个正则表达式
(1)使用一个正则表达式字面量
(2)调用RegExp对象的构造函数
9.2编写一个正则表达式的模式
9.2.1使用简单的模式
由直接匹配所构成
9.2.2使用特殊字符
字符 | 含义 |
\ | 匹配将依照下列规则: 在非特殊字符之前的反斜杠表示下一个字符是特殊的,不能从字面上解释。如'\t'匹配一个制表符。 反斜杠也可以将其后的特殊字符,转义为字面量。如/a\*/匹配像 "a*" 这样的字符串。 使用new RegExp("pattern")时不要忘记将\进行转义,因为\在字符串里面也是一个转义字符。 |
^ | 匹配输入的开始。如果多行标志被设置为true,那么也匹配换行符后紧跟的位置。 |
$ | 匹配输入的结束。如果多行标示被设置为true,那么也匹配换行符前的位置。 |
* | 匹配前一个表达式0次或多次。等价于 {0,}。 |
+ | 匹配前面一个表达式1次或者多次。等价于 {1,}。 |
? | 匹配前面一个表达式0次或者1次。等价于 {0,1}。如果紧跟在任何量词 *、 +、? 或 {} 的后面,将会使量词变为非贪婪的(匹配尽量少的字符),和缺省使用的贪婪模式(匹配尽可能多的字符)正好相反。例如,对 "123abc" 应用 /\d+/ 将会返回 "123",如果使用 /\d+?/,那么就只会匹配到 "1"。 |
. | 匹配除了换行符(\n)之外的任何单个字符。 |
(x) | 匹配 'x' 并且记住匹配项,括号被称为 捕获括号。 模式 /(foo) (bar) \1 \2/ 中的 '(foo)' 和 '(bar)' 匹配并记住字符串 "foo bar foo bar" 中前两个单词。模式中的 \1 和 \2 匹配字符串的后两个单词。注意 \1、\2、\n 是用在正则表达式的匹配环节。在正则表达式的替换环节,则要使用像 $1、$2、$n 这样的语法,例如,'bar foo'.replace( /(...) (...)/, '$2 $1' )。 |
(?:x) | 匹配 'x' 但是不记住匹配项。这种叫作非捕获括号,使得能够定义为与正则表达式运算符一起使用的子表达式。来看示例表达式 /(?:foo){1,2}/。如果表达式是 /foo{1,2}/,{1,2}将只对 ‘foo’ 的最后一个字符 ’o‘ 生效。如果使用非捕获括号,则{1,2}会匹配整个 ‘foo’ 单词。 |
x(?=y) | 匹配'x'仅仅当'x'后面跟着'y'.这种叫做正向肯定查找。 例如,/Jack(?=Sprat)/会匹配到'Jack'仅仅当它后面跟着'Sprat'。/Jack(?=Sprat|Frost)/匹配‘Jack’仅仅当它后面跟着'Sprat'或者是‘Frost’。但是‘Sprat’和‘Frost’都不是匹配结果的一部分。 |
x(?!y) |
匹配'x'仅仅当'x'后面不跟着'y',这个叫做正向否定查找。 例如,/\d+(?!\.)/匹配一个数字仅仅当这个数字后面没有跟小数点的时候。正则表达式/\d+(?!\.)/.exec("3.141")匹配‘141’但是不是‘3.141’ |
匹配‘x’或者‘y’。
匹配一个词的边界。一个词的边界就是一个词不被另外一个词跟随的位置或者不是另一个词汇字符前边的位置。注意,一个匹配的词的边界并不包含在匹配的内容中。换句话说,一个匹配的词的边界的内容的长度是0。(不要和[\b]混淆)
例子:
/\bm/匹配“moon”中得‘m’;
/oo\b/并不匹配"moon"中得'oo',因为'oo'被一个词汇字符'n'紧跟着。
/oon\b/匹配"moon"中得'oon',因为'oon'是这个字符串的结束部分。这样他没有被一个词汇字符紧跟着。
/\w\b\w/将不能匹配任何字符串,因为一个单词中的字符永远也不可能被一个非词汇字符和一个词汇字符同时紧跟着。
\cX
匹配一个非单词边界。他匹配一个前后字符都是相同类型的位置:都是单词或者都不是单词。一个字符串的开始和结尾都被认为是非单词。
例如,/\B../匹配"noonday"中得'oo', 而/y\B./匹配"possibly yesterday"中得’ye‘
\B
当X是处于A到Z之间的字符的时候,匹配字符串中的一个控制符。
例如,/\cM/ 匹配字符串中的 control-M (U+000D)。
\d 匹配一个数字。等价于[0-9]。
9.2.3使用插入语
任何正则表达式的插入语都会使这部分匹配的副字符串被记忆。一旦被记忆,这个副字符串就可以被调用于其它用途。
9.3使用正则表达式
使用正则表达式的方法:
1) exec :一个在字符串中执行查找匹配的RegExp方法,它返回一个数组(未匹配到则返回null)。
2) test:一个在字符串中测试是否匹配的RegExp方法,它返回true或false。
3) match:一个在字符串中执行查找匹配的String方法,它返回一个数组或者在未匹配到时返回null。
4) search:一个在字符串中测试匹配的String方法,它返回匹配到的位置索引,或者在失败时返回-1。
5) replace:一个在字符串中执行查找匹配的String方法,并且使用替换字符串替换掉匹配到的子字符串。
6) split:一个使用正则表达式或者一个固定字符串分隔一个字符串,并将分隔后的子字符串存储到数组中的String方法。
9.3.1使用括号的字符串匹配
一个正则表达式模式使用括号,将导致相应的子匹配被记住。
使用括号匹配的子字符串的数量是无限的。返回的数组中保存所有被发现的子匹配。
9.3.2通过标志进行高级搜索
g:全局搜索。
i:不区分大小写搜索。
m:多行搜索。
y:执行“粘性”搜索,匹配从目标字符串的当前位置开始,可以使用y标志
10以索引进行排序的数据集合
10.1数组对象
JavaScript中没有明确的数组数据类型。但是,我们可以通过使用内置Array对象和它的方法对数组进行操作。
10.1.1创建数组
var arr = new Array(element0, element1, ..., elementN);
var arr = Array(element0, element1, ..., elementN);
var arr = [element0, element1, ..., elementN];
创建一个长度不为0,但是又没有任何元素的数组:
var arr = new Array(arrayLength);
var arr = Array(arrayLength);
var arr = [];
arr.length = arrayLength;
如果你希望用单个元素初始化一个数组,而这个元素恰好又是数字(Number),那么你必须使用括号语法。当单个的数字(Number)传递给Array()构造函数时,将会被解释为数组长度,并非单个元素。
如果你需要创建任意类型的单元素数组,安全的方式是使用字面值。或者在向数组添加单个元素之前先创建一个空的数组。
10.1.2填充数组
可以通过给元素赋值来填充数组。
如果你在以上代码中给数组操作符的是一个非整形数值,那么将作为一个代表数组的对象的属性(property)创建,而非作为数组的元素。
可以在创建数组的时候去填充它。
10.1.3引用数组元素
可以通过数组元素的序号去引用这个元素。
10.1.4理解length
长度属性是特殊的,它总是返回最后一个元素的索引值加1。
也可以分配length属性。写一个小于数组元素数量的值会缩短数组,写0会彻底清空数组。
10.1.5遍历数组
foreach()方法提供了遍历数组元素的其他方法。被传递给forEach的函数会在数组的每个元素像上执行一次,元素作为参数传递给该函数。未赋值的值不会在forEach循环迭代。注意:在数组定义时省略的元素不会在forEach遍历时被列出,但是手动赋值为undefined的元素是会被列出的。
10.1.6数组的方法
concat()连接两个数组并返回一个新的数组。
join(ch)将数组的所有元素连接成一个字符串,按指定字符ch分隔。
push()在数组末尾添加一个或多个元素,并返回数组操作后的长度。
pop()从数组移出最后一个元素,并返回该元素。
shift()从数组移出第一个元素,并返回该元素。
unshift()在数组开头添加一个或多个元素,并返回数组的新长度。
slice(start_index,upto_index)从数组提取一个片段,并作为一个新数组返回。
splice(index,count_to_remove,addElement1,addElement2,...)从数组移出一些元素,(可选)并替换它们。
reverse()颠倒数组元素的顺序:第一个变成最后一个,最后一个变成第一个。
sort()给数组元素排序。也可以带一个回调函数来决定怎么比较数组元素。
indexOf(searchElement[,fromIndex])在数组中搜索searchElement 并返回第一个匹配的索引。
lastIndexOf(searchElement[,fromIndex])和 indexOf差不多,但是是从结尾开始,并且是反向搜索。
foreach(callback[,thisObject])在数组每个元素项上执行callback。
map(callback[,thisObject])在数组的每个单元项上执行callback函数,并把返回包含回调函数返回值的新数组。
filter(callback[,thisObject])返回一个包含所有在回调函数上返回为true的元素的新数组。
every(callback[,thisObject])当数组中每一个元素在callback上被返回true时就返回true。
some(callback[,thisObject])只要数组中有一项在callback上被返回true,就返回true。
reduce(callback[,initialValue])使用回调函数 callback(firstValue, secondValue) 把数组列表计算成一个单一值。
reduceRight(callback[,initialValue])和 reduce()相似,但是是从最后一个元素开始的。
10.1.7多维数组
10.1.8数组和正则表达式
当一个数组是一个正则表达式对一个字符串的匹配结果,这个数组返回属性和元素来提供正则匹配的信息。 RegExp.exec(), String.match()和 String.split()的返回值是一个数组。
10.1.9类似数组行为的对象
一些 JavaScript 对象, 例如 document.getElementsByTagName()返回的NodeList或者函数内部可用的 arguments对象,他们表面上看起来,外观和行为像数组,但是不共享他们所有的方法。arguments 对象提供一个length属性,但是不实现 foreach()方法。
Array的原生(prototype)方法可以用来处理类似数组行为的对象。
10.2数组推导式
var numbers = [1, 2, 3, 4];
var doubled = [i * 2 for (i of numbers)];
console.log(doubled); // logs 2,4,6,8
跟下面的map()方法的操作是等价的
var doubled = numbers.map(function(i){return i * 2;});
推导式也可以用来筛选满足条件表达式的元素
var numbers = [1, 2, 3, 21, 22, 30];
var evens = [i for (i of numbers) if (i % 2 === 0)];
console.log(evens); // logs 2,22,30
filter() 也可以达到相同的目的:
var evens = numbers.filter(function(i){return i % 2 === 0;});
map() 和filter() 类型的操作可以被组合(等效)为单个数组推导式。
var numbers = [1, 2, 3, 21, 22, 30];
var doubledEvens = [i * 2 for (i of numbers) if (i % 2 === 0)];
console.log(doubledEvens); // logs 4,44,60
数组推导式隐含了块作用域。新的变量(如例子中的i)类似于是采用let声明的。这意味着他们不能在推导式以外访问。
数组推导式的输入不一定必须是数组; 迭代器和生成器也是可以的。
甚至字符串也可以用来作为输入; 实现filter或者map行为
var str = 'abcdef';
var consonantsOnlyStr = [c for (c of str) if (!(/[aeiouAEIOU]/).test(c)) ].join(''); // 'bcdf'
var interpolatedZeros = [c+'0' for (c of str) ].join(''); // 'a0b0c0d0e0f0'
10.3类型化数组
类型化数组是类数组对象,其提供访问原始二进制数据的机制。
缓冲区和视图:类型化的数组结构
为了实现最大的灵活性和效率,JavaScript类型数组被分解为缓冲(Buffer)和视图(views)。缓冲(由ArrayBuffer实现)是代表数据块的对象,它没有格式可言,并没有提供任何机制来访问其内容。为了访问包含在缓冲区中的内存,您需要使用视图。视图提供了一个上下文,即数据类型、起始偏移量和元素数,这些元素将数据转换为实际类型数组。
ArrayBuffer
ArrayBuffer是一种数据类型,用于表示一个通用的、固定长度的二进制数据缓冲区。你不能直接操纵一个ArrayBuffer中的内容;你需要创建一个数组类型视图或DataView来代表特定格式的缓冲区,并从而实现读写缓冲区的内容。
类型数组视图
类型数组视图具有自描述性的名字,并且提供数据类型信息,例如Int8, Uint32, Float64等等。
11带键的集合
11.1Maps(映射)
11.1.1Map对象
ECMAScript 6 引入了一个新的数据结构来将一个值映射到另一个值。一个Map对象就是一个简单的键值对映射集合,可以按照数据插入时的顺序遍历所有的元素。
11.1.2Object和Map的比较
一般地,objects会被用于将字符串类型映射到数值。Object允许设置键值对、根据键获取值、删除键、检测某个键是否存在。而Map具有更多的优势。
Object的键均为Strings类型,在Map里键可以是任意类型
必须手动计算Object的尺寸,但是可以很容易地获取使用Map的尺寸
Map的遍历遵循元素的插入顺序
Object有原型,所以映射中有一些缺省的键。可以理解为(map = Object.create(null))
这两条提示可以帮你决定是使用Map还是Object:
如果键在运行时才能知道,或者所有的键类型相同,所有的值类型相同,那就使用Object。
如果需要对个别元素进行操作,使用Object。
11.1.3WeakMap对象
WeakMap对象也是键值对的集合。它的键必须是对象类型,值可以是任意类型。它的键被弱保持,也就是说,当其键所指对象没有其他地方引用的时候,它会被GC回收掉。WeakMap提供的接口与Map相同。
与Map对象不同的是,WeakMap的键是不可枚举的。不提供列出其键的方法。列表是否存在取决于垃圾回收器的状态,是不可预知的。
WeakMap对象的一个用例是存储一个对象的私有数据或隐藏实施细节。
11.2Sets(集合)
11.2.1Set对象
Set对象是一组值的集合,这些值是不重复的,可以按照添加顺序来遍历。
11.2.2数组和集合的转换
可以使用Array.from或扩充操作符来完成集合到数组的转换。同样,集合的构造器接受数组作为参数,可以完成从数组到集合的转换。需要重申的是,集合对象中的值不重复,所以数组转换为集合时,所有重复值将会被删除。
11.2.3数组和集合的对比
一般情况下,在JavaScript中使用数组来存储一组元素,而新的集合对象有这些优势:
数组中用于判断元素是否存在的indexOf函数效率低下
集合对象允许根据值删除元素,而数组中必须使用基于下标的splice方法
数组的indexOf方法无法找到NaN值
集合对象存储不重复的值,所以不需要手动处理包含重复值的情况
11.2.4WeakSet对象
WeakSet对象是一组对象的集合。WeakSet中的对象不重复且不可枚举。
与Set对象的主要区别有:
WeakSets中的值必须是对象类型,不可以是别的类型
WeakSet的“weak”指的是,对集合中的对象,如果不存在其他引用,那么该对象将可被垃圾回收。于是不存在一个当前可用对象组成的列表,所以WeakSets不可枚举
WeakSet的用例很有限,比如使用DOM元素作为键来追踪它们而不必担心内存泄漏。
11.3Map的键和Set的值的等值判断
Map的键和Set的值的等值判断都基于:
1) 判断使用与===相似的规则
2) -0和+0相等
3) NaN与自身相等(与===有所不同)
12使用对象
12.1对象和属性
对象中未赋值的属性的值为undefined
JavaScript 对象的属性也可以通过方括号访问或者设置
一个对象的属性名可以是任何有效的 JavaScript 字符串,或者可以被转换为字符串的任何类型,包括空字符串。然而,一个属性的名称如果不是一个有效的 JavaScript 标识符(例如,一个由空格或连字符,或者以数字开头的属性名),就只能通过方括号标记访问。这个标记法在属性名称是动态判定(属性名只有到运行时才能判定)时非常有用。
也可以通过存储在变量中的字符串来访问属性
可以在for...in语句中使用方括号标记以枚举一个对象的所有属性。
12.2枚举一个对象的所有属性
有三种原生的方法用于列出或枚举对象的属性:
1) for...in循环:依次访问一个对象及其原型链中所有可枚举的属性。
2) Object.keys(o):返回一个对象 o 自身包含(不包括原型中)的所有属性的名称的数组。
Object.getOwnPropertyNames(o):返回一个数组,它包含了对象 o 所有拥有的属性(无论是否可枚举)的名称。
12.3创建新对象
12.3.1使用对象初始化器
通过字面值创建对象。
12.3.2使用构造函数
可以通过两步来创建对象:
通过创建一个构造函数来定义对象的类型。首字母大写是非常普遍而且很恰当的惯用法。
通过 new 创建对象实例。
注意通过使用 this 将传入函数的值赋给对象的属性。
12.3.3使用Object.create方法
对象也可以用Object.create方法创建。该方法非常有用,因为它允许你为创建的对象选择其原型对象,而不用定义一个构造函数。
12.3.4继承
所有的 JavaScript 对象继承于至少一个对象。被继承的对象被称作原型,并且继承的属性可通过构造函数的 prototype 对象找到。
12.3.5对象属性索引
在 JavaScript 1.0 中,你可以通过名称或序号访问一个属性。但是在 JavaScript 1.1 及之后版本中,如果你最初使用名称定义了一个属性,则你必须通过名称来访问它;而如果你最初使用序号来定义一个属性,则你必须通过索引来访问它。
12.3.6为对象类型定义属性
你可以通过 prototype 属性为之前定义的对象类型增加属性。这为该类型的所有对象,而不是仅仅一个对象增加了一个属性。
12.3.7定义方法
一个方法 是关联到某个对象的函数,或者简单地说,一个方法是一个值为某个函数的对象属性。定义方法就像定义普通的函数,除了它们必须被赋给对象的某个属性。
12.3.8通过this引用对象
JavaScript 有一个特殊的关键字 this,它可以在方法中使用以指代当前对象。
12.3.9定义getter和setter
一个getter是一个获取某个特定属性的值的方法。一个setter是一个设定某个属性的值的方法。你可以为预定义的或用户定义的对象定义 getter 和 setter 以支持新增的属性。定义 getter 和 setter 的语法采用对象字面量语法。
12.3.10删除属性
以用delete 操作符删除一个不是继承而来的属性。
如果一个全局变量不是用 var 关键字声明的话,你也可以用 delete 删除它。
12.3.11比较对象
在 JavaScript 中 objects 是一种引用类型。两个独立声明的对象永远也不会相等,即使他们有相同的属性,只有在比较一个对象和这个对象的引用时,才会返回true。
13对象模型的细节
13.1基于类 vs 基于原型的语言
基于类的面向对象语言,比如 Java 和 C++,是构建在两个不同实体的概念之上的:即类和实例。
基于原型的语言(如 JavaScript)并不存在这种区别:它只有对象。基于原型的语言具有所谓原型对象(prototypical object)的概念。原型对象可以作为一个模板,新对象可以从中获得原始的属性。任何对象都可以指定其自身的属性,既可以是创建时也可以在运行时创建。而且,任何对象都可以作为另一个对象的原型(prototype),从而允许后者共享前者的属性。
13.1.1定义类
在基于类的语言中,需要专门的类定义符(class definition)定义类。在定义类时,允许定义特殊的方法,称为构造器(constructor),来创建该类的实例。在构造器方法中,可以指定实例的属性的初始值以及一些其他的操作。您可以通过将new 操作符和构造器方法结合来创建类的实例。
JavaScript 也遵循类似的模型,但却不同于基于类的语言。在 JavaScript 中你只需要定义构造函数来创建具有一组特定的初始属性和属性值的对象。任何 JavaScript 函数都可以用作构造器。 也可以使用 new 操作符和构造函数来创建一个新对象。
13.1.2子类和继承
基于类的语言是通过对类的定义中构建类的层级结构的。在类定义中,可以指定新的类是一个现存的类的子类。子类将继承父类的全部属性,并可以添加新的属性或者修改继承的属性。
JavaScript 通过将构造器函数与原型对象相关联的方式来实现继承。
13.1.3添加和移除属性
在基于类的语言中,通常在编译时创建类,然后在编译时或者运行时对类的实例进行实例化。一旦定义了类,无法对类的属性进行更改。然而,在 JavaScript 中,允许运行时添加或者移除任何对象的属性。如果您为一个对象中添加了一个属性,而这个对象又作为其它对象的原型,则以该对象作为原型的所有其它对象也将获得该属性。
13.2对象的属性
在访问一个对象的属性时,JavaScript 将执行下面的步骤:
1) 检查本地值是否存在。如果存在,返回该值。
2) 如果本地值不存在,检查原型链(通过 __proto__ 属性)。
3) 如果原型链中的某个对象具有指定属性的值,则返回该值。
4) 如果这样的属性不存在,则对象没有该属性。
13.3属性的继承
(1)判断实例的关系
JavaScript 的属性查找机制首先在对象自身的属性中查找,如果指定的属性名称没有找到,将在对象的特殊属性 __proto__ 中查找。这个过程是递归的;被称为“在原型链中查找”。
特殊的 __proto__ 属性是在构建对象时设置的;设置为构造器的 prototype 属性的值。
个对象都有一个 __proto__ 对象属性(除了 Object);每个函数都有一个 prototype 对象属性。因此,通过“原型继承(prototype inheritance)”,对象与其它对象之间形成关系。通过比较对象的 __proto__ 属性和函数的 prototype 属性可以检测对象的继承关系。JavaScript 提供了便捷方法:instanceof 操作符可以用来将一个对象和一个函数做检测,如果对象继承自函数的原型,则该操作符返回真。
(2)没有多继承
某些面向对象语言支持多重继承。也就是说,对象可以从无关的多个父对象中继承属性和属性值。JavaScript 不支持多重继承。
JavaScript 属性值的继承是在运行时通过检索对象的原型链来实现的。因为对象只有一个原型与之关联,所以 JavaScript 无法动态地从多个原型链中继承。
在 JavaScript 中,可以在构造器函数中调用多个其它的构造器函数。这一点造成了多重继承的假象。
14迭代器和生成器
14.1迭代器
一个迭代器对象 ,知道如何每次访问集合中的一项, 并记录它的当前在序列中所在的位置。在 JavaScript 中 迭代器是一个对象,它提供了一个 next() 方法,返回序列中的下一项。这个方法返回包含done和value两个属性的对象。
迭代器对象创建后,可以反复调用 next()使用。
一旦初始化,next()方法可以用来依次访问对象中的键-值。
一个for...in循环结构可以直接取代next()方法的调用。 当StopIteration exception异常抛出时这个循环会自动终止。
假如我们只想遍历一个对象的 keys 时,我们可以通过把第二个参数设置为 true 来实现。
使用 Iterator() 来访问对象内容的优点是对于那些已经加入对象的自定义属性(不用管属性名,一股脑的访问,或者只访问属性,上面就是这样)。它的原型不会包含在序列中。
Iterator() 也可以用于数组。
基于对象,通过传递true作为第二个参数,将会导致迭代结果发生数组索引上益。
也可以指定块作用域变量(let是个javascript1.7的关键字,可以使用 let 建立只存在于 for 循环上下文中的变量)包括索引和值(index and value)在for…in循环中使用let关键字和非结构化赋值。
14.2定义自定义迭代器
某些对象遍历集合的项目时应该使用特定的方式遍历。
1) 遍历一个序列对象时应该是一个接着一个。
2) 遍历一棵树时应该使用深度优先或者广度优先方式。
3) 遍历一个数据库查询结果对象时应该是一条一条地遍历,即使整个结果并没有被加载到一个数组中。
4) 一个迭代器在一个无限的数学序列(如斐波那契序列)应该能够一个一个地返回结果而不创建一个无限长度的数据结构。
JavaScript可以让你编写代码来表示自定义迭代逻辑并链接到一个对象。
14.3生成器:一个更好的方法来构建遍历器
虽然迭代器是一个有用的工具,但是由于需要显式地维持他们的内部状态,所以需要仔细地规划他们的构造(看得我眼花呀)。生成器给你提供了一个强大的选择:它允许你通过写一个可以保存自己状态的的简单函数来定义一个迭代算法。
一个生成器其实是一种特殊类型的函数(这个函数作为一个为迭代器工作的工厂),一个函数如果它里面包含了一个或一个以上的yield表达式,那么这个函数就成为一个生成器了。
当一个生成器函数被一个函数体调用时并不会马上执行;相反,它会返回一个generator-iterator对象。每次调用generator-iterator的next()方法将会执行这个函数体至下一个yield表达式并且返回一个结果。当函数执行完或者执行到一个return时抛出一个StopIteration exception异常。
一个生成器函数可以直接用作为一个类的__iterator__方法,大大的减少了创建一个自定义迭代器的代码量
并不是所有的生成器都会终止;它也可以创建一个无穷序列的生成器。
生成器可以有参数,但被限制在函数第一次调用时。生成器可以通过一个return语句来终止(因为他们将抛出一个topIteration exception)。
14.4高级生成器
生成器按照要求产生values,这样可以高效的表示序列,这点对于计算机来说很重要。
除了next()方法,generator-iterator对象也有一个send()方法可以用来修改生成器的内部状态。传递一个值给send()将被视为最后一个yield表达式的结果(生成器暂停)。你必须在你使用send(参数)前通过调用next()来启动生成器。
可以调用 throw 方法并且传递一个它应该抛出的异常值来强制生成器抛出一个异常。此异常将从当前上下文抛出并暂停生成器,类似当前的 yield 执行,只不过换成了 throw value 语句。
如果在抛出异常的处理过程中没有遇到 yield ,该异常将会被传递直到调用 throw() 方法,并且随后调用 next() 将会导致 StopIteration 异常被抛出。
生成器拥有一个 close() 方法来强制生成器结束。结束一个生成器会产生如下影响:
1) 所有生成器中有效的 finally 字句将会执行
2) 如果 finally 字句抛出了除 StopIteration 以外的任何异常,该异常将会被传递到 close() 方法的调用者
3) 生成器会终止
14.5生成器表达式
数组推导式的一个明显缺点是,它们会导致整个数组在内存中构造。当输入到推导式的本身是个小数组时它的开销是微不足道的--但是,当输入数组很大或者创建一个新的昂贵(或者是无限的)数组生成器时就可能出现问题。
生成器允许对序列延迟计算(lazy computation),在需要时按需计算元素。生成器表达式在句法上几乎和数组推导式相同--它用圆括号来代替方括号(而且用 for...in 代替 for each...in)--但是它创建一个生成器而不是数组,这样就可以延迟计算。你可以把它想象成创建生成器的简短语法。