第1章 什么是JavaScript
1.1 简短的历史回顾
1995年,JavaScript问世。起初的作用仅仅是替代Perl等服务器端语言处理输入验证,这在那个拨号上网的年代,在客户端就能就行基本验证已经是很让人兴奋的功能了。
1995年,网景公司一位名叫 Brendan Eich 的工程师,开始为即将发布的Netscape Navigator2开发一个叫Mocha(后改名为LiveScript)的脚本语言。在Netscape Navigator2(JavaScript 1.0)正式发布前,网景把LiveScript改名为 JavaScript,以便搭上媒体当时热烈炒作Java的顺风车。
1997年,JavaScript1.1作为提案被提交给欧洲计算机制造商协会(Ecma)。 第39技术委员会(TC39)承担了“标准化一门通用、跨平台、厂商中立的脚本语言的语法和语义”的任务。他们花了数月时间打造出 ECMA-262,也就是 ECMAScript 这个新的脚本语言标准。
1.2 JavaScript实现
完整的JavaScript实现包含以下几个部分:
-
核心(ECMAScript)
-
文档对象模型(DOM)
-
浏览器对象模型(BOM)
1.2.1 ECMAScript
ECMAScript,即ECMA-262定义的语言,但是不局限于Web浏览器,Web浏览器只是ECMAScript实现可能存在的一种 宿主环境。宿主环境提供ECMAScript的基准实现和与环境自身交互必需的扩展(如DOM)。Node.js也是一种宿主环境。
在基本层面上,ECMA-262描述了ECMAScript语言的如下部分:
-
语法
-
类型
-
语句
-
关键字
-
保留字
-
操作符
-
全局对象
ECMAScript只是对实现这个规范描述的所有方面的一门语言的称呼。JavaScript实现了ECMAScript,Adobe ActionScript也实现了。
ECMA-262第1版本质上与网景的JavaScript1.1相同,只不过删除了所有浏览器特定的代码,外加少量细微的修改。
ECMA-262第2版只做了一些编校工作,没有增减或改变任何特性。
ECMA-262第3版第一次真正对这个标准进行了更新,更新了字符串处理、错误定义和数值输出。还增加了正则表达式、新的控制语句、try/catch异常处理的支持,以及为了更好地让标准国际化所做的少量修改。对很多人来说,这标志着ECMAScript作为一门真正的编程语言的时代终于来了。
ECMA-262第4版是对这门语言的彻底修订,但由于跳跃过大,在发布之前被放弃。
ECMA-262第5版(ECMAScript 3.1)于2009年12月3日正式发布。第5版致力于厘清第3版存在的歧义,也增加了新功能,包括原生的解析和序列化JSON数据的JSON对象、方便继承和高级属性定义的方法,以及新的增强ECMAScript引擎解释和执行代码能力的严格模式。
ECMA-262第6版,俗称ES6、ES2015或ES Harmony,于2015年6月发布。这一版包含了大概这个规范有史以来最重要的一批增强特性。ES6正式支持了类、模块、迭代器、生成器、箭头函数、期约、反射、代理和众多新的数据类型。
ECMA-262第7版,也称为ES7或ES2016,于2016年6月发布。这次修订只包含少量语法层面的增强,如Array.prototype.includes和指数操作符(**)。
ECMA-262第8版,也称为ES8或ES2017,完成于2017月6月。这一版主要增加了异步函数(async/awit)、SharedArrayBuffer及Atomics API,以及Object.values()/Object.entries()/Object.getOwnPropertyDescriptors()和字符串填充方法,另外明确支持对象字面量最后的逗号。
ECMA-262第9版,也称为ES8或ES2018,发布于2018年6月。这次修订包括异步迭代、剩余和扩展属性、一组新的正则表达式特性、Promise.prototype.finally(),以及模板字面量修订。
ECMA-262第10版,也称为ES8或ES2019,发布于2019年6月。这次修订增加了Array.prototype.flat()/flatMap()、String.prototype.trimStart()/trimEnd()、Object.fromEntries()方法,以及Symbol.prototype.description属性,明确定义了Function.prototype.toString()的返回值并固定了Array.prototype.sort()的顺序。另外,这次修订解决了与JSON字符串兼容的问题,并定义了catch子句的可选绑定。
1.2.2 DOM
文档对象模型(DOM, Document Object Model)是一个应用编程接口(API),用于在HTML中使用扩展的XML。DOM将整个页面抽象为一组分层节点。HTML或XML页面的每个组成部分都是一种节点,包含不同的数据。
DOM通过创建表示文档的树,让开发者可以随心所欲地控制网页的内容和结构。使用DOM API,可以轻松地删除、添加、替换、修改节点。
1998年10月,DOM Level1成为W3C的推荐标准。这个规范由两个模块组成:DOM Core和DOM HTML。前者提供了一种映射XML文档,从而方便访问和操作文档任意部分的方式;后者扩展了前者,并添加了特定于HTML的对象和方法。
DOM Level1的目标是映射文档结构,而DOM Level2的目标则宽泛得多。这个对最初DOM的扩展增加了对鼠标和用户界面事件、范围、遍历的支持、而且通过对象接口支持了CSS。另外,DOM Level1中的DOM Core也被扩展以包含对XML命名空间的支持。
DOM Level2的新增了以下模块,以支持新的接口。
- DOM视图:描述追踪文档不同视图(如应用CSS样式前后的文档)的接口。
- DOM事件:描述事件及事件处理的接口。
- DOM样式:描述处理元素CSS样式的接口。
- DOM遍历和范围:描述遍历和操作DOM树的接口。
DOM Level3进一步扩展了DOM,增加了以统一的方式加载和保存文档的方法,还有验证文档的方法(DOM Validation)。在Level3中,DOM Core经过扩展支持了所以XML1.0的特性。
除了DOM Core和DOM HTML接口,有些其他语言也发布了自己的DOM标准。下面列出的语言是基于XML的,每一种都增加了该语言独有的DOM方法和接口:
- 可伸缩矢量图(SVG, Scalable Vector Graphics)
- 数学标记语言(MathML, Mathematical Markup Language)
- 同步多媒体集成语言(SMIL, Synchronized Multimedia Integration Language)
1.2.3 BOM
浏览器对象模型(BOM)API用于支持访问和操作浏览器的窗口。总的来说,BOM主要针对浏览器窗口和子窗口(frame),不过人们通常会把任何特定于浏览器的扩展都归在BOM的范畴内。比如,下面就是这样一些扩展:
- 弹出新浏览器窗口的能力。
- 移动、缩放和关闭浏览器窗口的能力。
- navigator对象,提供关于浏览器的详尽信息。
- location对象,提供浏览器加载页面的详尽信息。
- screen对象,提供关于用户屏幕分辨率的详尽信息。
- performance对象,提供浏览器内存占用、导航行为和时间统计的详尽信息。
- 对cookie的支持。
- 其他自定义对象,如XMLHttpRequest和IE的ActiveXObject。
第2章 HTML中的JavaScript
2.1 script元素
将JavaScript插入HTML的主要方法是使用
- async: 可选。表示应该立即开始下载脚本,但不阻止其他页面动作。只对外部脚本文件有效。
- charset: 可选。使用src属性指定的代码字符集。很少用,因为大部分浏览器不在乎它的值。
- crossorigin: 可选。配置相关请求的CORS(跨域资源共享)设置。
- defer: 可选。 表示在文档解析和显示完成后再执行脚本是没有问题的。只对外部脚本文件有效。
- integrity: 可选。允许比对接收到的资源和指定的加密签名以验证子资源完整性(SRI, Subresource Intergrity)。
- language: 废弃。
- src: 可选。表示包含要执行的代码的外部文件
- type: 可选。 代替language,表示代码块中脚本语言的内容类型(也称MIME类型)。如果这个值是module,则代代码会被当成ES6模块,这时才可以出现import和export关键字。
2.1.1 标签位置
现代Web应用程序通常将所有JavaScript引用放在元素中的页面内容后面。否则可能会导致页面渲染的明显延迟,在此期间浏览器窗口完全空白。
2.1.2 推迟执行脚本
HTML4.01为,因此最好只包含一个这样的脚本。
2.1.3 异步执行脚本
HTML5为。
给脚本添加async属性的目的是告诉浏览器,不必等脚本下载和执行完成后再加载页面,同样也不必等到该异步脚本下载和执行后再加载其他脚本。正因如此,异步脚本不应该在加载期间修改DOM。
异步脚本保证会在页面的load事件前执行,但可能会在DOMContentLoaded之前或之后。
2.1.4 动态加载脚本
JavaScript可以使用DOM API,通过向DOM中动态添加script元素来加载指定脚本。
2.2 行内代码与外部文件
虽然可以直接在HTML文件中嵌入JavaScript代码,但通常认为最佳实践是尽可能将JavaScript代码放在外部文件中。理由如下:
- 可维护性。用一个目录保存所有JavaScript文件更容易维护。开发者可以独立于HTML页面来编辑代码。
- 缓存。浏览器会根据特定的设置缓存所有外部链接的JavaScript文件。两个页面都用到同一个文件,该文件只会下载一次,所以页面加载更快。
2.3 文档模式
可以使用doctype来切换文档模式。最初的文档模式有两种:混杂模式(quirks mode)和标准模式(standards mode)。前者让IE像IE5一样(支持一些非标准的特性),后者让IE具有兼容标准的行为。
2.4 元素
针对早期浏览器不支持Javascript的问题,需要一个页面优雅降级的处理方案。出现,被用于给不支持JavaScript的浏览器提供替代内容。
可以包含任何可以出现在中的HTML元素,
在下列两种情况下,浏览器将显示包含在中的内容:
- 浏览器不支持脚本;
- 浏览器对脚本的支持被关闭。
任何一个条件满足,包含在中的内容就会被渲染。否则就不会元素中的任何内容。
第3章 语言基础
3.1 语法基础
3.1.1 区分大小写
ECMAScript中一切区分大小写。变量、函数名、操作符,都区分大小写。
3.1.2 标识符
标识符就是变量、函数、属性或函数参数的名称。标识符可以由一或多个下列字符组成:
- 第一个字符必须是一个字母、下划线(_)或美元符号($);
- 剩下的其他字符可以是字母、下划线、美元符号或数字。
3.1.3 注释
// 单行注释
/*
多行注释
*/
3.1.4 严格模式
ES5增加了严格模式(strict mode)的概念。严格模式是一种不同的JavaScript解析和执行模型,ES3的一些不规范写法在这种模式下会被处理,对于不安全的活动将抛出错误。
要启动严格模式,有两种方式:
"use strict" // 全局严格模式
----------------------------------------
function doSomething(){
"use strict" // 局部严格模式
}
3.2 关键字与保留字
略
3.3 变量
var,let,const声明变量。let与const只能在ES6及更晚的版本中使用。
3.3.1 var关键字
1. var声明作用域
使用var操作符定义的变量会成为包含它的函数的局部变量。
function test(){
var message = "hi" // 局部变量
}
test()
console.log(message) // 出错!
在函数内定义变量时省略var操作符,创建了全局变量【不建议】:
function test(){
message = "hi" // 全局变量
}
test()
console.log(message) // "hi"
2.var声明提升
使用var关键字声明的变量会自动提升到函数作用域顶部:
function foo(){
console.log(age)
var age = 26
}
foo() // undefined
等价于如下代码:
function foo(){
var age
console.log(age)
age = 26
}
foo() // undefined
这就是所谓的“提升” (hoist), 也就是把所有变量声明都拉到函数作用域的顶部。
反复多次使用var声明同一个变量也没问题。
3.3.2 let声明
let 跟 var的作用差不多,但有重要的区别:
- let声明的范围是块作用域,而var声明的范围是函数作用域。
- let不允许同一个块作用域中出现冗余申明。
- let声明不会在作用域中被提升,在let声明之前的执行瞬间被称为“暂时性死区”,在此阶段引用任何后面才声明的变量会抛出ReferenceError。
- 使用let在全局作用域中声明的变量,不会成为window对象的属性(var声明的变量则会)。
- for循环中建议用let,使用let声明迭代变量的时候,JS引擎会在后台为每个迭代循环声明一个新的迭代变量,避免for循环定义的迭代变量渗透到循环体外面。
3.3.3 const声明
与let基本相同,唯一的重要区别是:用它声明变量时必须同时初始化变量,且尝试修改const声明的变量会导致运行时错误。const 声明的限制只适用于它指向的变量的引用。换句话说,如果const变量引用的是一个对象,那么修改这个对象内部的属性并不违反const的限制。
3.3.4 声明风格及最佳实践
不使用var,优先const,let次之
3.4 数据类型
ECMAScript有6种简单数据类型(也称为原始类型):
- Undefined
- Null
- Boolean
- Number
- String
- Symbol
【ES2020新增原始类型:BigInt】
还有一种复杂数据类型叫Object。Object是一种无序名值对的集合。
3.4.1 typeof 操作符
对一个值使用typeof操作符会返回下列字符串之一:
- “undefined”表示值未定义
- "boolean"表示值为布尔值
- “string”表示值为字符串
- “number”表示值为数值
- “object”表示值为对象(而不是函数)或null【typeof null会返回"object"】
- “function"表示值为函数
- "symbol"表示值为符号
- “bigint”表示值为大整数(原书没有提到)
注意typeof 是一个操作符而不是函数,所以不需要参数(但可以使用参数)。
3.4.2 Undefind 类型
Undefind类型只有一个值,就是特殊值undefined。当使用var或let声明了变量但没有初始化时,相当于给变量赋予了undefind值。
小技巧:typeof xxx的结果是否是undefined来判断变量是否声明(前提是声明了的部分都进行了初始化)
3.4.3 Null 类型
Null类型同样只有一个值,即特殊值null。逻辑上讲,null值表示一个空对象指针。
在定义将来要保存对象值的变量时,建议使用null来初始化,不要使用其他值。
3.4.4 Boolean类型
要将其他值转换为布尔值,可以调用特定的Boolean()转型函数
| 数据类型 | 转换为true的值 | 转换为false的值 |
|---|---|---|
| Boolean | true | false |
| String | 非空字符串 | “” (空字符串) |
| Number | 非零数值(包括无穷值) | 0、NaN(后面会介绍) |
| Object | 任意对象 | null |
| Undefind | N/A(不存在) | undefind |
3.4.5 Number 类型
Number类型使用IEEE 754格式表示整数和浮点值(在某些语言中也叫双精度值)。
八进制:0o 十六进制:0x
1.浮点数
永远不要测试某个特定的浮点值【0.1+0.2不等于0.3】
2.值的范围
ECMAScript可以表示的最小数值保存在Number.MIN_VALUE中,最大数值保存在Number.MAX_VALUE中。
如果超出了范围,数值会被自动转换为Infinity/-Infinity(无穷)值。
可以使用isFinite()函数判断是否是一个有穷值。
3.NaN
“不是数值”(Not a Number),用于表示本来要返回数值的操作失败了。
- 任何涉及NaN的操作始终返回NaN。
- NaN不等于包括NaN在内的任何值。【console.log(NaN == NaN) // false】
ECMAScript提供了isNaN()函数。该函数会尝试把参数转换为数值,任何不能转换为数值的值都会导致这个函数返回true。
4.数值转换
3个函数可以将非数值转换为数值:Number()、parseInt()、parseFloat()。Number()是转型函数,可用于任意类型;后两个主要用于将字符串转为数值。
Number()函数基于如下规则执行转换。
- 布尔值,true转换为1,false转换为0
- 数值,直接返回
- null,返回0
- undefind,返回NaN
- 字符串,应用如下规则
-
- 如果字符串包含数值字符,包括数值字符前面带加减号的情况,则转换为一个十进制数值。
- 如果字符串包含有效的浮点值格式如“1.1”,则会转换为相应的浮点值(同样,忽略前面的零)。
- 如果字符串包含有效的十六进制格式如“0xf”,则会转换为与该十六进制值对应的十进制整数值。
- 如果是空字符串(不包含字符),则返回0。
- 如果字符串包含上述情况之外的其他字符,则返回NaN。
- 对象,调用valueOf()方法,并按照上述规则转换返回的值。如果转换结果是NaN,则调用toString()方法,再按照转换字符串的规则转换。
通常在需要得到整数时可以优先使用parseInt()函数。
parseInt更专注字符串是否包含数值模式。字符串最前面的空格会被忽略,从第一个非空格字符开始转换。如果第一个字符不是数值字符、加号或减号,parseInt()立即返回NaN。这意味着空字符串也会返回NaN(与Number()不一样)。parseInt()也接受第二个参数,用于指定底数(进制数)。
注意:parseInt涉及到的第二个参数坑比较多,默认值不是10,范围是[2,36]。
parseFloat()函数的工作方式与parseInt()类似。但是当parseInt()遇到第二个小数点时,剩余字符都会被忽略,因此,“22.34.5”将被转换为22.34。parseFloat()始终忽略字符串开头的零,parseFloat()只解析十进制值。
3.4.6 String 类型
1. 字符字面量
\n 换行;\t指标等等,略
2. 字符串的特点
ECMAScript中的字符串是不可变的(immutable),意思是一旦创建,它们的值就不能变了。要修改某个变量中的字符串值,必须先销毁原始的字符串,然后将包含新值的另一个字符串保存到该变量。
3. 转换为字符串
toString()方法可见于数值、布尔值、对象和字符串值。null和undefind值没有toString()方法。toString()可以接收一个底数参数,即以什么底数来输出数值的字符串表示。
不确定一个值是不是null/undefined的时候,用String()转型函数,它始终会返回表示相应类型值的字符串。String()转型函数遵循下面的规则:
- 值有toString()方法,调用该方法(不传参)并返回结果
- 值是null,返回"null"
- 值是undefined,返回"undefined"
4. 模板字面量
反引号``
模板字面量会保持反引号内部的空格
5. 字符串插值
通过在${}中使用一个JavaScript表达式实现。所有插入的值都会使用toString()强制转型为字符串。任何JavaScript表达式都可以用于插值。
6. 模板字面量标签函数
模板字面量也支持定义标签函数(tag function),而通过标签函数可以自定义插值行为。
7. 原始字符串
使用模板字面量也可直接获取原始的模板字面量内容(如换行符或Unicode字符),而不是被转换后的字符表示。
为此,可以使用默认的String.raw标签函数,也可以通过标签函数的第一个参数,即字符串数组的.raw属性取得每个字符串的原始内容
3.4.7 Symbol类型
**符号(Symbol)是原始值,且符号实例是唯一、不可变的。**用途是确保对象属性使用唯一标识符,不会发生属性冲突的危险。
1. 符号的基本用法
使用Symbol()函数初始化。【不需要new】
2. 使用全局符号注册表
如果运行时的不同部分需要共享和重用符号实例,那么可以用一个字符串作为键,在全局符号注册表中创建并重用符号。为此,需要使用Symbol.for()方法。这个方法对每个字符串键都执行幂等操作。
Symbol.keyFor()来查询全局注册表,这个方法接收符号,返回该全局符号对应的字符串键,如果查询的不是全局符号,则返回undefind。
3. 使用符号作为属性
凡是可以使用字符串或数值作为属性的地方,都可以使用符号。
注意:
类似于 Object.getOwnPropertyNames() 返回对象实例的 常规属性数组, Object.getOwnPropertySymbols() 返回 对象实例的符号属性数组。这两个方法的返回值彼此互斥。 Object.getOwnPropertyDescriptors() 会返回同时包含 常规和符号属性描述符的对象。 Reflect.ownKeys() 会返回 两种类型的键。
- Object.keys包括对象自身的(不含继承的)所有可枚举属性(不含Symbol属性)
- For…in 循环遍历对象自身的和继承的可枚举属性(不含Symbol属性)
- Object.getOwnPropertyNames(obj) 包含对象自身的所有属性(不含Symbol属性,但是包括不可枚举属性)
- Reflect.ownKeys(obj) 包含对象自身的所有属性,不管属性名是Symbol或字符串,也不管是否可枚举
4. 常用内置符号
ECMAScript6也引入了一批常用内置符号(well-known symbol),用于暴露语言内部行为,开发者可以直接访问、重写或模拟这些行为。
这些内置符号只是全局函数Symbol的普通字符串属性,指向一个符号的实例。所有这些内置符号属性都是不可写、不可枚举、不可配置的。
在提到ECMAScript规范时,经常会引用符号在规范中的名称,前缀为@@。比如,@@iterator指的就是Symbol.iterator。
具体可以参见ECMAScript 6 入门
3.4.8 Object 类型
ECMAScript中的对象其实就是一组数据和功能的集合。对象通过new操作符后跟对象类型的名称来创建。开发者可以通过创建Object类型的实例来创建自己的对象,然后再给对象添加属性和方法。
每个Object实例都有如下属性和方法。
- constructor: 用于创建当前对象的函数。在前面的例子中,这个属性的值就是Object()函数。
- hasOwnProperty(propertyName): 用于判断当前对象实例(不是原型)上是否存在给点的属性。
- isPrototypeOf(object): 用于判断当前对象是否为另一个对象的原型。
- propertyIsEnumerable(protertyName): 用于判断给点的属性是否可以使用for-in语句枚举。
- toLocaleString(): 返回对象的字符串表示。该字符串反映对象所在的本地化执行环境。
- toString(): 返回对象的字符串表示。
- valueOf(): 返回对象对应的字符串、数值或布尔值表示。通常与toString()的返回值相同。
3.5 操作符
语法层面没有任何难度,难度在于可能涉及类型转换。
1. 递增/递减操作符
++和--,分为前缀版和后缀版。
- 对于字符串,如果是有效的数值形式,则转换为数值再应用改变。变量类型从字符串变为数值。
- 对于字符串,如果不是有效的数值形式,则将变量的值设置为NaN。变量类型从字符串变为数值。
- 对于布尔值,如果是false则转换为0再应用改变。变量类型从布尔值变为数值。
- 对于布尔值,如果是true则转换为1再应用改变。变量类型从布尔值变为数值。
- 对于浮点值,加1或减1。
- 如果是对象,则调用valueOf()方法取得可以操作的值。对得到的值应用上述规则。如果是NaN,则调用toString()并再次应用其他规则。变量类型从对象变为数值。
2. 一元加和减
一元加由一个加号(+)表示。对数值没有任何影响。
如果将一元操作符应用到非数值,则会执行与使用Number()转型函数一样的类型转换。
3.5.2 位操作符
NaN和Infinity在位运算都会被当做0处理。
1. 按位非
按位非操作符用波浪符(~)表示,它的作用是返回数值的一补数。
let num1 = 25 // 二进制00000000000000000000000000011001
let num2 = ~num1 // 二进制11111111111111111111111111100110
console.log(num2) // -26
按位非的最终效果是对数值取反并减1。
2. 按位与
按位与操作符用和号(&)表示。
3. 按位或
按位或操作符用管道符(|)表示。
4.按位异或
按位异或操作符用脱字符(^)表示。
5. 有符号左移右移
有符号左移:<<
有符号右移:>>
6. 无符号左移右移
无符号左移:<<<
无符号右移:>>>
3.5.3 布尔操作符
布尔操作由3个: 逻辑非、逻辑与和逻辑或。
1. 逻辑非
逻辑非操作符由一个叹号(!)表示,可用于任何值。这个操作符始终返回布尔值。逻辑非操作符首先将操作数转换为布尔值,然后取反。逻辑非操作符遵循以下规则:
- 如果操作数是对象,则返回false
- 如果操作数是空字符串,则返回true
- 如果操作数是非空字符串,则返回false
- 如果操作数是数值0,则返回true
- 如果操作数是非0数值(包括Infinity),则返回false
- 如果操作数是null,则返回true
- 如果操作数是NaN,则返回true
- 如果操作数是undefind,则返回true
2. 逻辑与
逻辑与操作符由两个和号(&&)表示。
逻辑与操作符是一种短路操作符,如果第一个运算子的布尔值为true,则返回第二个运算子的值(注意是值,不是布尔值);如果第一个运算子的布尔值为false,则直接返回第一个运算子的值,且不再对第二个运算子求值 。
3. 逻辑或
逻辑或操作符由两个管道符(||)表示。
逻辑或操作符是一种短路操作符,如果第一个运算子的布尔值为false,则返回第二个运算子的值(注意是值,不是布尔值);如果第一个运算子的布尔值为true,则直接返回第一个运算子的值,且不再对第二个运算子求值 。
逻辑或同样也具有短路的特性。
利用逻辑或的特性,可以避免给变量赋值null或undefind
let myObject = preferredObject || backupObject
3.5.4 乘性操作符
乘法操作符,除法操作符,取余操作符
3.5.5 指数操作符
ECMAScript7新增了指数操作符**【等同于Math.pow()】
3.5.6 加性操作符
1. 加法操作符
如果两个操作数都是数值,加法操作符执行加法运算并根据如下规则返回结果。
- 如果有任一操作数是NaN,则返回NaN
- 如果是Infinity加Infinity,返回Infinity
- 如果是-Infinity加-Infinity,返回-Infinity
- 如果是Infinity加-Infinity,返回NaN
- 如果是+0 加 +0,返回+0
- 如果是-0 加 +0,返回+0
- 如果是-0 加 -0,返回-0
不过,如果有一个操作数是字符串,则要应用如下规则:
- 如果两个操作数都是字符串,则拼接
- 如果只有一个是字符串,则将另一个操作数转换为字符串,再拼接。
如果有任一操作数是对象、数值或布尔值,则调用它们的toString()方法以获取字符串,对于undefind和null则调用String()函数,分别获取“undefind"和”null“
2. 减法操作符
简单记忆,减法操作符会尽力将两个操作数转成数值(Number()。会优先调用对象的valueOf()方法,如果没有valueOf()则调用toString()方法,再将获得的值变为数值。如果操作数存在NaN,返回NaN。
- 如果是+0 减 +0,返回+0
- 如果是+0 减 -0,返回-0
- 如果是-0 减 -0,返回+0
3.5.7 关系操作符
关系操作符(>, < , >=, <=)
如果有NaN,一定返回false。
3.5.8 相等操作符
1. 等于和不等于(== , !=)
等于和不等于在比较时会进行类型转换。如果两个操作数不都是字符串,则尽力将两边的操作符转为数字。null只和null或undefind相等,undefind只和undefind或null相等。NaN不和任何值相等。
2. 全等和不全等(===, !==)
全等和不全等在比较时会首先判断数据类型是否相同,不相同则直接返回false。但是NaN仍然不和NaN相等。
3.6 语句
for-in语句用于枚举对象中的非符号(Symbol)健属性。
注意点再来一遍:
- Object.keys包括对象自身的(不含继承的)所有可枚举属性(不含Symbol属性)
- For…in 循环遍历对象自身的和继承的可枚举属性(不含Symbol属性)
- Object.getOwnPropertyNames(obj) 包含对象自身的所有属性(不含Symbol属性,但是包括不可枚举属性)
- Reflect.ownKeys(obj) 包含对象自身的所有属性,不管属性名是Symbol或字符串,也不管是否可枚举
for-of用于遍历可迭代对象的元素
switch语句可以用于所有的数据类型(很多其他语言只能用于数值),条件的值也不需要是常量,也可以是变量或者表达式。
第4章 变量、作用域与内存
4.1 原始值与引用值
JavaScript变量是松散类型的,包括两种不同类型的数据:原始值和引用值。原始值就是最简单的数据,引用值则是由多个值构成的对象。
保存原始值(Undefined,Null,Number,Boolean,String,Symbol,BigInt)的变量按值访问,操作的是储存在变量中的实际值。
保存引用值的变量是按引用访问的,操作的是对该对象的引用而不是对象本身。
4.1.1 动态属性
对于引用值,可以随时添加、修改和删除其属性和方法。
原始值不能有属性,尽管尝试给原始值添加属性不会报错。
4.1.2 复制值
通过变量把一个原始值赋给另一个变量时,原始值会被复制到新变量的位置。
在把引用从一个变量赋给另一个变量时,存储在变量中的值也会被复制到新变量所在的位置。复制的值实际是一个指针,指向存储在堆内存中的对象。
4.1.3 传递参数
ECMAScript中所有函数的参数都是按值传递的。(如果是引用值,传递的是指针的副本,尝试修改这个指针副本的指向不会影响原指针,但是修改这个指针副本指向的内容会同步影响到原指针指向的内容)
ECMAScript中函数的参数就是局部变量。
4.1.4 确定类型
typeof操作符最适合判断一个变量是否为原始类型,或者说它是判断一个变量是否是字符串、数值、布尔值或者undefined的最好方式。
typeof null返回的是"object"。
引用值的判断,使用instanceof。
4.2 执行上下文与作用域
执行上下文(又称作用域)(以下简称“上下文”)的概念在JavaScript中颇为重要。每个上下文都有一个关联的变量对象(variable object)。这个变量对象保存了上下文中所有变量和函数。
全局上下文即是最外层的上下文。浏览器中,全局上下文就是window对象。
每个函数调用都有自己的上下文。当代码执行流进入函数时,函数的上下文被推到一个上下文栈上。函数执行结束,上下文栈会弹出该函数上下文,将控制权返还给之前的执行上下文。
上下文中的代码在执行的时候,会创建变量对象的一个作用域链(scope chain)。代码正在执行的上下文的变量对象始终位于作用域的最前端。
如果上下文是函数,则其活动对象(activation object)用作变量对象。活动对象最初只有一个定义变量:arguments。作用域链中的下一个变量对象来自包含上下文(上层上下文),以此类推至全局上下文。全局上下文的变量对象始终是最后一个变量对象。
4.2.1 作用域链增强
在作用域链前端临时添加一个上下文,这个上下文在代码执行后会被删除。
- try/catch语句中的catch块
- with语句
4.2.2 变量声明
var,let(ES6),const(ES6)
关键字:变量提升、块级作用域、暂时性死区等
4.3 垃圾回收
JavaScript通过自动内存管理实现内存分配和闲置资源回收。基本思路很简单:确定哪个变量不会再使用,然后释放它占用的内存。
在浏览器的发展史上,用到过两种主要的标记策略:标记清理和引用计数。
4.3.1 标记清理
略
4.3.2 引用计数
略
4.3.3 性能
略
4.3.4 内存管理
优化内存占用的最佳手段就是保住在执行代码时只保持必要的数据。如果数据不再必要,那么把它设置为null,从而释放引用。这也可以叫做解除引用,适合全局变量和全局变量的属性。
1、通过const和let提升性能
因为它们是块级作用域,可以尽早回收内存。
2、隐藏类和删除操作
共享隐藏类可以带来潜在的性能提升
动态删除属性与动态添加属性导致的后果一样。最佳实践是把不想要的属性设置为null,这样可以保持隐藏类不变和继续共享,同时也能达到删除引用值供垃圾回收程序回收的效果。
3、内存泄漏
全局变量、定时器、闭包
第5章 基本引用类型
5.1 Date
要创建日期对象,使用new操作符来调用Date构造函数:
let now = new Date()
Date类型将日期保存为自1970年1月1日午夜(零时)至今所经过的毫秒数。
在不给Date构造函数传参的情况下,创建的对象将保存当前的日期和时间。
let now = new Date()
now.getTime() // 1602464952410(相当于Date.now())
GMT: 格林尼治标准时间
UTC: 协调世界时
GMT = UTC + 0(0时区)
5.1.1 继承的方法
**toLocaleString()**方法返回与浏览器运行的本地环境一致的日期和时间。
toString()方法通常返回带时区信息的日期和时间。
**valueOf()**返回日期的毫秒表示。
let now = new Date()
now.toLocaleString() // "2020/10/12 上午9:14:52"
now.toString() // Mon Oct 12 2020 09:14:52 GMT+0800 (中国标准时间)"
now.valueOf() // 1602465292914
let date1 = new Date(2019, 0, 1) // 2019年1月1日
let date2 = new Date(2019, 1, 1) // 2019年2月1日
console.log(date1 < date2) // true
console.log(date1 > date2) // false
常用方法
let now = new Date()
now.getMonth() // 1 (返回日期中的月(0~11),0表示一月,11表示十二月)
now.getDate() // 12 (返回日期中的日(1~31))
now.getDay() // 1 (表示周几,0表示周日,6表示周六)
now.getHours() // 9 (返回日期中的时(0~23))
5.2 RegExp
RegExp类型是ECMAScript支持正则表达式的接口,提供了大多数基础的和部分高级的正则表达式功能。
正则表达式使用类似Perl的简洁语法来创建:
let expression = /pattern/flags
pattern(模式)可以是任何简单或复杂的正则表达式,包括字符类、限定符、分组、向前查找和反向引用。
每个正则表达式可以带零个或多个flags(标记),用于控制正则表达式的行为:
-
g: 全局模式,表示查找字符串的全部内容,而不是找到第一个就结束。
-
i: 不区分大小写,表示在查找匹配时忽略pattern的字符串大小写。
-
m: 多行模式,表示查找到一行文本末尾时会继续查找。
-
y: 粘附模式,表示只查找从lastIndex开始及之后的字符串。
-
使用不同模式和标记可以创建出各种表达式:
// 匹配字符串中的所有"at" let pattern1 = /at/g // 匹配第一个"bat"或"cat",忽略大小写 let pattern2 = /[bc]at/i // 匹配所有以"at"结尾的三字符组合,忽略大小写 let pattern3 = /.at/gi如果要匹配元字符 ( [ { \ ^ $ | ] } ? * + . ,需要转义:
// 匹配第一个"[bc]at",忽略大小写 let pattern1 = /\[bc\]at/i // 匹配所有".at",忽略大小写 let pattern2 = /\.at/giRegExp实例的主要方法是exec()。主要用于配合捕获组的使用。这个方法只接收一个参数,即要应用模式的字符串。
let text = "attack on ryan" let pattern = /at(t)ack/ let matches = pattern.exec(text) // ["attack", "t", index: 0, input: "attack on ryan", groups: undefined]另一个常用方法是test()。接收一个字符串参数,如果能够匹配则返回true否则返回false。
let text = "attack on ryan" let pattern = /attack/ pattern.test(text) // true
5.3 原始值包装类型
为了方便操作原始值,ECMAScript提供了3中特殊的引用类型:Boolean、Number和 String。
每当用到某个原始值的方法或属性时,后台都会创建一个相应原始包装类型的对象,从而暴露出操作原始值的各种方法。
let s1 = "some text"
let s2 = s1.substring(2)
s1为基本字符串类型,本不应该存在**substring()**方法,但是后台会进行如下3个步骤:
- 创建一个String类型的实例
- 调用实例上的特定方法
- 销毁实例
可以想象成如下代码
let s1 = new String("some text")
let s2 = s1.substring(2)
s1 = null
因此给原始值添加属性或方法时无效的
let s1 = "some text"
s1.color = "cyan" // (临时创建的实例会在语句结束后销毁)
console.log(s1.color) // undefined
引用类型和原始值包装类型的主要区别在于对象的生命周期。在通过new实例化引用类型后,得到的实例会在离开作用域时被销毁,而自动创建的原始值包装类型则只存在于反问它的那行代码执行期间。
注意转型函数和构造函数的区别。
let value = "25"
let number = Number(value) //转型函数
console.log(typeof number) // "number"
let obj = new Number(value) //构造函数
console.log(typeof obj) // "object"
5.3.1 Boolean
注意理解原始布尔值和Boolean对象之间的区别,但是不建议使用Boolean
5.3.2 Nuber
valueOf()方法返回Number对象表示的原始数值。
toString()可以接受一个表示基数的参数,并返回相应基数形式的数值字符串。
toLocaleString()
toFixed()方法返回包含指定小数点位数的数值字符串。
toExponential()返回以科学计数法表示的数值字符串。
ES6新增的Number.isInteger()用于辨别一个数值是否保存为整数。小数位的0让人误以为是浮点值:
console.log(Number.isInteger(1)); // true
console.log(Number.isInteger(1.00)); // true
console.log(Number.isInteger(1.01)); // false
5.3.3 String
属性:
length
方法:
1、JavaScript字符
charAt()返回给定索引位置的字符。
charCodeAt()查看执行码元的字符编码。
fromCharCode()根据给定的UTF-16码元创建字符串中的字符。
Unicode: 每个字符使用另外16位去选择一个增补平面,这种每个字符使用两个16位码元的策略称为代理对。码点是Unicode中一个字符的完整标识,可能是16位也可能是32位。
codePointAt()
fromCodePoint()
2、normalize()方法
3、字符串操作方法
concat() ---> 更常用加号
提取子字符串:slice(),substring(),substr() 【注意入参的区别】
4、字符串位置方法
indexOf()和lastIndexOf()
5、字符串包含方法
ES6提供:startsWith(),endWith(),includes()
6、trim()方法
创建字符串的副本(不影响原字符串),删除前后所有空格符,再返回结果。
trimLeft(),trimRight()
7、repeat()方法
接收一个整数参数,表示将字符串重复多少次,然后返回拼接所有副本后的结果。
8、padStart()和padEnd()方法
填充字符串(默认是空格)
9、字符串迭代与解构
字符串原型上暴露了一个@@iterator方法,表示可以迭代字符串中的每个字符。
let msg = "abc"
let stringIterator = msg[Symbol.iterator]()
console.log(stringIterator.next()) // {value: "a", done: false}
console.log(stringIterator.next()) // {value: "b", done: false}
console.log(stringIterator.next()) // {value: "c", done: false}
console.log(stringIterator.next()) // {value: undefined, done: true}
for-of循环可以使用这个迭代器按序访问每个字符:
for (const c of "abc") {
console.log(c)
}
// a
// b
// c
有了迭代器之后,字符串可以解构。
let msg = "abc"
console.log([...msg]) // ["a", "b", "c"]
10、字符串大小写转换
toLowerCase(),toLocaleLowerCase(),toUpperCase(),toLocaleUpperCase()
不知道代码涉及什么语言,就用特定地区的转换方法
11、字符串模式匹配方法
match():与RegExp的exec()方法类似
search():返回第一个匹配的位置索引,没找到返回-1
replace():如果第一个参数是字符串不是正则,那么只会替换第一个子字符串
split(): 根据传入的分隔符将字符串拆分为数组【逆方法是join()】
12、localeCompare()方法
5.4 单例内置对象
ECMA-262对内置对象的定义是“任何由ECMAScript实现提供、与宿主环境无关,并在ECMAScript程序开始执行时就存在的对象”。这意味着开发者不用显式实例化内置对象,因为它们已经实例化好了。前面的Object、Array和String都是。Global和Math也是。
5.4.1 Global
Global对象时ECMAScript中最特别的对象,因为代码不会显式访问它。ECMA-262规定Global对象为兜底对象,在全局作用域中定义的变量和函数都会变成Global对象的属性。
1、URL编码方法
encodeURI()不会编码属于URL组件的特殊字符,但是encodeURIComponent()会。
分别对应:decodeURI()和decodeURIComponent()。
注意:escape()和unescape()已经在ECMA-262第3版中废弃!
2、eval()方法
注意XSS攻击
3、Global对象属性
undefined、NaN、Infinity、Object、Function、Array、Symbol等等
4、window对象
浏览器将window对象实现为Global对象的代理,因此所以全局作用域中声明的变量和函数都会变成window的属性。
var color = "red"
window.color = "red"
5.4.2 Math
常用属性和方法
Math.E // 自然对数的基数e的值
Math.PI // Π的值
Math.min(2,5,1,10) // 返回一组数值中的最小值
Math.max(2,5,1,10) // 返回一组数值中的最大值
Math.ceil() // 向上舍入为最接近的整数
Math.floor() // 向下舍入为最接近的整数
Math.round() // 四舍五入返回整数
Math.random() // 返回[0, 1)之间的随机数
第6章
6.1 Object
创建Object有两种方式,一种是使用new操作符和Object构造函数,另一种方式是使用对象字面量表示法(不会调用Object构造函数)。
对象字面量表示法中,属性名可以是字符串或数值,如果是数值属性会自动转换为字符串。
可以通过点语法或者中括号来存取对象的属性。通常,点语法是首选的属性存取方式,除非访问属性时必须使用变量。
6.2 Array
ECMAScript中的数组是一组有序的数据,且每个槽位可以存储任意类型的数据。ECMAScript中的数组也是动态大小的,会随着数据的添加而自动增长。
6.2.1 创建数组
一种是使用Array构造函数,给构造函数传入一个数值,然后length属性就会被自动创建并设置为这个值。
let colors = new Array(20) // 创建一个初始length为20的数组
也可以给Array构造函数传入要保存的元素。比如下面的代码会创建一个包含3个字符串值的数组。
let colors = new Array("red", "blue", "green")
另一种创建数组的方式是使用数组字面量(array literal)表示法。
let colors = ["red", "blue", "green"]
ES6提供创建数组的静态方法:from()和of()。Array.from()用于将类数组结构转换为数组实例【第二个参数是可选的映射函数,第三个可选参数用于指定映射函数中this的值】,Array.of()用于将一组参数转换为数组实例。
6.2.2 数组空位
使用数组字面量初始化数组时,可以使用一串逗号来创建空位(hole)。ECMAScript会将逗号之间相应索引位置的值当成空位,ES6规范重新定义了该如何处理这些空位。
const options = [,,,,,] //创建包含5个元素的数组
console.log(options.length) // 5
console.log(options) // [,,,,,]
for-of循环会将空位当成存在的元素,只不过值为undefined。
ES6之前的方法则会忽略这个空位,但具体的行为也会因方法而异。
避免使用数组空位,确实需要空位,显示用undefined替代。
6.2.3 数组索引
数组length属性不是只读的,修改length属性可以截断或增长数组。
6.2.4 检测数组
instanceof 操作符可以检测一个对象是不是数组。
if(value instanceof Array){
// 操作数组
}
但是如果网页里存在多个框架,可能会涉及两个不同的全局执行上下文,因此会有两个不同版本的Array构造函数,这个时候instanceof不一定返回正确的结果。
为解决这个问题,ECMAScript提供了Array.isArray()方法。这个方法的目的是确定一个值是否为数组,而不用管它在哪个全局执行上下文中创建的。
if(Array.isArray(value)){
// 操作数组
}
6.2.5 迭代器方法
ES6中,Array的原型上暴露了3个用于检索数组内容的方法:keys()、values()和entries()。keys()返回数组索引的迭代器,values()返回数组元素的迭代器、而entries()返回索引/值对的迭代器。
6.2.6 复制和填充方法
ES6新增了两个方法:批量复制方法copyWithin(),以及填充数组方法fill()。
6.2.7 转换方法
所有对象都有toLocaleString()、toString()和valueOf()方法。其中valueOf()返回的还是数组本身。toString()返回由数组中每个值调用toString()返回的字符串拼接而成的一个逗号分隔的字符串。toLocaleString()方法类似toString(),取数组每个元素值的时候会调用toLocaleString()方法,返回字符串拼接而成的一个逗号分隔的字符串。
join()方法接收一个参数,即字符串分隔符,返回包含所有项的字符串。
6.2.8 栈方法
ECMAScript数组提供了push()和pop()方法,以实现类似栈的行为。
push()方法接收任意数量的参数,并将它们添加到数组末尾,返回数组的最新长度。
pop()方法则用于删除数组的最后一项,同时减少数组的length值,返回被删除的项。
6.2.9 队列方法
使用shift()和push()方法,可以把数组当成队列来使用。
shift()方法会删除数组的第一项并返回它。
unshift()方法执行shift()相反的操作:在数组开头添加任意多个值,然后返回新的数组长度。
6.2.10 排序方法
数组有两种方法可以用来对元素重新排序:reverse()和sort()。【都会改变数组自身!】
sort()方法用于排序数组,默认情况下会按照升序排序,最小的值在前面,最大的值在后面。为此sort()会在每一项上调用String()转型函数,然后来比较字符串决定顺序。
let values = [0, 1, 5, 10, 15]
values.sort()
console.log(values) // [0, 1, 10, 15 ,5] // "5" > "15"
sort()方法接收一个比较函数,用于判断哪个值应该排在前面。比较函数接收两个参数,如果第一个参数应该排在第二个参数前面,则函数返回负值。如果第一个参数应该排在第二个参数后面,则返回正值。如果参数不用变化位置,则返回0。
6.2.11 操作方法
concat()方法可以在现有数组全部元素基础上创建一个新的数组。它首先会创建一个当前数组的副本,然后再把它的参数添加到副本末尾,最后返回这个新构建的数组。如果参数是一个或多个数组,concat()会把数组的每一项添加到结果数组。
slice()方法用于创建一个包含原始数组中一个或多个元素的新数组。slice()方法可以接收一个或两个参数:返回元素的开始索引和结束索引(不包含),不过不提供第二个参数,则默认从开始索引取到末尾。
splice()方法可以改变数组本身。它接收三个参数,第一个参数指定删除元素的开始位置,第二个参数指定删除元素的数量,第三个以及之后的参数指定在开始位置处要插入的元素。可以进行数组的删除、插入、替换操作。
6.2.12 搜索和位置方法
ECMAScript提供两类搜索数组的方法:按严格相等搜索和按断言函数搜索。
1、严格相等
ECMAScript提供了3个严格相等的搜索方法:indexOf()、lastIndexOf()和includes()(ES7)。三个方法在比较时会使用全等(===)比较。
相关知识可以查看Includes引发的一系列思考
2、断言函数
ECMAScript也允许按照定义的断言函数搜索数组,每个索引都会调用这个函数。
断言函数接收3个参数:元素、索引和数组本身。元素指的是数组中当前搜索的元素。
find()和findIndex()方法使用了断言函数。find()返回第一个匹配的元素,findIndex()返回第一个匹配元素的索引。
6.2.13 迭代方法
ECMAScript为数组定义了5个迭代方法。每个方法接收两个参数:以每一项为参数运行的函数、以及可选的作为函数运行上下文的作用域对象(影响函数中this的值)。传给每个方法的函数接收3个参数:数组元素、元素索引和数组本身。这5个迭代方法如下:
- every():对数组每一项都运行传入的函数,如果对每一项都返回true,则这个方法返回true。
- filter():对数组每一项都运行传入的函数,函数返回true的项会组成数组之后返回。
- forEach():对数组每一项都运行传入的函数,无返回值。
- map():对数组每一项都运行传入的函数,返回由每次函数调用的结果构成的数组。
- some():对数组每一项都运行传入的函数,如果有一项函数返回true,则这个方法返回true,否则返回false。
6.2.14 归并方法
ECMAScript为数组提供了两个归并方法:reduce()和reduceRight()。
这两个方法接收两个参数:第一个参数为初始值,第二个为当前值。每轮循环的返回值都会作为下轮循环的初始值,最后一轮循环的返回值即最终的返回值。
6.3 定型数组
ArrayBuffer,DataView等,略
6.4 Map
ES6的新增特性,Map是一种新的集合类型,为这门语言带来了真正的键/值存储机制。
6.4.1 基本API
与Object只能使用数值、字符串或符号作为键不同,Map可以使用任何JS数据类型作为键。
- set()
- get()
- has()
- delete()
- clear()
Map内部使用SameValueZero比较操作,NaN与NaN认为是同一个值,-0 与 +0也认为是一个值。
6.4.2 顺序与迭代
Map的默认迭代器返回entries()(或者Symbol.iterator属性,它引用entries())方法取得的值,也就是以插入顺序生成[key, value]形式的数组。
keys()和values()分别返回以插入顺序生成键和值的迭代器。
6.4.3 选择object还是Map
如果代码涉及大量删除操作,选择Map,其它情况,Map与object差距不大,一般来说Map内存占用更少。
6.5 WeakMap
ECMAScript6新增的“弱映射”(WeakMap)是一种新的集合类型,为这门语言带来了增强的键/值对存储机制。WeakMap是Map的“兄弟”类型,其API也是Map的子集。WeakMap中的“weak”(弱),描述的是JavaScript垃圾回收程序对待“弱映射”中键的方式。
6.5.1 基本API
弱映射中的键只能是Object或者继承自Object的类型,尝试使用非对象设置键会抛出TypeError。值的类型没有限制。
- set()
- get()
- has()
- delete()
6.5.2 弱键
WeakMap中的键不属于正式的引用,不会阻止垃圾回收。
6.5.3 不可迭代键
因为WeakMap中的键/值对任何时候都可能被销毁,所以没必要提供迭代其键/值对的能力。所以WeakMap不提供clear()这样一次性销毁所有键/值的方法。
WeakMap实例之所以限制只能用对象作为键,是为了保证只有通过键对象的引用才能取得值。如果允许原始值,那就没办法区分初始化时使用的字符串字面量和初始化之后使用的一个相等的字符串了。
6.6 Set
ES6新增的Set是一种新集合类型。Set在很多方面都像是加强的Map,这是因为它们的大多数API和行为都是共有的。
6.6.1 基本API
-
add()
-
has()
-
delete()
-
clear()
add()返回集合的实例,所以可以将多个添加操作连缀起来。
Set可以包含任何值,但不允许出现重复的值,集合与Map类似,使用SameValueZero操作(NaN与NaN认为是同一个值,-0 与 +0也认为是一个值)。
6.6.2 顺序与迭代
Set会维护值插入时的顺序,因此支持按顺序迭代。
集合实例可以通过values()方法及其别名方法keys()(或者Symbol.iterator属性,它引用values())取得迭代器(Iterator)。
集合的entries()方法返回一个迭代器,可以按照插入顺序产生包含两个元素的数组,这两个元素是集合中每个值的重复出现。
6.7 WeakSet
ECMAScript6新增的“弱集合”(WeakSet)是一种新的集合类型,为这门语言带来了集合数据结构。WeakSet是Set的“兄弟”类型,其API也是Set的子集。
6.7.1 基本API
弱集合中的值只能是Object或者继承自Object的类型,尝试使用非对象设置值会抛出TypeError。
-
add()
-
has()
-
delete()
6.7.2 弱值
WeakSet中的值不属于正式的引用,不会阻止垃圾回收。
6.7.3 不可迭代值
因为WeakSet中的值任何时候都可能被销毁,所以没必要提供迭代其值的能力,因此同样用不着像clear()这样一次性销毁所有值的方法。