ECMAScript 6.0(简称 ES6或ES2015) 2015 年 6 月正式发布
let
和 const
不允许重复声明变量,var
可以
let
和 const
声明的变量不会在预解析的时候解析(没有变量提升),var
可以
let
和 const
声明的变量会被所有代码块限制作用范围,作用域在{ }
内,var
为全局作用域
let
和 const
的区别
let
声明的变量的值可以改变,const
声明的变量的值不可以改变,用于声明常量
let
声明的时候可以不赋值,const
声明的时候必须赋值
解构赋值,就是快速的从对象或者数组中取出成员的一个语法方式
解构对象
const obj = { name: '孙悟空', age: 18, } // ES5 的方法得到对象中的成员 let name = obj.name let age = obj.age // 对象解构 let { name, age } = obj
解构数组
const arr = [ 'one', 'two'] // ES5 的方式从数组中获取成员 let a = arr[0] let b = arr[1] // 数组解构 let [a, b] = arr
ES5 中我们表示字符串的时候使用 ''
或者 ""
,ES6新增【`` 】(反引号)
反引号可以换行书写,单引号不行
let str = ` hello world`
反引号可在字符串里拼接变量
let num = 10 // ES5 需要字符串拼接变量的时候 let str = 'hello' + num + 'world' //模版字符串拼接变量 let str = `hello${num}world${num}`
includes
判断字符串中是否存在指定字符,返回true或false
let str = "ke" str.includes("e") //true str.startsWith("t") //false
repeat
表示将原字符串重复n次,返回一个新字符串
let str = "js" str.repeat(3) //jsjsjs str.repeat(0) //"" str.repeat(3.5) //jsjsjs str.repeat("3") //jsjsjs
0b111110111 === 503 // true 0o767 === 503 // true
Number.isFinite(100) //true Number.isNaN(NaN) //true
传统方法isFinite和isNaN,先调用Number将非数值的值转为数值,再进行判断
Number.isFinite 非数值类型,返回false
Number.isNaN 非NaN类型,返回false
用来判断一个数值是否为整数,JS整数和浮点数采用的是同样的储存方法
Number.isInteger(100) //true Number.isInteger(100.0) //true
误判情况
//转成二进制位超过了53个二进制位,2被丢弃 Number.isInteger(3.0000000000000002) // true //绝对值小于Number.MIN_VALUE Number.isInteger(5E-324) // false
ES2021,允许 JavaScript 的数值使用下划线(_
)作为分隔符
let budget = 10_00_0000_0; budget === 100000000;
不能放在数值的最前面或最后面
不能两个或两个以上的分隔符连在一起
小数点的前后不能有分隔符
科学计数法里面,表示指数的e
或E
前后不能有分隔符
表示 1 与大于 1 的最小浮点数之间的差,2.220446049250313e-16, 为JS能够表示的最小精度
0.1 + 0.2 === 0.3 // 0.30000000000000004 !== 0.3 falae
除一个数的小数部分,返回整数部分
Math.trunc(1.2) //1
判断一个数到底是正数、负数、还是零。对于非数值,会先将其转换为数值
参数为正数,返回【+1】
参数为负数,返回【-1】
参数为 0,返回【0】
参数为-0,返回【-0】
其他值,返回【NaN】
Math.sign(-10) // -1
将一个数组转为用逗号分隔的参数序列
console.log(...[1, 2, 3]) // 1 2 3 const [first, ...rest] = [1, 2, 3]; // first:1 rest:[2, 3]
扩展运算符可以将字符串转为真正的数组,能够正确识别 Unicode 字符
[...'he'] // [ "h", "e"]
可将两种对象转为真正的数组:类似数组的对象(array-like object)和可遍历(iterable)的对象(包括 ES6 新增的数据结构 Set 和 Map)
let arrayLike = { '0': 'a', '1': 'b', '2': 'c', length: 3 }; // ES5 的写法 var arr1 = [].slice.call(arrayLike) // ['a', 'b', 'c'] // ES6 的写法 let arr2 = Array.from(arrayLike) // ['a', 'b', 'c']
常见的类似数组的对象是 DOM 操作返回的 NodeList 集合,函数内部的arguments
对象,都可转换
Array.from()
还可以接受一个函数作为第二个参数,作用类似于数组的map()
方法,用来对每个元素进行处理,将处理后的值放入返回的数组
Array.from(arrayLike, x => x * x); // 等同于 Array.from(arrayLike).map(x => x * x); Array.from([1, 2, 3], (x) => x * x) // [1, 4, 9]
Array.from()
的另一个应用是,将字符串转为数组
将一组值转化为数组,即新建数组
Array.of(3, 11, 8) // [3,11,8]
Array()
当参数不少于 2 个,才会返回由参数组成的新数组,参数只有一个正整数时,实际上是指定数组的长度
Array.of()
基本上可以用来替代Array()
或new Array()
,并且不存在由于参数不同而导致的重载
filter 返回符合条件的数组内容
array.filter( item=> { return item })
find(),findIndex(),findLast(),findLastIndex()
参数是一个回调函数,可以接受三个参数,依次为当前的值、当前的位置和原数组。在函数中写要查找元素的条件
find()查找第一个符合条件的数组元素,并返回该元素,没有返回undefined
findIndex()则是返回下标,都不符合则返回【-1】
[1, 10, 15].find( (value, index, arr)=> { return value > 9 }) // 10
indexOf()
方法无法识别数组的NaN
成员,但是findIndex()
方法可以借助Object.is()
方法做到
使用给定值,填充一个数组,可以接受第二个和第三个参数,用于指定填充的起始位置和结束位置
['a', 'b', 'c'].fill(7, 1, 2) // ['a', 7, 'c']
返回一个布尔值,表示某个数组是否包含给定的值,第二个参数表示搜索的起始位置,默认为【0】
替代indexOf
,找到参数值的第一个出现位置,所以要去比较是否不等于-1
,表达起来不够直观。它内部使用严格相等运算符(===
)进行判断,这会导致对NaN
的误判
flat数组的扁平化处理,接收一个参数为扁平化的层数,原数组有空位,会被跳过
[1, 2, [3, [4, 5]]].flat(1) // [1, 2, 3,[4, 5]]
flatMap()
方法对原数组的每个成员执行一个函数
[2, 3, 4].flatMap((x) => [x, x * 2]) // [2, 4, 3, 6, 4, 8]
let name = '孙悟空'; const Person = { name: this.name, age: 18, hello: function ({ console.log('我的名字是', this.name) }) return {name:name, age:age} }; const Person = { name, age: 18, hello() { console.log(this.name) } return {name, age} };
允许对象的属性通过【[ ]】包裹,属性名表达式与简洁表示法,不能同时使用,会报错。
const a = { 'first word': 'hello', [lastWord]: 'world' }; a['first word'] // "hello" a[lastWord] // "world" a['last word'] // "world"
Object.assign(target, obj1,obj2)的第一个参数是目标对象,后面可以跟一个或多个源对象作为参数
target:参数合并后存放的对象,obj1:参数1,obj2:参数2
const obj1 = { name: "a" }; const obj2 = { name: "b" }; const obj3 = { age :100 }; Object.assign(obj1, obj2, obj3); //obj1 {name: 'b', age: 100}
方法判断两个值是否是相同的值
NaN===NaN //false +0===-0 //true Object.is(NaN,NaN) //true Object.is(+0,-0) //false
函数的name
属性,返回函数名。对象方法也是函数,因此也有name
属性
对象的每个属性都有一个描述对象(Descriptor),用来控制该属性的行为
Object.getOwnPropertyDescriptor`方法可以获取该属性的描述对象
描述对象的enumerable
属性,称为“可枚举性”,如果该属性为false
for...in
循环:只遍历对象自身的和继承的可枚举的属性
Object.keys()
:返回对象自身的所有可枚举的属性的键名
JSON.stringify()
:只串行化对象自身的可枚举的属性
Object.assign()
: 忽略enumerable
为false
的属性,只拷贝对象自身的可枚举的属性
(1)for...in
循环遍历对象自身的和继承的可枚举属性(不含 Symbol 属性)
(2)Object.keys(obj)
返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含 Symbol 属性)的键名
(3)Object.getOwnPropertyNames(obj)
返回一个数组,包含对象自身的所有属性(不含 Symbol 属性,但是包括不可枚举属性)的键名
(4)Object.getOwnPropertySymbols(obj)
返回一个数组,包含对象自身的所有 Symbol 属性的键名
(5)Reflect.ownKeys(obj)
返回一个数组,包含对象自身的(不含继承的)所有键名,不管键名是 Symbol 或字符串,也不管是否可枚举
this
关键字总是指向函数所在的当前对象,ES6 新增关键字super
,指向当前对象的原型对象
ES2018 扩展运算符【...】引入对象,解构赋值和扩展运算详情可看【数组篇】
箭头函数只能简写函数表达式,不能简写声明式函数
语法: (函数的行参) => { 函数体内要执行的代码 }
函数的行参只有一个的时候可以不写 ()
其余情况必须写
函数体只有一行代码的时候,可以不写 {}
,并且会自动 return,默认返回undefined
//单行语句可以省略return var sum = (num1, num2) => num1 + num2 //返回一个对象或对象的形式,必须在对象的外面加上括号,单个参数可以省略括号 let getTempItem = id => ({ id: id, name: "Temp" });
箭头函数内部没有 this,导致内部的this
就是外层代码块的this
,是固定的
不可以当作构造函数,不可以使用arguments
对象,不可以使用yield
命令,bind
方法无效
下面两个场合不应该使用箭头函数
定义对象的方法,且该方法内部包括this
对象不构成单独的作用域,导致jumps
箭头函数定义时的作用域就是全局作用域
const cat = { lives: 9, jumps: () => { this.lives--; } }
需要动态this
代码运行时,点击按钮会报错,因为button
的监听函数是一个箭头函数,this 此时是全局对象。普通函数,this
就会动态指向被点击的按钮对象
var button = document.getElementById('press'); button.addEventListener('click', () => { this.classList.toggle('on'); });
参数的默认值
ES6 之前,不能直接为函数的参数指定默认值,只能采用变通的方法。参数赋值了,但是布尔值为false
,则赋值失效,改为默认值。为了避免这个问题,通常需要先判断一下参数是否被赋值
function log(x, y) { if (typeof x === 'undefined') { x = 'Hello'; } y = y || 'World'; console.log(x, y); } log('Hello', '') // Hello World
在 ES6 中我们可以直接把默认值写在函数的行参位置,而且无需判断类型
function log(x, y = 'World') { console.log(x, y); }
注意事项
// 默认值采用0 function m1({x = 0, y = 0} = {}) { return [x, y]; } function m1(x = 0, y = 0) { return [x, y]; } // 传值非m2({x: 3, y: 8}),二者都有,则默认值采用undefined function m2({x, y} = { x: 0, y: 0 }) { return [x, y]; }
如果传入undefined,将触发该参数等于默认值,null则没有这个效果
res参数
用于获取函数的多余参数,这样就不需要使用arguments
对象【类数组,还需要转化为数组】
function add(...values) { let sum = 0; for (var val of values) { sum += val } } add(2, 5, 3)
函数参数的尾逗号
ES2017 允许函数的最后一个参数有尾逗号
function fn(param1,param2,) { /* ... */ }
指某个函数的最后一步是调用另一个函数
function f(x){ return g(x) }
以下三种情况,都不属于尾调用
//最后一步为赋值 function f(x){ let y = g(x); return y; } //最后一步为+1 function f(x){ return g(x) + 1; } //最后一步为 return undefined function f(x){ g(x); }
使用场景:递归(复杂度 O(n) ==> 复杂度 O(1) ), Fibonacci 数列,...
递归非常耗费内存,需要同时保存多个调用帧,容易“栈溢出”。但尾递只存在一个调用帧,不会发生“栈溢出”
ES6 的尾调用优化只在严格模式下开启,正常模式是无效的,正常模式采用“循环”替换“递归”
ES2019对函数实例的Function.prototype.toString()
方法做出了修改
toString()
方法返回函数代码本身,以前会省略注释和空格
修改后的toString()
方法,明确要求返回一模一样的原始代码
function /* foo comment */ foo () {} foo.toString() // "function /* foo comment */ foo () {}"
JS的try...catch
结构,要求catch
命令后面必须跟参数,接受try
代码块抛出的错误对象
ES2019允许catch
语句省略参数
ES6 新增的原始数据类型Symbol
,表示独一无二的值。属于 JavaScript 语言的原生数据类型之一
原数据类型:undefined、null、布尔值(Boolean)、字符串(String)、数值(Number)、对象(Object)
使用Symbol作为对象属性名,接受一个字符串作为参数,控制台显示,比较容易区分
能防止某一个键被不小心改写或覆盖
Symbol 值作为对象属性名时,不能用点运算符
对象的Symbol属性遍历时会在最后
let name = Symbol() let age = Symbol('age') let obj ={ [name]:"孙悟空", [age]:18, gender:'男' } // Symbol属性只能通过这种方式访问 obj[age] === 18 obj['gender'] === ’男‘
为各种数据结构,提供一个统一的、简便的访问接口
使得数据结构的成员能够按某种次序排列
【for...of】循环使用Iterator ,对象不能直接使用【for...of】
for in
得到对象的key
,数组和字符串的下标
for of
和forEach
得到值
类似于数组,但成员的值都是唯一的,没有重复的值,是一个对象构造函数
let obj = new Set([1, 2, 3, 2, 3]) //Set(3) {1, 2, 3}
实例的属性和方法
Set.size
:返回Set实例的成员总数
Set.add(value)
:添加某个value
Set.delete(value)
:删除某个value,返回一个布尔值,表示删除是否成功
Set.has(value)
:返回一个布尔值,表示该值是否为Set
的成员
Set.clear()
:清除所有成员,没有返回值
遍历
Set.keys()
:返回键名
Set.values()
:返回键值
Set.entries()
:返回键值对
Set.forEach()
:遍历每个成员
常用去重
[...new Set('ababbc')].join('') // "abc" [...new Set(arr=[2,2,3])] // arr=[2,3]
WeakSet结构 类似于 Set结构,但WeakSet 的成员只能是对象
类似于对象,键值对的集合,”键“的范围不限于字符串,键可以是各种类型的值(包括对象)
let map = new Map([ ["name","孙悟空"] ]) map.set({a:1},"18") map.set(['a'], 555); map.get(['a']) // undefined,两个不同的数组实例,内存地址是不一样的
实例的属性和方法
Map.size
:返回 Map 结构的成员总数
Map.set(key,value)
:添加key对应得value,返回 Map 结构本身
Map.get(key)
:获取key对应的value
Map.delete(key)
:删除某个键(键名+键值)
Map.has(key)
:某个键是否在当前 Map 对象之中
Map.clear()
:清除所有成员,没有返回值
遍历
Map.keys():返回键名
Map.values():返回键值
Map.entries():返回所有成员
Map.forEach():遍历 Map 的所有成员
在对象和对象的属性值之间设置一个代理,获取该对象的值或者设置该对象的值, 以及实例化等多种操作, 都会被拦截住,进行统一处理
用于拦截某个属性的读取操作,可以接受三个参数,依次为目标对象、属性名和 proxy 实例本身【操作行为所针对的对象,经过Proxy化的原对象,可选】
let target = {} let proxy = new Proxy(target,{ get(target, prop){ return target[prop] } })
拦截某个属性的赋值操作,接受四个参数,依次为目标对象、属性名、属性值和 Proxy 实例本身【可选】
let target = {} let proxy = new Proxy(target, { set(target, prop, value){ if(prop === "data"){ box.innerHTML = value } target[prop] = value; } })
拦截HasProperty
操作,即判断对象是否具有某个属性时,这个方法会生效。如:in
运算符
接受两个参数,分别是目标对象、需查询的属性名
目标对象不可扩展,或某个属性不可配置,has
拦截就会报错,has()
拦截对for...in
循环不生效
let target = { _prop: "内部数据" } let proxy = new Proxy(target, { has(target, key) { if (key[0] === '_') { return false; } return key in target; } })
Proxy 代理的情况下,目标对象内部的this
关键字会指向 Proxy 代理。不做任何拦截的情况下,也无法保证与目标对象的行为一致
let target = new Set() const proxy = new Proxy(target, { get(target, key) { const value = target[key] // 遇到 Function 都手动绑定一下 this if (value instanceof Function) { return value.bind(target) //不能是 call、apply } return value } }) proxy.add(1)
apply
拦截函数的调用、call
和apply
操作
construct
拦截new
命令
deleteProperty
拦截delete
操作,如果这个方法抛出错误或者返回false
,当前属性就无法被delete
命令删除
defineProperty
拦截 Object.defineProperty()
操作
getOwnPropertyDescriptor
拦截Object.getOwnPropertyDescriptor()
,返回一个属性描述对象或者undefined
getPrototypeOf
拦截获取对象原型,拦截以下操作:
Object.prototype.__proto__
Object.prototype.isPrototypeOf()
Object.getPrototypeOf()
Reflect.getPrototypeOf()
instanceof
isExtensible
拦截Object.isExtensible()
操作
ownKeys
拦截对象自身属性的读取操作,拦截以下操作:
Object.getOwnPropertyNames()
Object.getOwnPropertySymbols()
Object.keys()
for...in
循环
preventExtensions
拦截Object.preventExtensions()
,该方法必须返回一个布尔值,否则会被自动转为布尔值
setPrototypeOf
拦截Object.setPrototypeOf()
方法
Proxy.revocable
返回一个可取消的 Proxy 实例
将Object
对象的部分属于语言内部的方法,放到Reflect
对象上。现阶段,某些方法同时在Object
和Reflect
对象上部署,未来的新方法将只部署在Reflect
对象上。之后从Reflect
对象上可以拿到语言内部的方法
修改某些Object
方法的返回结果,让其变得更合理
try { Object.defineProperty(target, property, attributes); // success } catch (e) { // failure } if (Reflect.defineProperty(target, property, attributes)) { // success } else { // failure }
让Object
的是命令式操作变成函数行为
'assign' in Object // true Reflect.has(Object, 'assign') // true
Reflect方法与 Proxy 是一一对应的,Proxy
方法拦截target
对象的属性赋值行为。采用Reflect.set
方法将值赋值给对象的属性,确保完成原有的行为,然后再部署额外的功能
var loggedObj = new Proxy(obj, { get(target, name) { console.log('get', target, name); return Reflect.get(target, name); }, deleteProperty(target, name) { console.log('delete' + name); return Reflect.deleteProperty(target, name); }, has(target, name) { console.log('has' + name); return Reflect.has(target, name); } });
静态方法
//同Function.prototype.apply.call(func, thisArg, args),用于绑定this对象后执行给定函数 Reflect.apply(target, thisArg, args) //同new target(...args),提供了一种不使用new,来调用构造函数的方法 Reflect.construct(target, args) //查找并返回target对象的name属性,如果没有该属性,则返回undefined Reflect.get(target, name, receiver) //设置target对象的name属性等于value Reflect.set(target, name, value, receiver) //同于Object.defineProperty【已废除】,用来为对象定义属性 Reflect.defineProperty(target, name, desc) //同delete obj[name],用于删除对象的属性 Reflect.deleteProperty(target, name) //对应name in obj里面的in运算符 Reflect.has(target, name) //返回对象的所有属性,等同Object.getOwnPropertyNames与Object.getOwnPropertySymbols结合 Reflect.ownKeys(target) //对应Object.isExtensible,返回一个布尔值,表示当前对象是否可扩展 Reflect.isExtensible(target) //对应Object.preventExtensions方法,让一个对象变为不可扩展,返回一个布尔值 Reflect.preventExtensions(target) //同Object.getOwnPropertyDescriptor,用于得到指定属性的描述对象 Reflect.getOwnPropertyDescriptor(target, name) //读取对象的__proto__属性,对应Object.getPrototypeOf(obj) Reflect.getPrototypeOf(target) //设置目标对象的原型(prototype),对应Object.setPrototypeOf(obj, newProto),返回一个布尔值 Reflect.setPrototypeOf(target, prototype)
Promise 是异步编程的一种解决方案,比传统的解决方案回调函数, 更合理和更强大。ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象
指定回调函数方式更灵活易懂,解决异步 回调地狱
状态不会再次改变,有三种状态:pending
(进行中)、fulfilled
(已成功)和rejected
(已失败)
Promise
也有一些缺点:
无法取消Promise
,一旦新建它就会立即执行,无法中途取消
不设置回调函数,Promise
内部抛出的错误,不会反应到外部
当处于pending
状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)
如果某些事件不断地反复发生,一般来说,使用 Stream 模式是比部署Promise
更好的选择
当一个回调函数嵌套一个回调函数的时候,会出现一个嵌套结构,多层嵌套导致回调地狱发生
发送多个请求,第二个请求需要第一个请求的结果中的某一个值作为参数,第三个则需要第二个,...,嵌套结构的增多,导致代码横向发展(不健康),不方便阅读和维护
基础用法
new Promise1(function (resolve, reject) { // 函数体,resolve 表示成功的回调,reject 表示失败的回调 }).then(function (res) { // 成功的函数 }).catch(function (err) { // 失败的函数 }) new Promise2(function (resolve, reject) { } Promise2.then(()=>{ }) Promise2.catch(()=> { })
异步加载图片示例
function loadImageAsync(url) { return new Promise(function(resolve, reject) { const image = new Image() image.src = url image.onload = function() { resolve(image) } image.onerror = function() { reject(new Error('图片加载失败:' + url)) } }) }
实现的 Ajax 操作示例
const getJSON = function(url) { return new Promise(function(resolve, reject){ const client = new XMLHttpRequest(); client.open("GET", url); client.onreadystatechange = handler; client.send(); if (this.status === 200) { resolve(this.response) } else { reject(new Error(this.statusText)) } }) } getJSON("/posts.json").then( (res)=> { }, (err)=> { } );
为Promise 实例添加状态改变时的回调函数。then
方法的第一个参数是resolved
状态的回调函数,第二个参数是rejected
状态的回调函数,它们都是可选的
then
方法返回新的Promise
实例,可以采用链式写法,即then
方法后面再调用另一个then
方法
指定发生错误时的回调函数
异步方法返回一个 Promise 对象,如果该对象状态变为resolved
,则会调用then()
方法指定的回调函数;如果异步操作抛出错误,状态就会变为rejected
,就会调用catch()
方法指定的回调函数,处理这个错误。另外,then()
方法指定的回调函数,如果运行中抛出错误,也会被catch()
方法捕获
ES2018 引入,指定不管 Promise 对象最后状态如何,都会执行的操作
接受一个数组【成员是 Promise 实例】作为参数,包装成一个新的 Promise 实例
成员不是 Promise 实例,会将参数转为 Promise 实例。参数可以不是数组,但必须具有 Iterator 接口
const p = Promise.all([p1, p2, p3])
p的 fulfilled
状态:只有数组的全部Promise实例的状态都变成fulfilled
,p
的状态才会变成fulfilled
,此时实例的返回值组成一个数组,传递给p
的回调函数
p的 rejected
状态:只要实例之中有一个被rejected
,p
的状态就变成rejected
,此时第一个被reject
的实例的返回值,会传递给p
的回调函数
同上,将多个 Promise 实例,包装成一个新的 Promise 实例
参数实例第一个改变状态的,p
的状态就跟着改变,接受其返回值传递给p
的回调函数
const p = Promise.race([p1, p2, p3])
ES2020确定一组异步操作是否都结束了(不管成功或失败)
接受一个数组作为参数,数组的成员都是Promise 对象,并返回一个新的 Promise 对象。参数数组的所有 Promise 对象都发生状态变更,它的状态才会变成fulfilled
ES2021引入,接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例返回
只要参数实例有一个变成fulfilled
状态,包装实例就会变成fulfilled
状态;如果所有参数实例都变成rejected
状态,包装实例就会变成rejected
状态
将现有对象转为 Promise 对象
返回一个新的 Promise 实例,该实例的状态为rejected
ES6 提供的一种异步编程解决方案,是一个状态机,封装了多个内部状态,可以改写异步的回调函数
function
关键字与函数名之间有一个星号【*,在哪个位置都可】
调用 Generator 函数后,函数不执行,返回一个指向内部状态的指针对象【遍历器对象,遍历函数内部的每一个状态】
函数体内部使用yield
表达式,暂停执行的标记,定义不同的内部状态【yield:产出】
每次调用next
方法,内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一个yield
表达式或return
语句为止,
let st = function *state(){ yield 1; yield 2; return 3; }() st.next() //{ value: 1, done: false } st.next() //{ value: 2, done: false } st.next() //{ value: 3, done: true } st.next() //{ value: undefined, done: true } // value:状态值,done:遍历是否结束 //yield表达式本身没有返回值,或者说总是返回undefined //next方法可以带一个参数,该参数就会被当作上一个yield表达式的返回值
异步操作同步化
function* main() { var result = yield request("http://some.url"); var resp = JSON.parse(result); console.log(resp.value); } function request(url) { makeAjaxCall(url, function(response){ it.next(response); }); } var it = main(); it.next();
class Person { constructor(name,age){ this.name = name; this.age = age; } say(){ console.log(this.age) } } let obj = new Person("孙悟空",18)
class List{ constructor(ele){ this.element = ele } get html(){ return this.element.innerHTML } set html(arr){ this.element.innerHTML = arr.map(item=>`
通过static修饰的属性和方法
ES2022正式为class
添加了私有属性,方法是在属性名之前使用#
表示,只能在类的内部使用
class Person { #count = 0; static name = "Person这个类" constructor(name,age){ this.name = name; this.age = age; } say(){ console.log( this.age ) } static eat(){ console.log("eat") } } let obj = new Person("孙悟空",18) Person.eat()
ES6 规定,子类必须在constructor()
方法中调用super()
,否则就会报错。这是因为子类自己的this
对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再对其进行加工,添加子类自己的实例属性和方法。如果不调用super()
方法,子类就得不到自己的this
对象
class Person { static name = "Person这个类" constructor(name){ this.name = name; } say(){ console.log(this.name) } } class Student extends Person{ constructor(name, score){ super(name) this.score = score } say(){ super.say() } } let obj = new Student("孙悟空", 200)
JS 现有两种模块:ES6 模块,简称 ESM; CommonJS 模块【Node.js 专用】,简称 CJS。两者不兼容
ES6 模块使用 export
、 import
;CommonJS 模块使用 module.exports
、 require()
写法 export default :
// 快速写法,默认输出,一个模块只能用一次,不能输出变量 // import命令可以为该匿名函数指定任意名字,引入时不需要{} export default A1 import a1 from "./a.js"
写法 export
export {A1, A2} import {A1, A2} from "./a.js" import {A1 as a1, A2 as a2} from "./a.js" import * as A from "./a.js" //通过 A.A1、A.A2 调用 // as 可以重命名 // export对外输出的三种接口:函数(Functions),类(Class),变量(var、let、const 声明的Variables) // import命令具有提升效果,会提升到整个模块的头部,首先执行
混合
export function each() { } export default function() { } //export default默认输出 export { each as forEach } //export 具名输出 import _, { each, forEach } from 'lodash'; //混合引入
整体加载fs
模块,生成一个对象(_fs
),然后再从这个对象上面读取方法
这种加载称为“运行时加载”,因为只有运行时才能得到这个对象,导致完全没办法在编译时做“静态优化”
// CommonJS模块 let { stat, exists } = require('fs'); // 等同于 let _fs = require('fs'); let stat = _fs.stat; let exists = _fs.exists;
ES6 模块不是对象,而是通过export
命令显式指定输出的代码,再通过import
命令输入
从fs
模块加载所需的方法,其他方法不加载。称为“编译时加载”或者静态加载
ES6 可以在编译时就完成模块加载,效率较 CJS 加载高。这也导致了没法引用 ES6 模块本身,因为它不是对象
// ES6模块自动采用严格模式 import { stat, exists, readFile } from 'fs'
模块的继承、跨模块常量、import( )支持动态加载模块...
Math.pow(x,y) //返回 x 的 y 次幂 // Math常用方法 Math.random() //返回 0 ~ 1 之间的随机数 Math.floor(x) //向下舍入 Math.ceil(x) //向行上舍入 Math.round(x) //四舍五入 Math.trunc(x) //去掉小数部分,只保留整数部分
方法 | 描述 |
---|---|
abs(x) | 返回 x 的绝对值 |
acos(x) | 返回 x 的反余弦值 |
asin(x) | 返回 x 的反正弦值 |
atan(x) | 以介于 -PI/2 与 PI/2 弧度之间的数值来返回 x 的反正切值 |
atan2(y,x) | 返回从 x 轴到点 (x,y) 的角度(介于 -PI/2 与 PI/2 弧度之间) |
ceil(x) | 对数进行上舍入 |
cos(x) | 返回数的余弦 |
exp(x) | 返回 Ex 的指数 |
floor(x) | 对 x 进行下舍入 |
log(x) | 返回数的自然对数(底为e) |
max(x,y,z,...,n) | 返回 x,y,z,...,n 中的最高值 |
min(x,y,z,...,n) | 返回 x,y,z,...,n中的最低值 |
pow(x,y) | 返回 x 的 y 次幂 |
random() | 返回 0 ~ 1 之间的随机数 |
round(x) | 四舍五入 |
sin(x) | 返回数的正弦 |
sqrt(x) | 返回数的平方根 |
tan(x) | 返回角的正切 |
tanh(x) | 返回一个数的双曲正切函数值 |
trunc(x) | 将数字的小数部分去掉,只保留整数部分 |
includes:判断字符串是否包含指定的子字符串,返回symbol,区分大小写
indexOf:某个指定的字符串值在字符串中首次出现的位置。没有匹配返回 -1
//str:规定需检索的字符串值,start:开始检索的位置 string.includes(str, start) string.indexOf(str,start)
如果仅仅查找数据是否在数组中,建议使用includes
如果是查找数据的索引位置,建议使用indexOf
[1, 2, NaN].includes(NaN) // true [1, 2, NaN].indexOf(NaN) // -1
ES2017 引入async 函数,使得异步操作变得更加方便, Generator 函数的语法糖。内置执行器完全自动化,更好的语义、更广的适用性
async
函数就是将 Generator 函数的星号(*
)替换成async
,将yield
替换成await
async
表示函数里有异步操作,await
表示需要等待后面的表达式执行完毕,返回结果才会执行下一步
async
函数内部return
语句返回的Promise对象,会成为then
方法回调函数的参数
async function f() { return 'hello world' } f().then(v => console.log(v)) // "hello world"
await
命令后面是一个 Promise 对象,返回该对象的结果。如果不是 Promise 对象,就直接返回对应的值
async
函数内部抛出错误,会导致返回的 Promise 对象变为reject
状态。抛出的错误对象可以通过
async内部的try catch处理
then方法的第二个参数处理
catch
方法回调函数处理【推荐】
//方式一 async function f() { try{ var res = await ajax("http://baidu.com") }catch(err){ console.log("err",err) } } async function f() { throw new Error('出错了'); } //方式二 f().then( v => console.log('resolve', v), e => console.log('reject', e) //reject Error: 出错了 ) //方式三 f().then( v => console.log('resolve', v) ).catch( e => console.log('reject', e) //reject Error: 出错了 )
对象数据
let obj = { name:"孙悟空", age:18 } Object.values(obj) // ['孙悟空', 18] Object.entries(obj) // [['name', '孙悟空'],['age', 18]] Object.getOwnPropertyDescriptors(obj) //{{name: {valeu: '孙悟空', ...}}, {age: {valeu: 18, ...}}}
克隆对象
let obj = { name:"孙悟空", age:100, location:{ provice:"蓬莱", city:"仙岛" }, // 修改属性名称,避免陷入死循环 get nameget(){ return this.name }, set nameset(value){ this.name = value // 首字母大写功能 // this.name = value.substring(0,1).toUpperCase()+value.substring(1) }, //只设置city,防止破坏province,简化路径【obj.location.city】=>【obj.city】 get city(){ return this.location.city }, set city(value){ this.location.city = value }, } //Object.assign(obj2,obj) //无法克隆get、set方法,其他方法复制出错 Object.defineProperties(obj2,Object.getOwnPropertyDescriptors(obj))
padStart、padEnd方法可以使得字符串达到固定长度。有两个参数,字符串目标最小长度和填充内容
let str= "wukong" str.padStart(9,"x") //xxxwukong str.padStart(5,"x") //wukong str.padEnd(9,"x") //wukongxxx str.padEnd(5,"x") //wukong
新加一行时不必给上一行再补充一个逗号,版本控制工具【Git】的修改记录也更加整洁
let obj1 = { name:"孙悟空", age:18, location:"蓬莱" } let obj2 = { gender:"男" } let {name,...other} = obj1 // name: "孙悟空", other: { age:18, location:"蓬莱"} {...obj1,...obj2} // { name:"孙悟空", age:18, location:"蓬莱", gender:"男"}
JS正则表达式可以返回一个匹配的对象,一个包含匹配字符串的类数组,比如: 以 YYYY-MM-DD的格式解析日期,
这样的代码可读性很差,并且在改变正则表达式的结构的时候很有可能就会改变匹配对象的索引
ES9允许使用命名捕获 【?
let str = "今天是2023-06-19" let reg1 = /([0-9]{4})-([0-9]{2})-([0-9]{2})/g let reg2 = /(?[0-9]{4})-(? [0-9]{2})-(? [0-9]{2})/g let res1 = reg1.exec(str) // 简述:['2023-06-19', '2023', '06', '19', groups: undefined] let res2 = reg2.exec(str) // 在此基础修改【groups】:[groups: {year: '2023', month: '06', day: '19'}]
Promise回调无论是成功还是失败,最后都运行finally方法。用于隐藏对话框,关闭数据连接等
function ajax(){ return new Promise((resolve, reject)=> { reject('') }) } ajax().then(res=>{ }).catch(err=>{ }).finally(()=>{ })
function* fn() { yield 1 yield 2 } const asyncI = fn() asyncI.next() // {value: 1, done: false} asyncI.next() // {value: 2, done: false} asyncI.next() // {value: undefined, done: true}
function* fn() { yield new Promise(resolve=>resolve("1")) yield new Promise(resolve=>resolve("2")) } const asyncI = fn(); asyncI.next().value.then(res=>{console.log(res)}) // 1 asyncI.next().value.then(res=>{console.log(res)}) // 2
value
属性的返回值是一个 Promise 对象,用来放置异步操作。此写法,不太符合直觉,语义也比较绕
Generator 函数返回一个同步遍历器,异步 Generator 函数的作用,是返回一个异步遍历器对象。在语法上,异步 Generator 函数就是async函数与 Generator 函数的结合
async function* fn() { yield new Promise(resolve=>resolve("1111")) yield new Promise(resolve=>resolve("2222")) } const asyncI = fn(); asyncI.next().then(res=>{ console.log(res) // {value: '1111', done: false} return asyncI.next() }).then(res=>{ console.log(res) // {value: '2222', done: false} return asyncI.next() }).then(res=>{ console.log(res) // {value: undefined, done: true} })
for...of
循环用于遍历同步的 Iterator 接口,for await...of`循环,用于遍历异步的 Iterator 接口
async function test() { for await (let i of asyncI) { console.log(i) } } test()
function timer(t) { return new Promise(resolve => { setTimeout(() => { resolve(t) }, t) }) } async function* fn() { yield timer(1000)//任务1 yield timer(2000)//任务2 yield timer(3000)//任务3 } // 使用 for await ...of async function fn1() { for await(const val of fn()) { console.log("start",Date.now()) console.log(val); console.log("end",Date.now()) } } fn1();
// 传统写法 function main(inputFilePath) { const readStream = fs.createReadStream( inputFilePath, { encoding: 'utf8', highWaterMark: 1024 } ); readStream.on('data', (chunk) => { console.log('>>> '+chunk); }); readStream.on('end', () => { console.log('### DONE ###'); }); } // 异步遍历器写法 async function main(inputFilePath) { const readStream = fs.createReadStream( inputFilePath, { encoding: 'utf8', highWaterMark: 1024 } ); for await (const chunk of readStream) { console.log('>>> '+chunk); } console.log('### DONE ###'); }
五. ES10新特性
将键值对列表转换为对象,如map对象,【forEach】也可实现
let arr = [["name", "kerwin"], ["age", 100]]; Object.fromEntries(arr) // {name: 'kerwin', age: 100} // map对象转对象 let m = new Map() m.set("name","孙悟空") Object.fromEntries(m) // {name: '孙悟空'} // url参数转对象 let str ="name='孙悟空'&age=18" let searchParams = new URLSearchParams(str) Object.fromEntries(searchParams) // {name: '孙悟空', age: '18'}
trimStart 和 trimEnd 方法在实现与 trimLeft 和 trimRight 相同,去除字符串的前边或后边的空格
let str = " 孙悟空 " srt.trim // "孙悟空" str.trimStart(str) // "孙悟空 " str.trimEnd(str) // " 孙悟空" str.trimLeft(str) // "孙悟空 " str.trimRight(str) // " 孙悟空"
为Symbol对象添加了只读属性 description ,该对象返回包含Symbol描述的字符串
let s = Symbol("孙悟空") s.description //孙悟空
ES10之前catch的参数是必须的,ES10之后,如无需知晓错误参数,统一操作,即可忽略
function test() { try { } catch(e) { console.log(e) } }
之前的 promise 的处理只要其中有一个失败,就要走 catch ,而拿不到其他成功的回调
Promise.allSettled 返回一个数组,成员是所有需要处理的 promise 的回调的结果,无论成功或失败
const promises = [ ajax('/200接口'), ajax('/800接口') ]; // 如多个请求是同一资源,但服务器存在替换,此时只需要拿到任意一个即可 Promise.allSettled(promises).then(results=>{ // 过滤出成功的请求 results.filter(item =>item.status === 'fulfilled'); // 过滤出失败的请求 results.filter(item=> item.status === 'rejected'); })
标准用法的 import 导入的模块是静态的,会使所有被导入的模块,在加载时就被编译,降低首页加载速度
希望根据条件导入模块或者按需导入模块,可以使用动态导入代替静态导入,如点击通过点击事件
async function (){ if(xxx){ let res1 = await import("./1.js") }else{ let res2 = await import("./2.js") } }
返回一个对象,有一个 url 属性,值为当前模块的url路径,只能在模块内部使用
使用场景:如果需要A模块,但又不能修改A模块,可以采用此方式【类似于继承】,再写入该模块的方法
export * as A from './a.js' test(){ }
返回一个包含所有匹配正则表达式的结果的迭代器。可以使用 for...of 遍历,或者使用展开运算符【...】或者 Array.from 转换为数组
业务场景:拿到某标签的值
let str = `
JS 能够准确表示的整数范围在-253到253之间(不含两个端点),超过这个范围,无法精确表示这个值,这使得 JS 不适合进行科学和金融方面的精确计算
2**53 //9007199254740992 9007199254740992 //9007199254740992 9007199254740993 //9007199254740992 Math.pow(2,53) === Math.pow(2,53)+1
BigInt 类型的数据必须添加后缀n
,BigInt 类型只能和 BigInt 类型运算,不能和 int
类型计算
let b = BigInt(3) // 3n 1n + 2n // 3n
使用场景:后端传递的id的值大于2^53,前端接收的处理时,会将值处理错
让后端传递id时,将id的类型从 int 改为 string
使用【json-bigint】库,自行安装
import JSONBigInt from 'json-bigint' // id从 int型 转 string型 let JSONBigIntStr = JSONBigInt({ storeAsString: true }) JSONBigIntStr.parse(id) // id从 int型 转 bigint型 let JSONBigIntNative = JSONBigInt({ useNativeBigInt: true }) JSONBigIntNative.parse(id)
globalThis 提供了一个标准的方式来获取不同环境下【node、浏览器、app等】的全局 this 对象
省略对环境的判断的兼容处理,通常用于跨平台库的编写
// 以前 var getGlobal = function () { if (typeof self !== 'undefined') return self if (typeof window !== 'undefined') return window if (typeof global !== 'undefined') return global throw new Error('unable to locate global object') }; let globals = getGlobal() if (globals.document) { console.log("进行dom操作相关") } else { console.log("不能进行dom操作") } // 现在 if (globalThis.document) { console.log("进行dom操作相关") } else { console.log("不能进行dom操作") }
【??】逻辑运算符,当左侧操作数为 null 或 undefined 时,返回右侧的操作数,否则返回左侧的操作
let obj = null ?? '孙悟空' // 孙悟空
【??】和 【||】 的区别
【''】和 【0】,【??】的左侧 为 【''】或者【0】时,依然会返回左侧的值
【||】 会对左侧的数据进行boolean类型转换,所以【''】和 【0】会被转换成false,返回右侧的值
【?.】可选链前面的值如果是null或undefined,则不再执行后面的,之前返回可选链前面的值
【undefined.undefined】会报错,为避免错误的发生,不能直接取值,
let obj = { name:"孙悟空", // location:{ // city:"蓬莱" // } } // 【&&】如果为真,去下一个值,直到最后 obj && obj.location && obj.location.city // 【?.】可选链操作符 obj?.location?.city obj?.location?.city || '蓬莱'
逻辑赋值操作符 ??=、&&=、 ||=
let a = true let b = false a = a && b //false a &&= b //false a = a || b //true a ||= b //true a = b ?? "true" //false a ??= "true" //false
方便程序员查看代码,分隔符可以分割十进制、二进制、十六进制的数据,不能在头和尾出现,不能连续出现
const number = 1_000_000_000_000 const hex = 0xA1_B2_C3
所有匹配都会被替代项替换
模式可以是字符串或正则表达式,而替换项可以是字符串或针对每次匹配执行的函数。并返回一个全新的字符串
let str ="I wish to wish the wish you wish to wish" // 方式一 replace + 正则 let newStr = str.(/wish/g, "wish") // 方式二 replaceAll let newStr = str.replaceAll("wish", "wish")
Promise.all // 当所有成功才执行 Promise.race // 取最快成功的那一个 Promise.allSettlned // 不管成功失败都取,自行过滤
只要参数实例有一个变成fulfilled
状态,包装实例就会变成fulfilled
状态
如果所有参数实例都变成rejected
状态,包装实例就会变成rejected
状态
业务场景:多个账号【qq、微信、手机、邮箱】登录同一用户,但凡账号符合其中一个,登录成功
let ajax1 = function(){ return new Promise((resolve, reject) =>{ resolve() }) } ... Promise.any([ajax1(), ajax2(), ajax3()]).then(res =>{ ... }).catch(err={ ... })
ES6:Set、Map、WeakSet、WeakMap
垃圾回收机制常用算法:引用计数法【引用为0时,回收】、标记清除法
WeakSet使用传入值必须为 引用类型,引用计数不增加,当应用类型设置为空时,size和for方法无法使用
使用场景:如数组中存放Dom节点,当节点变量被删除时【设置为null】,数组中依然存在引用,造成内存泄漏,使用WeakSet可以有效避免内存泄漏的发生
注意:添加值时是通过变量的形式,删除的也是变量,则无法回收内存,需要添加节点本身或变量所引用值得本身,不能通过变量传入,删除变量的方式,导致代码量大。【WeakSet为强引用方式】
在一般情况下,对象的引用是强引用的,这意味着只要持有对象的引用,它就不会被垃圾回收。只有当该对象没有任何的强引用时,垃圾回收才会销毁该对象并且回收该对象所占的内存空间
WeakRef
允许保留对另一个对象的弱引用,而不会阻止被弱引用对象被垃圾回收
清理器注册表功能,用来指定目标对象被垃圾回收机制清除以后,所要执行的回调函数
let obj = {} const registry = new FinalizationRegistry(data => { console.log("被销毁了", data) }); registry.register( obj , data); //参数1:观察的对象;参数2:想要传递的数据
新增【#】私有属性,之前程序员之间的君子协定【_】下划线,但还是能实例修改;新增静态属性【static】
class Cache{ // 私有属性 #obj = {}; get(key){ return this.#obj[key] }; #myFun(){ } // 静态属性私有化 配合 静态方法 static #num = 0; static getCount(){ return this.#count } }
新增静态代码块,允许在类中通过static
关键字定义一系列静态代码块,代码块会在类被创造的时候执行一次
代码块配合静态成员变量按照顺序在类初始化的时候执行一次。可以使用super
关键字来访问父类的属性
class myClass{ static obj = new Map() static { this.obj.set("name","孙悟空") this.obj.set("age",18) } }
通过 in来判断某个对象是否拥有某个私有属性
class Cache { #obj = {} hasObj(){ return #obj in this } }
顶层await
只能用在 ES6 模块,不能用在 CommonJS 模块,不能用在普通
使用场景:顶层await + 模块化 + 动态导入
// 模块B ./moduleB.js function ajax(){ return new Promise((resolve)=>{ setTimeout(()=>{ resolve("data") },2000) } }) let data = await ajax() export·default { name : "moduleB", data } // 模块A 动态导入 模块B ./index.js let moduleB = await import("./moduleB.js")
可以替代通过【leng】取倒数元素,代码简洁
let arr = [1, 2, 3, 4] arr[1] === arr.at(1) arr[arr.length-1] === arr.at(-1) arr[arr.length-2] === arr.at(-2)
新增【d】正则的结束索。原先只有开始索引,匹配对象在原对象的起始位置
let str = "今天是2023-06-20 let reg = /(?[0-9]{4})-(? [0-9]{2})-(? [0-9]{2})/d reg.exec(str) // 无结束索引打印 [..., index: 3 ] // 结束索引打印,indices存放结束索引位置和其他信息 [..., index: 3, indices: Array]
find:第一个符合条件的元素
findIndex:第一个符合条件的元素的下标
findLast:最后一个符合条件的元素
findLastIndex:最后一个符合条件的元素的下标
let arr = [1,2,3,4,5] arr.find( val=> { return val % 2 === 0 }) // 2 arr.findIndex( val=> { return val % 2 === 0 }) // 1 arr.findLast( val=> { return val % 2 === 0 }) // 4 arr.findLastIndex( val=> { return val % 2 === 0 }) // 3
Error对象新增 cause
属性来指明错误出现的原因。手动为错误添加更多的信息,帮助开发者更好地定位错误
function getData(){ try{ } catch(e){ throw new Error('New error ',{cause:"错误信息..."}) } } try{ getData() }catch(e){ console.log(e.cause) }
in
语句用来判断一个属性(name)是否属于一个对象(obj),返回Symbol
Map 转为数组
[...myMap]
数组 转为 Map
new Map([ arr ])
Map 转为对象
Map 的键都是字符串,无损地转为对象。非字符串的键名,键名会被转成字符串,再作为对象的键名
let obj = {} for (let [k,v] of Map) { obj[k] = v }
对象转为 Map
let map = new Map(Object.entries(obj))
Map 转为 JSON
Map 的键名都是字符串,可以选择转为对象 JSON
JSON.stringify(strMapToObj(Map))
Map 的键名有非字符串,可以选择转为数组 JSON
JSON.stringify([...map])
JSON 转为 Map
所有键名都是字符串
objToStrMap(JSON.parse(jsonStr))
JSON 是一个数组,且每个数组成员本身,又是一个有两个成员的数组
new Map(JSON.parse(jsonStr))
严格模式主要有以下限制:
变量必须声明后再使用
函数的参数不能有同名属性,否则报错
不能使用with
语句
不能对只读属性赋值,否则报错
不能使用前缀 0 表示八进制数,否则报错
不能删除不可删除的属性,否则报错
不能删除变量delete prop
,会报错,只能删除属性delete global[prop]
eval
不会在它的外层作用域引入变量
eval
和arguments
不能被重新赋值
arguments
不会自动反映函数参数的变化
不能使用arguments.callee
不能使用arguments.caller
禁止this
指向全局对象
不能使用fn.caller
和fn.arguments
获取函数调用的堆栈
增加了保留字(比如protected
、static
和interface
)
...
参考资料地址:
千锋教育 kerwin ES6-ES13:千锋教育最新版Web前端ES6-ES13教程,JavaScript高级进阶视频教程_哔哩哔哩_bilibili
ECMAScript 6 入门 阮一峰:https://es6.ruanyifeng.com