今天是2021年8月15日 ,在自学的同时开始阅读前端相关的书籍,愿不断在学习中进步。 这一本是《JavaScript高级程序设计》(第四版),也俗称红宝书,第四版中加入了ES6。 下一本目标是《JavaScript Dom编程艺术》(第二版),已经在边上摆着了。 再下一本?可能是《你不知道的JavaScript》(上卷),也有可能是《图解HTTP》,对面试有点帮助 。 开冲!
第1章,什么是JavaScript
1995年,JavaScript问世
JS之父:Brendan Eich
完整的JavaScript实现包含以下几个部分
- 核心(ECMAScript)
- 文档对象模型(DOM)
- 浏览器对象模型(BOM)
DOM是一个API,将整个页面抽象为一组分层节点,页面的每个组成部分都是一种节点,包含不同数据。通过DOM可以轻松地删除、添加、替换、修改节点。
BOM,可以操控浏览器显示页面之外的部分。
※小结
- EMCAScript:由ECMA-262定义并提供核心功能
- 文档对象模型(DOM):提供与网页内容交互的方法和接口
- 浏览器对象模型(BOM):提供与浏览器交互的方法和接口
第2章,HTML中的JavaScript
script元素有下列8个属性
- async:可选,异步
- charset:可选,使用src属性指定的代码字符集
- crossorigin:可选,配置相关请求的CORS(跨域资源共享)设置
- defer:可选,表示文档解析和显示完成后再执行脚本是可行的,只对外部脚本文件有效
- integrity:可选,可用于确保CDN不会提供而已内容
- language:废弃
- src:可选,表示包含要执行的代码的外部文件
- type:可选,代替language,表示脚本语言的内容类型
页面在浏览器解析到<body>的起始标签时开始渲染 浏览器在解析到结束的</html>标签才会执行
推迟执行脚本
- defer属性
- 告诉浏览器应该立即开始下载,但执行应该推迟
- 按照出现的顺序执行
- 只对外部脚本文件有效
异步执行脚本
- async属性
- 告诉浏览器立即开始下载,但执行应该推迟
- 不保证按照出现的次序执行
- 只对外部脚本文件有效
行内代码与外部文件 推荐使用外部文件,理由如下:
- 可维护性:JS代码如果分散到很多HTML页面,会导致维护困难。
- 缓存:浏览器会根据特定的设置缓存所有外部链接的JS文件,意味着如果两个页面用到同一个文件,则该文件只需下载一次,最终意味着页面加载更快。
- 适应未来:把JS代码放到外部文件中,就不必考虑用XHTML或其他注释黑科技。
doctype文档模式
- 混杂模式
- 标准模式
- 准标准模式
※小结
- 要包含外部JS文件,必须将src属性设置为要包含文件的URL,文件可以跟网页在同一台服务器上,也可以位于完全不同的域
- 通常把<script>元素放到页面末尾,介于主内容之后及</body>标签之前
- defer和async的异同
第3章,语言基础
var、let和const
之前总结了一篇文章,写的比较详细:深入理解var、let和const
声明风格及最佳实践
- 不使用var
- const优先,let次之
const声明可以让浏览器运行时强制保持变量不变,也可以让静态代码分析工具提前发现不合法的赋值操作。
数据类型(六种简单数据类型和一种复杂数据类型)
- number
- boolean
- string
- symbol(ES6新增)
- null
- undefined
- object
undefined == true //true
永远不用显式地将变量值设置为undefined。但null不是,任何时候,只要变量要保存对象,而当时又没有对象可保存,就要用null来填充
数值转换
- number()
- parseInt()
- parseFloat()
转换为字符串
- toString
toString()方法可见于数值、布尔值、对象和字符串值,null和undefined值没有toString()方法。
用加号操作符给一个值加上一个空字符串""也可以将其转换为字符串
字符串插值:`${}`
指数操作符
- Math.pow()有自己的操作符**
第4章,变量、作用域与内存
ECMAScript变量可以包含两种不同类型的数据
- 原始值:就是最简单的数据
- 引用值:多个值构成的对象
在把一个值赋给变量时,JS引擎必须确定这个值是原始值还是引用值。保存原始值的变量是按值(value)访问的,因为我们操作的就是 存储在变量中的实际值。
引用值是保存在内存中的对象。JS不允许直接操作对象所在内存地址。在操作对象时,实际上操作的是对该对象的引用(reference)而非实际的对象本身。为此,保存引用值的变量是按引用(by reference)访问的。
动态属性
- 对于引用值而言,可以随时添加、修改和删除其属性和方法。
- 而原始值不能有属性,尽管尝试给原始值添加属性不会报错。
复制值
- 在通过变量把一个原始值赋值到另一个变量时,原始值会被复制到新变量的位置。
- 把引用值从一个变量赋给另一个变量时,存储在变量中的值也会被复制到新变量所在的位置。区别在于,这里复制的值实际上是一个指针,它指向存储在堆内存中的对象。如下:
let obj1 =new Object();
let obj2 =new Object();
obj1.name = "frank"
console.log(obj2.name) //"frank"
传递参数:所有函数的参数都是按值传递的。
function setName(obj){
obj.name="frank"
obj = new Oject()
obj.name="Greg"
}
let person = new Object()
setName(person)
console.log(person.name) //"frank"
如果person是按引用传递的,那么person应该自动将指针改为指向name为“Greg”的对象。而是当我们再次访问person.name时,他的值是“frank”,这表明函数中参数的值改变之后,原始的引用仍然没变。当obj在函数内部被重写时,它变成了一个指向本地对象的指针,而那个本地对象在函数执行结束时就被销毁了。
确定类型
typeof操作符适合用来判断一个变量是否为原始类型(如果值是对象或null,typeof返回object)
typeof虽然对原始值很有用,但是对引用值的用处不大。ECMAScript提供了instanceof操作符
语法:result = variable instanceof constructor
如果变量是给定引用类型的实例,则instanceof操作符返回true
console.log(person instanceof Object) //变量person是Object吗?
console.log(colors instanceof Array) //变量colors是Array吗?
按照定义,所有引用值都是Object的实例,因此检测任何引用值和Object构造函数都会返回true,如果监测的是原始值,则会返回false,因为原始值不是对象。
执行上下文与作用域
全局上下文就是常说的window对象,因此所有通过var定义的全局变量和函数都会成为window对象的属性和方法。使用let和const的顶级声明不会定义在全局上下文中,但在作用域链解析上效果是一样的。上下文在其所有代码都执行完毕后会被销毁,包括定义在它上面的所有变量和函数(全局上下文在应用程序退出前才会被销毁,比如关闭网页或退出浏览器。)
上下文中的代码在执行的时候,会创建变量对象的一个作用域链(scope chain)。这个作用域链决定了各级上下文中的代码在访问变量和函数时的顺序。
总结:内部上下文可以通过作用域链访问外部上下文中的一切,但外部上下文无法访问内部上下文中的任何东西。
作用域链增强
- try/catch语句的catch块
- with语句
- 这两种情况下,都会在作用域链前端添加一个变量对象。
- 对with语句来说,会向作用域链前端添加指定的对象
- 对catch语句而言,则会创建一个新的变量对象,这个变量对象会包含要抛出的错误对象的声明。
垃圾回收
通过自动内存管理实现内存分配和闲置资源回收
基本思路:确定哪个变量不会再使用,然后释放它所占用的内存。这个过程是周期性的,即垃圾回收程序每隔一定时间就会自动运行。
- JS最常用的垃圾回收策略是标记清理
- 另一种没那么常用的垃圾回收策略是引用计数
内存管理
- 通过const和let声明提升性能
- 隐藏类和删除操作
- 内存泄漏(意外声明全局变量、定时器、闭包等)
- 静态分配与对象池
这部分关于垃圾回收的知识点感觉有点突兀,后面关于”垃圾回收、内存“这块内容,专门总结一篇文章好了
※小结
JS变量可以保存为两种类型的值:原始值和引用值。
- 原始值大小固定,因此保存在栈内存上
- 从一个变量到另一个变量复制原始值会创建该值的第二个副本
- 引用值是对象,存储在堆内存上
- 包含引用值的变量实际上只包含指向响应对象的一个指针,而不是对象本身
- 从一个变量到另一个变量复制引用值只会复制指针,因此结果是两个变量都指向同一个对象
- typeof可以确定值的原始类型,而instanceof用于确保值的引用类型
任何变量都存在于某个上下文中
- 执行上下文分全局上下文、函数上下文和块级上下文
- 代码执行流每进入一个新上下文,都会创建一个作用域链,用于搜索变量和函数
- 函数或块的局部上下文不仅可以访问自己作用域内的变量,也可以访问任何包含上下文乃至全局上下文中的变量
- 全局上下文只能访问全局上下文中的变量和函数,不能直接访问局部上下文中的任何数据
- 变量的执行上下文用于确定什么时候释放内存
JS是使用垃圾回收的编程语言,开发者不需要操心内存分配和回收
- 离开作用域的值会被自动标记为可回收,然后在垃圾回收期间被删除
- 主流的垃圾回收算法是标记清理,即先给当前不适用的值加上标记,再回来回收它们的内存。
- 引用计数是另一种垃圾回收策略,需要记录值被引用了多少次。JS引擎不再使用这样的算法,但某些旧版本的IE仍会然会受这种算法的影响
- 引用计数在代码中存在循环引用时会出现问题
- 解除变量的引用不仅可以消除循环引用,而且对垃圾回收也有帮助。为促进内存回收,全局对象、全局对象的属性和循环引用都应该在不需要时解除引用。
第5章,基本引用类型
引用值(或者对象)是某个特定引用类型的实例。
对象被认为是某个特定引用类型的实例。新对象通过new
操作符后跟一个构造函数来创建。构造函数就是用来创建新对象的函数,例如下行
let now = new Date()
这行代码创建了引用类型Date的一个新实例,并将它保存在变量now
中。Date()
在这里就是构造函数,它负责创建一个只有默认属性和方法的简单对象。
Date
要创建日期对象,就使用new
操作符来调用Date
构造函数
let now = new Date()
两个辅助方法 Date.parse()
和 Date.UTC()
**Date.parse()
**方法接收一个表示日期的字符串参数,尝试将这个字符串转换为表示该日期的毫秒数。
比如要创建一个表示“2019年5月23日”的日期对象,可以使用如下代码
let someDate = new Date("May 23, 2019")
如果传给Date.parse()
的字符串并不表示日期,则该方法会返回NaN。如果直接把表示日期的字符串传给Date构造函数,那么Date会在后台调用Date.parse()。
换句话说,下面这行代码跟前面那行代码是等价的。
let someDate = new Date("May 23, 2019")
这两行代码得到的日期对象相同。
**Date.UTC()
**方法也返回日期的毫秒表示,但使用的是跟Date.parse()
不同的信息来生成这个值。
传给Date.UTC()
的参数是年、零起点月数、日(131)、时(023)、分、秒和毫秒。这些参数中,只有前两个(年和月)是必需的。如果不提供日,那么默认为1日。其他参数的默认值都是0。
// GMT时间2000年1月1日0点
let y2k = new Date(Date.UTC(2000,0))
// GMT时间2005年5月5日下午5点55分55秒
let allFives = new Date(Date.UTC(2005,4,5,17,55,55))
ECMAScript还提供了Date.now()
方法,返回表示方法执行时日期和时间的毫秒数。
Date类型的valueOf()
方法不反悔字符串,这个方法被重写后返回的是日期的毫秒表示。
原始值包装类型
ECMAScript提供了3种特殊的引用类型:Boolean
、Number
和String
每当用到某个原始值的方法或属性时,后台都会创建一个相应原始包装类型的对象,从而暴露出原始值的各种方法。
let s1 = "some text"
let s2 = s1.substring(2)
这里,原始值本身不是对象,因此逻辑上不应该有方法,而实际上这个例子又确实按照预期运行了。因为后台进行了后台处理,相当于做了以下3步处理
- 创建一个String类型的实例
- 调用实例上的特定方法
- 销毁实例
可以把这3步想象成执行了以下代码
let s1 = new String("some text")
let s2 = s1.substring(2)
s1 = null
这种行为可以让原始值拥有对象的行为,对Boolean和Number包装类型也有效。
**引用类型与原始值包装类型的主要区别在于对象的生命周期。**再通过new实例化引用类型后,得到的实例会在离开作用域时被小汇,而自动创建的原始值包装对象则只存在于访问它的那行代码执行期间。这意味着不能在运行时给原始值添加属性和方法。
let s1 = "some text"
s1.color = "red"
console.log(s1.color) //undefined
这里的原因是第二行代码运行时会临时创建一个String对象,而当第三行代码执行时,这个对象已经销毁了。实际上,第三行代码在这里创建了自己的String对象,但这个对象没有color属性。
Boolean
Boolean的实例会重写valueOf()
方法,返回一个原始值true或false。toString()
方法被调用时也会被覆盖,返回字符串“true”或“false”
Number
Number类型也重写了valueOf()
、toLocalString()
和toString()
方法。valueOf()
方法返回Number对象表示的原始数值,另外两个方法返回数值字符串。toString()
方法可选地接收一个表示基数地参数,并返回相应基数形式地数值字符串
let num = 10
console,log(num.toString()) //"10"
console,log(num.toString(2)) //"1010"
console,log(num.toString(8)) //"12"
console,log(num.toString(10)) //"10"
console,log(num.toString(16)) //"a"
toFixed()
方法返回包含指定小数点位数的数值字符串。如果数值本身的小数位数超过了参数指定的位数,则四舍五入到最接近的小数位
let num = 10.005
console.log(num.toFixed(2)) //"10.01"
toExponential()
返回以科学计数法表示的数值字符串。接收一个参数,表示结果中小数的位数。
toPrecision()
方法会根据情况返回最合理的输出结果,可能是固定长度,也可能是科学计数法。这个方法接收一个参数,表示结果中数字的总位数。
let num = 99
console.log(num.toPrecision(1)) //"1e+2"
console.log(num.toPrecision(2)) //"99"
console.log(num.toPrecision(1)) //"1e+2"
isInteger()
用于辨别一个数值是否保存为整数。有时,小数位的0可能会让人误以为数值是一个浮点值。
console.log(Number.isInteger(1)) //"true"
console.log(Number.isInteger(1.00)) //"true"
console.log(Number.isInteger(1.01)) //"false"
String
每个String对象都有一个length属性,表示字符串中字符的数量
concat()
用于将一个或多个字符串拼接成一个新字符串。可以接受任意多个参数,因此可以一次性拼接多个字符串。
3个从字符串提取子字符串的方法 P121
slice()、substr()、substring()
- 这三个方法都返回调用他们的字符串的子字符串,而且都接收一或两个参数。第一个参数表示子字符串开始的位置,第二个参数表示子字符串结束的位置。
- 对
slice()
和substring()
而言,第二个参数是提取结束的位置(即该位置之前的字符会被提取出来)。 - 对
substr()
而言,第二个参数表示返回的子字符串数量。
任何情况下,省略第二个参数都意味着提取到字符串末尾。
let str = "hello world"
console.log(str.slice(3)) //"lo world"
console.log(str.substring(3)) //"lo world"
console.log(str.substr(3)) //"lo world"
console.log(str.slice(3,7)) //"lo w"
console.log(str.substring(3,7)) //"lo w"
console.log(str.substr(3,7)) //"lo worl"
当某个参数是负数时,这3个方法的行为又有不同。
slice()
方法将所有负值参数都当成字符串长度加上负参数值substr()
方法将第一个负参数值当成字符串长度加上该值,将第二个负参数转换为0。substring()
方法会将所有负参数值都转换为0。
let str = "hello world"
console.log(str.slice(-3)) //"rld"
console.log(str.substring(-3)) //"hello world"
console.log(str.substr(-3)) //"rld"
console.log(str.slice(3,-4)) //"lo w"
console.log(str.substring(3,-4)) //"hell"
console.log(str.substr(3,-4)) //" "(empty string)
第六行,substring(3,0)
等价于substring(0,3)
,因为这个方法会将较小的参数作为起点,将较大的参数作为终点。
字符串位置方法
indexOf()
和lastIndexOf()
两个方法从字符串中搜索传入的字符串,并返回下标(如果没找到,则返回-1),区别在于前者从字符串开头向后开始查找,后者从字符串末尾向前开始查找。
这两个方法都可以接收第二个参数,表示开始搜索的位置。
let str = "hello world"
console.log(str.indexOf("o",6)) //7
console.log(str.lastIndexOf("o",6)) //4
字符串包含方法
3个用于判断字符串种是否包含另一个字符串的方法:startWith()
、endWith()
和incluedes()
,都返回一个表示是否包含的布尔值。区别在于
startWith()
检查开始于索引0的匹配项endWith()
检查开始于索引(string.length - substring.length)
的匹配项includes()
检查整个字符串
startWith()
、includes()
接收可选的第二个参数,表示开始搜索的位置。
endsWith()
接收可选的第二个参数,表示应该当作字符串末尾的位置。如果不提供这个参数,那么默认就是字符串长度。
trim()方法
这个方法创建字符串的一个副本,删除前、后所有空格符,再返回结果。
repeat()方法
这个方法接收一个整数参数,表示要将字符串复制多少次,然后返回拼接所有副本后的结果。
padStart()和padEnd()方法
这两个方法会复制字符串,如果小于指定长度,则再相应一边填充字符,直至满足长度条件。第一个参数是长度,第二个参数是可选的填充字符串,默认为空格。
可选的第二个参数并不限于一个字符。如果提供了多个字符的字符串,则会将其拼接并截断以匹配长度。
let str = "foo"
console.log(str.padStart(6)) //" foo"
console.log(str.padStart(9,".")) //"......foo"
console.log(str.padEnd(8,"bar")) //"foobarba"
console.log(str.padEnd(9,".")) //"foo......"
字符串大小写转换
toLowerCase()
、toLocaleLowerCase()
、toUpperCase()
和toLocaleUpperCase()
单例内置对象
Global
事实上,不存在全局变量或全局函数这种东西。在全局作用域中定义的变量和函数都会变成Global对象的属性。
window对象
浏览器将window对象时限为Global对象的代理,因此所有全局作用域中声明的变量和函数都变成了window的属性。
Math
min()
max()
用于确定一组数值中的最大值和最小值
Math.ceil()
方法始终向上舍入为最接近的整数Math.floor()
方法始终向下舍入为最接近的整数Math.round()
方法执行四舍五入Math.fround()
方法返回数值最接近的单精度(32位)浮点值表示
Math.random()
方法返回一个0~1范围内的随机数,包括0但不包含1
第6章,集合引用类型
Object
在对象字面量表示法中,属性名可以是字符串或数值
let person = {
"name":"Frank",
"age":29,
5:true
}
这个例子会得到一个带有属性name
、age
和 5
的对象。 (数值属性会自动转换为字符串)
let person = {} // 与new Object()相同
console.log(person.name) //"Frank"
console.log(person["name"]) //"Frank"
这两种读取属性的方式没有区别。如果属性名中包含可能会导致语法错误的字符,或者包含关键字/保留字时,可以使用中括号语法。
Array
创建数组
跟其他语言不同的是,数组中的每个槽位可以存储任意类型的数据,同时也是动态大小的,会随着数据添加而自动增长。
let colors = Array(3) //创建一个包含3个元素的数组
let names = Array("Greg") //创建一个只包含一元素“Greg”的数组
ES6新增用于创建数组的静态方法 from()
和 of()
。 from()
用于将类数组结构转换为数组实例,而 of()
用于将一组参数转换为数组实例。
使用场景
from()
可将函数的多个参数arguments转换为真正的数组of()
按照传入的数据组成一个数组
数组空位
const options = [,,,,,] //创建包含5个元素的数组
ES6将这些空位当成存在的元素,只不过值为undefined
数组索引
数组length
属性的独特之处在于,它不是只读的。通过修改 length
属性,可以从数组末尾删除或添加元素
检测数组
isArray()
方法
复制和填充方法 p143
批量复制方法 fill()
,填充数组方法 copyWithin()
fill()
方法用一个固定值填充一个数组中从起始索引到终止索引内的全部元素,不包括终止索引。忽略超出数组边界、零长度及方向相反的索引范围
const zeroes = [0,0,0,0,0]
zeroes.fill(5) //用5填充整个数组
zeroes.fill(6,3) //用6填充索引大于等于3的元素
zeroes.fill(7,1,3) //用6填充索引大于等于1且小于3的元素
zeroes.fill(8,-4,-1) //用8提案冲索引大于等于1且小于4的元素
copyWithin()
方法浅复制数组的一部分到同一数组中的另一个位置,并返回它,不会改变原数组的长度。
转换方法
valueOf()
返回的还是数组本身
toString()
返回由数组中每个值的等效字符串拼接而成的一个逗号分隔的字符串,也就是说,对数组每个值都会调用 toString()
方法,得到最终的字符串。
栈方法
栈是一种后进先出(LIFO)的结构,数据项的插入(称为推入,push)和删除(称为弹出,pop)只在栈的一个地方发生,即栈顶。
push()
方法接收任意数量的参数,并将它们添加到数组末尾,返回数组的最新长度。
pop()
方法则用于删除数组的最后一项,同时减少数组的length值,返回被删除的项。
队列方法
队列以**先进先出(FIFO)**形式限制访问。队列在列表末尾添加数据,但从列表开头获取数据。
shift()
删除数组的第一项,并返回它
unshift()
在数组开头添加任意多个值,然后返回新的数组长度
排序方法
reverse()
方法将数组元素反向排列
sort()
方法,默认情况下,会按照升序重新排列数组元素。为此,sort()
会在每一项上调用 string()
转型函数,然后比较字符串来决定顺序。
let values = [0,1,5,10,15]
values.sort()
alert(values) //0,1,10,15,5
一开始数组中数值的顺序是正确的,但调用 sort()
会按照这些数值的字符串形式(字符串编码顺序)重新排序。
很明显这在多数情况下都不是最合适的,为此 sort()
方法可以接收一个比较函数,用于判断哪个值应该排在前面。
function compare (value1,value2){
if (value1 < value2) {
return -1
} else if (value1 > value2){
return 1
} else {
return 0
}
}
这个比较函数可以适用于大多数数据类型,可以把他当作参数传给sort()
方法
let values = [0,1,5,10,15]
values.sort(compare)
alert(values) //0,1,5,10,15
比较函数也可以产生降序效果,只需要把返回值交换一下即可。
此外这个比较函数还可简写为一个箭头函数
values.sort((a,b) => a<b ? 1 : a>b ? -1 : 0)
如果数组的元素是数值,这个比较函数还可以写的更简单,因为这时可以直接用第二个值减去第一个值
function compare (value1,value2){
return value2 - value1
}
比较函数就是返回小于0、0和大于0的数值,因此剑法操作完全可以满足要求。
操作方法
concat()
在现有数组全部元素基础上创建一个新的数组,用于连接数组。
slice()
用于创建一个包含原有数组中一个或多个元素的新数组。(裁剪出原数组中的一部分)
splice()
主要目的是在数组中间插入、替换、删除元素。返回一个数组,包含从原数组中被删除的元素。
之前总结过一篇 slice
和 splice
的帖子 CSDN链接
迭代方法
每个方法接收两个参数:以每一项为参数运行的函数,以及可选的作为函数运行上下文的作用于对象(影响函数中this的值)。
传给每个方法的函数接收3个参数:数组元素、元素索引和数组本身。
every()
对数组每一项都运行传入的函数,如果对每一项函数都返回true,则这个方法返回truefilter()
对数组每一项都运行传入的函数,函数返回true的项会组成数组之后返回forEach()
对数组每一项都运行传入的函数,没有返回值map()
对数组每一项都运行传入的函数,返回由每次函数调用的结果构成的数组some()
对数组每一项都运行传入的函数,如果有意向函数返回true,则这个方法返回true。
这些方法都不改变调用他们的数组。
归并方法
reduce()
和reduceRight()
这两个方法都会迭代数组的所有项,并在此基础上构建一个最终返回值。
reduce()
方法从数组第一项开始遍历到最后一项,reduceRight()
从最后一项开始遍历至第一项。
Map
ES6新增特性,Map是一种新的集合类型,为这门语言带来了真正的键/值存储机制。
使用new关键字和Map构造函数可以创建一个空映射
const m = new Map(),
在创建的同时初始化实例
const m1 = new Map({
["key1","val1"],
["key2","val2"],
["key3","val3"],
});
alert(m1.size) //3
初始化之后,可以使用set()
方法再添加键值对。另外可以使用get()
和has()
进行查询,可以通过size
属性获取映射中的键值对的数量,还可以使用delete()
和 clear()
删除值。
与Object不同的是,Object只能使用数值、字符串或符号作为键,Map可以使用任何JS数据类型作为键。
选择Object还是Map P168
-
内存占用
给定固定大小的内存,Map大约可以比Object多存储50%的键值对
-
插入性能
如果代码涉及大量插入操作,那么显然Map性能更佳
-
查找速度
如果代码涉及大量查找操作,那么某些情况下可能选择Object更好一些
-
删除性能
对大多数浏览器引擎来说,Map的
delete()
操作都比插入和查找更快。如果代码涉及大量删除操作,那么毫无疑问应该选择Map
Set
Set在很多方面更像是加强的Map,因为他们大多数API和行为都是共有的。
const m = new Set() //创建一个空集合
const s1 = new Set(["val1","val2","val3"])
初始化之后,可以使用 add()
增加值,使用 has()
查询,通过size取得元素数量,以及使用 delete()
和 clear()
删除元素
第7章,迭代器与生成器
迭代器模式描述了一个方案,即可以把有些结构称为 “可迭代对象” ,因为他们实现了正式的Iterable接口,而且可以通过迭代器iterator消费。
实现了Iterable接口的内置类型:
- 字符串
- 数组
- 映射
- 集合
- arguments对象
- NodeList等Dom集合类型
接收可迭代对象的原生语言特性包括:
- for-of循环
- 数组结构
- 扩展操作符
- Array.from()
- 创建集合
- 创建映射
- Promise.all()接收由promise组成的可迭代对象
- Promise.race()接收由promise组成的可迭代对象
- yield* 操作符,在生成器中使用
迭代器协议
迭代器API使用next()
方法在可迭代对象中遍历数据,每次成功调用next()
,都会返回一个IteratorResult对象,其中包含迭代器返回的下一个值。
next()
方法返回的迭代器对象包含两个属性:done和value。
- done是一个布尔值,表示是否还可以再次调用
next()
取得下一个值。done:true状态称为“耗尽”。 - value包含可迭代对象的下一个值(done为false时),或者undefined(done为true时)。
只要迭代器到达done:true状态,后续调用 next()
就一直返回同样的值了
不同迭代器的实例相互之间没有联系,只会独立地遍历可迭代对象:
let arr = ['foo' , 'baz'];
let iter1 = arr[Symbol.iterator]();
let iter2 = arr[Symbol.iterator]();
console.log(iter1.next()); //{done:false, value:'foo'}
console.log(iter2.next()); //{done:false, value:'foo'}
console.log(iter1.next()); //{done:false, value:'bar'}
console.log(iter2.next()); //{done:false, value:'bar'}
迭代器并不与可迭代对象某个时刻的快照绑定,仅仅是使用游标来记录遍历可迭代对象的哩程。如果可迭代对象在迭代期间被修改了,那么迭代器也会反映相应的变化。
生成器
生成器的形式是一个函数,函数名称前面加一个星号(*)表示它是一个生成器。 *左右空格无关。
箭头函数不能用来定义生成器函数
调用生成器函数会产生一个 生成器对象,生成器对象一开始处于暂停执行状态,具有next()方法,调用这个方法会让生成器开始或恢复执行。
生成器函数只会在初次调用next()方法后开始执行。
通过yield中断执行
yield关键字可以让生成器停止和开始执行,也是生成器最有用的地方。
生成器函数在遇到yield关键字之前会正常执行。遇到这个关键字后,执行会停止,函数作用域的状态会被保留。停止执行的生成器函数只能通过在生成器对象上调用next()方法来恢复执行。
function * generatorFn(){
yield;
}
let generatorObject = generatorFn();
console.log(generatorObject.next()); //{done:false, value:undefined}
console.log(generatorObject.next()); //{done:true, value:undefined}
此时yield关键字有点像函数的中间返回语句, 它生成的值会出现在next()方法返回的对象中。
function * generatorFn(){
yield 'foo';
yield 'bar';
yield 'baz';
}
let generatorObject = generatorFn();
console.log(generatorObject.next()); //{done:false, value:'foo'}
console.log(generatorObject.next()); //{done:false, value:'bar'}
console.log(generatorObject.next()); //{done:true, value:'baz'}
生成器函数内部的执行流程会针对每个生成器对象区分作用域。在一个生成器对象上调用next()不会影响其他生成器。
yield关键字只能在生成器函数内部使用,用在其他地方会抛出错误,yield关键字必须直接位于生成器函数定义中。
提前终止生成器
-
return()
与迭代器不同,所有生成器对象都有return()方法,只要通过它进入关闭状态,就无法恢复了。后续调用next()会显示done:true状态。
-
throw()
- throw()方法会在暂停的时间将一个提供的错误注入到生成器对象中。如果错误未被处理,生成器就会关闭。
-
不过,加入生成器函数内部处理了这个错误,那么生成器就不会关闭,而且还可以恢复执行。错误处理会跳过对应的yield,因此在这个例子中会跳过一个值。
第8章,对象、类与面向对象编程
合并对象
ES6专门为合并对象提供了Object.assign()
方法。这个方法接收一个目标对象和一个或多个源对象作为参数,然后将每个源对象中可枚举和自有属性复制到目标对象。以字符串和符号为键的属性会被复制。
let dest,src,result;
// 简单复制
dest = {};
src = {id:'src'};
result = Object.assign(dest, src);
// Object.assign修改目标对象,也会返回修改后的目标对象
console.log(dest === result); //true
console.log(dest !== src); //true
console.log(result) //{id:src}
console.log(dest); //{id:src}
对象解构
let person = {
name:'Matt';
age:27;
}
let{name:personName , age:personAge} = person;
console.log(personName); //Matt
console.log(personAge); //27
null和undefined不能被解构,否则会抛出错误。
构造函数
按照惯例,构造函数名称的首字母要大写。
构造函数内部的this被赋值为这个新对象(即this指向新对象)
同一个构造函数的不同实例上的函数虽然同名但是不相等,要解决这个问题,可以把函数定义转移到构造函数外部。
原型模式
每个函数都会创建一个prototype属性,这个属性是一个对象,包含应该由特定引用类型的实例共享的属性和方法。实际上,这个对象就是通过调用构造函数创建的对象的原型。使用原型对象的好处是,在它上面定义的属性和方法可以被对象实例共享。
默认情况下,所有原型对象自动获得一个名为constructor的属性,指回与之关联的构造函数。
实例与构造函数原型之间有直接的联系,但实例与构造函数之间没有。
Object原型的原型是null
console.log(Person.prototype.__proto__ === Object.prototype) //true
console.log(Person.prototype.__proto__.constructor === Object) //true
console.log(Person.prototype.__proto__.__proto__ === null) //true
同一个构造函数创建的两个实例共享同一个原型对象。
原型层级
再通过对象访问属性时,会按照这个属性的名称开始搜索。搜索开始于对象实例本身,如果在这个实例上发现了给定的名称,则返回该名称对应的值。如果没有找到这个属性,则搜索会沿着指针进入原型对象,然后再原型对象上找到属性后,再返回对应的值。
使用delete操作符可以完全删除实例上的这个属性,从而让标识符解析过程能够继续搜索原型对象。
hasOwnProperty()
方法用于确定某个属性是在实例上还是原型对象上
in操作符会在可以通过对象访问指定属性时返回true,无论该属性是在实例上还是在原型上。
只要通过对象可以访问,in操作符就返回true,而hasOwnProperty()
只有属性存在于实例上时才返回true。因此,只要in操作符返回true且hasOwnProperty()返回false,就说明该属性是一个原型属性。
要获得对象上所有可枚举的实例属性,可以使用 Object,keys()
方法。
对象迭代
Object.values()
返回对象值的数组
Object.entries()
返回键值对的数组