《JavaScript高级程序设计(第4版)》的笔记

JavaScript高级程序设计(第4版)

马特·弗里斯比
278个想法

https://weread.qq.com/web/reader/751326d0720befab7514782k4e73277021a4e732ced3b55

工业革命是钢铁铸就的,互联网革命则是JavaScript造就的。25年的反复锻造与打磨,成就了JavaScript在今天的应用程序开发中毋庸置疑的统治地位,但并非一开始就是如此。
Brendan Eich只用10天就写出了JavaScript的第一版。初生的JavaScript看似弱不禁风,但历史表明,第一印象并不代表一切。今天,这门语言的每个细节,也就是这本书所涉及的方方面面,都是反复推敲的产物。并非所有决定都让人满意,也没有完美的编程语言,不过单从无所不在这方面看,JavaScript倒是很接近完美。它是目前唯一一个可以随处部署的语言:服务器、桌面浏览器、手机浏览器,甚至原生移动应用程序中都有它的身影。
JavaScript目前的使用者有不同层次的软件工程师,他们的背景各异。无论是以开发设计精良、优雅的软件为目标,还是仅仅为了完成业绩而简单堆砌一个系统,JavaScript都能派上用场。
怎么使用JavaScript完全取决于你。一切尽在你的掌握之中。
在我超过15年的软件开发生涯中,JavaScript工具和最佳实践已经发生了天翻地覆的变化。2004年,我开始接触这门语言,当时还是雅虎地球村(Geocities)、雅虎群组(Yahoo Groups)和Macromedia Flash播放器的天下。JavaScript给人感觉像个玩具,当时我在RSS、MySpace Profile Pages等流行的沙盒环境中开始使用它。后来我又帮助一些个人网站修改和自定义功能,那种感觉就像在狂野的西部拓荒,而我也因此喜欢上了它。
当初我创建第一家公司的时候,配置主机装个数据库要花几天时间,而JavaScript只要扔到HTML里就可以跑起来。“前端应用程序”是不存在的,主要是零七碎八的函数。后来Ajax因为jQuery火了而变得更加流行,这才打开了通向新世界的大门,可靠、稳定的应用程序应运而生。这股风潮愈演愈烈,直到有一天遇到了发展瓶颈,但突然间,强大的框架诞生了。前端模型、数据绑定、路由管理、反应式视图,全都爆发出来了。我就在这个时候搬到硅谷,帮人打理一家公司。很快,使用我代码的用户达到了几百万。置身硅谷这么长时间以来,我也为开源做了一些贡献,培训了不计其数的软件工程师,也走了一点儿运。我的上一家公司在2018年被Stripe收购,我现在就供职于这家公司,致力于为互联网构建其经济基础设施。
我很高兴在马特第一次到帕洛阿尔托的一家小型创业公司领导工程化时结识了他。那家公司叫Claco,当时我刚成为它的顾问。他追求伟大软件的活力和激情溢于言表,而这家羽翼未丰的公司很快就开发出一款漂亮的产品。一如为硅谷公司设立标杆的惠普,这家创业公司也诞生在一间平房里。但这可不是寻常的民房,而是一间“黑客屋”,里面十几位才华横溢的软件工程师经常通宵达旦地工作。虽然过的不是什么高档次生活——他们坐的都是别人扔在大街上的那种沙发床和旧椅子——他们在这间房子里每天所写代码的数量和质量却引人瞩目。连续工作几小时后,大多数人会把精力投入到公司的另一个子项目上,然后又是几个小时的工作。不太会写代码的人也常受启发,发现自己学习的渴望,然后仅仅几个星期后就变成了代码能手。
马特是促成这种开发效率的关键角色。他是“黑客屋”里经验最丰富的人,恰好也是思维最清晰、最专业的一个。拿到计算机工程学位并不能说明什么,只要在窗户或者白板上看到马特写的算法、性能计算以及代码,你就知道马特又在专注于他的下一个大项目。随着我对他了解的加深,我们成为了好朋友。他的领悟能力,他对培训工作的热爱,以及几乎可以把所有东西转化成笑话的能力,都是我所欣赏的品质。
虽然马特是一位极具才华的软件工程师和项目领导,但他之所以能成为本书作者独一无二的人选,还是凭借他独有的经验和知识。
他不仅仅花时间教别人,而且还把这本书写完了。
在Claco,他开发了多款整体性产品,端到端地帮助教师在课堂上提供更好的学习体验。在DoorDash,他是第一位工程师,开发了一个可靠的物流配送系统并实现了高速增长,目前公司估值超过了120亿美元。最后,在Google,马特写的软件已经被这个星球上的数十亿人使用了。
全情投入,快速增长,誉满天下——多数软件工程师终其一生也只能体验到其中一项,而且还得运气好。马特不仅体验到了全部,还成为了畅销书作者。除了本书,他还写了两本JavaScript和Angular的书。说实话,我就想知道他什么时候能写一本书,把自己管理时间的奥秘分享出来。
本书是一部翔实的工具书,满满的都是JavaScript知识和实用技术。我热切希望本书读者能够不断学习,并亲手打造属于自己的梦想。欢迎大家多多挑错,多记笔记,别忘了打开代码编辑器,毕竟互联网革命才刚刚开始!
Zach Tratar
Stripe软件工程师
Jobstart联合创始人、前CEO

1.1 简短的历史回顾

两个版本的JavaScript:Netscape Navigator中的JavaScript,以及IE中的JScript。

1997年,JavaScript 1.1作为提案被提交给欧洲计算机制造商协会(Ecma)。第39技术委员会(TC39)承担了“标准化一门通用、跨平台、厂商中立的脚本语言的语法和语义”的任务(参见TC39-ECMAScript)。TC39委员会由来自网景、Sun、微软、Borland、Nombas和其他对这门脚本语言有兴趣的公司的工程师组成。他们花了数月时间打造出ECMA-262,也就是ECMAScript(发音为“ek-ma-script”)这个新的脚本语言标准。

1.2 JavaScript实现

ECMA-262将这门语言作为一个基准来定义,以便在它之上再构建更稳健的脚本语言。

https://es6.ruanyifeng.com/

ECMA-262第6版,俗称ES6、ES2015或ES Harmony(和谐版),于2015年6月发布。这一版包含了大概这个规范有史以来最重要的一批增强特性。ES6正式支持了类、模块、迭代器、生成器、箭头函数、期约、反射、代理和众多新的数据类型。

https://maninboat.gitbooks.io/you-dont-know-js-es6/content/ch8.3.html

ECMA-262第7版,也称为ES7或ES2016,于2016年6月发布。这次修订只包含少量语法层面的增强,如Array.prototype.includes和指数操作符。

ECMA-262第7版,也称为ES7或ES2016,于2016年6月发布。这次修订只包含少量语法层面的增强,如Array.prototype.includes和指数操作符。

ECMA-262第8版,也称为ES8、ES2017,完成于2017年6月。这一版主要增加了异步函数(async/await)、SharedArrayBuffer及Atomics API,以及Object.values()/Object.entries()/Object. getOwnPropertyDescriptors()和字符串填充方法,另外明确支持对象字面量最后的逗号。

ECMA-262第9版,也称为ES9、ES2018,发布于2018年6月。这次修订包括异步迭代、剩余和扩展属性、一组新的正则表达式特性、Promise finally(),以及模板字面量修订。

ECMA-262第10版,也称为ES10、ES2019,发布于2019年6月。这次修订增加了Array.prototype. flat()/flatMap()、String.prototype.trimStart()/trimEnd()、Object.fromEntries()方法,以及Symbol.prototype.description属性,明确定义了Function.prototype.toString()的返回值并固定了Array.prototype.sort()的顺序。另外,这次修订解决了与JSON字符串兼容的问题,并定义了catch子句的可选绑定。

万维网联盟(W3C, World Wide Web Consortium)开始了制定DOM标准的进程。

目前,W3C不再按照Level来维护DOM了,而是作为DOM Living Standard来维护,其快照称为DOM4。

1.4 小结

JavaScript是一门用来与网页交互的脚本语言,包含以下三个组成部分。
❑ ECMAScript:由ECMA-262定义并提供核心功能。
❑ 文档对象模型(DOM):提供与网页内容交互的方法和接口。
❑ 浏览器对象模型(BOM):提供与浏览器交互的方法和接口。
JavaScript的这三个部分得到了五大Web浏览器(IE、Firefox、Chrome、Safari和Opera)不同程度的支持。所有浏览器基本上对ES5(ECMAScript 5)提供了完善的支持,而对ES6(ECMAScript 6)和ES7(ECMAScript 7)的支持度也在不断提升。这些浏览器对DOM的支持各不相同,但对Level 3的支持日益趋于规范。HTML5中收录的BOM会因浏览器而异,不过开发者仍然可以假定存在很大一部分公共特性。

2.1 <script>元素

https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/script#%E5%B1%9E%E6%80%A7

<script>元素有下列8个属性。

把所有JavaScript文件都放在<head>里,也就意味着必须把所有JavaScript代码都下载、解析和解释完成后,才能开始渲染页面(页面在浏览器解析到<body>的起始标签时开始渲染)。对于需要很多JavaScript的页面,这会导致页面渲染的明显延迟,在此期间浏览器窗口完全空白。为解决这个问题,现代Web应用程序通常将所有JavaScript引用放在<body>元素中的页面内容后面

defer的属性。这个属性表示脚本在执行的时候不会改变页面的结构。也就是说,脚本会被延迟到整个页面都解析完毕后再运行。因此,在<script>元素上设置defer属性,相当于告诉浏览器立即下载,但延迟执行。

async属性与defer类似。当然,它们两者也都只适用于外部脚本,都会告诉浏览器立即开始下载。不过,与defer不同的是,标记为async的脚本并不保证能按照它们出现的次序执

2.5 小结

JavaScript是通过<script>元素插入到HTML页面中的。这个元素可用于把JavaScript代码嵌入到HTML页面中,跟其他标记混合在一起,也可用于引入保存在外部文件中的JavaScript。本章的重点可以总结如下。
❑ 要包含外部JavaScript文件,必须将src属性设置为要包含文件的URL。文件可以跟网页在同一台服务器上,也可以位于完全不同的域。
❑ 所有<script>元素会依照它们在网页中出现的次序被解释。在不使用defer和async属性的情况下,包含在<script>元素中的代码必须严格按次序解释。
❑ 对不推迟执行的脚本,浏览器必须解释完位于<script>元素中的代码,然后才能继续渲染页面的剩余部分。为此,通常应该把<script>元素放到页面末尾,介于主内容之后及标签之前。
❑ 可以使用defer属性把脚本推迟到文档渲染完毕后再执行。推迟的脚本原则上按照它们被列出的次序执行。
❑ 可以使用async属性表示脚本不需要等待其他脚本,同时也不阻塞文档渲染,即异步加载。异步脚本不能保证按照它们在页面中出现的次序执行。
❑ 通过使用<noscript>元素,可以指定在浏览器不支持脚本时显示的内容。如果浏览器支持并启用脚本,则<noscript>元素中的任何内容都不会被渲染。

第3章 语言基础

语法
❑ 数据类型
❑ 流控制语句
❑ 理解函数任何语言的核心所描述的都是这门语言在最基本的层面上如何工作,涉及语法、操作符、数据类型以及内置功能,在此基础之上才可以构建复杂的解决方案。

3.1 语法

ECMAScript中一切都区分大小写。无论是变量、函数名还是操作符,都区分大小写。

所谓标识符,就是变量、函数、属性或函数参数的名称。标识符可以由一或多个下列字符组成:
❑ 第一个字符必须是一个字母、下划线(_)或美元符号($);
❑ 剩下的其他字符可以是字母、下划线、美元符号或数字。标识符中的字母可以是扩展ASCII(Extended ASCII)中的字母,也可以是Unicode的字母字符,如À和Æ(但不推荐使用)。

按照惯例,ECMAScript标识符使用驼峰大小写形式,即第一个单词的首字母小写,后面每个单词的首字母大写

3.3 变量

ECMAScript变量是松散类型的,意思是变量可以用于保存任何类型的数据。每个变量只不过是一个用于保存任意值的命名占位符。有3个关键字可以声明变量:var、const和let。

let声明的范围是块作用域,而var声明的范围是函数作用域。

let与var的另一个重要的区别,就是let声明的变量不会在作用域中被提升。

在let声明之前的执行瞬间被称为“暂时性死区”(temporal dead zone),在此阶段引用任何后面才声明的变量都会抛出ReferenceError。

使用let在全局作用域中声明的变量不会成为window对象的属性(var声明的变量则会)。

let声明仍然是在全局作用域中发生的,相应变量会在页面的生命周期内存续。因此,为了避免SyntaxError,必须确保页面不会重复声明同一个变量。

不能使用let进行条件式声明是件好事,因为条件声明是一种反模式,它让程序变得更难理解。如果你发现自己在使用这个模式,那一定有更好的替代方式。

let出现之前,for循环定义的迭代变量会渗透到循环体外部

改成使用let之后,这个问题就消失了,因为迭代变量的作用域仅限于for循环块内部

const的行为与let基本相同,唯一一个重要的区别是用它声明变量时必须同时初始化变量,且尝试修改const声明的变量会导致运行时错误。

const声明的限制只适用于它指向的变量的引用。换句话说,如果const变量引用的是一个对象,那么修改这个对象内部的属性并不违反const的限制。

不能用const来声明迭代变量(因为迭代变量会自增)

只想用const声明一个不会被修改的for循环变量,那也是可以的。

ECMAScript 6增加let和const从客观上为这门语言更精确地声明作用域和语义提供了更好的支持。

使用const声明可以让浏览器运行时强制保持变量不变,也可以让静态代码分析工具提前发现不合法的赋值操作。因此,很多开发者认为应该优先使用const来声明变量,只在提前知道未来会有修改时,再使用let。

3.4 数据类型

ECMAScript有6种简单数据类型(也称为原始类型): Undefined、Null、Boolean、Number、String和Symbol。Symbol(符号)是ECMAScript 6新增的。还有一种复杂数据类型叫Object(对象)。Object是一种无序名值对的集合。

typeof function () {} // “function”
typeof Function // “function”
typeof Object // “function”
typeof Object // “function”
typeof [] // “object”
typeof {} // “object”

Object instanceof Function // true
Function instanceof Object // true

对一个值使用typeof操作符会返回下列字符串之一:
❑ “undefined”表示值未定义;
❑ “boolean”表示值为布尔值;
❑ “string”表示值为字符串;
❑ “number”表示值为数值;
❑ “object”表示值为对象(而不是函数)或null;
❑ “function”表示值为函数;
❑ “symbol”表示值为符号。

严格来讲,函数在ECMAScript中被认为是对象,并不代表一种数据类型。可是,函数也有自己特殊的属性。为此,就有必要通过typeof操作符来区分函数和其他对象。

Null类型同样只有一个值,即特殊值null。逻辑上讲,null值表示一个空对象指针,这也是给typeof传一个null会返回”object”的原因

任何时候,只要变量要保存对象,而当时又没有那个对象可保存,就要用null来填充该变量。这样就可以保持null是空对象指针的语义,并进一步将其与undefined区分开来。

这两个布尔值不同于数值,因此true不等于1,false不等于0。

布尔值字面量true和false是区分大小写的,因此True和False(及其他大小混写形式)是有效的标识符,但不是布尔值。

Number类型使用IEEE 754格式表示整数和浮点值(在某些语言中也叫双精度值)。

整数也可以用八进制(以8为基数)或十六进制(以16为基数)字面量表示。对于八进制字面量,第一个数字必须是零(0),然后是相应的八进制数字(数值0~7)。如果字面量中包含的数字超出了应有的范围,就会忽略前缀的零,后面的数字序列会被当成十进制数

ECMAScript 2015或ES6中的八进制值通过前缀0o来表示;严格模式下,前缀0会被视为语法错误,如果要表示八进制值,应该使用前缀0o。——译者注

八进制字面量在严格模式下是无效的,会导致JavaScript引擎抛出语法错误。

八进制字面量在严格模式下是无效的,会导致JavaScript引擎抛出语法错误。

要创建十六进制字面量,必须让真正的数值前缀0x(区分大小写),然后是十六进制数字(0~9以及A~F)。十六进制数字中的字母大小写均可。

使用八进制和十六进制格式创建的数值在所有数学操作中都被视为十进制数值。

因为存储浮点值使用的内存空间是存储整数值的两倍,所以ECMAScript总是想方设法把值转换为整数。在小数点后面没有数字的情况下,数值就会变成整数。类似地,如果数值本身就是整数,只是小数点后面跟着0(如1.0),那它也会被转换为整数

对于非常大或非常小的数值,浮点值可以用科学记数法来表示。科学记数法用于表示一个应该乘以10的给定次幂的数值。ECMAScript中科学记数法的格式要求是一个数值(整数或浮点数)后跟一个大写或小写的字母e,再加上一个要乘的10的多少次幂。

科学记数法也可以用于表示非常小的数值,例如0.00000000000000003。这个数值用科学记数法可以表示为3e-17。默认情况下,ECMAScript会将小数点后至少包含6个零的浮点值转换为科学记数法(例如,0.000000 3会被转换为3e-7)。

浮点值的精确度最高可达17位小数,但在算术计算中远不如整数精确。例如,0.1加0.2得到的不是0.3,而是0.30000000000000004。

ECMAScript可以表示的最小数值保存在Number.MIN_VALUE中,这个值在多数浏览器中是5e-324;可以表示的最大数值保存在Number.MAX_VALUE中,这个值在多数浏览器中是1.797693134862315 7e+308。如果某个计算得到的数值结果超出了JavaScript可以表示的范围,那么这个数值会被自动转换为一个特殊的Infinity(无穷)值。任何无法表示的负数以-Infinity(负无穷大)表示,任何无法表示的正数以Infinity(正无穷大)表示。

console.log(0/0); // NaN
console.log(-0/+0); // NaN

有一个特殊的数值叫NaN,意思是“不是数值”(Not a Number),用于表示本来要返回数值的操作失败了(而不是抛出错误)。比如,用0除任意数值在其他语言中通常都会导致错误,从而中止代码执行。但在ECMAScript中,0、+0或-0相除会返回NaN

有一个特殊的数值叫NaN,意思是“不是数值”(Not a Number),用于表示本来要返回数值的操作失败了(而不是抛出错误)。比如,用0除任意数值在其他语言中通常都会导致错误,从而中止代码执行。但在ECMAScript中,0、+0或-0相除会返回NaN

console.log(5/0); // Infinity
console.log(5/-0); // -Infinity

如果分子是非0值,分母是有符号0或无符号0,则会返回Infinity或-Infinity

如果分子是非0值,分母是有符号0或无符号0,则会返回Infinity或-Infinity

NaN计算操作,返回NaN;NaN不等于其本身,用isNaN()判断。

NaN有几个独特的属性。首先,任何涉及NaN的操作始终返回NaN(如NaN/10),在连续多步计算时这可能是个问题。其次,NaN不等于包括NaN在内的任何值。

ECMAScript提供了isNaN()函数。该函数接收一个参数,可以是任意数据类型,然后判断这个参数是否“不是数值”。把一个值传给isNaN()后,该函数会尝试把它转换为数值。某些非数值的值可以直接转换成数值,如字符串”10”或布尔值。任何不能转换为数值的值都会导致这个函数返回true。

#TODO

注意 虽然不常见,但isNaN()可以用于测试对象。此时,首先会调用对象的valueOf()方法,然后再确定返回的值是否可以转换为数值。如果不能,再调用toString()方法,并测试其返回值。这通常是ECMAScript内置函数和操作符的工作方式,本章后面会讨论。

有3个函数可以将非数值转换为数值:Number()、parseInt()和parseFloat()。Number()是转型函数,可用于任何数据类型。

Number()函数基于如下规则执行转换。
❑ 布尔值,true转换为1,false转换为0。
❑ 数值,直接返回。
❑ null,返回0。
❑ undefined,返回NaN。
❑ 字符串,应用以下规则。
__ ■ 如果字符串包含数值字符,包括数值字符前面带加、减号的情况,则转换为一个十进制数值。因此,Number(“1”)返回1, Number(“123”)返回123, Number(“011”)返回11(忽略前面的零)。
__ ■ 如果字符串包含有效的浮点值格式如”1.1”,则会转换为相应的浮点值(同样,忽略前面的零)。
__ ■ 如果字符串包含有效的十六进制格式如”0xf”,则会转换为与该十六进制值对应的十进制整数值。
__ ■ 如果是空字符串(不包含字符),则返回0。
__ ■ 如果字符串包含除上述情况之外的其他字符,则返回NaN。
❑ 对象,调用valueOf()方法,并按照上述规则转换返回的值。如果转换结果是NaN,则调用toString()方法,再按照转换字符串的规则转换。

parseInt()工作机制:

  1. 从非空字符开始转换
  2. 非数值、加号、减号开头返回NaN
  3. 空字符串也返回NaN
  4. 非以上情况继续转换
  5. 可支持不同的进制格式字符
  6. 非数值/末尾字符结束转换
  7. 第二个参数支持指定2~36进制,强制识别

parseInt(‘0xA’, 10) // 0xA 十进制为10,此处返回0

考虑到用Number()函数转换字符串时相对复杂且有点反常规,通常在需要得到整数时可以优先使用parseInt()函数。parseInt()函数更专注于字符串是否包含数值模式。字符串最前面的空格会被忽略,从第一个非空格字符开始转换。如果第一个字符不是数值字符、加号或减号,parseInt()立即返回NaN。这意味着空字符串也会返回NaN(这一点跟Number()不一样,它返回0)。如果第一个字符是数值字符、加号或减号,则继续依次检测每个字符,直到字符串末尾,或碰到非数值字符。比如,”1234blue”会被转换为1234,因为”blue”会被完全忽略。类似地,”22.5”会被转换为22,因为小数点不是有效的整数字符。
假设字符串中的第一个字符是数值字符,parseInt()函数也能识别不同的整数格式(十进制、八进制、十六进制)。换句话说,如果字符串以”0x”开头,就会被解释为十六进制整数。如果字符串以”0”开头,且紧跟着数值字符,在非严格模式下会被某些实现解释为八进制整数。

考虑到用Number()函数转换字符串时相对复杂且有点反常规,通常在需要得到整数时可以优先使用parseInt()函数。parseInt()函数更专注于字符串是否包含数值模式。字符串最前面的空格会被忽略,从第一个非空格字符开始转换。如果第一个字符不是数值字符、加号或减号,parseInt()立即返回NaN。这意味着空字符串也会返回NaN(这一点跟Number()不一样,它返回0)。如果第一个字符是数值字符、加号或减号,则继续依次检测每个字符,直到字符串末尾,或碰到非数值字符。比如,”1234blue”会被转换为1234,因为”blue”会被完全忽略。类似地,”22.5”会被转换为22,因为小数点不是有效的整数字符。假设字符串中的第一个字符是数值字符,parseInt()函数也能识别不同的整数格式(十进制、八进制、十六进制)。换句话说,如果字符串以”0x”开头,就会被解释为十六进制整数。如果字符串以”0”开头,且紧跟着数值字符,在非严格模式下会被某些实现解释为八进制整数。

传底数参数相当于让parseInt()自己决定如何解析,所以为避免解析出错,建议始终传给它第二个参数。

parseFloat()函数的工作方式跟parseInt()函数类似,都是从位置0开始检测每个字符。同样,它也是解析到字符串末尾或者解析到一个无效的浮点数值字符为止。这意味着第一次出现的小数点是有效的,但第二次出现的小数点就无效了,此时字符串的剩余字符都会被忽略。因此,”22.34.5”将转换成22.34。

parseFloat()函数的另一个不同之处在于,它始终忽略字符串开头的零。这个函数能识别前面讨论的所有浮点格式,以及十进制格式(开头的零始终被忽略)。十六进制数值始终会返回0。因为parseFloat()只解析十进制值,因此不能指定底数。最后,如果字符串表示整数(没有小数点或者小数点后面只有一个零),则parseFloat()返回整数。

https://developer.mozilla.org/zh-CN/docs/Glossary/Unicode

String(字符串)数据类型表示零或多个16位Unicode字符序列。字符串可以使用双引号(”)、单引号(’)或反引号(`)标示

String(字符串)数据类型表示零或多个16位Unicode字符序列。字符串可以使用双引号(”)、单引号(’)或反引号(`)标示

注意 如果字符串中包含双字节字符,那么length属性返回的值可能不是准确的字符数。第5章将具体讨论如何解决这个问题。

3.5 操作符

后缀版与前缀版的主要区别在于,后缀版递增和递减在语句被求值后才发生。

ECMAScript中的所有数值都以IEEE 754 64位格式存储,但位操作并不直接应用到64位表示,而是先把值转换为32位整数,再进行位操作,之后再把结果转换为64位。

4.3 垃圾回收

JavaScript最常用的垃圾回收策略是标记清理(mark-and-sweep)。当变量进入上下文,比如在函数内部声明一个变量时,这个变量会被加上存在于上下文中的标记。

另一种没那么常用的垃圾回收策略是引用计数(reference counting)。其思路是对每个值都记录它被引用的次数。

开发者不知道什么时候运行时会收集垃圾,因此最好的办法是在写代码时就要做到:无论什么时候开始收集垃圾,都能让它尽快结束工作。

5.3 原始值包装类型

JavaScript字符串使用了两种Unicode编码混合的策略:UCS-2和UTF-16。对于可以采用16位编码的字符(U+0000~U+FFFF)

5.5 小结

JavaScript比较独特的一点是,函数实际上是Function类型的实例,也就是说函数也是对象。因为函数也是对象,所以函数也有方法,可以用于增强其能力。

6.2 Array

Array.from() 方法从一个类似数组或可迭代对象创建一个新的,浅拷贝的数组实例。

显然form比偶of更加强大。

Array构造函数还有两个ES6新增的用于创建数组的静态方法:from()和of()。from()用于将类数组结构转换为数组实例,而of()用于将一组参数转换为数组实例。

Array构造函数还有两个ES6新增的用于创建数组的静态方法:from()和of()。from()用于将类数组结构转换为数组实例,而of()用于将一组参数转换为数组实例。

function fn () { console.log(Array.from(arguments)) }
fn(1,2,3,4,5) // Array(5) [ 1, 2, 3, 4, 5 ]

Array.of()可以把一组参数转换为数组。这个方法用于替代在ES6之前常用的Array.prototype.slice.call(arguments)

Array.of()可以把一组参数转换为数组。这个方法用于替代在ES6之前常用的Array.prototype. slice.call(arguments)

在ES6中,Array的原型上暴露了3个用于检索数组内容的方法:keys()、values()和entries()。keys()返回数组索引的迭代器,values()返回数组元素的迭代器,而entries()返回索引/值对的迭代器

栈是一种后进先出(LIFO, Last-In-First-Out)的结构,也就是最近添加的项先被删除。数据项的插入(称为推入,push)和删除(称为弹出,pop)只在栈的一个地方发生,即栈顶。

push()、shift()

队列以先进先出(FIFO, First-In-First-Out)形式限制访问。队列在列表末尾添加数据,但从列表开头获取数据。

队列以先进先出(FIFO, First-In-First-Out)形式限制访问。队列在列表末尾添加数据,但从列表开头获取数据。

6.3 定型数组

只是在介绍一些基础理论还有api使用,并没有涉及具体场景以及区别,详细内容更建议看https://es6.ruanyifeng.com/#docs/arraybuffer

定型数组(typed array)是ECMAScript新增的结构,目的是提升向原生库传输数据的效率。实际上,JavaScript并没有“TypedArray”类型,它所指的其实是一种特殊的包含数值类型的数组。

定型数组(typed array)是ECMAScript新增的结构,目的是提升向原生库传输数据的效率。实际上,JavaScript并没有“TypedArray”类型,它所指的其实是一种特殊的包含数值类型的数组。

OpenGL ES是OpenGL专注于2D和3D计算机图形的子集。这个新API被命名为WebGL(Web Graphics Library)

注意 SharedArrayBuffer是ArrayBuffer的一个变体,可以无须复制就在执行上下文间传递它。关于这种类型,请参考第27章。

ArrayBuffer()是一个普通的JavaScript构造函数,可用于在内存中分配特定数量的字节空间。

ArrayBuffer某种程度上类似于C++的malloc(),但也有几个明显的区别

7.1 理解迭代

Q 迭代是什么?
A 有条件的重复执行,循环是是最基础的迭代方式。
迭代器模式——某些内置的类型拥有Symbol.iterator属性(该属性是迭代器工厂模式,调用可返回一个新的迭代器)。
很多时候不需要显示调用,某些常用操作语法会自动调用。
迭代器API使用next()方法在可迭代对象中遍历数据。每次成功调用next(),都会返回一个IteratorResult对象,其中包含迭代器返回的下一个值。若不调用next(),则无法知道迭代器的当前位置。

非内置支持迭代器的类型也可使用Symbol.iterator定义一个迭代器,显示声明next函数。须知迭代器是一次性的,为了创建多歌迭代器,Symbol.iterator可以使用闭包返回迭代器。

Q 为了解决什么问题?
A迭代之前需要事先知道如何使用数据结构、 遍历顺序并不是数据结构固有的。

Q特点是什么?

A迭代器不关注类型的数据结构和遍历顺序(递增、递减等),也可以提前终止迭代器。

——————

Q 生成器是什么?
A 一种异步编程解决方案,函数内能暂停和恢复代码执行、带星号(*)的funciton就是一个生成器,初始化的时状态为暂停执行。通过next()方法让生成器开始或恢复执行。yield关键字可以让生成器停止和开始执行。

Q 生成器的作用
A 生成器对象作为可迭代对象,next() 开始/恢复执行生成器,yield暂停;yied *可产生迭代器对象。

Q 生成器跟迭代器的联系。
A 浅显的理解:基于迭代器实现,也有相同的next()函数。也可以把生成器作为迭代对象使用。

7.1 理解迭代

7.1 理解迭代

在ECMAScript较早的版本中,执行迭代必须使用循环或其他辅助结构。随着代码量增加,代码会变得越发混乱。很多语言都通过原生语言结构解决了这个问题,开发者无须事先知道如何迭代就能实现迭代操作。这个解决方案就是迭代器模式。

7.2 迭代器模式

迭代器模式(特别是在ECMAScript这个语境下)描述了一个方案,即可以把有些结构称为“可迭代对象”(iterable),因为它们实现了正式的Iterable接口,而且可以通过迭代器Iterator消费。

‘’[Symbol.iterator] // function Symbol.iterator()
‘’Symbol.iterator //String Iterator { }

实现Iterable接口(可迭代协议)要求同时具备两种能力:支持迭代的自我识别能力和创建实现Iterator接口的对象的能力。在ECMAScript中,这意味着必须暴露一个属性作为“默认迭代器”,而且这个属性必须使用特殊的Symbol.iterator作为键。

Symbol.iterator是一个默认迭代器,要想知道某个类型的变量是否有迭代功能, 可以检测该变量是否拥有该属性;且此属性返回一个迭代器器工厂函数(调用此工厂函数会返回一个新的迭代器),部分内置类型已实现Iterable接口。

实现Iterable接口(可迭代协议)要求同时具备两种能力:支持迭代的自我识别能力和创建实现Iterator接口的对象的能力。在ECMAScript中,这意味着必须暴露一个属性作为“默认迭代器”,而且这个属性必须使用特殊的Symbol.iterator作为键。

实现Iterable接口(可迭代协议)要求同时具备两种能力:支持迭代的自我识别能力和创建实现Iterator接口的对象的能力。在ECMAScript中,这意味着必须暴露一个属性作为“默认迭代器”,而且这个属性必须使用特殊的Symbol.iterator作为键。

在进行某些操作的时候,会自动调用类型的Symbol.itator迭代工厂函数生成一个迭代器:
for (let i of ‘str’) {console.log(i)} // 依次输出s、t、r

实际写代码过程中,不需要显式调用这个工厂函数来生成迭代器。实现可迭代协议的所有类型都会自动兼容接收可迭代对象的任何语言特性。

实际写代码过程中,不需要显式调用这个工厂函数来生成迭代器。实现可迭代协议的所有类型都会自动兼容接收可迭代对象的任何语言特性。

迭代器API使用next()方法在可迭代对象中遍历数据。每次成功调用next(),都会返回一个IteratorResult对象,其中包含迭代器返回的下一个值。若不调用next(),则无法知道迭代器的当前位置。

为了让一个可迭代对象能够创建多个迭代器,必须每创建一个迭代器就对应一个新计数器。为此,可以把计数器变量放到闭包里,然后通过闭包返回迭代器

7.3 生成器

拥有在一个函数块内暂停和恢复代码执行的能力。这种新能力具有深远的影响,比如,使用生成器可以自定义迭代器和实现协程。

8.1 理解对象

数据属性包含一个保存数据值的位置。值会从这个位置读取,也会写入到这个位置。

使用解构,可以在一个类似对象字面量的结构中,声明多个变量,同时执行多个赋值操作。

8.2 创建对象

ECMAScript的构造函数就是能创建对象的函数。

constructor本来是用于标识对象类型的。不过,一般认为instanceof操作符是确定对象类型更可靠的方式。

构造函数也是函数。并没有把某个函数定义为构造函数的特殊语法。

ECMAScript的Object类型有一个方法叫Object.getPrototypeOf(),返回参数的内部特性[[Prototype]]的值。

Object.values()返回对象值的数组,Object.entries()返回键/值对的数组。

8.4 类

类可以包含构造函数方法、实例方法、获取函数、设置函数和静态类方法

ECMAScript中没有正式的类这个类型。从各方面来看,ECMAScript类就是一种特殊函数。声明一个类之后,通过typeof操作符检测类标识符,表明它是一个函数

可以把方法定义在类构造函数中或者类块中,但不能在类块中给原型添加原始值或对象作为成员数据:

类定义也支持获取和设置访问器。

可以在类上定义静态方法。这些方法通常用于执行不特定于实例的操作,也不要求存在类的实例。与原型成员类似,静态成员每个类上只能有一个。

类继承使用的是新语法,但背后依旧使用的是原型链。

派生类的方法可以通过super关键字引用它们的原型。这个关键字只能在派生类中使用,而且仅限于类构造函数、实例方法和静态方法内部。在类构造函数中使用super可以调用父类构造函数。

new.target保存通过new关键字调用的类或函数。通过在实例化时检测new.target是不是抽象基类,可以阻止对抽象基类的实例化

ES6类为继承内置引用类型提供了顺畅的机制,开发者可以方便地扩展内置类型

软件设计原则:“组合胜过继承(composition over inheritance)。”

9.1 代理基础

代理是使用Proxy构造函数创建的。这个构造函数接收两个参数:目标对象和处理程序对象。

使用代理的主要目的是可以定义捕获器(trap)。捕获器就是在处理程序对象中定义的“基本操作的拦截器”。

只有在代理对象上执行这些操作才会触发捕获器。在目标对象上执行这些操作仍然会产生正常的行为。

9.2 代理捕获器与反射方法

对于在代理对象上执行的任何一种操作,只会有一个捕获处理程序被调用。不会存在重复捕获的情况。

Q 捕获器跟反射API的区别??

get()捕获器会在获取属性值的操作中被调用。对应的反射API方法为Reflect.get()。

get()捕获器会在获取属性值的操作中被调用。对应的反射API方法为Reflect.get()。

9.4 小结

宏观上看,代理是真实JavaScript对象的透明抽象层。代理可以定义包含捕获器的处理程序对象,而这些捕获器可以拦截绝大部分JavaScript的基本操作和方法。在这个捕获器处理程序中,可以修改任何基本操作的行为,当然前提是遵从捕获器不变式。

代理的应用场景是不可限量的。开发者使用它可以创建出各种编码模式,比如(但远远不限于)跟踪属性访问、隐藏属性、阻止修改或删除属性、函数参数验证、构造函数参数验证、数据绑定,以及可观察对象。

第10章 函数

因为函数实际上是对象。每个函数都是Function类型的实例,而Function也有属性和方法,跟其他引用类型一样。因为函数是对象,所以函数名就是指向函数对象的指针,而且不一定与函数本身紧密绑定。

函数想象为对象,把函数名想象为指针

10.1 箭头函数

箭头函数虽然语法简洁,但也有很多场合不适用。箭头函数不能使用arguments、super和new.target,也不能用作构造函数。此外,箭头函数也没有prototype属性。

10.2 函数名

ECMAScript 6的所有函数对象都会暴露一个只读的name属性,其中包含关于函数的信息。

10.3 理解参数

arguments对象是一个类数组对象(但不是Array的实例),因此可以使用中括号语法访问其中的元素

ECMAScript函数的参数只是为了方便才写出来的,并不是必须写出来的。

然箭头函数中没有arguments对象,但可以在包装函数中把它提供给箭头函数

10.5 默认参数值

函数的默认参数只有在函数被调用时才会求值,不会在函数定义时求值。而且,计算默认值的函数只有在调用函数但未传相应参数时才会被调用。

10.6 参数扩展与收集

箭头函数虽然不支持arguments对象,但支持收集参数的定义方式,因此也可以实现与使用arguments一样的逻辑:

10.7 函数声明与函数表达式

JavaScript引擎在任何代码执行之前,会先读取函数声明,并在执行上下文中生成函数定义。而函数表达式必须等到代码执行到它那一行,才会在执行上下文中生成函数定义。

10.9 函数内部

使用arguments.callee就可以让函数逻辑与函数名解耦

函数名只是保存指针的变量。因此全局定义的sayColor()函数和o.sayColor()是同一个函数,只不过执行的上下文不同。

10.10 函数属性与方法

函数还有两个方法:apply()和call()。

使用call()或apply()的好处是可以将任意对象设置为任意函数的作用域,这样对象可以不用关心方法。

10.14 闭包

包指的是那些引用了另一个函数作用域中变量的函数,通常是在嵌套函数中实现的。

11.1 异步编程

同步行为对应内存中顺序执行的处理器指令。

异步行为类似于系统中断,即当前进程外部的实体可以触发代码执行。

第二个指令块(加操作及赋值操作)是由系统计时器触发的,这会生成一个入队执行的中断。到底什么时候会触发这个中断,这对JavaScript运行时来说是一个黑盒,因此实际上无法预知(尽管可以保证这发生在当前线程的同步代码执行之后,否则回调都没有机会出列被执行)。无论如何,在排定回调以后基本没办法知道系统状态何时变化。

随着代码越来越复杂,回调策略是不具有扩展性的。“回调地狱”这个称呼可谓名至实归。嵌套回调的代码维护起来就是噩梦。

11.2 期约

2012年Promises/A+组织分叉(fork)了CommonJS的Promises/A建议,并以相同的名字制定了Promises/A+规范。这个规范最终成为了ECMAScript 6规范实现的范本。

❑ 待定(pending)
❑ 兑现(fulfilled,有时候也称为“解决”, resolved)
❑ 拒绝(rejected)

无论落定为哪种状态都是不可逆的。只要从待定转换为兑现或拒绝,期约的状态就不再改变。

期约故意将异步行为封装起来,从而隔离外部的同步代码。

对这个静态方法而言,如果传入的参数本身是一个期约,那它的行为就类似于一个空包装。因此,Promise.resolve()可以说是一个幂等方法

Promise.reject()会实例化一个拒绝的期约并抛出一个异步错误(这个错误不能通过try/catch捕获,而只能通过拒绝处理程序捕获)

拒绝期约的错误并没有抛到执行同步代码的线程里,而是通过浏览器异步消息队列来处理的。因此,try/catch块并不能捕获该错误。

then()和catch()的onRejected处理程序在语义上相当于try/catch。出发点都是捕获错误之后将其隔离,同时不影响正常逻辑执行。

11.3 异步函数

[插图]

11.4 小结

异步函数是将期约应用于JavaScript函数的结果。异步函数可以暂停执行,而不阻塞主线程。

12.1 window对象

BOM的核心是window对象,表示浏览器的实例。window对象在浏览器中有两重身份,一个是ECMAScript中的Global对象,另一个就是浏览器窗口的JavaScript接口。

如果在这里使用let或const替代var,则不会把变量添加给全局对象

window.devicePixelRatio实际上与每英寸像素数(DPI, dots per inch)是对应的。DPI表示单位像素密度,而window.devicePixelRatio表示物理像素与逻辑像素之间的缩放系数。

12.2 location对象

它既是window的属性,也是document的属性。也就是说,window.location和document.location指向同一个对象。

URLSearchParams提供了一组标准API方法,通过它们可以检查和修改查询字符串。

14.1 节点层级

其中,document节点表示每个文档的根节点。在这里,根节点的唯一子节点是<html>元素,我们称之为文档元素(documentElement)。文档元素是文档最外层的元素,所有其他元素都存在于这个元素之内。每个文档只能有一个文档元素。

15.1 Selectors API

则querySelector()方法会抛出错误。

querySelectorAll()返回的NodeList实例一个属性和方法都不缺,但它是一个静态的“快照”,而非“实时”的查询。

第16章 DOM2和DOM3

DOM1(DOM Level 1)主要定义了HTML和XML文档的底层结构。DOM2(DOM Level 2)和DOM3(DOM Level 3)在这些结构之上加入更多交互能力,提供了更高级的XML特性。

16.2 样式

浏览器在每个元素上都暴露了getBoundingClientRect()方法,返回一个DOMRect对象,包含6个属性:left、top、right、bottom、height和width。这些属性给出了元素在页面中相对于视口的位置。图16-4[插图]展示了这些属性的含义。

17.1 事件流

IE事件流被称为事件冒泡,这是因为事件被定义为从最具体的元素(文档树中最深的节点)开始触发,然后向上传播至没有那么具体的元素(文档)。

Netscape Communicator团队提出了另一种名为事件捕获的事件流。事件捕获的意思是最不具体的节点应该最先收到事件,而最具体的节点应该最后收到事件

由于旧版本浏览器不支持,因此实际当中几乎不会使用事件捕获。通常建议使用事件冒泡,特殊情况下可以使用事件捕获。

DOM2 Events规范规定事件流分为3个阶段:事件捕获、到达目标和事件冒泡。

17.2 事件处理程序

在JavaScript中指定事件处理程序的传统方式是把一个函数赋值给(DOM元素的)一个事件处理程序属性。

DOM2 Events为事件处理程序的赋值和移除定义了两个方法:addEventListener()和remove-EventListener()。

大多数情况下,事件处理程序会被添加到事件流的冒泡阶段,主要原因是跨浏览器兼容性好。把事件处理程序注册到捕获阶段通常用于在事件到达其指定目标之前拦截事件。如果不需要拦截,则不要使用事件捕获。

IE实现了与DOM类似的方法,即attachEvent()和detachEvent()。这两个方法接收两个同样的参数:事件处理程序的名字和事件处理函数。

17.3 事件对象

preventDefault()方法用于阻止特定事件的默认动作。

stopPropagation()方法用于立即阻止事件流在DOM结构中传播,取消后续的事件捕获或冒泡。

17.4 事件类型

当用户按下键盘上的某个字符键时,首先会触发keydown事件,然后触发keypress事件,最后触发keyup事件。

对于非字符键,在键盘上按一下这个键,会先触发keydown事件,然后触发keyup事件。如果按住某个非字符键不放,则会重复触发keydown事件,直到这个键被释放,此时会触发keyup事件。

对于keydown和keyup事件,event对象的keyCode属性中会保存一个键码,对应键盘上特定的一个键。对于字母和数字键,keyCode的值与小写字母和数字的ASCII编码一致。

浏览器在event对象上支持charCode属性,只有发生keypress事件时这个属性才会被设置值,包含的是按键字符对应的ASCII编码。

一旦有了字母编码,就可以使用String.fromCharCode()方法将其转换为实际的字符了。

17.5 内存与性能

事件委托利用事件冒泡,可以只使用一个事件处理程序来管理一种类型的事件。

被innerHTML删除的元素上如果有事件处理程序,就不会被垃圾收集程序正常清理。

如果在页面卸载后事件处理程序没有被清理,则它们仍然会残留在内存中。之后,浏览器每次加载和卸载页面(比如通过前进、后退或刷新),内存中残留对象的数量都会增加,这是因为事件处理程序不会被回收。

17.6 模拟事件

任何时候,都可以使用document.createEvent()方法创建一个event对象。这个方法接收一个参数,此参数是一个表示要创建事件类型的字符串。

DOM3增加了自定义事件的类型。自定义事件不会触发原生DOM事件,但可以让开发者定义自己的事件。要创建自定义事件,需要调用createEvent(“CustomEvent”)。

18.1 使用requestAnimationFrame

因此,实现平滑动画最佳的重绘间隔为1000毫秒/60,大约17毫秒。以这个速度重绘可以实现最平滑的动画,因为这已经是浏览器的极限了。

因为requestAnimationFrame()只会调用一次传入的函数,所以每次更新用户界面时需要再手动调用它一次。同样,也需要控制动画何时停止。结果就会得到非常平滑的动画。

通过requestAnimationFrame节流

19.5 富文本编辑

在空白HTML文件中嵌入一个iframe。通过designMode属性,可以将这个空白文档变成可以编辑的,实际编辑的则是<body>元素的HTML。

20.1 Atomics与SharedArrayBuffer

SharedArrayBuffer与ArrayBuffer具有同样的API。二者的主要区别是ArrayBuffer必须在不同执行上下文间切换,SharedArrayBuffer则可以被任意多个执行上下文同时使用。

浏览器的JavaScript编译器和CPU架构本身都有权限重排指令以提升程序执行效率。正常情况下,JavaScript的单线程环境是可以随时进行这种优化的。但多线程下的指令重排可能导致资源争用,而且极难排错。

20.2 跨上下文消息

跨文档消息,有时候也简称为XDM(cross-document messaging),是一种在不同执行上下文(如不同工作线程或不同源的页面)间传递信息的能力。

20.3 Encoding API

Encoding API主要用于实现字符串与定型数组之间的转换。规范新增了4个用于执行转换的全局类:TextEncoder、TextEncoderStream、TextDecoder和TextDecoderStream。

20.4 File API与Blob API

File API仍然以表单中的文件输入字段为基础,但是增加了直接访问文件信息的能力。

FileReader类型表示一种异步文件读取机制。可以把FileReader想象成类似于XMLHttpRequest,只不过是用于从文件系统读取文件,而不是从服务器读取数据。F

blob表示二进制大对象(binary larget object),是JavaScript对不可修改二进制数据的封装类型。包含字符串的数组、ArrayBuffers、ArrayBufferViews,甚至其他Blob都可以用来创建blob。

对象URL有时候也称作Blob URL,是指引用存储在File或Blob中数据的URL。

创建对象URL,可以使用window.URL.createObjectURL()方法并传入File或Blob对象。这个函数返回的值是一个指向内存中地址的字符串。

使用完数据之后,最好能释放与之关联的内存。只要对象URL在使用中,就不能释放内存。如果想表明不再使用某个对象URL,则可以把它传给window.URL.revokeObjectURL()。页面卸载时,所有对象URL占用的内存都会被释放。不过,最好在不使用时就立即释放内存,以便尽可能保持页面占用最少资源。

20.8 Page Visibility API

Web开发中一个常见的问题是开发者不知道用户什么时候真正在使用页面。如果页面被最小化或隐藏在其他标签页后面,那么轮询服务器或更新动画等功能可能就没有必要了

20.9 Streams API

流的基本单位是块(chunk)。块可是任意数据类型,但通常是定型数组。

20.10 计时API

微秒 [1] ,时间单位,符号μs(英语:microsecond ),1微秒等于百万分之一秒(10的负6次方秒),1毫秒等于千分之一秒(10的负3次方秒) [2] 。

为此,必须使用不同的计时API来精确且准确地度量时间的流逝。HighResolution Time API定义了window.performance.now(),这个方法返回一个微秒精度的浮点值。

为此,必须使用不同的计时API来精确且准确地度量时间的流逝。HighResolution Time API定义了window.performance.now(),这个方法返回一个微秒精度的浮点值。

Navigation Timing API提供了高精度时间戳,用于度量当前页面加载速度。

Resource Timing API提供了高精度时间戳,用于度量当前页面加载时请求资源的速度。

20.11 Web组件

概念上讲,影子DOM(shadow DOM)Web组件相当直观,通过它可以将一个完整的DOM树作为节点添加到父DOM树。

浏览器会尝试将无法识别的元素作为通用元素整合进DOM。当然,这些元素默认也不会做任何通用HTML元素不能做的事。

调用customElements.define()方法可以创建自定义元素。

20.12 Web Cryptography API

生成、使用和应用加密密钥对,加密和解密消息

在需要生成随机值时,很多人会使用Math.random()。这个方法在浏览器中是以伪随机数生成器(PRNG, PseudoRandom Number Generator)方式实现的。所谓“伪”指的是生成值的过程不是真的随机。PRNG生成的值只是模拟了随机的特性。浏览器的PRNG并未使用真正的随机源,只是对一个内部状态应用了固定的算法。

初始状态在重复自身之前都会产生2128-1个伪随机值。这种循环被称为置换循环(permutation cycle),而这个循环的长度被称为一个周期(period)。

伪随机数生成器主要用于快速计算出看起来随机的值。

密码学安全伪随机数生成器(CSPRNG, Cryptographically Secure PseudoRandom Number Generator)额外增加了一个熵作为输入,例如测试硬件时间或其他无法预计行为的系统特性。这样一来,计算速度明显比常规PRNG慢很多,但CSPRNG生成的值就很难预测,可以用于加密了。

加密、散列、签名和生成密钥

❑ SHA-1(Secure Hash Algorithm 1):架构类似MD5的散列函数。

❑ SHA-2(Secure Hash Algorithm 2):构建于相同耐碰撞单向压缩函数之上的一套散列函数。

这个算法被认为是安全的,广泛应用于很多领域和协议,包括TLS、PGP和加密货币(如比特币)。

21.2 错误处理

错误处理在编程中的重要性毋庸置疑。所有主流Web应用程序都需要定义完善的错误处理协议,大多数优秀的应用程序有自己的错误处理策略,尽管主要逻辑是放在服务器端的。

InternalError类型的错误会在底层JavaScript引擎抛出异常时由浏览器抛出。例如,递归过多导致了栈溢出。

RangeError错误会在数值越界时抛出。

ReferenceError会在找不到对象时发生。

最后一种错误类型是URIError,只会在使用encodeURI()或decodeURI()但传入了格式错误的URI时发生。

使用throw操作符时,代码立即停止执行,除非try/catch语句捕获了抛出的值。

捕获错误的目的是阻止浏览器以其默认方式响应;抛出错误的目的是为错误提供有关其发生原因的说明。

何没有被try/catch语句处理的错误都会在window对象上触发error事件。

Web应用程序开发中的一个常见做法是建立中心化的错误日志存储和跟踪系统。

21.3 调试技术

浏览器控制台是个读取-求值-打印-循环(REPL, read-eval-print-loop),与页面的JavaScript运行时并发。

21.4 旧版IE的常见错误

旧版IE中所有DOM对象都是用COM对象实现的,并非原生JavaScript对象。在涉及垃圾回收时,这可能会导致很多奇怪的行为。其中,”member not found”错误是IE中垃圾回收程序常报告的错误。

22.2 浏览器对XPath的支持

XPath是为了在DOM文档中定位特定节点而创建的,因此它对XML处理很重要。

第23章 JSON

理解JSON最关键的一点是要把它当成一种数据格式,而不是编程语言。

JSON也不是只能在JavaScript中使用,它是一种通用数据格式。很多语言都有解析和序列化JSON的内置能力。

23.1 语法

JSON没有变量、函数或对象实例的概念。

23.2 解析与序列化

在序列化JavaScript对象时,所有函数和原型成员都会有意地在结果中省略。此外,值为undefined的任何属性也会被跳过。最终得到的就是所有实例属性均为有效JSON数据类型的表示。

24.1 XMLHttpRequest对象

发送GET请求最常见的一个错误是查询字符串格式不对。查询字符串中的每个名和值都必须使用encodeURIComponent()编码,所有名/值对必须以和号(&)分隔

XHR模拟表单提交。为此,第一步需要把Content-Type头部设置为”application/x-www-formurlencoded”,这是提交表单时使用的内容类型。

现代Web应用程序中经常需要对表单数据进行序列化,因此XMLHttpRequest Level 2新增了FormData类型。FormData类型便于表单序列化,也便于创建与表单类似格式的数据然后通过XHR发送。

24.2 进度事件

Progress Events是W3C的工作草案,定义了客户端-服务器端通信。

24.3 跨源资源共享

最好在访问本地资源时使用相对URL,在访问远程资源时使用绝对URL。

默认情况下,跨源请求不提供凭据(cookie、HTTP认证和客户端SSL证书)。可以通过将withCredentials属性设置为true来表明请求会发送凭据。

24.4 替代性跨源技术

这种动态创建图片的技术经常用于图片探测(image pings)。图片探测是与服务器之间简单、跨域、单向的通信。

图片探测频繁用于跟踪用户在页面上的点击操作或动态显示广告。当然,图片探测的缺点是只能发送GET请求和无法获取服务器响应的内容。这也是只能利用图片探测实现浏览器与服务器单向通信的原因。

JSONP格式包含两个部分:回调和数据。

24.5 Fetch API

系统级网络协议已经成功完成消息的一次往返传输。至于真正的“成功”请求,则需要在处理响应时再定义。

因为服务器没有响应而导致浏览器超时,这样真正的fetch()失败会导致期约被拒绝

Headers对象是所有外发请求和入站响应头部的容器。

Headers对象与Map对象极为相似。这是合理的,因为HTTP头部本质上是序列化后的键/值对,它们的JavaScript表示则是中间接口。

顾名思义,Request对象是获取资源请求的接口。这个接口暴露了请求的相关信息,也暴露了使用请求体的不同方式。

24.6 Beacon API

unload事件对浏览器意味着没有理由再发送任何结果未知的网络请求(因为页面都要被销毁了)

24.7 Web Socket

客户端与服务器之间可以发送非常少的数据,不会对HTTP造成任何负担。使用更小的数据包让Web Socket非常适合带宽和延迟问题比较明显的移动应用。

所有名和值都是URL编码的,因此必须使用decodeURIComponent()解码。

为绕过浏览器对每个域cookie数的限制,有些开发者提出了子cookie的概念。子cookie是在单个cookie存储的小块数据,本质上是使用cookie的值在单个cookie中存储多个名/值对。

25.2 Web Storage

Web Storage的第2版定义了两个对象:localStorage和sessionStorage。localStorage是永久存储机制,sessionStorage是跨会话的存储机制。这两种浏览器存储API提供了在浏览器中不受页面刷新影响而存储数据的两种方式。

26.1 理解模块模式

模块系统的核心是管理依赖。

26.3 使用ES6之前的模块加载器

为了统一CommonJS和AMD生态系统,通用模块定义(UMD, Universal Module Definition)规范应运而生。UMD可用于创建这两个系统都可以使用的模块代码。

第27章 工作者线程

工作者线程的价值所在:允许把主线程的工作转嫁给独立的实体,而不会改变现有的单线程模型。

27.1 工作者线程简介

JavaScript环境实际上是运行在托管操作系统中的虚拟环境。在浏览器中每打开一个页面,就会分配一个它自己的环境。

使用工作者线程,浏览器可以在原始页面环境之外再分配一个完全独立的二级子环境。这个子环境不能与依赖单线程交互的API(如DOM)互操作,但可以与父环境并行执行代码。

工作者线程是以实际线程实现的。

工作者线程并行执行。

工作者线程可以共享某些内存

Web工作者线程规范中定义了三种主要的工作者线程:专用工作者线程、共享工作者线程和服务工作者线程。现代浏览器都支持这些工作者线程。

27.2 专用工作者线程

支持传统多线程模型的语言中,可以使用锁、互斥量,以及volatile变量。在JavaScript中,有三种在上下文间转移信息的方式:结构化克隆算法(structured clone algorithm)、可转移对象(transferable objects)和共享数组缓冲区(shared array buffers)。

始终保持固定数量的线程活动,需要时就把任务分派给它们。

27.5 小结

工作者线程可以是专用线程、共享线程。专用线程只能由一个页面使用,而共享线程则可以由同源的任意页面共享。

服务工作者线程用于让网页模拟原生应用程序。服务工作者线程也是一种工作者线程,但它们更像是网络代理,而非独立的浏览器线程。

28.1 可维护性

JavaScript并不强迫开发者把任何东西都定义为对象。它支持任何编程风格,包括传统的面向对象编程、声明式编程,以及函数式编程。

最好的方法是永远不要修改不属于你的对象。只有你自己创建的才是你的对象,包括自定义类型和对象字面量。

28.2 性能

只要函数中有引用超过两次的全局对象,就应该把这个对象保存为一个局部变量。

达夫设备的基本思路是以8的倍数作为迭代次数从而将循环展开为一系列语句。

求模、逻辑AND与和逻辑OR或都很适合替代成位操作。

A.5 数组打平方法

CMAScript 2019在Array.prototype上增加了两个方法:flat()和flatMap()。这两个方法为打平数组提供了便利。如果没有这两个方法,则打平数组就要使用迭代或递归。

A.6 Object.fromEntries()

ECMAScript 2019又给Object类添加了一个静态方法fromEntries(),用于通过键/值对数组的集合构建对象。这个方法执行与Object.entries()方法相反的操作。

这个方法可以方便地将Map实例转换为Object实例,因为Map迭代器返回的结果与fromEntries()的参数恰好匹配

B.6 类与模块

TC39委员会决定在ES6类和模块中定义的所有代码默认都处于严格模式。