文章已同步至掘金:https://juejin.cn/post/6844903902031118344
欢迎访问😃,有任何问题都可留言评论哦~
1)let 与 const
ES6中新增加了两个重要的 JavaScript 关键字: let 和 const
- let 声明的变量只在 let 命令所在的代码块内有效
- const 声明一个只读的常量,一旦声明,常量的值就不能改变
let
1.代码块内有效
let 是在代码块内有效,var 是在全局范围内有效
2.不能重复声明
let 只能声明一次 var 可以声明多次:
for 循环计数器很适合用 let
3.不存在变量提升
let 不存在变量提升,var 会变量提升:
变量 b 用 var 声明存在变量提升,所以当脚本开始运行的时候,b 已经存在了,但是还没有赋值,所以会输出 undefined。
变量 a 用 let 声明不存在变量提升,在声明变量 a 之前,a 不存在,所以会报错。
const
const 声明一个只读变量,声明之后不允许改变。意味着,一旦声明必须初始化,否则会报错。
基本用法:
暂时性死区:
ES6 明确规定,代码块内如果存在 let 或者 const,代码块会对这些命令声明的变量从块的开始就形成一个封闭作用域。代码块内,在声明变量 PI 之前使用它会报错。
注: const 如何做到变量在声明初始化之后不允许改变的?其实 const 其实保证的不是变量的值不变,而是保证变量指向的内存地址所保存的数据不允许改动。此时,你可能已经想到,简单类型和复合类型保存值的方式是不同的。是的,对于简单类型(数值 number、字符串 string 、布尔值 boolean),值就保存在变量指向的那个内存地址,因此 const 声明的简单类型变量等同于常量。而复杂类型(对象 object,数组 array,函数 function),变量指向的内存地址其实是保存了一个指向实际数据的指针,所以 const 只能保证指针是固定的,至于指针指向的数据结构变不变就无法控制了,所以使用 const 声明复杂类型对象时要慎重
2)解构赋值
解构赋值是对赋值运算符的扩展。
他是一种针对数组或者对象进行模式匹配,然后对其中的变量进行赋值。
在代码书写上简洁且易读,语义更加清晰明了;也方便了复杂对象中数据字段获取
解构模型
在解构中,有下面两部分参与:
解构的源,解构赋值表达式的右边部分。
- 解构的目标,解构赋值表达式的左边部分。
数组模型的解构(Array)
基本
可嵌套
可忽略
不完全解构
剩余运算符
字符串等
在数组的解构中,解构的目标若为可遍历对象,皆可进行解构赋值。可遍历对象即实现 Iterator 接口的数据
解构默认值
当解构模式有匹配结果,且匹配结果是 undefined 时,会触发默认值作为返回结果
- a 与 b 匹配结果为 undefined ,触发默认值:a = 3; b = a =3
- a 正常解构赋值,匹配结果:a = 1,b 匹配结果 undefined ,触发默认值:b = a =1
- a 与 b 正常解构赋值,匹配结果:a = 1,b = 2
对象模型的解构(Object)
基本
可嵌套可忽略
不完全解构
剩余运算符
解构默认值
3)字符串
子串的识别
ES6 之前判断字符串是否包含子串,用 indexOf 方法,ES6 新增了子串的识别方法
- includes():返回布尔值,判断是否找到参数字符串。
- startsWith():返回布尔值,判断参数字符串是否在原字符串的头部
- endsWith():返回布尔值,判断参数字符串是否在原字符串的尾部
以上三个方法都可以接受两个参数,需要搜索的字符串,和可选的搜索起始位置索引
注:
- 这三个方法只返回布尔值,如果需要知道子串的位置,还是得用 indexOf 和 lastIndexOf
- 这三个方法如果传入了正则表达式而不是字符串,会抛出错误。而 indexOf 和 lastIndexOf 这两个方法,它们会将正则表达式转换为字符串并搜索它
字符串重复
repeat():返回新的字符串,表示将字符串重复指定次数返回。
console.log("Hello,".repeat(2)); // "Hello,Hello,"
如果参数是小数,向下取整
console.log("Hello,".repeat(3.2)); // "Hello,Hello,Hello,"
如果参数是 0 至 -1 之间的小数,会进行取整运算,0 至 -1 之间的小数取整得到 -0 ,等同于 repeat 零次
console.log("Hello,".repeat(-0.5)); // ""
如果参数是 NaN,等同于 repeat 零次
console.log("Hello,".repeat(NaN)); // ""
如果参数是负数或者 Infinity ,会报错:
如果传入的参数是字符串,则会先将字符串转化为数字
字符串补全
- padStart:返回新的字符串,表示用参数字符串从头部(左侧)补全原字符串
- padEnd:返回新的字符串,表示用参数字符串从尾部(右侧)补全原字符串
以上两个方法接受两个参数,第一个参数是指定生成的字符串的最小长度,第二个参数是用来补全的字符串。如果没有指定第二个参数,默认用空格填充。
如果指定的长度小于或者等于原字符串的长度,则返回原字符串:
console.log("hello".padStart(5,"A")); // "hello"
如果原字符串加上补全字符串长度大于指定长度,则截去超出位数的补全字符串:
console.log("hello".padEnd(10,",world!")); // "hello,worl"
常用于补全位数:
console.log("123".padStart(10,"0")); // "0000000123"
模板字符串
模板字符串相当于加强版的字符串,用反引号 ,除了作为普通字符串,还可以用来定义多行字符串,还可以在字符串中加入变量和表达式。
普通用法
普通字符串
多行字符串:
字符串插入变量和表达式。
变量名写在 {} 中可以放入 JavaScript 表达式。
字符串中调用函数:
注意要点
模板字符串中的换行和空格都是会被保留的
标签模板
标签模板,是一个函数的调用,其中调用的参数是模板字符串
当模板字符串中带有变量,会将模板字符串参数处理成多个参数。
应用
过滤 HTML 字符串,防止用户输入恶意内容。
国际化处理(转化多国语言)
4)数值
数值的表示
二进制表示法新写法: 前缀 0b 或 0B
八进制表示法新写法: 前缀 0o 或 0O
常量
Number.EPSILON
Number.EPSILON 属性表示 1 与大于 1 的最小浮点数之间的差。
它的值接近于 2.2204460492503130808472633361816E-16,或者 2-52。
测试数值是否在误差范围内:
属性特征
最大/最小安全整数
安全整数
安全整数表示在 JavaScript 中能够精确表示的整数,安全整数的范围在 2 的 -53 次方到 2 的 53 次方之间(不包括两个端点),超过这个范围的整数无法精确表示。
最大安全整数
安全整数范围的上限,即 2 的 53 次方减 1 。
最小安全整数
安全整数范围的下限,即 2 的 53 次方减 1 的负数
属性特征
方法
Number 对象新方法
Number.isFinite()
用于检查一个数值是否为有限的( finite ),即不是 Infinity
从全局移植到 Number 对象的方法
逐步减少全局方法,用于全局变量的模块化。
方法的行为没有发生改变
Number.parseInt()
用于将给定字符串转化为指定进制的整数。
Math对象的扩展
ES6 在 Math 对象上新增了 17 个数学相关的静态方法,这些方法只能在 Math 中调用。
普通计算
Math.cbrt
用于计算一个数的立方根。
Math.imul
两个数以 32 位带符号整数形式相乘的结果,返回的也是一个 32 位的带符号整数。
Math.hypot
用于计算所有参数的平方和的平方根。
Math.clz32
用于返回数字的32 位无符号整数形式的前导0的个数。
数字处理
Math.trunc
用于返回数字的整数部分。
Math.fround
用于获取数字的32位单精度浮点数形式。
判断
Math.sign
判断数字的符号(正、负、0)。
对数方法
Math.expm1()
用于计算 e 的 x 次方减 1 的结果,即 Math.exp(x) - 1 。
Math.log1p(x)
用于计算1 + x 的自然对数,即 Math.log(1 + x)
Math.log10(x)
用于计算以 10 为底的 x 的对数。
Math.log2()
用于计算 2 为底的 x 的对数。
双曲函数方法
- Math.sinh(x): 用于计算双曲正弦
- Math.cosh(x): 用于计算双曲余弦
- Math.tanh(x): 用于计算双曲正切
- Math.asinh(x): 用于计算反双曲正弦
- Math.acosh(x): 用于计算反双曲余弦
- Math.atanh(x): 用于计算反双曲正切
指数运算符
5)对象
对象字面量
属性的简介表示法
ES6允许对象的属性直接写变量,这时候属性名是变量名,属性值是变量值
方法名也可以简写
如果是Generator 函数,则要在前面加一个星号:
属性名表达式
ES6允许用表达式作为属性名,但是一定要将表达式放在方括号内。
注意点:属性的简洁表示法和属性名表达式不能同时使用,否则会报错。
对象的扩展运算符
拓展运算符(…)用于取出参数对象所有可遍历属性然后拷贝到当前对象。
基本用法
可用于合并两个对象
注意点
自定义的属性和拓展运算符对象里面属性的相同的时候:自定义的属性在拓展运算符后面,则拓展运算符对象内部同名的属性将被覆盖掉
自定义的属性在拓展运算度前面,则变成设置新对象默认属性值。
拓展运算符后面是空对象,没有任何效果也不会报错。
拓展运算符后面是null或者undefined,没有效果也不会报错。
对象的新方法
Object.assign(target, source_1, ···)
用于将源对象的所有可枚举属性复制到目标对象中。
基本用法
- 如果目标对象和源对象有同名属性,或者多个源对象有同名属性,则后面的属性会覆盖前面的属性
- 如果该函数只有一个参数,当参数为对象时,直接返回该对象;当参数不是对象时,会先将参数转为对象然后返回
因为 null 和 undefined 不能转化为对象,所以会报错:
注意点
assign 的属性拷贝是浅拷贝:
同名属性替换
数组的处理
Object.assign([2,3], [5]); // [5,3]
会将数组处理成对象,所以先将 [2,3] 转为 {0:2,1:3} ,然后再进行属性复制,所以源对象的 0 号属性覆盖了目标对象的 0。
Object.is(value1, value2)
用来比较两个值是否严格相等,与(===)基本类似。
基本用法
与(===)的区别
6)数组
数组创建
Array.of()
将参数中所有值作为元素形成数组。
Array.from()
将类数组对象或可迭代对象转化为数组。
参数
Array.from(arrayLike[, mapFn[, thisArg]])
返回值为转换后的数组。
- arrayLike
想要转换的类数组对象或可迭代对象。
console.log(Array.from([1, 2, 3])); // [1, 2, 3]
- mapFn
可选,map 函数,用于对每个元素进行处理,放入数组的是处理后的元素。
console.log(Array.from([1, 2, 3], (n) => n * 2)); // [2, 4, 6]
- thisArg
可选,用于指定 map 函数执行时的 this 对象。
类数组对象
一个类数组对象必须含有 length 属性,且元素属性名必须是数值或者可转换为数值的字符。
转换可迭代对象
转换map
转换set
转换字符串
扩展的方法
查找
- find() 查找数组中符合条件的元素,若有多个符合条件的元素,则返回第一个元素
- findIndex() 查找数组中符合条件的元素索引,若有多个符合条件的元素,则返回第一个元素索引
填充
- fill() 将一定范围索引的数组元素内容填充为单个指定的值
- copyWithin() 将一定范围索引的数组元素修改为此数组另一指定范围索引的元素
遍历
- entries() 遍历键值对
- keys 遍历键名
- values() 遍历键值
包含
- includes() 数组是否包含指定值
嵌套数组转一维数组
- flat()
- flatMap()
先对数组中每个元素进行了的处理,再对数组执行 flat() 方法。
注:以上方法使用详情请参考我的文章:JS数组中常用的方法总结
数组缓冲区
数组缓冲区是内存中的一段地址。
定型数组的基础。
实际字节数在创建时确定,之后只可修改其中的数据,不可修改大小。
创建数组缓冲区
通过构造函数创建
视图
视图是用来操作内存的接口
视图可以操作数组缓冲区或缓冲区字节的子集,并按照其中一种数值数据类型来读取和写入数据
DataView 类型是一种通用的数组缓冲区视图,其支持所有8种数值型数据类型
创建:
定型数组
数组缓冲区的特定类型的视图。
可以强制使用特定的数据类型,而不是使用通用的 DataView 对象来操作数组缓冲区
创建
通过数组缓冲区生成
通过构造函数
注意要点
length 属性不可写,如果尝试修改这个值,在非严格模式下会直接忽略该操作,在严格模式下会抛出错误。
定型数组可使用 entries()、keys()、values()进行迭代。
find() 等方法也可用于定型数组,但是定型数组中的方法会额外检查数值类型是否安全,也会通过 Symbol.species 确认方法的返回值是定型数组而非普通数组。concat() 方法由于两个定型数组合并结果不确定,故不能用于定型数组;另外,由于定型数组的尺寸不可更改,可以改变数组的尺寸的方法,例如 splice() ,不适用于定型数组。
所有定型数组都含有静态 of() 方法和 from() 方法,运行效果分别与 Array.of() 方法和 Array.from() 方法相似,区别是定型数组的方法返回定型数组,而普通数组的方法返回普通数组。
定型数组不是普通数组,不继承自 Array 。
定型数组中增加了 set() 与 subarray() 方法。 set() 方法用于将其他数组复制到已有定型数组, subarray() 用于提取已有定型数组的一部分形成新的定型数组。
扩展运算符
复制数组
合并数组
console.log([...[1, 2],...[3, 4]]); // [1, 2, 3, 4]
7)函数
函数参数的扩展
默认参数
基本用法
注意点:使用函数默认参数时,不允许有同名参数。
只有在未传递参数,或者参数为 undefined 时,才会使用默认参数,null 值被认为是有效的值传递。
函数参数默认值存在暂时性死区,在函数参数默认值表达式中,还未初始化赋值的参数值无法作为其他参数的默认值
不定参数
不定参数用来表示不确定参数个数,形如,…变量名,由…加上一个具名参数标识符组成。具名参数只能放在参数组的最后,并且有且只有一个不定参数。
基本用法
箭头函数
箭头函数提供了一种更加简洁的函数书写方式。基本语法是
参数 => 函数体
基本用法
当箭头函数没有参数或者有多个参数,要用 () 括起来。
当箭头函数函数体有多行语句,用 {} 包裹起来,表示代码块,当只有一行语句,并且需要返回结果时,可以省略 {} , 结果会自动返回。
当箭头函数要返回对象的时候,为了区分于代码块,要用 () 将对象包裹起来
注意点:没有 this、super、arguments 和 new.target 绑定。
箭头函数体中的 this 对象,是定义函数时的对象,而不是使用函数时的对象
不可以作为构造函数,也就是不能使用 new 命令,否则会报错
适合使用的场景
ES6 之前,JavaScript 的 this 对象一直很令人头大,回调函数,经常看到 var self = this 这样的代码,为了将外部 this 传递到回调函数中,那么有了箭头函数,就不需要这样做了,直接使用 this 就行
所以,当我们需要维护一个 this 上下文的时候,就可以使用箭头函数。
不适合使用的场景
定义函数的方法,且该方法中包含 this
需要动态this的时候
button 的监听函数是箭头函数,所以监听函数里面的 this 指向的是定义的时候外层的 this 对象,即 Window,导致无法操作到被点击的按钮对象。
8)Symbol
ES6 引入了一种新的原始数据类型 Symbol ,表示独一无二的值,最大的用法是用来定义对象的唯一属性名。
ES6 数据类型除了 Number 、 String 、 Boolean 、 Objec t、 null 和 undefined ,还新增了 Symbol 。
基本用法
Symbol 函数栈不能用 new 命令,因为 Symbol 是原始数据类型,不是对象。可以接受一个字符串作为参数,为新创建的 Symbol 提供描述,用来显示在控制台或者作为字符串的时候使用,便于区分。
使用场景
作为属性名
用法:
由于每一个 Symbol 的值都是不相等的,所以 Symbol 作为对象的属性名,可以保证属性不重名。
Symbol 作为对象属性名时不能用.运算符,要用方括号。因为.运算符后面是字符串,所以取到的是字符串 sy 属性,而不是 Symbol 值 sy 属性。
注意点
Symbol 值作为属性名时,该属性是公有属性不是私有属性,可以在类的外部访问。但是不会出现在 for…in 、 for…of 的循环中,也不会被 Object.keys() 、 Object.getOwnPropertyNames() 返回。如果要读取到一个对象的 Symbol 属性,可以通过 Object.getOwnPropertySymbols() 和 Reflect.ownKeys() 取到。
定义常量
在 ES5 使用字符串表示常量。例如:
但是用字符串不能保证常量是独特的,这样会引起一些问题:
但是使用 Symbol 定义常量,这样就可以保证这一组常量的值都不相等。用 Symbol 来修改上面的例子
Symbol 的值是唯一的,所以不会出现相同值得常量,即可以保证 switch 按照代码预想的方式执行
Symbol.for()
Symbol.for() 类似单例模式,首先会在全局搜索被登记的 Symbol 中是否有该字符串参数作为名称的 Symbol 值,如果有即返回该 Symbol 值,若没有则新建并返回一个以该字符串参数为名称的 Symbol 值,并登记在全局环境中供搜索。
Symbol.keyFor()
Symbol.keyFor() 返回一个已登记的 Symbol 类型值的 key ,用来检测该字符串参数作为名称的 Symbol 值是否已被登记。
9)Map 与 Set
Map 对象
Map 对象保存键值对。任何值(对象或者原始值) 都可以作为一个键或一个值。
Maps 和 Objects 的区别
- 一个 Object 的键只能是字符串或者 Symbols,但一个 Map 的键可以是任意值。
- Map 中的键值是有序的(FIFO 原则),而添加到对象中的键则不是。
- Map 的键值对个数可以从 size 属性获取,而 Object 的键值对个数只能手动计算。
- Object 都有自己的原型,原型链上的键名有可能和你自己在对象上的设置的键名产生冲突。
Map 中的 key
key是字符串
key是对象
key是函数
key是NaN
虽然 NaN 和任何值甚至和自己都不相等(NaN !== NaN 返回true),NaN作为Map的键来说是没有区别的。
Map的迭代
对 Map 进行遍历,以下两个最高级。
- for…of
- forEach()
Map 对象的操作
Map 与 Array的转换
Map 的克隆
Map 的合并
Set 对象
Set 对象允许你存储任何类型的唯一值,无论是原始值或者是对象引用。
Set 中的特殊值
Set 对象存储的值总是唯一的,所以需要判断两个值是否恒等。有几个特殊值需要特殊对待:
- +0 与 -0 在存储判断唯一性的时候是恒等的,所以不重复;
- undefined 与 undefined 是恒等的,所以不重复;
- NaN 与 NaN 是不恒等的,但是在 Set 中只能存一个,不重复。
代码:
类型转换
Array
Set 对象作用
数组去重
并集
交集
差集
10)Reflect 与 Proxy
Proxy 与 Reflect 是 ES6 为了操作对象引入的 API 。
Proxy 可以对目标对象的读取、函数调用等操作进行拦截,然后进行操作处理。它不直接操作对象,而是像代理模式,通过对象的代理对象进行操作,在进行这些操作时,可以添加一些需要的额外操作
Reflect 可以用于获取目标对象的行为,它与 Object 类似,但是更易读,为操作对象提供了一种更优雅的方式。它的方法与 Proxy 是对应的
Proxy
一个 Proxy 对象由两个部分组成: target 、 handler 。在通过 Proxy 构造函数生成实例对象时,需要提供这两个参数。 target 即目标对象, handler 是一个对象,声明了代理 target 的指定行为。
实例方法
get(target, propKey, receiver)
用于 target 对象上 propKey 的读取操作。
get() 方法可以继承
set(target, propKey, value, receiver)
用于拦截 target 对象上的 propKey 的赋值操作。如果目标对象自身的某个属性,不可写且不可配置,那么set方法将不起作用。
第四个参数 receiver 表示原始操作行为所在对象,一般是 Proxy 实例本身。
注意,严格模式下,set代理如果没有返回true,就会报错。
apply(target,ctx,args)
用于拦截函数的调用、call 和 reply 操作。target 表示目标对象,ctx 表示目标对象上下文,args 表示目标对象的参数数组
has(target, propKey)
用于拦截 HasProperty 操作,即在判断 target 对象是否存在 propKey 属性时,会被这个方法拦截。此方法不判断一个属性是对象自身的属性,还是继承的属性
注意:此方法不拦截 for … in 循环
construct(target, args)
用于拦截 new 命令。返回值必须为对象。
deleteProperty(target, propKey)
用于拦截 delete 操作,如果这个方法抛出错误或者返回 false ,propKey 属性就无法被 delete 命令删除
defineProperty(target, propKey, propDesc)
用于拦截 Object.definePro若目标对象不可扩展,增加目标对象上不存在的属性会报错;若属性不可写或不可配置,则不能改变这些属性
erty操作
getOwnPropertyDescriptor(target, propKey)
用于拦截 Object.getOwnPropertyD() 返回值为属性描述对象或者 undefined
ptor属性
getPrototypeOf(target)
主要用于拦截获取对象原型的操作。包括以下操作:
- -Object.prototype.proto
- -Object.prototype.isPrototypeOf()
- -Object.getPrototypeOf()
- -Reflect.getPrototypeOf()
- -instanceof
注意,返回值必须是对象或者 null ,否则报错。另外,如果目标对象不可扩展(non-extensible),getPrototypeOf 方法必须返回目标对象的原型对象
isExtensible(target)
用于拦截 Object.isExtensible 操作。
该方法只能返回布尔值,否则返回值会被自动转为布尔值
注意:它的返回值必须与目标对象的isExtensible属性保持一致,否则会抛出错误
ownKeys(target)
用于拦截对象自身属性的读取操作。主要包括以下操作:
- -Object.getOwnPropertyNames()
- -Object.getOwnPropertySymbols()
- -Object.keys()
- -or…in
方法返回的数组成员,只能是字符串或 Symbol 值,否则会报错
若目标对象中含有不可配置的属性,则必须将这些属性在结果中返回,否则就会报错
若目标对象不可扩展,则必须全部返回且只能返回目标对象包含的所有属性,不能包含不存在的属性,否则也会报错
preventExtensions(target)
拦截 Object.preventExtensions 操作。
该方法必须返回一个布尔值,否则会自动转为布尔值。
setPrototypeOf
主要用来拦截 Object.setPrototypeOf 方法
返回值必须为布尔值,否则会被自动转为布尔值
若目标对象不可扩展,setPrototypeOf 方法不得改变目标对象的原型
Proxy.revocable()
用于返回一个可取消的 Proxy 实例
Reflect
ES6 中将 Object 的一些明显属于语言内部的方法移植到了 Reflect 对象上(当前某些方法会同时存在于 Object 和 Reflect 对象上),未来的新方法会只部署在 Reflect 对象上。
Reflect 对象对某些方法的返回结果进行了修改,使其更合理
Reflect 对象使用函数的方式实现了 Object 的命令式操作
静态方法
Reflect.get(target, name, receiver)
查找并返回 target 对象的 name 属性。
Reflect.set(target, name, value, receiver)
将 target 的 name 属性设置为 value。返回值为 boolean ,true 表示修改成功,false 表示失败。当 target 为不存在的对象时,会报错
Reflect.has(obj, name)
是 name in obj 指令的函数化,用于查找 name 属性在 obj 对象中是否存在。返回值为 boolean。如果 obj 不是对象则会报错 TypeError。
Reflect.deleteProperty(obj, property)
是 delete obj[property] 的函数化,用于删除 obj 对象的 property 属性,返回值为 boolean。如果 obj 不是对象则会报错 TypeError。
Reflect.construct(obj, args)
等同于 new target(…args)。
Reflect.getPrototypeOf(obj)
用于读取 obj 的 proto 属性。在 obj 不是对象时不会像 Object 一样把 obj 转为对象,而是会报错。
Reflect.setPrototypeOf(obj, newProto)
用于设置目标对象的 prototype。
Reflect.apply(func, thisArg, args)
等同于 Function.prototype.apply.call(func, thisArg, args) 。func 表示目标函数;thisArg 表示目标函数绑定的 this 对象;args 表示目标函数调用时传入的参数列表,可以是数组或类似数组的对象。若目标函数无法调用,会抛出 TypeError 。
Reflect.apply(Math.max, Math, [1, 3, 5, 3, 1]); // 5
Reflect.defineProperty(target, propertyKey, attributes)
用于为目标对象定义属性。如果 target 不是对象,会抛出错误
Reflect.getOwnPropertyDescriptor(target, propertyKey)
用于得到 target 对象的 propertyKey 属性的描述对象。在 target 不是对象时,会抛出错误表示参数非法,不会将非对象转换为对象。
Reflect.isExtensible(target)
用于判断 target 对象是否可扩展。返回值为 boolean 。如果 target 参数不是对象,会抛出错误。
Reflect.preventExtensions(target)
用于让 target 对象变为不可扩展。如果 target 参数不是对象,会抛出错误。
Reflect.ownKeys(target)
用于返回 target 对象的所有属性,等同于 Object.getOwnPropertyNames 与Object.getOwnPropertySymbols 之和。
组合使用
Reflect 对象的方法与 Proxy 对象的方法是一一对应的。所以 Proxy 对象的方法可以通过调用 Reflect 对象的方法获取默认行为,然后进行额外操作。
使用场景扩展
实现观察者模式
11)迭代器
Iterator
Iterator 是 ES6 引入的一种新的遍历机制,迭代器有两个核心概念:
- 迭代器是一个统一的接口,它的作用是使各种数据结构可被便捷的访问,它是通过一个键为Symbol.iterator 的方法来实现
- 迭代器是用于遍历数据结构元素的指针(如数据库中的游标)
迭代过程
迭代过程如下:
- 通过 Symbol.iterator 创建一个迭代器,指向当前数据结构的起始位置
- 随后通过 next 方法进行向下迭代指向下一个位置, next 方法会返回当前位置的对象,对象包含了 value 和 done 两个属性, value 是当前属性的值, done 用于判断是否遍历结束
- 当 done 为 true 时则遍历结束
下面通过一个简单的例子进行说明:
上面的例子,首先创建一个数组,然后通过 Symbol.iterator 方法创建一个迭代器,之后不断的调用 next 方法对数组内部项进行访问,
当属性 done 为 true 时访问结束。
迭代器是协议(使用它们的规则)的一部分,用于迭代。该协议的一个关键特性就是它是顺序的:迭代器一次返回一个值。这意味着如果可迭代数据结构是非线性的(例如树),迭代将会使其线性化。
可迭代的数据结构
以下是可迭代的值:
- Array
- String
- Map
- Set
- Dom元素(正在进行中)
我们将使用 for…of 循环(参见下文的 for…of 循环)对数据结构进行迭代。
Array
数组 ( Array ) 和类型数组 ( TypedArray ) 他们是可迭代的。
String
字符串是可迭代的,单他们遍历的是 Unicode 码,每个码可能包含一个到两个的 Javascript 字符。
Map
Map 主要是迭代它们的 entries ,每个 entry 都会被编码为 [key, value] 的项, entries 是以确定的形势进行迭代,其顺序是与添加的顺序相同。
注意: WeakMaps 不可迭代
Set
Set 是对其元素进行迭代,迭代的顺序与其添加的顺序相同
注意: WeakSets 不可迭代
arguments
arguments 目前在 ES6 中使用越来越少,但也是可遍历的
普通对象不可迭代
普通对象是由 object 创建的,不可迭代:
for…of循环
for…of 是 ES6 新引入的循环,用于替代 for…in 和 forEach() ,并且支持新的迭代协议。它可用于迭代常规的数据类型,如 Array 、 String 、 Map 和 Set 等等。
迭代常规数据类型
Array
String
Map
Set
可迭代的数据结构
of 操作数必须是可迭代,这意味着如果是普通对象则无法进行迭代。如果数据结构类似于数组的形式,则可以借助 Array.from() 方法进行转换迭代。
const arrayLink = {length: 2, 0: "zero", 1: "one"}
let 、const 和 var 用于 for…of
如果使用 let 和 const ,每次迭代将会创建一个新的存储空间,这可以保证作用域在迭代的内部。
从上面的例子我们看到,最后一句会报异常,原因 num 的作用域只在循环体内部,外部无效,具体可查阅 let 与 const 章节。使用 var 则不会出现上述情况,因为 var 会作用于全局,迭代将不会每次都创建一个新的存储空间。
12)Class类
在ES6中,class (类)作为对象的模板被引入,可以通过 class 关键字定义类。
class 的本质是 function。
它可以看作一个语法糖,让对象原型的写法更加清晰、更像面向对象编程的语法。
基础用法
类定义
类表达式可以为匿名或命名。
类声明
注意要点:不可重复声明。
注意要点
类定义不会被提升,这意味着,必须在访问前对类进行定义,否则就会报错。
类中方法不需要 function 关键字。
方法间不能加分号。
类的主体
属性
prototype
ES6 中,prototype 仍旧存在,虽然可以直接自类中定义方法,但是其实方法还是定义在 prototype 上的。 覆盖方法 / 初始化时添加方法
添加方法
静态属性
静态属性:class 本身的属性,即直接定义在类内部的属性( Class.propname ),不需要实例化。 ES6 中规定,Class 内部只有静态方法,没有静态属性。
公共属性
实例属性
实例属性:定义在实例对象( this )上的属性。
name属性
返回跟在 class 后的类名(存在时)。
方法
constructor 方法
constructor 方法是类的默认方法,创建类的实例化对象时被调用。
返回对象
静态方法
原型方法
实例方法
类的实例化
new
class 的实例化必须通过 new 关键字。
实例化对象
共享原型对象
decorator
decorator 是一个函数,用来修改类的行为,在代码编译时产生作用。
类修饰
一个参数
第一个参数 target,指向类本身。
多个参数——嵌套实现
实例属性
上面两个例子添加的是静态属性,若要添加实例属性,在类的 prototype 上操作即可。
方法修饰
3个参数:target(类的原型对象)、name(修饰的属性名)、descriptor(该属性的描述对象)。
修饰器执行顺序
由外向内进入,由内向外执行。
封装与继承
getter / setter
定义
getter 不可单独出现
getter 与 setter 必须同级出现
extends
通过 extends 实现类的继承。
class Child extends Father { ... }
super
子类 constructor 方法中必须有 super ,且必须出现在 this 之前。
调用父类构造函数,只能出现在子类的构造函数。
调用父类方法, super 作为对象,在普通方法中,指向父类的原型对象,在静态方法中,指向父类
注意要点
不可继承常规对象。
13)模块
在 ES6 前, 实现模块化使用的是 RequireJS 或者 seaJS(分别是基于 AMD 规范的模块化库, 和基于 CMD 规范的模块化库)。
ES6 引入了模块化,其设计思想是在编译时就能确定模块的依赖关系,以及输入和输出的变量。
ES6 的模块化分为导出(export) @与导入(import)两个模块。
特点
- ES6 的模块自动开启严格模式,不管你有没有在模块头部加上
use strict;
- 模块中可以导入和导出各种类型的变量,如函数,对象,字符串,数字,布尔值,类等
- 每个模块都有自己的上下文,每一个模块内声明的变量都是局部变量,不会污染全局作用域
- 每一个模块只加载一次(是单例的), 若再去加载同目录下同文件,直接从内存中读取
export 与 import
基本用法
模块导入导出各种类型的变量,如字符串,数值,函数,类。
- 导出的函数声明与类声明必须要有名称(export default 命令另外考虑)。
- 不仅能导出声明还能导出引用(例如函数)。
- export 命令可以出现在模块的任何位置,但必需处于模块顶层。
- import 命令会提升到整个模块的头部,首先执行。
建议使用大括号指定所要输出的一组变量写在文档尾部,明确导出的接口。
函数与类都需要有对应的名称,导出文档尾部也避免了无对应名称。
as 的用法
export 命令导出的接口名称,须和模块内部的变量有一一对应关系。
导入的变量名,须和导出的接口名称相同,即顺序可以不一致。
不同模块导出接口名称命名重复, 使用 as 重新定义变量名。
import 命令的特点
只读属性:不允许在加载模块的脚本里面,改写接口的引用指向,即可以改写 import 变量类型为对象的属性值,不能改写 import 变量类型为基本类型的值。
单例模式:多次重复执行同一句 import 语句,那么只会执行一次,而不会执行多次。import 同一模块,声明不同接口引用,会声明对应变量,但只执行一次 import 。
静态执行特性:import 是静态执行,所以不能使用表达式和变量。
export default 命令
- 在一个文件或模块中,export、import 可以有多个,export default 仅有一个。
- export default 中的 default 是对应的导出接口变量。
- 通过 export 方式导出,在导入时要加{ },export default 则不需要。
- export default 向外暴露的成员,可以使用任意变量来接收。
复合使用
注: import() 是提案,这边暂时不延伸讲解。
export 与 import 可以在同一模块使用,使用特点:
- 可以将导出接口改名,包括 default。
- 复合使用 export 与 import ,也可以导出全部,当前模块导出的接口会覆盖继承导出的。
14)Promise 对象
是异步编程的一种解决方案。
从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。
具体内容请参考我的文章–>JS中的 异步解决方案
Promise
状态的特点
Promise 异步操作有三种状态:pending(进行中)、fulfilled(已成功)和 rejected(已失败)。除了异步操作的结果,任何其他操作都无法改变这个状态。
Promise 对象只有:从 pending 变为 fulfilled 和从 pending 变为 rejected 的状态改变。只要处于 fulfilled 和 rejected ,状态就不会再变了即 resolved(已定型)。
状态的缺点
- 无法取消 Promise ,一旦新建它就会立即执行,无法中途取消。
- 如果不设置回调函数,Promise 内部抛出的错误,不会反应到外部。
- 当处于 pending 状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。
then 方法
then 方法接收两个函数作为参数,第一个参数是 Promise 执行成功时的回调,第二个参数是 Promise 执行失败时的回调,两个函数只会有一个被调用。
then 方法的特点
在 JavaScript 事件队列的当前运行完成之前,回调函数永远不会被调用。
通过 .then 形式添加的回调函数,不论什么时候,都会被调用。
通过多次调用
.then
可以添加多个回调函数,它们会按照插入顺序并且独立运行。
then 方法将返回一个 resolved 或 rejected 状态的 Promise 对象用于链式调用,且 Promise 对象的值就是这个返回值。
then 方法注意点
简便的 Promise 链式编程最好保持扁平化,不要嵌套 Promise。
注意总是返回或终止 Promise 链。
创建新 Promise 但忘记返回它时,对应链条被打破,导致 p4 会与 p2 和 p3 同时进行。
大多数浏览器中不能终止的 Promise 链里的 rejection,建议后面都跟上 .catch(error => console.log(error));
15)Generator 函数
ES6 新引入了 Generator 函数,可以通过 yield 关键字,把函数的执行流挂起,为改变执行流程提供了可能,从而为异步编程提供解决方案
Generator 函数组成
Generator 有两个区分于普通函数的部分:
- 一是在 function 后面,函数名之前有个 * ;
- 函数内部有 yield 表达式。
其中 * 用来表示函数为 Generator 函数,yield 用来定义函数内部的状态。
执行机制
调用 Generator 函数和调用普通函数一样,在函数名后面加上()即可,但是 Generator 函数不会像普通函数一样立即执行,而是返回一个指向内部状态对象的指针,所以要调用遍历器对象Iterator 的 next 方法,指针就会从函数头部或者上一次停下来的地方开始执行。
- 第一次调用 next 方法时,从 Generator 函数的头部开始执行,先是打印了 one ,执行到 yield 就停下来,并将yield 后边表达式的值 ‘1’,作为返回对象的 value 属性值,此时函数还没有执行完, 返回对象的 done 属性值是 false。
- 第二次调用 next 方法时,同上步 。
- 第三次调用 next 方法时,先是打印了 three ,然后执行了函数的返回操作,并将 return 后面的表达式的值,作为返回对象的 value 属性值,此时函数已经结束,多以 done 属性值为true 。
- 第四次调用 next 方法时, 此时函数已经执行完了,所以返回 value 属性值是 undefined ,done 属性值是 true 。如果执行第三步时,没有 return 语句的话,就直接返回 {value: undefined, done: true}。
函数返回的遍历器对象的方法
next 方法
一般情况下,next 方法不传入参数的时候,yield 表达式的返回值是 undefined 。当 next 传入参数的时候,该参数会作为上一步yield的返回值。
next 不传参
除了使用 next ,还可以使用 for… of 循环遍历 Generator 函数生产的 Iterator 对象。
return 方法
return 方法返回给定值,并结束遍历 Generator 函数。
return 方法提供参数时,返回该参数;不提供参数时,返回 undefined 。
遍历器对象抛出了两个错误,第一个被 Generator 函数内部捕获,第二个因为函数体内部的catch 函数已经执行过了,不会再捕获这个错误,所以这个错误就抛出 Generator 函数体,被函数体外的 catch 捕获。
yield 表达式*
yield* 表达式表示 yield 返回一个遍历器对象,用于在 Generator 函数内部,调用另一个 Generator 函数。
使用场景
实现 Iterator
为不具备 Iterator 接口的对象提供遍历方法。
Reflect.ownKeys() 返回对象所有的属性,不管属性是否可枚举,包括 Symbol。
jane 原生是不具备 Iterator 接口无法通过 for… of遍历。这边用了 Generator 函数加上了 Iterator 接口,所以就可以遍历 jane 对象了。
16)async / await
具体用法请参考我的 JS中异步解决方案
async
async 是 ES7 才有的与异步操作有关的关键字,和 Promise , Generator 有很大关联的。
语法
async function name([param[, param[, ... param]]]) { statements }
- name: 函数名称。
- param: 要传递给函数的参数的名称。
- statements: 函数体语句。
返回值
async 函数返回一个 Promise 对象,可以使用 then 方法添加回调函数。
async 函数中可能会有 await 表达式,async 函数执行时,如果遇到 await 就会先暂停执行 ,等到触发的异步操作完成后,恢复 async 函数的执行并返回解析值。
await 关键字仅在 async function 中有效。如果在 async function 函数体外使用 await ,你只会得到一个语法错误。
await
await 操作符用于等待一个 Promise 对象, 它只能在异步函数 async function 内部使用。
语法
[return_value] = await expression;
- expression: 一个 Promise 对象或者任何要等待的值。
返回值
返回 Promise 对象的处理结果。如果等待的不是 Promise 对象,则返回该值本身。
如果一个 Promise 被传递给一个 await 操作符,await 将等待 Promise 正常处理完成并返回其处理结果。
正常情况下,await 命令后面是一个 Promise 对象,它也可以跟其他值,如字符串,布尔值,数值以及普通函数。
await针对所跟不同表达式的处理方式:
- Promise 对象:await 会暂停执行,等待 Promise 对象 resolve,然后恢复 async 函数的执行并返回解析值。
- 非 Promise 对象:直接返回对应的值。
评论区