第1章 什么是JavaScript
1.在此之前,要验证某个必填字段是否已填写,或者某个输入的值是否有效,需要与服务器的一次往返通信
从简单的输入验证脚本到强大的编程语言,JavaScript的崛起没有任何人预测到。它很简单,学会只要几分钟;它又很复杂,掌握它要很多年。要真正学好用好JavaScript,理解其本质、历史及局限性是非常重要的。
2.规范其语法或特性的标准
3.ECMAScript,脚本语言标准
4.各家浏览器均以ECMAScript作为自己JavaScript实现的依据,虽然具体实现各有不同
5.JavaScript:核心(ECMAScript),文档对象模型(DOM),浏览器对象模型(BOM)
6.ECMAScript符合性
7.五大浏览器IE,Firefox,Safari,Chrome和Opera
8.文档对象模型(DOM,Document Object Model)是一个应用编程接口(API),用于在HTML中使用扩展的XML。DOM将整个页面抽象为一组分层节点。HTML或XML页面的每个组成部分都是一种节点,包含不同的数据。
9.注意 DOM并非只能通过JavaScript访问,而且确实被其他很多语言实现了。不过对于浏览器来说,DOM就是使用ECMAScript实现的,如今已经成为JavaScript语言的一大组成部分
10.浏览器对象模型(BOM)API,用于支持访问和奥做浏览器的窗口。使用BOM,开发者可以操控浏览器显示页面之外的部分。
而BOM真正独一无二的地方,当然也是问题最多的地方,就是它是唯一一个没有相关标准的JavaScript实现。HTML5改变了这个局面,这个版本的HTML以正式规范的形式覆盖了尽可能多的BOM特性。
11.JavaScript是一门用来与网页交互的脚本语言
第2章 HTML中的JavaScript
1.在<script>元素中的代码被计算完成之前,页面的其余内容不会被加载,也不会被显示
在使用行内JavaScript代码时,要注意代码中不能出现字符串</script>会被当做结束标签,需使用转义字符<\/script>
2.与解释行内JavaScript一样,在解释外部JavaScript文件时,页面也会阻塞。(阻塞时间也包含下载文件的时间。)在XHTML文档中,可以忽略结束标签
3.服务器经常会根据文件扩展来确定响应的正确MIME类型。如果不打算使用.js扩展名,一定要确保服务器能返回正确的MIME类型
4.使用了src属性的<script>元素不应该再在<script>和</script>标签中再包含其他JavaScript代码。如果两者都提供的话,则浏览器只会下载并执行脚本文件,从而忽略行内代码。
5.<script>元素的src属性可以是一个完整的URL,而且这个URL指向的资源可以跟包含它的HTML页面不在同一个域中,
浏览器在解析这个资源时,会向src属性指定的路径发送一个GET请求,以获得相应资源,假定是一个JavaScript文件。这个初始的请求不受浏览器同源策略限制,但返回并被执行的JavaScript则受限制。当然,这个请求仍然受父页面HTTP/HTTPS协议的限制。
6.不管包含的是什么代码,浏览器都会按照<script>在页面中出现的顺序依次解释它们,前提是它们没有使用defer和async属性。
7.把所有JavaScript文件都放在<head>里,也就意味着必须把所有JavaScript代码都下载、解析和解释完成后,才能开始渲染页面(页面在浏览器解析到<body>的起始标签时开始渲染)。对于需要很多JavaScript的页面,这回导致页面渲染的明显延迟,在此期间浏览器窗口完全空白。为解决这个问题,现代Web应用程序通常将所有JavaScript引用放在<body>元素中的页面内容后面
8.defer(推迟执行脚本):这个属性表示脚本在执行的时候不会改变页面的结构,也就是说,脚本会被延迟到整个页面都解析完毕后再运行。因此,在<script>元素上设置defer属性,相当于告诉浏览器立即下载,但延迟执行
即使将<script>元素包含在页面的<head>中,但它们会在浏览器解析到结束的</html>
9.defer属性只对外部脚本文件才有效。这是HTML5中明确规定的,因此支持HTML5的浏览器会忽略行内脚本的defer属性
对于XHTML文档,指定defer属性时应该写成defer="defer".
10.async(异步执行脚本),给脚本添加async属性的目的是告诉浏览器,不必等脚本下载和执行完后再加载页面,同样也不必等到该异步脚本下载和执行后再加载其他脚本。正因为如此,异步脚本不应该在加载期间修改DOM
异步脚本保证会在页面的load事件前执行,但可能会在DOMContentLoaded之前或之后。使用async也会告诉页面你不会使用document.write,不过好的web开发实践根本就不推荐使用这个方法
11.动态加载脚本:
let script = document.creatElement('script');
script.src = ' .js';
document.head.appendChild(script);
默认情况下,这种方式创建的<script>元素是以异步方式加载的,相当于添加了async属性。但不是所有浏览器都支持async属性
因此统一动态脚本的加载行为,可以明确将其设置为同步加载
script.async = false;
以这种方式获取的资源对浏览器预加载器是不可见的。这回严重影响它们在资源获取队列中的优先级。根据应用程序的工作方式以及怎么使用,这种方式可能会严重影响性能。可在文档头部显式声明它们:
<link rel="preload" href=" .js">
12.可扩展超文本标记语言(XHRML,Extensible HyperText Markup Language)是将HTML作为XML的应用重新包装的结果。与HTML不同,在XHTML中使用JavaScript必须指定type属性且值为text/javascript
13.<![CDATA]]>,这种格式适用于所有现代浏览器。虽然有点黑科技的味道,但它可以通过XHTML验证,而且对XHTML之前的浏览器也能优雅地降级。
14.注意:XHTML模式会在页面的MIME类型被指定为"application/xhtml+xml"时触发。并不是所有浏览器都支持以这种方式送达的XHTML.
15.外部文件<script>:可维护性,缓存(浏览器会根据特定的设置缓存所有外部链接的JavaScript文件,这意味着如果两个页面都用到同一个文件,则该文件只需下载一次。着最终意味着页面加载更快)适应未来(包含外部JavaScript文件的语法在HTML和XHTML中是一样的)
16.在配置浏览器请求外部文件时,要重点考虑的一点是它们会占用多少带宽。在SPDY/HTTP2中,预请求的消耗已显著降低,以轻量,独立JavaScript组件形式向客户端送达脚本更具优势
17.IE5.5发明了文档模式的概念,即可以使用doctype切换文档模式。混杂模式,标准模式*虽然这两种模式的主要区别只体现在通过CSS渲染的内容方面,但对JavaScript也有一些关联影响,或称为副作用),除此之外,还有准标准模式,
18.准标准模式通过过渡性文档类型(Transitional)和框架集文档类型(Frameset)来触发
19.<noscript>元素出现,被用于给不支持JavaScript的浏览器提供替代内容
其可以包含任何可以出现在<body>中的HTML元素,<script>除外
两种情况(浏览器不支持脚本,浏览器对脚本的支持被关闭)
页面优雅降级的处理方案
第3章 语法基础
1.任何语言的核心所描述的都是这门语言在最基本的层面上如何工作,涉及语法,操作符,数据类型以及内置功能,在此基础之上才可以构建复杂的解决方案。
2.ECMAScript的语法很大程度上借鉴了C语言和其他类C语言,如Java和Perl
3.ECMAScript区分大小写
4.Unicode,统一码
5.关键字、保留字、true、false和null不能作为标识符。
6.单行注释以两个斜杠字符开头
块注释以一个斜杠和一个星号(/*)开头
7.要对整个脚本启用严格模式,在脚本开头加上一行:"use strict";
其实是一个预处理指令,选择这种语法形式的目的是不破坏ECMAScipt 3语法
也可放在函数体开头
8.省略分号意味着由解析器确定语句在哪里结尾
记着加分号有助于防止省略造成的问题,比如可以避免输入内容不完整。此外,加分号也便于开发者通过删除空行来压缩代码(如果没有结尾的分号,只删除空行,则会导致语法错误)。加分号也有助于在某些情况下提升性能,因为解析器会尝试在合适的位置补上分号以纠正语法错误
9.一般来说,最好还是不要使用关键字和保留字作为标识符和属性名,以确保兼容过去和未来的ECMAScript版本
10.变量:
ECMAScript变量是松散类型的,意思是变量可以用于保存任何类型的数据。每个变量只不过是一个用于保存任意值的命名占位符。
11.var关键字:
var message;
不初始化的情况下,变量会保存一个特殊值undefined
var message = "hi";
像这样初始化变量不会将它标识为字符串类型,只是一个简单的赋值而已。随后,不仅可以改变保存的值,也可以改变值的类型
12.使用var操作符定义的变量会成为包含它的函数的局部变量。比如,使用var在一个函数内部定义一个变量,就意味着该变量将在函数退出时被销毁
13.在函数内定义变量时省略var操作符,可以创建一个全局变量
14.在局部作用域中定义的全局变量很难维护,也会造成困惑
在严格模式下,如果像这样给未声明的变量赋值,则会导致抛出ReferenceError
15.在严格模式下,不能定义名为eval和arguments的变量,否则会导致语法错误
16.var 声明提升:
function foo() {
console.log(age);
var age = 26;
}
foo();
不会报错,使用var关键字声明的变量会自动提升到函数作用域顶部
等价于
function foo() {
var age;
console.log(age);
age =26;
}
foo();
这就是所谓的“提升”,也就是把所有变量声明都拉到函数作用域的顶部。此外,反复多次使用var声明同一个变量也没有问题
17.let声明的范围是块作用域,而var声明的范围是函数作用域
另一个重要的区别,就是let声明的变量泵不会在作用域中被提升
18.块作用域是函数作用域的子集,因此适用于var的作用域限制同样也适用于let
let不允许同一个块作用域中出现冗余声明
19.JavaScript引擎会记录用于变量声明的标识符及其所在的块作用域,因此嵌套使用相同的标识符不会报错,而这是因为同一个块中没有重复声明
指出变量在相关作用域如何存在
20.在解析代码时,JavaScript引擎也会注意出现在块后面的let声明,只不过在此之前不能以任何方式来引用未声明的变量。在let声明之前的执行瞬间被称为“暂时性死区"(temporal dead zone),在此阶段引用任何后面才声明的变量都会抛出ReferenceErroe
21.全局声明:与var关键字不同,使用let在全局作用域中声明的变量不会成为window对象的属性(var 声明的变量则会)
不过,let声明仍然是在全局作用域中发生的,相应变量会在页面的生命周期内存续。因此,为了避免SyntaxErroe,必须确保页面不会重复声明同一个变量。
22.条件声明:
在使用var声明变量时,由于声明会被提升,JavaScript引擎会自动将多余的声明在作用域顶部合并为一个声明。因此let的作用域是块,所以不可能检查前面是否已经使用let声明过同名变量,同时也就不可能在没有声明的情况下声明它。
23.注意:不能使用let进行条件式声明是件好事,因为条件声明是一种反模式,它让程序变得更难理解。如果你发现自己在使用这个模式,那一定有更好的替代方式。
24.for循环中的let声明:
在let出现之前,for循环定义的迭代变量会渗透到循环体外部
在使用var的时候,最常见的问题就是对迭代变量的奇特声明和修改
25.const声明:
const的行为与let基本相同,唯一一个重要的区别是用它声明变量时必须同时初始化变量,且尝试修改const声明的变量会导致运行时错误
26.const声明的限制只适用于它指向的变量的引用。换句话说,如果const变量引用的是一个对象,那么修改这个对象内部的属性并不违反const的限制
JavaScript引擎会为for循环中的let声明分别创建独立的变量实例,虽然const变量跟let变量很相似,但是不能用const来声明迭代变量(因为迭代变量会自增)
27.用const声明一个不会被修改的for循环变量,每次迭代只是创建一个新变量。这对for-of和for-in循环特别有意义
28.声明风格及最佳实践:
一.不使用var
限制自己只使用let和const有助于提升代码质量,因为变量有了明确的作用域,声明位置,以及不变的值
二.const优先,let次之
使用const声明可以让浏览器运行时强制保持变量不变,也可以让静态代码分析工具提前发现不合法的赋值操作。因此,很多开发者认为应该优先使用const来声明变量,只在提前知道未来会有修改时,再使用let。这样可以让开发者更有信息地推断某些变量的值永远不会变,同时也能迅速发现因意外赋值导致的非预期行为
29.数据类型:
ESMAScript有6中简单数据类型(也称为原始类型):Undefined,Null,Boolean,Number,String和Symbol.
Symbol(符号)是ECMAScript6新增的。还有一种复杂数据类型叫Object(对象)。Object是一种无序名值对的集合。
ECMAScript的数据类型很灵活,一种数据类型可以当做多种数据类型来使用
30.typeof操作符:
因为ECMAScript的类型系统是松散的,所以需要一种手段来确定任意变量的数据类型。typeop操作符就是为此而生的。
undefined:未定义 boolean:布尔值 string:字符串 number:数值 object:对象(而不是函数)或null
function:函数 symbol:符号
31.注意:因为typeop是一个操作数而不是函数,所以不需要参数(但可以使用参数)
32.注意:typeof在某些情况下返回的结果可能会让人费解,但技术上讲还是正确的。比如,调用typeof null返回的是"object"。这是因为特殊值null被认为是一个对空对象的引用
33.注意:严格来讲,函数在ECMAScript中被认为是对象,并不代表一种数据类型。可是,函数也有自己特殊的属性。为此,就有必要通过typeop操作符来区分函数和其他对象。
34.undefined类型:
当使用var或let声明了变量但没有初始化时,就相当于给变量赋予了undefined值
35.默认情况下,任何未经初始化的变量都会取得undefined值
36.注意:一般来说,永远不用显式地给某个变量设置undefined值。字面值undefined主要用于比较,而且在ECMA-262第3版之前是不存在的。增加这个特殊值的目的就是为了正式明确空对象指针(null)和未初始化变量的区别。
37.声明了没有初始化,值为undefined
没有声明则报错
38.对未声明的变量,只能执行一个有用的操作,就是对它调用typeop。(对未声明的变量调用delete也不会报错,但这个操作没什么用,实际上在严格模式下会抛出错误)
39.在对未初始化的变量调用typeof时,返回的结果是"undefined",但对未声明的变量调用它时,返回的结果还是"undefined"
无论是声明还是未声明,typeop返回的都是字符串"undefined".逻辑上讲这是对的,因为虽然严格来讲这两个变量存在根本性差异,但它们都无法执行实际操作
40.注意:即使未初始化的变量会被自动赋予undefined值,但我们仍然建议在声明变量的同时进行初始化。这样,当typeop返回"undefined"时,你就会知道那是因为给定的变量尚未声明,而不是声明了但未初始化。
41.undefined是一个假值。因此,如果需要,可以用更简洁的方式检测它。不过要记住,也有很多其他可能的同样是价值。所以一定要明确自己想检测的就是undefined这个字面值,而不仅仅是假值
42.null类型:
逻辑上讲,null值表示一个空对象指针,这也是给typeop传一个null会返回"object"的原因
在定义将来要保存对象值的变量时,建议使用null来初始化,不要使用其他值。这样,只要检查这个变量的值是不是null就可以知道这个变量是否在后来被重新赋予了一个对象的引用
43.undefined值是由null值派生而来的
用等于操作符(==)比较null和undefined始终返回true.但要注意,这个操作符会为了比较而转换它的操作数
44.Boolean类型:
Boolean(布尔值)类型是ECMAScript中使用最频繁的类型之一,有两个字面值:true和false。这两个布尔值不同于数值,因此true不等于1,false不等于0,True和False是有效的标识符,但不是布尔值
46.if等流控制语句会自动执行其他类型值到布尔值的转换
由于存在这种自动转换,理解流控制语句中使用的是什么变量就变得非常重要。错误地使用对象而不是布尔值会明显改变应用程序的执行流
47.Number类型:
表示整数和浮点值(在某些语言中也叫双精度值),不同的数值类型相应地也有不同的数值字面量格式
48.最基本的数值字面量格式是十进制整数,直接写出来即可
如果字面量中包含的数字超出了应有的范围,就会忽略前缀的零,后面的数字序列会被当成十进制数
49.八进制字面量在严格模式下是无效的,会导致JavaScript引擎抛出语法错误
50.浮点数:
要定义浮点数,数值中必须包含小数点,而且小数点后面必须至少有一个数字。虽然小数点前面不是必须有整数,但推荐加上
51.因为存储浮点值使用的内存空间是存储整数值的两倍,所以ECMAScript总是想方设法把值转换为整数。在小数点后面没有数字的情况下,数值就会变成整数
52.浮点值的精确度最高可达17位小数,但在算术计算中远不如整数精确。例如,0.1加0.2得到的不是0.3,而是0.300 000 000 000 000 04.由于这种微小的舍入错误,导致很难测试特定的浮点值
if (a +b == 0.3),如果两个数值分别是0.05和0.25那没问题。0.1,0.2测试将失败,因此永远不要测试某个特定的浮点数。
53.注意:之所以存在这种舍入错误,是因为使用了IEEE 754数值,这种错误并非ECMAScript所独有。其他使用相同格式的语言也有这个问题。
54.ECMAScript可以表示的最小数值保存在Number.MIN_VALUE中,这个值在多数浏览器中是5e-324
可以表示的最大数值保存在Number.MAX_VALUE中,这个值在多数浏览器中是1.797 693 134 862 315 7e+308
55.如果某个计算得到的数值结果超出了JavaScript可以表示的范围,那么这个数值会被自动转换为一个特殊的Infinity(无穷大)值
56.NaN:
有一个特殊的数值叫NaN,意思是”不是数值“(Not a Number),用于表示本来要返回数值的操作失败了(而不是抛出错误)。比如,用0除任意数值在其他语言中通常都会导致错误,从而中止代码执行。但在ECMAScript中,0,+0或-0相除会返回NaN
如果分子是非0值,分母是有符号0或无符号0,则会返回Infinity或-Infinity
57.NaN有几个独特的属性。首先,任何涉及NaN的操作始终返回NaN(如NaN/10),在连续多步计算时这可能是个问题。其次,NaN不等于包括NaN在内的任何值
58.数值转换:有3个函数可以将非数值转换为数值:Number(),parseInt()和parseFloat().Number是转型函数,可用于任何数据类型。后两个函数主要用于将字符串转换为数值。
59.注意:isNaN()可以用于测试对象。首先会调用对象的valueOf()方法,然后再确定返回的值是否可以转换为数组。如果不能,再调用toString()方法,并测试其返回值。这通常是ECMAScript内置函数和操作符的工作方式
60.一元加操作符与Number()函数遵循相同的转换规则
61.通常在需要得到整数时可以优先使用parseInt()函数。parseInt()函数更专注于字符串是否包含数值模式。字符串最前面的空格会被忽略,从第一个非空格字符开始转换。
比如:”123blue"会被转换为1234,因为"blue"会被完全忽略,"22.5"会被转换为22,因为小数点不是有效地整数字符。
62.parseFloat()函数的工作方式跟parseInt()函数类似,都是从位置0开始检测每个字符。同样,它也是解析到字符串末尾或者解析到一个无效的浮点数值字符为止。这意味着第一次出现的小数点是有效的,但第二次出现的小数点就无效了,此时字符串的剩余字符否会被忽略
63.psrseFloat()函数的另一个不同之处在于,它始终忽略字符串开头的零。这个函数能识别前面讨论的所有浮点格式,以及十进制格式(开头的零始终被忽略)
64.ECMAScript语法中表示字符串的引号没有区别。
要注意的是,以某种引号作为字符串开头,必须仍然以该种引号作为字符串结尾。
65.注意:如果字符串中包含双字节字符,那么length属性返回的值可能不是准确的字符数。
66.字符串的特点:
ECMAScript中的字符串是不可变的(immutable),意思是一旦创建,它们的值就不能变了。要修改某个变量中的字符串值,必须先销毁原始的字符串,然后将包含新值的另一个字符串保存到该变量
67.转换为字符串:
有两种方式把一个值转换为字符串。首先是使用几乎所有值都有的toString()方法。这个方法唯一的用途就是返回当前值的字符串等价物。
68.toString()方法可见于数值,布尔值,对象和字符串值。(没错,字符串值也有toString()方法,该方法只是简单地返回自身的一个副本)null和undefined值没有toString()方法
多数情况下,toString()不接受任何参数。不过,在对数值调用这个方法时,toString()可以接受一个底数参数,即以什么底数来输出数值的字符串表示。
69.因为null和undefined没有toString()方法,所以String()方法就直接返回了这两个值的字面量文本
70.注意:用加号操作符给一个值加上一个空字符串“”也可以将其转换为字符串
71.模板字面量:
ECMAScript6新增了使用模板字面量定义字符串的能力。与使用单引号或双引号不同,模板字面量保留换行字符,可以跨行定义字符串
72.格式正确的模板字符串看起来可能会缩进不当
73.字符串插值:
模板字面量最常用的一个特性是支持字符串插值,也就是可以在一个连续定义中插入一个或多个值。技术上讲,模板字面量不是字符串,而是一种特殊的JavaScript句法表达式,只不过求值后得到的是字符串。模板字面量在定义时立即求值并转换为字符串实例,任何插入的变量也会从它们最接近的作用域中取值。
字符串插值通过在${}中使用一个JavaScript表达式实现:
value + 'to the' +expent ;
'${value} to ths ${exponent} power is
所有插入的值都会使用toString()强制转型为字符串,而且任何JavaScript表达式都可以用于插值。嵌套的模板字符串无须转义
74.Symbol类型:
Symbol(符号)是ECMAScript6新增的数据类型。符号是原始值,且符号实例是唯一,不可变的。符号的用途是确保对象属性使用唯一标识符,不会发生属性冲突的危险。
符号就是用来创建唯一记号,进而同作非字符串形式的对象属性
75.符号的基本用法:
符号需要使用Symbol()函数初始化。因为符号本身是原始类型,所以typeop操作符对符号返回symbol
符号没有字面量语法,这也是它们发挥作用的关键。按照规范,你只要创建Symbol()实例并将其用作对象的新属性,就可以保证它不会覆盖已有的对象属性,无论是符号属性还是字符串属性
最重要的是,symbol()函数不能与new关键字一起作为构造函数使用。这样做是为了避免创建符号包装对象,像使用Boolean,String或Number那样,它们都支持构造函数且可用于初始化包含原始值的包装对象
76.使用全局符号注册表:
如果运行时的不同部分需要共享和重用符号实例,那么可以用一个字符串作为键,在全局符号注册表中创建并重用符号
77.Symbol.for全局符号,Symbol普通符号
78.使用符号作为属性:
凡是可以使用字符串或数值作为属性的地方,都可以使用符号。这就包括了对象字面量属性和Object.defineProperty()/Object.defineProperties()定义的属性。对象字面量只能在计算属性语法中使用符号作为属性
79.因为符号属性是对内存中符号的一个引用,所以直接创建并用作属性的符号不会丢失。但是,如果没有显式地保存对这些属性的引用,那么必须遍历对象的所有符号属性才能找到相应的属性键
80.这些内置符号最重要的用途之一是重新定义它们,从而改变原生结构的行为
81.注意:在提到ECMAScript规范时,经常会引用符号在规范中的名称,前缀为@@。比如,@@iterator指的就是Symbol.iterator
82.Symbol.asyncIterator:
根据ECMAScript规范,这个符号作为一个属性表示“一个方法,该方法返回对象默认的AsyncIterator。由for-await-of语句使用”。换句话说,这个符号表示实现异步迭代器API的函数
83.注意:Symbol.asyncIterator是ES2018规范定义的,因此只有版本非常新的浏览器支持它。
84.在ES6中,instanceof操作符会使用Symbol.hasInstance函数来确定关系。以Symbol.hasInstance为键的函数会执行同样的操作,只是操作数对调了以下
Symbol.hasInstance,这个属性定义在Function的原型上,因此默认在所有函数和类上都可以调用。由于instanceof操作符会在原型链上寻找这个属性定义,就跟在原型链上寻找其他属性一样,因此可以在继承的类上通过静态方法重新定义这个函数
85.ES6中的Array.prototype.concat()方法会根据接收到的对象类型选择如何将一个类数组对象凭借成数组实例。覆盖Symbol.isConcat-Spreadable的值可以修改这个行为
数组对象默认情况下会被打平到已有的数组,false或假值会导致整个对象被追加到数组末尾。类数组对象默认情况下会被追加到数组末尾,true或真值会导致这个类数组对象被打平到数组实例。其他不是类数组对象的对象在Symbol.isConcatSpreadable被设置为true的情况下将被忽略
86.Symbol.match:
String.prototype.match()方法会使用以Symbol.match为键的函数来对正则表达式求值。正则表达式的原型上默认有这个函数的定义,因此所有正则表达式实例默认是这个String方法的有效参数
87.ECMAScript只要求在给构造函数提供参数时使用括号。如果没有参数,完全可以省略括号(不推荐)
88.无论使用前缀递增还是前缀递减操作符,变量的值都会在语句被求值之前改变(在计算机科学中,这通常被称为具有副作用)
89.注意:默认情况下,ECMAScript中的所有整数都表示为有符号数。不过,确实存在无符号整数。对无符号整数来说,第32位不表示符号,因为只有正值。无符号整数比有符号整数的范围更大,因为符号位被用来表示数值了。
90.指数操作符**
91.警告:由于with语句影响性能且难于调试其中的代码,通常不推荐在产品代码中使用with语句
92.switch语句在比较每个条件的值时会使用全等操作符,因此不会强制转换数据类型(比如,字符串"10"不等于数值10)
93.函数对任何语言来说都是核心组件,因为它们可以封装语句,然后在任何地方,任何时间执行。
ECMAScript中的函数使用function关键字声明,后跟一组参数,然后是函数体
94.注意:最佳实践是函数要么返回值,要么不返回值。只在某个条件下返回值的函数会带来麻烦,尤其是调试时。
95.与其他语言不同,ECMAScript不区分整数和浮点值,只有Number一种数值数据类型
Object是一种复杂数据类型,它是这门语言中所有对象的基类。
96.ECMAScript中的函数与其他语言中的函数不一样:
·不需要指定函数的返回值,因为任何函数可以在任何时候返回任何值
·不指定返回值的函数实际上会返回特殊值undefined
第4章 变量、作用域与内存
1.ECMAScript变量可以包含两种不同类型的数据:原始值和引用值。原始值(primitive value)就是最简单的数据,引用值(reference value)则是由多个值构成的对象
保存原始值的变量是按值(by value)访问的,因此我们操作的就是存储在变量中的实际值
在操作对象时,实际上操作的是对该对象的引用(reference)而非实际的对象本身。为此,保存引用值的变量是按引用访问的。
2.注意:在很多语言中,字符串是使用对象表示的,因此被认为是引用类型。ECMAScript打破了这个惯例
3.原始值不能有属性,尽管尝试给原始值添加属性不会报错,undefined
而且在下一行,属性会不见了。记住,只有引用值可以动态添加后面可以使用的属性
4.复制值:
在通过变量把一个原始值赋值到另一个变量时,原始值会被复制到新变量的位置
原始值和新变量可以独立使用,互不干扰。
在把引用值从一个变量赋给另一个变量时,存储在变量中的值也会被复制到新变量所在的位置。区别在于,这里复制的值实际上是一个指针,它指向存储在堆内存中的对象。操作完成后,两个变量实际上指向同一个对象,因此一个对象上面的变化会在另一个对象上反映出来。
5.传递参数:
ECMAScript中所有函数的参数都是按值传递的。这意味着函数外的值会被复制到函数内部的参数中,就像从一个变量复制到另一个变量一样。如果是原始值,那么就跟原始值变量的复制一样,如果是引用值,那么就跟引用值变量的复制一样。
!变量有按值和按引用访问,而传参则只有按值传递
6.在按值传递参数时,值会被复制到一个局部变量(即一个命名参数,或者用ECMAScript的话说,就是arguments对象中的一个槽位)。在按引用传递参数时,值在内存中的位置会被保存在一个局部变量,这意味着对本地变量的修改会反映到函数外部。(这在ECMAScript中是不可能的)
7.即使对象是按值传进函数的,传来的参数obj也会通过引用访问对象
当函数内部给obj设置了name属性时,函数外部的对象也会反映这个变化,因为obj指向的对象保存在全局作用域的堆内存上。许多开发者错误地认为,当在局部作用域中修改对象而变化反映到全局时,就意味着参数是按引用传递的。
函数中参数的值改变之后,原始的引用仍然没变。当obj在函数内部被重写时,它变成了一个指向本地对象的指针。而那个本地对象在函数执行结束时就被销毁了。
8.注意:ECMAScript中函数的参数就是局部变量
9.我们通常不关心一个值是不是对象,而是想知道它是什么类型的对象。为了解决这个问题,ECMAScript提供了instanceof操作符 console.log(person instanceof Object);
10.注意:typeof操作符在用于检测函数时也会返回"function",而在IE和Firefox中,typeof对正则表达式返回"object"
11.执行上下文与作用域:
变量或函数的上下文决定了它们可以访问哪些数据,以及它们的行为。每个上下文都有一个关联的变量对象(variable object),而这个上下文中定义的所有变量和函数都存在于这个对象上。虽然无法通过代码访问变量对象,但后台处理数据会用到它。
在浏览器中,全局上下文就是我们常说的window对象,因此所有通过var定义的全局变量和函数都会成为window对象的属性和方法。使用let和const的顶级声明不会定义在全局上下文中,但在作用域解析上效果是一样的。上下文在其所有代码都执行完毕后会被销毁,包括定义在它上面的所有变量和函数(全局上下文在应用程序推出前才会被销毁,比如关闭网页或退出浏览器)
每个函数调用都有自己的上下文。当代码执行流进入函数时,函数的上下文被推到一个上下文栈上。在函数执行完之后,上下文栈会弹出该函数上下文,将控制权返还给之前的执行上下文,ECMAScript程序的执行流就是通过这个上下文栈进行控制的
11.上下文中的代码在执行的时候,会创建变量对象的一个作用域链(scope chain)。这个作用域链决定了各级上下文中的代码在访问变量和函数时的顺序。代码正在执行的上下文的变量对象始终位于作用域链的最前端。如果上下文是函数,则其活动对象用作变量对象。活动对象最初只有一个定义变量:arguments.(全局上下文中没有这个变量)作用域链中的下一个变量对象来自包含上下文,再下一个对象来自再下一个包含上下文。以此类推直至全局上下文;全局上下文的变量对象始终是作用域链的最后一个变量对象
12.可以访问父上下文中的变量
13.内部上下文可以通过作用域链访问外部上下文中的一切,但外部上下文无法访问上下文中的任何东西。上下文之间的连线是线性的、有序的。
14.注意:函数参数被认为是当前上下文中的变量,因为也跟上下文中的其他变量遵循相同的访问规则
15.作用域链增强:
虽然执行上下文主要有全局上下文和函数上下文两种(eval()调用内部存在第三种上下文),但有其他方式来增强作用域链。某些语句会导致在作用域链前端临时添加一个上下文,这个上下文在代码执行后被删除。通常在两种情况下会出现这个现象,即代码执行到下面任意一种情况时:
·try/catch语句的catch块
·with语句
16.对with语句来说,会向作用域链前端添加指定的对象;对catch语句而言,则会创建一个新的变量对象,这个变量对象会包含要抛出的错误对象的声明
17.使用var的函数作用域声明:
在使用var声明变量时,变量会被自动添加到最接近的上下文。
如果变量未经声明就被初始化了,那么它就会自动被添加到全局上下文
18.注意:未经声明而初始化变量是JavaScript编程中一个非常常见的错误,会导致很多问题。为此,在初始化变量之前一定要先声明变量。在严格模式下,未经声明就初始化变量会报错
19.var声明会被拿到函数或全局作用域的顶部,位于作用域中所有代码之前。这个现象叫做“提升”(hoisting)。提升让同一作用域中的代码不必考虑变量是否已经声明就可以直接使用。可是在实践中,提升也会导致合法却奇怪的现象,即在变量声明之前使用变量。
20.声明之前输出undefines
21.ReferenceError 没有定义
22.let与var的另一个不同之处是在同一作用域内不能声明两次。重复的var声明会被忽略,而重复的let声明会抛出SyntaxError
23.let的行为非常适合在循环中声明迭代变量。使用var声明的迭代变量会泄露到循环外部,这种情况听该避免
24.严格来讲,let在JavaScript运行时中也会被提升,但由于“暂时性死去”的缘故,实际上不能在声明之前使用let变量。因此,从写JavaScript代码的角度说,let的提升跟var是不一样的
25.使用const声明的变量必须同时初始化为某个值。一经声明,在其生命周期的任何时候都不能再重新赋予新值
const a; //SyntaxError; 常量声明时没有初始化
const b = 3;
console.log(b); //3
b = 4 ; //TypeError:给常量赋值
26.如果想让整个对象都不能修改,可以使用Object.freeze(),这样再给属性赋值时虽然不会报错,但会静默失败
27.由于const声明暗示变量的值是单一类型且不可修改,JavaScript运行时编译器可以将其所有实例都替换成实际的值,而不会通过查询表进行变量查找。谷歌的V8引擎就执行这种优化
28.注意:开发实践表明,如果开发流程并不会因此而受很大影响,就听该尽可能地多使用const声明,除非确实需要一个将来会重新赋值的变量。这样可以从根本上保证提前发现重新赋值导致的bug
29.引用局部变量会让搜索自动停止,而不继续搜索下一级变量对象
30.使用块级作用域声明并不会改变搜索流程,但可以给词法层级添加额外的层次
31.在局部变量color声明之后的任何代码都无法访问全局变量color,除非适应完全限定的写法window.color
32.注意:标示符查找并非没有代价。访问局部变量比访问全局变量要快,因为不用切换作用域。不过,JavaScript引擎在优化标识符查找上做了很多工作,将来这个差异可能就微不足道了
33.垃圾回收:
JavaScript是使用垃圾回收的语言,也就是说执行环境负责在代码执行时管理内存。在C和C++等语言中,跟踪内存使用对开发者来说是个很大的负担,也是很多问题的来源。JavaScript为开发者卸下了这个负担
通过自动内存管理实现内存分配和闲置资源回收。
基本思路:确定哪个变量不会再使用,然后释放它占用的内存。这个过程是周期性的,即垃圾回收程序每隔一定时间(或者说在代码执行过程中某个预定的收集时间)就会自动运行。垃圾回收过程是一个近似且不完美的方案,因为某块内存是否还有用,属于“不可判定的”问题,意味着靠算法是解决不了的
34.以函数中局部变量的正常声明周期为例。函数中的局部变量会在函数执行时存在。此时,栈(或堆)内存会分配空间以保存相应的值。函数在内部使用了变量,然后退出。此时,就不再需要哪个局部变量了,它占用的内存可以释放,供后面使用。
垃圾回收程序必须跟踪记录哪个变量还会使用,以及哪个变量不会再使用,以便回收内存。如何标记未使用的变量也许有不同的实现方式。不过,在浏览器的发展史上,用到过两种主要的标记策略:标记清理和引用计数
35.标记清理:
JavaScript最常用的垃圾回收策略是标记清理
标记过程的实现并不重要,关键是策略
垃圾回收程序运行的时候,会标记内存中存储的所有变量(记住,标记方法有很多种)。然后,它会将所有在上下文中的变量,以及被在上下文中的变量引用的变量的标记去掉。在此之后再被加上标记的变量就是待删除的了,原因是任何在上下文中的变量都访问不到它们了。随后垃圾回收程序做一次内存清理,销毁带标记的所有值并收回它们的内存
36.引用计数:其思路是对每个值都记录它被引用的次数
当一个值的引用数为0时,就说明没办法再访问到这个值了,因此可以安全地回收其内存了。垃圾回收程序下次运行的时候就会释放引用数为0的值的内存
会遇到的问题:循环引用。所谓循环引用,就是对象A有一个指针指向对象B,而对象B也引用了对象A。引用数永远不会变成0,如果函数被多次调用,则会导致大量内存永远不会被释放
37.在IE8及更早版本的IE中,并非所有对象都是原生JavaScript对象。BOM和DOM中的对象是C++实现的组件对象模型(COM,Component Object Model)对象,而COM对象使用引用计数实现垃圾回收。
换句话说,只要涉及COM对象,就无法避开循环引用问题。
38.为避免类似的循环引用代码,应该在确保不适用的情况下切断原生JavaScript对象与DOM元素之间的连接
myObject.element = null;
element.someObject = null;
把变量设置为null实际上会切断变量与其之前引用值之间的关系。当下次垃圾回收程序运行时,这些值就会被删除,内存也会被回收
39.为了补救这一点,IE9把BOM和DOM都改成了JavaScript对象,这同时也避免了由于存在两套垃圾回收算法而导致的问题,还消除了常见的内存泄露现象
40.性能:
垃圾回收程序会周期性运行,如果内存中分配了很多变量,则可能造成性能损失,因此垃圾回收的时间调度很重要。尤其是在内存有限的移动设备上,垃圾回收有可能会显示拖慢渲染的速度和帧速率
41.现代垃圾回收程序会基于对JavaScript运行时环境的探测来决定何时运行。探测机制因引擎而异,但基本上都是根据已分配对象的大小和数量来判断
42.IE7发布后,JavaScript引擎的垃圾回收程序被调优为动态改变分配变量、字面量或数组槽位等会触发垃圾回收的阈值。IE7的起始阈值都与IE6的相同,如果垃圾回收程序回收的内存不到已分配的15%,这些变量、字面量或数组槽位的阈值就会翻倍。如果又一次垃圾回收的内存不到已分配的15%,这些变量,字面量或数组槽位的阈值就会翻倍。如果有一次回收的内存达到已分配的85%,则阈值重置为默认值。这么一个简单的修改,极大地提升了重度依赖JavaScript的网页在浏览器中的性能
43.警告:在某些浏览器中是有可能(但不推荐)主动触发垃圾回收的。在IE中,window.CollectGarbage()方法会立即触发垃圾回收。在Opera7及更高版本中,调用window.opera.collect()也会启动垃圾回收程序。
44.内存管理:
在使用垃圾回收的编程环境中,开发者通常无须关心内存管理。不过,JavaScript运行在一个内存管理与垃圾回收都很特殊的环境。分配给浏览器的内存通常比分配给桌面软件的要少很多,分配给移动浏览器的就更少了。这更多出于安全考虑而不是别的,就是为了避免运行大量JavaScript的网页耗尽系统内存而导致操作系统崩溃。这个内存限制不仅影响变量分配,也影响调用栈以及能够同时在一个线程中执行的语句数量。
将内存占用量保存在一个较小的值可以让页面性能更好。优化内存占用的最佳手段就是保证在执行代码时只保存必要的数据。如果数据不再必要,那么把它设置为null,从而释放其引用。这也可以叫做解除引用。这个建议最适合全局变量和全局对象的属性。局部变量在超出作用域后会被自动解除引用
45.全局变量,应该在不再需要时手动解除其引用, = null
46.解除对一个值的引用并不会自动导致相关内存被回收。解除引用的关键在于确保相关的值已经不在上下文里了,因此它在下次垃圾回收时会被回收
47.ES6增加的let const这两个关键字不仅有助于改善代码风格,而且同样有助于改进垃圾回收的过程。因为const和let都以块(而非函数)为作用域,所以相比于使用var,使用这两个新关键字可能会更早地让垃圾回收程序介入,尽早回收应该回收的内存。在块作用域比函数作用域更早终止的情况下,这就有可能发生
48.隐藏类和删除操作:
截止2017年,Chrome是最流行的浏览器,使用V8JavaScript引擎。V8在将解释后的JavaScript代码编译为实际的机器码时会利用“隐藏类”。如果你的代码非常注重性能,那么这一点可能对你很重要
运行期间,V8会将创建的对象与隐藏类关联起来,以跟踪它们的属性特征。能够共享共同隐藏类的对象性能会更好,但不一定总能够做到。
49.两个实例会对应两个不同的隐藏类。根据这种操作的频率和隐藏类的大小,这有可能对性能产生明显影响
当然,解决方案就是避免JavaScript的“先创建再补充”(ready-fire-aim)式的动态属性赋值,并在构造函数中一次性声明所有属性。
50.使用delete关键字会导致生成相同的隐藏类片段
51.在代码结束后,即使两个实例使用了同一个构造函数,它们也不再共享一个隐藏列。动态删除属性与动态添加属性导致的后果一样。最佳实践是把不想要的属性设置为null.这样可以保持隐藏类不变和继续共享,同时也能达到删除引用值供垃圾回收程序回收的效果
52.内存泄露:
写得不好的JavaScript可能出现难以察觉且有害的内存泄露问题。在内存有限的设备上,或者在函数会被调用很多次的情况下,内存泄露可能是个大问题。JavaScript中的内存泄露大部分是由不合理的引用导致的
意外声明全局变量是最常见但也最容易修复的内存泄露问题。没使用关键字声明变量
53.定时器也可能会悄悄地导致内存泄露,定时器的回调通过闭包引用了外部变量
54.静态分配与对象池:
为了提升JavaScript性能,最后要考虑的一点往往就是压榨浏览器。此时,一个关键问题就是如何减少浏览器执行垃圾回收的次数。开发者无法直接控制什么时候开始收集垃圾,但可以间接控制触发垃圾回收的条件。理论上,如果能够合理使用分配的内存,同时避免多余的垃圾回收,那就可以保住因释放内存而损失的性能。
55.浏览器决定何时运行垃圾回收程序的一个标准就是对象更替的速度。如果有很多对象被初始化,然后一下子又都超出了作用域,那么浏览器就会采用更激进的方式调度垃圾回收程序运行,这样当然会影响性能
56.调用这个函数时,会在堆上创建一个新对象,然后修改它,最后再把它返回给调用者,如果这个矢量对象的生命周期很短,那么它会很快失去所有对它的引用,成为可以被回收的值。假如这个矢量加法函数频繁被调用,那么垃圾回收调用程序会发现这里对象更替的速度很快,从而会更频繁地安排垃圾回收
57.在哪里创建矢量可以不让垃圾回收调度程序盯上呢?
一个策略是使用对象池。在初始化的某一时刻,可以创建一个对象池,用来管理一组可回收的对象。应用程序可以向这个对象池请求一个对象、设置其属性、使用它,然后在操作完成后再把它还给对象池。由于没发生对象初始化,垃圾回收探测就不会发现有对象更替,因此垃圾回收程序就不会那么频繁地运行。
58.如果对象池只按需分配矢量(在对象不存在时创建新的,在对象存在时则复用存在的),那么这个实现本质上是一种贪婪算法,有单调增长但为静态的内存。这个对象池必须使用某种结构维护所有对象,数组是比较好的选择。不过,使用数组来实现,必须留意不要招致额外的垃圾回收。、
59.注意:静态分配是优化的一种极端形式。如果你的应用程序被垃圾回收严重地拖了后腿,可以利用它提升性能。但这种情况并不多见。大多数情况下,这都属于过早优化,因此不用考虑。
60.小结:
JavaScript变量可以保存两种类型的值:原始值和引用值。原始值可能是以下6中原始数据类型之一:Undefined,Null,Boolean,Number,String和Symbol。原始值和引用值有以下特点:
·原始值大小固定,因此保存在栈内存上
·从一个变量到另一个变量复制原始值会创建该值的第二个副本
·引用值是对象,存储在堆内存上
·包含引用值的变量实际上只包含指向相应对象的一个指针,而不是对象本身
·从一个变量到另一个变量赋值引用值只会赋值指针,因此结果是两个变量都指向同一个对象
·typeop操作符可以确定值的原始类型,而instanceof操作符用于确保值的引用类型
任何变量(不管包含的是原始值还是引用值)都存在于某个执行上下文中(也称作作用域)。这个上下文(作用域)决定了变量的生命周期,以及它们可以访问代码的哪些部分。执行上下文可以总结如下:
·执行上下文分全局上下文,函数上下文和块级上下文
·代码执行流每进入一个新上下文,都会创建一个作用域链,用于搜索变量和函数
·函数或块的局部上下文不仅可以访问自己作用域内的变量,而且也可以访问任何包含上下文乃至全局上下文中的变量。
·全局上下文只能访问全局上下文中的变量和函数,不能直接访问局部上下文中的任何数据。
·变量的执行上下文用于确定什么时候释放内存
JavaScript是使用垃圾回收的编程语言,开发者不需要操作内存分配和回收。JavaScript的垃圾回收程序可以总结如下:
·离开作用域的值会被自动标记为可回收,然后在垃圾回收期间被删除
·主流的垃圾回收算法是标记清理,即先给当前不使用的值加上标记,再回来回收它们的内存。
·引用计数是另一种垃圾回收策略,需要记录值被引用了多少次。JavaScript引擎不再使用这种算法,但某些旧版本的IE仍然会受这种算法的影响,原因是JavaScript会访问非原生JavaScript对象(如DOM元素)
·引用计数在代码中存在循环引用时会出现问题
·解除变量的引用不仅可以消除循环引用哦,而且对垃圾回收也有帮助。为促进内存回收,全局对象,全局对象的属性和循环引用都应该在不需要时解除引用
第5章 基本引用类型
1.引用类型是把数据和功能组织到一起的结构,经常被人错误地称作“类”。虽然从技术上讲JavaScript是一门面向对象语言,但ECMAScript缺少传统的面向对象编程语言所具备的某些基本结构,包括类和接口。引用类型有时候也被称为对象定义,因为它们描述了自己的对象应有的属性和方法
2.对象被认为是某个特定引用类型的实例。新对象通过使用new操作符后跟一个构造函数来创建。构造函数就是用来创建新对象的函数
创建一个只有默认属性和方法的简单对象。原生引用类型,帮助开发者实现常见的任务
3.注意:函数也是一种引用类型
4.自协调世界时(UTC,Universal Time Coordinated)时间1970年1月1日午夜(零时)至今所经过的毫秒数。
使用这种存储格式,Date类型可以精确表示1970年1月1日之前及之后285616年的日期
5.Date.parse()方法接收一个表示日期的字符串参数,尝试将这个字符串转换为表示该日期的毫秒数。
如果直接把表示日期的字符串传给Date构造函数,那么Date会在后台调用Date.parse()
let someDate = new Date(Date.parse("May 23,2019"));
等价于
let someDate = new Date("May 23,2019");
6.Date.UTC()方法也返回日期的毫秒表示,如果不提供日,默认为1日,其他参数的默认值都是0
//GMT时间2000年1月1日零点
let y2k = new Date(Date.UTC(2000,0));
//GMT时间2005年5月5日下午5点55分5秒
let allFives = new Date(Date.UTC(2005,4,5,17,55,55))月数为4
7.与Date.parse()一样,Date.UTC()也会被Date构造函数隐式调用,但有一个区别:这种情况下创建的是本地日期,不是GMT日期
8.继承的方法:
toLocaleString()方法返回与浏览器运行的本地环境一致的日期和时间。这通常意味着格式中包含针对时间的AM(上午)或PM(下午),但不包含时区信息(具体格式可能因浏览器而不同)
toString()方法通常返回带时区信息的日期和时间,而时间也是以24小时制(0~23)表示的
Date类型的valueOf()方法根本就不返回字符串,这个方法被重写后返回的是日期的毫秒表示。因此,操作符(如小于号和大于号)可以直接使用它返回的值
9.日期格式化方法:
Date类型有几个专门用于格式化日期的方法,它们都会返回字符串:
toDateString()显示日期中的周几、月、日、年(格式特定于实现)
toTimeString()显示日期中的时、分、秒和时区(格式特定于实现)
toLocaleDateString()显示日期中的周几、月、日、年(格式特定于实现和地区)
toLocaleTimeString()显示日期中的时、分、秒(格式特定于实现和地区)
toUTCString()显示完整的UTC日期(格式特定于实现)
这些方法的输出与toLocaleString()和toString()一样,会因浏览器而异。因此不能用于在用户界面上一致地显示日期
10.注意:还有一个方法叫toGMTString(),这个方法跟toUTCString()是一样的,目的是为了向后兼容。不过,规范建议新代码使用toUTCString()
11.ECMAScript通过RegExp类型支持正则表达式
12.表示匹配模式的标记:
g:全局模式,表示查找字符串的全部内容,而不是找到第一个匹配的内容就结束
i:不区分大小写,表示在查找匹配时忽略pattern和字符串的大小写
m:多行模式,表示查找到一行文本末尾使会继续查找
y:粘附模式,表示只查找从lastIndex开始及之后的字符串
u:Unicode模式,启用Unicode匹配
s:dotAll模式,表示元字符,匹配任何字符(包括\n或\r)
let pattern1 =/at/g;
13.与其他语言中的正则表达式类似,所有元字符在模式中也必须转义
14.正则表达式也可以使用RegExp构造函数来创建,它接收两个参数:模式字符串和(可选的)标记字符串
任何使用字面量定义的正则表达式也可以通过构造函数来创建
15.所有元字符都必须二次转义,包括转义字符序列,如\n(\转义后的字符串是\\,在正则表达式字符串中则要写成\\\\
16.RegExp实例方法:
RegExp实例的主要方法是exec(),主要用于配合捕获组使用。这个方法只接受一个参数,即要应用模式的字符串。如果找到了匹配项,则返回包含第一个匹配信息的数组;如果没找到匹配项,则返回null
17.正则表达式的另一个方法是test(),接收一个字符串参数。如果输入的文本与模式匹配,则参数返回true,否则返回false.这个方法适用于只想测试模式是否匹配,而不需要实际匹配内容的情况。test()经常用在if语句中
18.无论正则表达式是怎么创建的,继承的方法toLocaleString()和toString()都返回正则表达式的字面量表示
正则表达式的valueOf()方法返回正则表达式本身
19.RegExp构造函数属性:
RegExp构造函数本身也有几个属性。(在其他语言中,这种属性被称为静态属性)这些属性适用于作用域中的所有正则表达式,而且会根据最后执行的的正则表达式操作而改变。这些属性还有一个特点,就是可以通过两种不同的方式访问它们。换句话说,每个属性都有一个全名和一个简写。
Opera不支持简写属性名,IE不支持多行匹配
20.注意:RegExp构造函数的所有属性都没有任何Web标准出处,因此不要在生产环境中使用它们
21.原始值包装类型:
为了方便操作原始值,ECMAScript提供了3种特殊的引用类型:Boolean,Number和String。这些类型具有其他引用类型一样的特点,但也具有与各种原始类型对应的特殊行为。每当用到某个原始值的方法或属性时,后台都会创建一个相应原始包装类型的对象,从而暴露出操作原始值的各种方法。
22.以读模式访问的,也就是要从内存中读取变量保存的值。在以读模式访问字符串值的任何时候,后台都会执行以下3步:
一,创建一个String类型的实例
二,调用实例上的特定方法
三,销毁实例
可以把这3步想象成执行了如下3行ECMAScript代码:
let s1 = new String("some text");
let s2 = s1.substring(2);
sl =null;
23.引用类型与原始值包装类型的主要区别在于对象的生命周期。在通过new实例化引用类型后,得到的实例会在离开作用域时被销毁,而自动创建的原始值包装对象则只存在于访问它的那行代码执行期间。这意味着不能在运行时给原始值添加属性和方法
24.let s1 = "some text";
s1.color = "red";
console.log(s1.color); //undefined
这里的第二行代码尝试给字符串s1添加了一个color属性。可是,第三行代码访问color属性时,它却不见了。原因就是第二行代码运行时会临时创建一个String对象,而当第三行代码执行时,这个对象已经被销毁了。实际上,第三行代码在这里创建了自己的String对象,但这个对象没有color属性。
可以显式地使用Boolean,Number和String构造函数创建原始值包装对象。不过应该在确实必要时再这么做,否则容易让开发者疑惑,分不清它们到底是原始值还是引用值。在原始值包装类型的实例上调用typeop会返回"object",所有原始值包装对象都会转换为布尔值true
25.Object构造函数作为一个工厂方法,能根据传入值的类型返回相应原始值包装类型的实例。如:
let obj = new Object("some text");
console.log(obj instanceof String); //true
如果传给Object的是字符串,则会创建一个String的实例。如果是数值,则会创建Number的实例。布尔值则会得到Boolean的实例
26.所有对象在布尔表达式中都会自动转换为true,
27.typeof操作符对原始值返回"boolean",但对引用值返回"object".同样,Boolean对象是Boolean类型的实例,在使用instaceof操作符时返回true,但对原始值则返回false
28.!理解原始布尔值和Boolean对象之间的区别非常重要,强烈建议永远不要使用后者
29.Number:
与Boolean类型一样,Number类型重写了balueof(),toLocaleString()和toString()方法。valueOf()方法返回Number对象表示的原始数值,另外两个方法返回数值字符串。toString()方法可选地接收一个表示基数的参数,并返回相应基数形式的数值字符串
30.toFixed()方法返回包含指定小数点位数的数值字符串
31.注意:toFixed()方法可以表示有0~20个小数位的数值。某些浏览器可能支持更大的范围,但这是通常被支持的范围。
32.用于格式化数值的方法是toExponential(),返回以科学记数法(也称为指数记数法)表示的数值字符串。与toFixed()一样,toExponential()也接收一个参数,表示结果中小数的位数
33.toPrecision()方法会根据情况返回最合理的输出结果,可能是固定长度,也可能是科学记数法形式。这个方法接收一个参数,表示结果中数字的总位数(不包含指数)
34.原始数值在调用typeof时始终返回"number",而Number对象则返回"object".类似地,Number对象是Number类型的实例,而原始数值不是
35.ES6新增了Number.isInteger()方法,用于辨别一个数值是否保存为整数。有时候,小数位的0可能会让人误以为数值是一个浮点值
36.JavaScript字符:
JavaScript字符串由16位码元(code unit)组成。对多数字符来说,每16为码元对应一个字符。换句话说,字符串的length属性表示字符串包含多少16位码元
37.JavaScript字符串使用了两种Unicode编码混合的策略:UCS-2和UTF-16。对于可以采用16为编码的字符(U+0000`U+FFFF),这两种编码实际上是一样的
38.模式局限:
虽然ECMAScript 对正则表达式的支持有了长足的进步,但仍然缺少Perl 语言中的一些高级特性。
下列特性目前还没有得到ECMAScript 的支持(想要了解更多信息,可以参考Regular-Expressions.info
网站):
39.注意 要深入了解关于字符编码的内容,推荐Joel Spolsky 写的博客文章:“The Absolute
Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and
Character Sets (No Excuses!)”。
另一个有用的资源是Mathias Bynens 的博文:“JavaScript’s Internal Character Encoding:
UCS-2 or UTF-16?”。
40.只要字符编码大小与码元大小一一对应,这些方法就能如期工作。
这个对应关系在扩展到Unicode增补字符平面就不成立了。问题很简单,即16位只能唯一表示65536个字符。这对于大多数语言字符集是足够了,在Unicode中称为基本多语言平面(BMP).为了表示更多的字符,Unicode采用了一个策略,即每个字符使用另外16位去选择一个增补平面。这种每个字符使用两个16位码元的策略称为代理对。
41.fromCharCode()方法仍然返回正确的结果,因为它实际上是基于提供的二进制表示直接组合成字符串。浏览器可以正确解析代理对(由两个码元构成)
42.为正确解析既包含单码元字符又包含代理对字符的字符串,可以使用codePointAt()来代替charCodeAt().跟使用charCodeAt()时类似,codePointAt()接收16位码元的索引并返回该索引上的码点(code point).码点是Unicode中一个字符的完整标识。
码点可能是16位,也可能是32位,而codePointAt()方法可以从指定码元位置识别完整的码点
43.注意:如果传入的码元索引并非代理对的开头,就会返回错误的码点。这种错误只有检测单个字符的时候才会出现,现在通过从左到右按正确的码元遍历字符串来规避。迭代字符串可以智能地识别代理对的码点
44.与charCodeAt()有对应的codePointAt()一样,fromCharCode()也有一个对应的fromCodePoint()。这个方法接收任意数量的码点,返回对应字符拼接起来的字符串
45.通过比较字符串与其调用normalize()的返回值,就可以知道该字符串是否已经规范化了
46.stringValue调用concat()方法可以接收任意多个参数,因此可以一次性拼接多个字符串
47.ECMAScript提供了3个从字符串中提取子字符串的方法:slice(),substr()和substring()。这3个方法都返回调用它们的字符串的一个子字符串,而且都接收一或两个参数。第一个参数表示子字符串开始的位置,第二个参数表示子字符串结束的位置。对slice()和substring()而言,第二个参数是提取结束的位置(即该位置之前的字符会被提取出来)。对substr()而言,第二个参数表示返回的子字符串数量。任何情况下,省略第二个参数都意味着提取到字符串末尾。与concat()方法一样,slice(),substr()和substring()也不会修改调用它们的字符串,而只会放回提取到的原始新字符串值
48.当参数为负值时,slice()方法将所有负值参数都当成字符串长度加上负参数值,而substr()方法将第一个负参数值当成字符串长度加上该值,将第二个负参数值转换为0.substring()方法会将所有父参数值都转换为0
49.字符串位置方法:
有两个方法用于在字符串中定位子字符串:indexOf()和lastIndexOf().这两个方法从字符串中搜索传入的字符串,并返回位置(如果没找到,则返回-1)。两者的区别在于,indexOf()方法从字符串开头开始查找子字符串,而lastIndexOf()方法从字符串末尾开始查找子字符串
50.字符串包含方法:
ECMAScript6增加了3个用于判断字符串中是否包含另一个字符串的方法:startsWith(),endsWith()和includes().这些方法都会从字符串中搜索传入的字符串,并返回一个表示是否包含的布尔值,它们的区别在于,startsWith()检查开始于索引0的匹配项,endsWith()检查开始于索引(string.length - sunstring.length)的匹配项,而includes()检查整个字符串
51.trim()方法:
ECMAScript在所有字符串上都提供了trim()方法。这个方法会创建字符串的一个副本,删除前,后所有空格符,再返回结果
52.repeat()方法:
ECMAScript在所有字符串上都提供了repeat()方法。这个方法接收一个整数参数,表示要将字符串复制多少次,然后返回拼接所有副本后的结果。
53.padStart()和padEnd()方法:
padStart()和padEnd()方法会复制字符串,如果小于指定长度,则在相应以边填充字符,直至满足长度条件。这两个方法的第一个参数是长度,第二个参数是可选的填充字符串,默认为空格(U+0020)
54.字符串迭代与解构:
字符串的原型上暴露了一个@@iterator方法,表示可以迭代字符串的每个字符
55.字符串大小写转换:
4个方法:toLowerCase(),toLocaleLowerCase(),toUpperCase()和toLocaleUpperCase().
toLocaleLowerCase()和toLocaleUpperCase()方法旨在基于特定地区实现,在很多地区,地区特定的方法与通用的方法是一样的。但在少数语言中(如土耳其语),Unicode大小写转换需应用特殊规则,要使用地区特定的方法才能实现正确转换。
56.match()方法返回的数组与RegExp对象的exec()方法返回的数组是一样的;第一个元素是与整个模式匹配的字符串,其余元素则是与表达式中的捕获组匹配的字符串(如果有的话)
另一个查找模式的字符串方法是search().这个方法唯一的参数与maatch()方法一样:正则表达式字符串或RegExp对象。这个方法返回模式第一个匹配的位置索引,如果没找到则返回-1.search()始终从字符串开头向后匹配模式
57.为简化字符串替换操作,ECMAScript提供了replace()方法。这个方法接收两个参数,第二个参数可以是一个RegExp对象或一个字符串(这个字符串不会准换为正则表达式),第二个参数可以是一个字符串或一个函数。如果第一个参数是字符串,那么只会替换第一个子字符串。要想替换所有字字符串,第一个参数必须为正则表达式并且带全局标记
58.split()。这个方法会根据传入的分隔符将字符串拆分成数组。作为分隔符的参数可以是字符串,也可以是RegExp对象。(字符串分隔符不会被这个方法当成正则表达式)还可以传入第二个参数,即数组大小,确保返回的数组不会超过指定大小
59.localeCompare(),这个方法比较两个字符串,
localeCompare()的独特之处在于,实现所在的地区(国家和语言)决定了这个方法如何比较字符串
60.HTML方法:
早期浏览器扩展了规范,增加了辅助生成HTML标签的方法。这些方法基本上已经没有人使用了,因为结果通常不是语义化的标记
61.Global:
Global对象是ECMAScript中最特别地对象,因为代码不会显式访问它。ECMA-262规定Global对象为一种兜底对象,它所针对的是不属于任何对象的属性和方法。事实上,不存在全局变量或全局函数这种东西。在全局作用域中定义的变量和函数都被变成Global对象的属性。
62.URL编码方法:
encodeURI()和encodeURIComponent()方法用于编码统一资源标识符(URL),以便传给浏览器。有效的URI不能包含某些字符,比如空格。使用URI编码方法来编码URI可以让浏览器能够理解它们,同时又以特殊的UTF-8编码替换掉所有无效字符
63.ecnodeURI()方法用于对整个URI进行编码,比如"www.wrox.com/illegal value.js".而encodeURIComponent()方法用于编码URI中单独的组件,比如前面URL中的"illegal value.js"
这两个方法的主要区别是,encodeURI()不会编码属于URL组件的特殊字符,比如冒号、斜杠、问号、井号,而encodeURIComponent()会编码它发现的所有非标准字符
64.注意:一般来说,使用encodeURIComponent()应该比使用encodeURI()的频率更高,这是因为编码查询字符串参数比编码基准URI的次数更多
65.与encodeURI()和encodeURIComponent()相对的是decodeURI()和decodeURLComponent().decodeURI()只对使用encodeURI()编码过的字符解码。decodeURIComponent()解码所有被encodeURIComponent()编码的字符,基本上就是解码所有特殊值
66.eval()方法:
这个方法就是一个完整的ECMAScript解释器,它接收一个参数,即一个要执行的ECMAScript(JavaScript)字符串
67.当解释器发现eval()调用时,会将参数解释为实际的ECMAScript语句,然后将其插入到该位置。通过eval()执行的代码属于该调用所在上下文,被执行的代码与该上下文拥有相同的作用域链。这意味着定义在包含上下文中的变量可以在eval()调用内部被引用
68.通过eval()定义的任何变量和函数都不会被提升,这是因为在解析代码的时候,它们是被包含在一个字符串中的。它们只是在eval()执行的时候才会被创建
在严格模式下,在eval()内部创建的变量和函数无法被外部访问。换句话说,最后两个例子会报错。同样,在严格模式下,赋值给eval也会导致错误
69.注意:解释代码字符串的能力是非常强大的,但也非常危险。在使用eval()的时候必须极为慎重,特别是在解释用户输入的内容时。因为这个方法会对XSS利用暴露出很大的攻击面。恶意用户可能插入会导致你网站或应用崩溃的代码。
70.Global对象属性:
Global对象有很多属性,像undefined,NaN和Infinity等特殊值都是Global对象的属性。此外,所有原生引用类型构造函数,比如Object和Function,也都是Global对象的属性
71.window对象:
虽然ECMA-262没有规定直接访问Global对象的方式,但浏览器将window对象实现为Global对象的代理。因此,所有全局作用域中声明的变量和函数都变成了window的属性
72.注意:window对象在JavaScript中远不止实现了ECMAScript的Global对象那么简单
73.获取Gloval对象的方式:
let global = function(){
return this;
}();
这段代码创建了一个立即调用的函数表达式,返回了this的值。当一个函数在没有明确(通过成为某个对象的方法,或者通过call()/apply())指定this值的情况下执行时,this值等于Global对象。因此,调用一个简单返回this的函数是在任何执行上下文中获取Global对象的通用方式
74.Math:
ECMAScript提供了Math对象作为保存数学公式、信息和计算的地方。Math对象 提供了一些辅助计算的属性和方法
75.注意:Math对象上提供的计算要比直接在JavaScript实现的快得多,因为Math对象上的计算使用了JavaScript引擎中更高效的实现和处理器指令。但使用Math计算的问题是精度会因浏览器、操作系统、指令集和硬件而异。
76.舍入方法:
Math.ceil()方法始终向上舍入为最接近的整数
Math.floor()方法始终向下舍入为最接近的整数
Math.round()方法执行四舍五入
Math.fround()方法返回数值最接近的单精度(32位)浮点值表示
对于25和26(不包含)之间的所有值,Math.ceil()都会返回26,因为它始终向上舍入。
Math.round()只在数值大于等于25.5时返回26,否则返回25
Math.floor()对所有25和26(不包含)之间的值都返回25
77.random()方法:
Math.random()方法返回一个0~1范围内的随机数,其中包含0但不包含1.对于希望显示随机名言或随机新闻的网页,这个方法是非常方便的。可基于以下公式从一组整数中随机选择一个数:
number = Math.floor(Math.random() * total_number_of_choices + first_possible_value)
这里使用了Math.floor()方法,因为Math.random()始终返回小数,即便乘以一个数再加上一个数也是小数。
如果想从1~10范围内随机选择一个数:
let num = Math.floor(Math.random() * 10 + 1);
如果想选择一个2~10范围内的值,
let nim = Math.floor(Math.random() * 9 + 2);
78.注意:如果是为了加密而需要生成随机数(传给生成器的输入需要较高的不确定性),建议使用window.crypto.getRandomValues()
79.小结:
JavaScript比较独特的一点是,函数实际上是Function类型的实例,也就是说函数也是对象。因为函数也是对象,所以函数也有方法,可以用于增强其能力。
由于原始值包装类型的存在,JavaScript中的原始值可以被当成对象来使用。有3种原始值包装类型:Boolean,Number和String。它们都具备如下特点:
·每种包装类型都映射到同名的原始类型
·以读模式访问原始值时,后台会实例化一个原始值包装类型的对象,借助这个对象可以操作相应的数据
·涉及原始值的语句执行完毕后,包装对象就会被销毁。
第6章 集合引用类型
1.Object:
Object是ECMAScript中最常用的类型之一。虽然Object的实例没有多少功能,但很适合存储和在应用程序间交换数据。
显式地创建Object的实例有两种。第一种是使用new操作符和Object构造函数,另一种方式是使用对象字面量表示法。
2.在ECMAScript中,表达式上下文指的是期待返回值的上下文。语句上下文中,比如if语句的条件后面,则表示一个语句块的开始
3.在最后一个属性后面加上逗号在非常老的浏览器中会导致报错,但所有现代浏览器都支持这种写法
4.数值属性会自动转换为字符串
5.可以用对象字面量表示法来定义一个只有默认属性和方法的对象,只要使用一对大括号,中间留空就行了
6.注意:在使用对象字面量表示发定义对象时,并不会实际调用Object构造函数
7.函数内部会使用typeof操作符测试每个属性是否存在,然后根据属性有无构造并显示一条消息。然后,这个函数被调用了两次,每次都通过一个对象字面量传入了不同的数据。两种情况下,函数都正常运行
8.最好的方式是对必选参数使用命名参数,再通过一个对象字面量来封装多个可选参数
9.使用中括号时,要在括号内使用属性名的字符串形式
使用中括号的主要优势就是可以通过变量访问属性
如果属性名中包含可能会导致语法错误的字符,或者包含关键字/保留字时,也可以使用中括号语法
10.通常,点语法是首选的属性存取方式,除非访问属性时必须使用变量
11.Array:
跟其他语言中的数组一样,ECMAScript数组也是一组有序的数据,但跟其他语言不同的是,数组中每个槽位可以存储任意类型的数据。
ECMAScript数组也是动态大小的,会随着数据添加而自动增长
12.创建数组时可以给构造函数传一个值。如果这个值是数值,则会创建一个长度为指定数值的数组;如果这个值是其他类型的,则会创建一个只包含该特定值的数组
13.注意:与对象一样,在使用数组字面量表示法创建数组不会调用Array构造函数
14.Array构造函数还有两个ES6新增的用于创建数组的静态方法:from()和of().from()用于将类数组结构转换为数组实例,而of()用于将一组参数转换为数组实例
15.Array.from()的第一个参数是一个类数组对象,即任何可迭代的结构,或者有一个length属性和可索引元素的结构
Array.from()还接收第二个可选的映射函数参数。这个函数可以直接增强新数组的值,而无须像调用Array.from().map()那样先创建一个中间数组。还可以接收第三个可选参数,用于指定映射函数中this的值。但这个重写的this值在箭头函数中不适用
16.Array.of()可以把一组参数转换为数组。这个方法用于替代在ES6之前常用的Array.prototype.slice.call(arguments),一种异常笨拙的将arguments对象转换为数组的写法
17.数组空位:
使用数组字面量初始化数组时,可以使用一串逗号来创建空位(hole).ECMAScript会将逗号之间相应索引位置的值当成空位
ES6新增的方法和迭代器与早期ECMAScript版本中存在的方法行为不同。ES6新增方法普遍将这些空位当成存在的元素,只不过值为undefined
18.注意:由于行为不一致和存在性能隐患,因此实践中要避免使用数组空位。如果确实需要空位,则可以显式地用undefined值代替
19.数组索引:
在中括号中提供的索引表示要访问的值。如果索引小于数组包含的元素数,则返回存储在相应位置的元素。设置数组的值方法就是替换指定位置的值。如果把一个值设置给超过数组最大索引的索引,则数组长度会自动扩展到该索引值加1
20.数组中元素的数量保存在length属性中,这个属性始终返回0或大于0的值
21.数组length属性的独特之处在于,它不是只读的。通过修改length属性,可以从数组末尾删除或添加元素
22.注意:数组最多可以包含4294967295个元素,这对于大多数编程任务应该足够了。如果尝试添加更多项,则会导致抛出错误。以这个最大值作为初始值创建数组,可能导致脚本运行时间过长的错误
23.检测数组:
一个经典的ECMAScript问题是判断一个对象是不是数组。在只有一个网页(因而只有一个全局作用域)的情况下,使用instanceof操作符就足矣
Array.isArray()方法的目的是确定一个值是否为数组,而不管它是在哪个全局执行上下文中创建的
24.迭代器方法:
在ES6中,Array的原型上暴露了3个用于检索数组内容的方法:keys(),values()和entries()
key()返回数组索引的迭代器
values()返回数组元素的迭代器
entries()返回索引/值对的迭代器
25.复制和填充方法:
ES6新增了两个方法:批量复制方法copyWithin(),以及填充数组方法fill()
使用fill()方法可以向一个已有的数组中插入全部或部分相同的值。开始索引用于指定开始填充的位置,它是可选的、如果不提供结束索引,则一直填充到数组末尾。负值索引从数组末尾开始计算,也可以将负索引想象成数组长度加上它得到的一个正索引
copyWithin()会按照指定范围浅复制数组中的部分内容,然后将它们插入到指定索引开始的位置。开始索引和结束索引则与fill()使用同样的计算方法
26.转换方法:
所有对象都有toLocaleString(),toString()和valueOf()方法。其中,valueOf()返回的还是数组本身。而toString()返回由数组中每个值的等效字符串拼接而成的一个逗号分隔的字符串。也就是说,对数组的每个值都会调用其toString()方法,以得到最终的字符串
27.在调用数组的toLocaleString()方法时,会得到一个逗号分隔的数组值的字符串。它与另外两个方法唯一的区别是,为了得到最终的字符串,会调用数组每个值的toLocaleString()方法,而不是toString()方法
28.join()方法接收一个参数,即字符串分隔符,返回包含所有项的字符串
29.注意:如果数组中某一项是null或undefined,则在join(),toLocaleString(),toString()和valueOf()返回的结果中会以空字符串表示
30.栈方法:
ECMAScript数组提供了push()和pop()方法,以实现类似栈的行为
push()方法接收任意数量的参数,并将它们添加到数组末尾,返回数组的最新长度。
pop()方法则用于删除数组的最后一项,同时减少数组的length值,返回被删除的项
31.队列方法:
队列在列表末尾添加数据,但从列表开头获取数据。这个数组方法叫shift(),它会删除数组的第一项并返回它,然后数组长度减1.使用shift()和push(),可以把数组当成队列来使用
32.unshift()就是执行跟shift()相反的操作:在数组开头添加任意多个值,然后返回新的数组长度。通过使用unshift()和pop(),可以在相反方向上模拟队列,即在数组开头添加新数据,在数组末尾取得数据
33.排序方法:
reverse()方法就是将数组元素反向排列
默认情况下,sort()会按照升序重新排列数组元素,即最小的值在前面,最大的值在后面。为此,sort()会在每一项上调用String()转型函数,然后比较字符串来决定顺序。即使数组的元素都是数值,也会先把数组转换为字符串载比较、排序
34.比较函数
35.注意:reverse()和sort()都返回调用它们的数组的引用
36.操作方法:
concat()方法可以在现有数组全部元素基础上创建一个新数组。它首先会创建一个当前数组的副本,然后再把它的参数添加到副本末尾,最后返回这个新构建的数组。如果传入一个或多个数组,则concat()会把这些数组的每一项都添加到结果数组。如果参数不是数组,则直接把它们添加到结果数组末尾
37.方法slice()用于创建一个包含原有数组中一个或多个元素的新数组。slice()方法可以接收一个或两个参数;返回元素的开始索引和结束索引。如果只有一个参数,则slice()会返回该索引到数组末尾的所有元素。如果有两个参数,则slice()返回从开始索引到结束索引对应的所有元素,其中不包含结束索引对应的元素
38.注意:如果slice()的参数有负值,那么就以数值长度加上这个负值的结果确定位置。比如,在包含5个元素的数组上调用slice(-2,-1),就相当于调用slice(3,4).如果结束位置小于开始位置,则返回空数组
39.或许最强大的数组方法就属splice()了,splice()的主要目的是在数组中间插入元素,但有3种不同的方式使用这个方法:删除(传2个参数),插入:3个参数,替换:3个参数
40.搜索和位置方法:
ECMAScript提供两类搜索数组的方法:按严格相等搜索和按断言函数搜索
41.严格相等:
ECMAScript提供了3个严格相等的搜索方法:indexOf(),lastIndexOf()和includes().其中,前两个方法在所有版本中都可用,而第三个方法是ECMAScript7新增的。这些方法都接收两个参数:要查找的元素和一个可选的起始搜索位置。indexOf()和includes()方法从数组前头(第一项)开始向后搜索,而lastIndexOf()从数组末尾(最后一项)开始向前搜索。
indexOf()和lastIndexOf()都返回要查找的元素在数组中的位置,如果没找到则返回-1.includes()返回布尔值,表示是否至少找到一个与置顶元素匹配的项。在比较第一个参数跟数组每一项时,会使用全等(===)比较,也就是说两项必须严格相等
42.断言函数:
ECMAScript也允许按照定义的断言函数搜索数组,每个索引都会调用这个函数。断言函数的返回值决定了相应索引的元素是否被认为匹配。
断言函数接收3个参数:元素、索引和数组本身。其中元素是数组中当前搜索的元素,索引是当前元素的索引,而数组就是正在搜索的数组。断言函数返回真值,表示是否匹配
find()和findIndex()方法使用了断言函数。这两个方法都从数组的最小索引开始。find()返回第一个匹配的元素,findIndex()返回第一个匹配元素的索引。这两个方法也都接收第二个可选的参数,用于指定断言函数内部this的值
43.迭代方法:
ECMAScript为数组定义了5个迭代方法。每个方法接收两个参数:以每一项为参数运行的函数,以及可选的作为函数运行上下文的作用域对象(影响函数中this的值)。传给每个方法的函数接收3个参数:数组元素、数组索引和数组本身。因具体方法而异,这个函数的执行结果可能会也可能不会影响方法的返回值。数组的5个迭代方法如下。
every():对数组每一项都运行传入的函数,如果对每一项函数都返回ture,则这个方法返回true
filter():对数组每一项都运行传入的函数,函数返回true的项会组成数组之后返回
forEach():对数组每一项都运行传入的函数,没有返回值
map():对数组每一项都运行传入的函数,返回由每次函数调用的结果构成的数组
some():对数组每一项都运行传入的函数,如果有一项函数返回true,则这个方法返回true
在这些方法中,every()和some()是最相似的,都是从数组中搜索符合某个条件的元素。对every()来说,传入的函数必须对每一项都返回true,它才会返回true;否则,它就返回false.而对some()来说,只要有一项让传入的函数返回true,它就会返回true
44.归并方法:
reduce()和reduceRight(),这两个方法都会迭代数组的所有项,并在此基础上构建一个最终返回值。reduce()方法从数组第一项开始遍历到最后一项,而reduceRight()从最后一项开始遍历至第一项
45.定型数组中值的下溢和上溢不会影响到其他索引,但仍然考虑数组的元素应该是什么类型。定型数组对于可以存储的每个索引只接受一个相关位,而不考虑它们对实际数值的影响。