ES6 ES6是 ECMAScript 2015 的简称,是 JavaScript 语言的一个重要版本,引入了许多新的语法和功能,使得 JavaScript 更加强大和灵活
let 和 const let
1 2 let star = '罗志祥' ;let star = '小猪' ;
1 2 3 4 { let girl = '周扬青' ; } console .log (girl);
不仅仅针对花括号,例如 if()
里面
1 2 console .log (song); let song = '恋爱达人' ;
1 2 3 4 let school = 'abc' ;function fn ( ) { console .log (school); }
const
一定要赋初始值
一般常量使用大写(潜规则)
也具有块级作用域
1 2 3 4 { const pyaler = 'uzi' ; } console .log (player);
1 2 const team = ['uzi' , 'MXLG' , 'Ming' , 'Letme' ];team.push ('Meiko' );
解构赋值 ES6 允许按照一定模式从数组和对象中提取值,对变量进行赋值,这被称为解构赋值
1 2 3 const F4 = ['小沈阳' , '刘能' , '赵四' , '宋小宝' ];let [xiao, liu, zhao, song] = F4 ; console .log (xiao, liu, zhao, song);
1 2 3 4 5 6 7 8 9 10 const zhao = { name : '赵本山' , age : '不详' , xiaopin : function ( ){ console .log ("我可以演小品AAA" ); } } let {name, age, xiaopin} = zhao;console .log (name, age);console .log (xiaopin);
模板字符串 模板字符串是一种使用反引号 `` 包裹字符串,并且支持插入变量或表达式的新语法。它可以避免使用 + 号连接字符串和变量,并且支持多行字符串和标签模板等特性
1 2 3 4 5 6 7 8 9 10 11 12 let name = "Bob" ;let age = 19 ;let message = `Hello ${name} , you are ${age} years old.` ; console .log (message);let poem =`Do not go gentle into that good night, Old age should burn and rave at close of day; Rage, rage against the dying of the light.` ; console .log (poem);
对象的简化写法 ES6 允许在大括号里面,直接写入变量和函数,作为对象的属性和方法,这样的书写更加简洁
1 2 3 4 5 6 7 8 9 10 11 12 let name = 'aaa' ;let change = function ( ) { console .log ('aaa' ); } const school = { name, change, improve ( ) { consolg.log ('bbb' ); } }
箭头函数 ES6 允许使用箭头(=>)定义函数
this 是静态的,this 始终指向函数声明时所在作用域下的 this 的值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 function A ( ){ console .log (this .name ) } let B = ( ) => { console .log (this .name ); } window .name = '尚硅谷' ;const school = { name : 'ATGUIGU' } A () B () A.call (school); B.cal (school);
1 2 3 4 5 6 let A (name,age) => { this .name =name; this .age =age; } let me = new A ('xiao' , 123 );console .me
1 2 3 4 let fn = ( ) => { console .log (arguments ); } fn (1 , 2 , 3 )
1 2 3 let add = n => { return n + 1 ; }
省略花括号,当代码体只有一条语句的时候,此时 return 也必须省略
默认参数 ES6 允许给函数参数赋值初始值
1 2 3 4 5 function add (a, b, c = 12 ){ return a+b+c; } let result = add (1 , 2 );console .log (result)
1 2 3 4 5 6 7 8 function A ({host = '127.0.0.1' , username, password, port} ){ console .log (host + username + password + port) } A ({ username : 'ran' , password : '123456' , port : 3306 })
rest 参数 ES6 引入 rest 参数,用于获取函数的实参,用来代替 arguments
1 2 3 4 function date (...args ){ console .log (args); } date ('aaa' , 'bbb' , 'ccc' );
扩展运算符 扩展运算符是能将数组转换为逗号分隔的参数序列
1 2 3 4 5 const tfboys=['AA' , 'BB' , 'CC' ];function chunwan ( ){ console .log (arguments ); } chunwan (...tfboys);
1 2 3 4 const A = ['aa' , 'bb' ];const B = ['cc' , 'dd' ];const C = [...A, ...B];console .log (C)
1 2 3 const A = ['a' , 'b' , 'c' ];const B = [...A];console .log (B)
1 2 3 const A = documents.querySelectorAll ('div' );const B = [...A];console .log (B)
Symbol ES6 引入了一种新的原始数据类型 Symbol,表示独一无二的值 。它是 JavaScript 语言的第七种数据类型 ,是一种类似于字符串的数据类型。Symbol特点:
Symbol 的值是唯一的 ,用来解决命名冲突的问题
Symbol 值不能与其他数据进行运算
Symbol 定义的对象属性不能使用 for ... in
循环遍历,但是可以使用 Reflect.ownKeys
来获取对象的所有键名
1 2 3 4 5 6 7 let s = Symbol ('aa' );let s2= Symbol ('aa' );console .log (s === s2) let s3 = Symbol .for ('bb' );let s4 = Symbol .for ('bb' );comsole.log (s3 === s4)
注意,Symbol()
函数前不能使用 new 命令,否则会报错。这是因为生成的 Symbol 是一个原始类型的值,不是对象,所以不能使用 new 命令来调用。另外,由于 Symbol 值不是对象,所以也不能添加属性。基本上,它是一种类似于字符串的数据类型
Symbol.prototype.description 前面说过,Symbol()
函数创建 Symbol 值时,可以用参数添加一个描述
1 const sym = Symbol ('foo' );
上面代码中,sym 这个值的描述就是字符串 foo。但是,读取这个描述需要将 Symbol 显式转为字符串,即下面的写法:
1 2 3 4 const sym = Symbol ('foo' );String (sym) sym.toString ()
上面的用法不是很方便。ES2019 提供了一个 Symbol 值的实例属性 description,直接返回 Symbol 值的描述
1 2 3 const sym = Symbol ('foo' );sym.description
作为属性名的 Symbol 由于每一个 Symbol 值都是不相等的,这意味着只要 Symbol 值作为标识符,用于对象的属性名,就能保证不会出现同名的属性。这对于一个对象由多个模块构成的情况非常有用,能防止某一个键被不小心改写或覆盖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 let mySymbol = Symbol ();let a = {};a[mySymbol] = 'Hello!' ; let a = { [mySymbol]: 'Hello!' }; let a = {};Object .defineProperty (a, mySymbol, { value : 'Hello!' });a[mySymbol]
上面代码通过方括号结构和 Object.defineProperty()
方法,将对象的属性名指定为一个 Symbol 值。注意,Symbol 值作为对象属性名时 ,不能用点运算符
1 2 3 4 5 6 const mySymbol = Symbol ();const a = {};a.mySymbol = 'Hello!' ; a[mySymbol] a['mySymbol' ]
上面代码中,因为点运算符后面总是字符串,所以不会读取 mySymbol 作为标识名所指代的那个值,导致 a 的属性名实际上是一个字符串,而不是一个 Symbol 值。同理,在对象的内部,使用 Symbol 值定义属性时,Symbol 值必须放在方括号之中
1 2 3 4 5 6 7 let s = Symbol ();let obj = { [s]: function (arg ) { ... } }; obj[s](123 );
上面代码中,如果 s 不放在方括号中,该属性的键名就是字符串 s ,而不是 s 所代表的那个 Symbol 值 。Symbol 类型还可以用于定义一组常量,保证这组常量的值都是不相等的:
1 2 3 4 5 6 7 8 9 const log = {};log.levels = { DEBUG : Symbol ('debug' ), INFO : Symbol ('info' ), WARN : Symbol ('warn' ) }; console .log (log.levels .DEBUG , 'debug message' );console .log (log.levels .INFO , 'info message' );
还有一点需要注意,Symbol 值作为属性名时,该属性还是公开属性 ,不是私有属性
消除魔术字符串 魔术字符串指的是,在代码之中多次出现、与代码形成强耦合的某一个具体的字符串或者数值。风格良好的代码,应该尽量消除魔术字符串,改由含义清晰的变量代替
1 2 3 const shapeType = { triangle : Symbol () };
上面代码中,除了将 shapeType.triangle
的值设为一个 Symbol,其他地方都不用修改
属性名的遍历 Symbol 值作为属性名,遍历对象的时候,该属性不会出现在 for ... in
、for ... of
循环中,也不会被 Object.keys()
、Object.getOwnPropertyNames()
、JSON.stringify()
返回
但是,它也不是私有属性,有一个 Object.getOwnPropertySymbols()
方法,可以获取指定对象的所有 Symbol 属性名。该方法返回一个数组,成员是当前对象的所有用作属性名的 Symbol 值
1 2 3 4 5 6 7 8 9 10 11 const obj = {};const foo = Symbol ('foo' );obj[foo] = 'bar' ; for (let i in obj) { console .log (i); } Object .getOwnPropertyNames (obj) Object .getOwnPropertySymbols (obj)
上面代码中,使用 for ... in
循环和 Object.getOwnPropertyNames()
方法都得不到 Symbol 键名,需要使用 Object.getOwnPropertySymbols()
方法。另一个新的 API,Reflect.ownKeys()
方法可以返回所有类型的键名,包括常规键名和 Symbol 键名
1 2 3 4 5 6 7 let obj = { [Symbol ('my_key' )]: 1 , enum : 2 , nonEnum : 3 }; Reflect .ownKeys (obj)
由于以 Symbol 值作为键名,不会被常规方法遍历得到。我们可以利用这个特性,为对象定义一些非私有的、但又希望只用于内部的方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 let size = Symbol ('size' );class Collection { constructor ( ) { this [size] = 0 ; } add (item ) { this [this [size]] = item; this [size]++; } static sizeOf (instance ) { return instance[size]; } } let x = new Collection ();Collection .sizeOf (x) x.add ('foo' ); Collection .sizeOf (x) Object .keys (x) Object .getOwnPropertyNames (x) Object .getOwnPropertySymbols (x)
上面代码中,对象 x 的 size 属性是一个 Symbol 值,所以 Object.keys(x)
、Object.getOwnPropertyNames(x)
都无法获取它。这就造成了一种非私有的内部方法的效果
Symbol.for() 有时,我们希望重新使用同一个 Symbol 值,Symbol.for()
方法可以做到这一点。它接受一个字符串作为参数,然后搜索有没有以该参数作为名称的 Symbol 值。如果有,就返回这个 Symbol 值,否则就新建一个以该字符串为名称的 Symbol 值,并将其注册到全局
1 2 3 4 let s1 = Symbol .for ('foo' );let s2 = Symbol .for ('foo' );s1 === s2
上面代码中,s1 和 s2 都是 Symbol 值,但是它们都是由同样参数的 Symbol.for
方法生成的,所以实际上是同一个值
Symbol.for()
与 Symbol()
这两种写法,都会生成新的 Symbol。它们的区别是,前者会被登记在全局环境中供搜索,后者不会。Symbol.for()
不会每次调用就返回一个新的 Symbol 类型的值,而是会先检查给定的 key 是否已经存在,如果不存在才会新建一个值。比如,如果你调用 Symbol.for("cat")
30 次,每次都会返回同一个 Symbol 值,但是调用 Symbol("cat")
30 次,会返回 30 个不同的 Symbol 值
1 2 Symbol ("bar" ) === Symbol ("bar" ) Symbol .for ("bar" ) === Symbol .for ("bar" )
Symbol.keyFor() Symbol.keyFor()
方法返回一个已登记的 Symbol 类型值的 key
1 2 3 4 5 let s1 = Symbol .for ("foo" );Symbol .keyFor (s1) let s2 = Symbol ("foo" );Symbol .keyFor (s2)
上面代码中,变量 s2 属于未登记的 Symbol 值,所以返回 undefined 。注意,Symbol.for()
为 Symbol 值登记的名字,是全局环境的,不管有没有在全局环境运行
内置的 Symbol 值 除了定义自己使用的 Symbol 值以外,ES6 还提供了 11 个内置的 Symbol 值,指向语言内部使用的方法
对象的 Symbol.hasInstance
属性,指向一个内部方法。当其他对象使用 instanceof 运算符,判断是否为该对象的实例时,会调用这个方法。比如,foo instanceof Foo
在语言内部,实际调用的是 Foo[Symbol.hasInstance](foo)
1 2 3 4 5 6 7 class MyClass { [Symbol .hasInstance ](foo) { return foo instanceof Array ; } } [1 , 2 , 3 ] instanceof new MyClass ()
Symbol.isConcatSpreadable
对象的 Symbol.isConcatSpreadable
属性等于一个布尔值,表示该对象用于 Array.prototype.concat()
时,是否可以展开
1 2 3 4 5 6 7 let arr1 = ['c' , 'd' ];['a' , 'b' ].concat (arr1, 'e' ) arr1[Symbol .isConcatSpreadable ] let arr2 = ['c' , 'd' ];arr2[Symbol .isConcatSpreadable ] = false ; ['a' , 'b' ].concat (arr2, 'e' )
对象的 Symbol.species
属性,指向一个构造函数。创建衍生对象时,会使用该属性
1 2 3 4 5 6 7 8 9 class MyArray extends Array {} const a = new MyArray (1 , 2 , 3 );const b = a.map (x => x);const c = a.filter (x => x > 1 );b instanceof MyArray c instanceof MyArray
上面代码中,子类 MyArray 继承了父类 Array,a 是 MyArray 的实例,b 和 c 是 a 的衍生对象。你可能会认为,b 和 c 都是调用数组方法生成的,所以应该是数组(Array 的实例),但实际上它们也是 MyArray 的实例。Symbol.species
属性就是为了解决这个问题而提供的。现在,我们可以为 MyArray 设置 Symbol.species
属性
1 2 3 class MyArray extends Array { static get [Symbol .species ]() { return Array ; } }
上面代码中,a.map(x => x)
生成的衍生对象,就不是 MyArray 的实例,而直接就是 Array 的实例
总之,Symbol.species
的作用在于,实例对象在运行过程中,需要再次调用自身的构造函数时,会调用该属性指定的构造函数。它主要的用途是,有些类库是在基类的基础上修改的,那么子类使用继承的方法时,作者可能希望返回基类的实例,而不是子类的实例
对象的 Symbol.match
属性,指向一个函数。当执行 str.match(myObject)
时,如果该属性存在,会调用它,返回该方法的返回值
1 2 3 4 5 6 7 8 9 10 11 String .prototype .match (regexp)regexp[Symbol .match ](this ) class MyMatcher { [Symbol .match ](string) { return 'hello world' .indexOf (string); } } 'e' .match (new MyMatcher ())
对象的 Symbol.replace
属性,指向一个方法,当该对象被 String.prototype.replace
方法调用时,会返回该方法的返回值
1 2 3 4 const x = {};x[Symbol .replace ] = (...s ) => console .log (s); 'Hello' .replace (x, 'World' )
Symbol.replace
方法会收到两个参数,第一个参数是 replace 方法正在作用的对象,上面例子是 Hello,第二个参数是替换后的值,上面例子是 World
对象的 Symbol.search
属性,指向一个方法,当该对象被 String.prototype.search
方法调用时,会返回该方法的返回值
1 2 3 4 5 6 7 8 9 10 11 12 13 String .prototype .search (regexp)regexp[Symbol .search ](this ) class MySearch { constructor (value ) { this .value = value; } [Symbol .search ](string) { return string.indexOf (this .value ); } } 'foobar' .search (new MySearch ('foo' ))
对象的 Symbol.split
属性,指向一个方法,当该对象被 String.prototype.split
方法调用时,会返回该方法的返回值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class MySplitter { constructor (value ) { this .value = value; } [Symbol .split ](string) { let index = string.indexOf (this .value ); if (index === -1 ) { return string; } return [ string.substr (0 , index), string.substr (index + this .value .length ) ]; } } 'foobar' .split (new MySplitter ('foo' )) 'foobar' .split (new MySplitter ('bar' )) 'foobar' .split (new MySplitter ('baz' ))
上面方法使用 Symbol.split
方法,重新定义了字符串对象的 split 方法的行为
对象的 Symbol.iterator
属性,指向该对象的默认遍历器方法
1 2 3 4 5 6 7 8 const myIterable = {};myIterable[Symbol .iterator ] = function * () { yield 1 ; yield 2 ; yield 3 ; }; [...myIterable]
对象的 Symbol.toPrimitive 属性,指向一个方法。该对象被转为原始类型的值时,会调用这个方法,返回该对象对应的原始类型值。Symbol.toPrimitive
被调用时,会接受一个字符串参数,表示当前运算的模式,一共有三种模式:
Number:该场合需要转成数值
String:该场合需要转成字符串
Default:该场合可以转成数值,也可以转成字符串
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 let obj = { [Symbol .toPrimitive ](hint) { switch (hint) { case 'number' : return 123 ; case 'string' : return 'str' ; case 'default' : return 'default' ; default : throw new Error (); } } }; 2 * obj 3 + obj obj == 'default' String (obj)
对象的 Symbol.toStringTag
属性,用来设定一个字符串(设为其他类型的值无效,但不报错)。在目标对象上面调用 Object.prototype.toString()
方法时,如果 Symbol.toStringTag
属性存在,该属性设定的字符串会出现在 toString()
方法返回的字符串之中,表示对象的类型。也就是说,这个属性可以用来定制 [object Object]
或 [object Array]
中 object 后面的那个大写字符串
1 2 3 4 5 6 7 8 9 10 11 12 ({[Symbol .toStringTag ]: 'Foo' }.toString ()) class Collection { get [Symbol .toStringTag ]() { return 'xxx' ; } } let x = new Collection ();Object .prototype .toString .call (x)
对象的 Symbol.unscopables
属性,指向一个对象。该对象指定了使用 with 关键字时,哪些属性会被 with 环境排除
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 class MyClass { foo ( ) { return 1 ; } } var foo = function ( ) { return 2 ; };with (MyClass .prototype ) { foo (); } class MyClass { foo ( ) { return 1 ; } get [Symbol .unscopables ]() { return { foo : true }; } } var foo = function ( ) { return 2 ; };with (MyClass .prototype ) { foo (); }
上面代码通过指定 Symbol.unscopables
属性,使得 with 语法块不会在当前作用域寻找 foo 属性,即 foo 将指向外层作用域的变量
迭代器 迭代器是一种遵循迭代协议的对象,可以按照一定的顺序访问一个集合中的元素。迭代器有一个 next 方法,每次调用返回一个包含 value 和 done 属性的对象,value 表示当前元素的值,done 表示是否还有更多元素。ES6 提供了一种新的语法 for ... of
循环,可以方便地遍历迭代器
1 2 3 4 5 6 7 const xiyou=['AA' , 'BB' , 'CC' , 'DD' ];for (let v of xiyou){ console .log (v) } let iterator = xiyou[Symbol .iterator ]();console .log (iterator.next ()); console .log (iterator.next ());
生成器 生成器是一种特殊的函数,可以返回一个迭代器对象,并且可以在函数体内使用 yield 关键字暂停和恢复执行。生成器使用 function*
关键字声明,并且可以接收参数
1 2 3 4 5 6 7 8 9 function * gen (){ yield '耳朵' ; yield '尾巴' ; yield '真奇怪' ; } let iterator = gen ();console .log (iteretor.next ()); console .log (iteretor.next ()); console .log (iteretor.next ());
用生成器函数的方式解决 回调地狱
问题:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 function one ( ){ setTimeout (() => { console .log ('111' ) iterator.next () }, 1000 ) } function two ( ){ setTimeout (() => { console .log ('222' ) iterator.next (); }, 2000 ) } function three ( ){ setTimeout (() => { console .log ('333' ) iterator.next (); }, 3000 ) } function * gen ( ){ yield one (); yield two (); yield three (); } let iterator = gen ();iterator.next ();
Promise Promise 是一种用于处理异步操作的对象,它表示一个未来可能完成或失败的事件。Promise 有三种状态:pending(等待)、fulfilled(成功)、rejected(失败)。Promise 可以通过 then 方法添加成功或失败时执行的回调函数,也可以通过 catch 方法添加失败时执行的回调函数。Promise 还可以通过 all 和 race 方法组合多个 Promise 对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 const p =new Promise ((resolve, reject ) => { setTimeout (()=> { resolve ('用户数据' ); }) }); let result = p.then (value => { console .log (value) throw 123 },reason => { console .log (reason) }) console .log (result);
Promise.prototype.then() Promise 实例具有 then 方法,也就是说,then 方法是定义在原型对象 Promise.prototype
上的。它的作用是为 Promise 实例添加状态改变时的回调函数。前面说过,then 方法的第一个参数是 resolved 状态的回调函数,第二个参数是 rejected 状态的回调函数,它们都是可选的
then 方法返回的是一个新的 Promise 实例(注意,不是原来那个 Promise 实例)。因此可以采用链式写法,即 then 方法后面再调用另一个 then 方法
1 2 3 4 5 getJSON ("/posts.json" ).then (function (json ) { return json.post ; }).then (function (post ) { });
Promise.prototype.catch() Promise.prototype.catch()
方法是 .then(null, rejection)
或 .then(undefined, rejection)
的别名,用于指定发生错误时的回调函数
1 2 3 4 5 6 getJSON ('/posts.json' ).then (function (posts ) { }).catch (function (error ) { console .log ('发生错误!' , error); });
Promise.prototype.finally() finally()
方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。该方法是 ES2018 引入标准的
1 2 3 4 promise .then (result => {···}) .catch (error => {···}) .finally (() => {···});
不管 promise 最后的状态,在执行完 then 或 catch 指定的回调函数以后,都会执行 finally 方法指定的回调函数
1 2 3 4 5 6 7 8 9 10 11 Promise .resolve (2 ).then (() => {}, () => {})Promise .resolve (2 ).finally (() => {})Promise .reject (3 ).then (() => {}, () => {})Promise .reject (3 ).finally (() => {})
Promise.all() Promise.all()
方法用于将多个 Promise 实例,包装成一个新的 Promise 实例
1 const p = Promise .all ([p1, p2, p3]);
上面代码中,Promise.all()
方法接受一个数组作为参数,p1、p2、p3 都是 Promise 实例,如果不是,就会先调用下面讲到的 Promise.resolve
方法,将参数转为 Promise 实例,再进一步处理。另外,Promise.all()
方法的参数可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例。p的状态由 p1、p2、p3 决定,分成两种情况:
只有 p1、p2、p3 的状态都变成 fulfilled,p 的状态才会变成 fulfilled,此时 p1、p2、p3 的返回值组成一个数组,传递给 p 的回调函数
只要 p1、p2、p3 之中有一个被 rejected,p 的状态就变成 rejected,此时第一个被 reject 的实例的返回值,会传递给 p 的回调函数
Promise.race() Promise.race()
方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例
1 const p = Promise .race ([p1, p2, p3]);
上面代码中,只要 p1、p2、p3 之中有一个实例率先改变状态,p 的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数
Promise.race()
方法的参数与 Promise.all()
方法一样,如果不是 Promise 实例,就会先调用下面讲到的 Promise.resolve()
方法,将参数转为 Promise 实例,再进一步处理
下面是一个例子,如果指定时间内没有获得结果,就将 Promise 的状态变为reject,否则变为resolve
1 2 3 4 5 6 7 8 9 const p = Promise .race ([ fetch ('/resource-that-may-take-a-while' ), new Promise (function (resolve, reject ) { setTimeout (() => reject (new Error ('request timeout' )), 5000 ) }) ]); p.then (console .log ) .catch (console .error );
上面代码中,如果 5 秒之内 fetch 方法无法返回结果,变量 p 的状态就会变为 rejected,从而触发 catch 方法指定的回调函数
Promise.allSettled() 有时候,我们希望等到一组异步操作都结束了,不管每一个操作是成功还是失败,再进行下一步操作。但是,现有的 Promise 方法很难实现这个要求。Promise.all()
方法只适合所有异步操作都成功的情况,如果有一个操作失败,就无法满足要求。
为了解决这个问题,ES2020 引入了 Promise.allSettled()
方法,用来确定一组异步操作是否都结束了(不管成功或失败)。所以,它的名字叫做"Settled",包含了 "fulfilled"和"rejected"两种情况
Promise.allSettled()
方法接受一个数组作为参数,数组的每个成员都是一个 Promise 对象,并返回一个新的 Promise 对象。只有等到参数数组的所有 Promise 对象都发生状态变更(不管是 fulfilled 还是 rejected),返回的 Promise 对象才会发生状态变更
1 2 3 4 5 6 7 8 const promises = [ fetch ('/api-1' ), fetch ('/api-2' ), fetch ('/api-3' ), ]; await Promise .allSettled (promises);removeLoadingIndicator ();
Promise.any() ES2021 引入了 Promise.any()
方法。该方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例返回
1 2 3 4 5 6 7 8 9 Promise .any ([ fetch ('https://v8.dev/' ).then (() => 'home' ), fetch ('https://v8.dev/blog' ).then (() => 'blog' ), fetch ('https://v8.dev/docs' ).then (() => 'docs' ) ]).then ((first ) => { console .log (first); }).catch ((error ) => { console .log (error); });
只要参数实例有一个变成 fulfilled 状态,包装实例就会变成 fulfilled 状态;如果所有参数实例都变成 rejected 状态,包装实例就会变成 rejected 状态
Promise.resolve() 有时需要将现有对象转为 Promise 对象,Promise.resolve()
方法就起到这个作用
1 const jsPromise = Promise .resolve ($.ajax ('/whatever.json' ));
上面代码将 jQuery 生成的deferred对象,转为一个新的 Promise 对象。Promise.resolve()
等价于下面的写法
1 2 3 Promise .resolve ('foo' )new Promise (resolve => resolve ('foo' ))
Promise.reject() Promise.reject(reason)
方法也会返回一个新的 Promise 实例,该实例的状态为 rejected
1 2 3 4 5 6 7 const p = Promise .reject ('出错了' );const p = new Promise ((resolve, reject ) => reject ('出错了' ))p.then (null , function (s ) { console .log (s) });
集合 Set ES6 提供了新的数据结构 set,它类似于数组,但成员的值都是唯一的,集合实现了 iterator 接口,所以可以使用扩展运算符和 for ... of
进行遍历,集合的属性和方法:
size 返回集合的元素个数
add 增加一个新元素,返回当前集合
delete 删除元素,返回 boolean 值 has 检测集合中是否包含某个元素,返回 boolean 值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 let s = new Set ();let s2 = new Set (['A' , 'B' , 'C' , 'D' ])console .log (s2.size );s2.add ('E' ); s2.delete ('A' ) console .log (s2.has ('C' ));s2.clear () console .log (s2);
Map ES6 提供了 Map 数据结构。它类似于对象,也是键值对的集合。但是"键"的范围不限于字符串,各种类型的值(包括对象)都可以当作键。Map 也实现了 iterator 接口,所以可以使用扩展运算符和 for ... of
进行遍历 Map 的属性和方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 let m = new Map ();m.set ('name' , 'ran' ); m.set ('change' , () => { console .log ('改变!' ) }) let key={ school : 'atguigu' } m.set (key, ['成都' , '西安' ]); console .log (m.size );m.delete ('name' ); console .log (m.get ('change' ));m.clear () for (let v of m){ console .log (v); }
Class ES6 提供了一种新的语法,可以让 JavaScript 支持类和继承这两个面向对象编程的重要概念。类是一种定义对象属性和方法的模板,可以通过 new 关键字创建类的实例。继承是一种让子类拥有父类属性和方法的机制,可以通过 extends 和 super 关键字实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 class Person { constructor (name,age ) { this .name = name; this .age = age; } sayHi ( ) { console .log (`Hello ${this .name} , you are ${this .age} years old.` ); } } let alice = new Person ("Alice" , 18 );alice.sayHi (); class Student extends Person { constructor (name, age, grade ) { super (name, age); this .grade = grade; } sayHi ( ) { console .log (`Hello ${this .name} , you are ${this .age} years old and in grade ${this .grade} .` ); } study ( ) { console .log (`${this .name} is studying hard.` ); } } let bob = new Student ("Bob" , 19 , 12 );bob.sayHi (); bob.study ();
数值扩展 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 function equal (a, b ){ if (Math .abs (a - b) < Number .EPSILON ) { return true ; } else { return false ; } } console .log (equal (0.1 + 0.2 === 0.3 )) console .log (equal (0.1 + 0.2 , 0.3 )) let b = 0b1010 ; let o = 0o777 ; let d = 100 ; let x = 0xff ; console .log (x) console .log (Number .isFinite (100 )); console .log (Number .isFinite (100 / 0 )); console .log (Number .isFinite (Infinity )); console .log (Number .isNaN (123 )) console .log (Number .parseInt ('5213123love' )); console .log (Number .parseFloat ('5.123123神器' )); console .log (Number .isInteger (5 )); console .log (Number .isInteger (2.5 )); console .log (Math .trunc (3.45345345345 )) console .log (Math .sign (100 )) console .log (Math .sign (0 )) console .log (Math .sign (-123 ))
对象方法扩展 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 console .log (Object .is (120 , 120 )) console .log (Object .is (NaN , NaN )) const a = { name : 'ran' , age : 12 } const b = { pass : 'i love you' } console .log (Object .assign (a, b)) const school = { name : '尚硅谷' } const cities = { xiaoqu : ['北京' , '上海' ] } Object .setPrototypeOf (school, cities)console .log (Object .getPrototypeOf (school)) console .log (school)
模块化 基本介绍
模块化是指将一个大的程序文件,拆分成许多小的文件,然后将小文件组合起来
模块化的好处:
防止命名冲突
代码复用
高维护性
模块化规范产品
CommonJS ====> NodeJS、Browserify
AMD ====> requireJS
CMD ====> seaJS
模块功能主要有两个命令构成:export 和 import。export 命令用于规定模块的对外接口;import命令用于输入其他模块提供的功能
1 2 3 4 export let school = '尚硅谷' export function teach ( ) { console .log ('教技能' ) }
1 2 import * as m1 from "./src/js/m1.js" ;console .log (m1);
暴露语法
1 2 3 4 5 6 let school = '尚硅谷' ;function findjob ( ){ console .log ('找工作吧' ); } export {school, findjob}
1 2 3 4 5 6 7 export default { school : 'ATGUIGU' , change :function ( ) { console .log ('我们可以改变你' ) } }
引入语法
1 2 3 import * as m1 from "./src/js/m1.js" import * as m2 from "./src/js/m2.js" import * as m3 from "./src/js/m3.js"
1 2 3 4 import {school, teach} from "./src/js/m1.js" import {school as guigu, findJob} from "./src/js/m2.js" import {default as m3 } from "./src/js/m3.js"
1 import m3 from "./src/js/m3.js"
模块化方式 2 1 <script src="./src//js/app.js" type=modeule></script>
ES7
Array.prototype.includes
:用来检测数组中是否包含某个元素,返回布尔类型值
在 ES7 中引入指数操作符 **
,用来实现幂运算,功能与 Math.pow
结果相同
1 2 3 4 5 6 7 const mingzhu = ['西游记' , '红楼梦' , '水浒传' , '三国演义' ]console .log (mingzhu.includes ('西游记' )) console .log (mingzhu.includes ('金瓶梅' )) console .log (2 **10 )
ES8 async async 和 await 两种语法结合可以让异步代码像同步代码一样
async 函数的返回值为 promise 对象
async 返回的 promise 对象的结果值由 async 函数执行的返回值决定
1 2 3 4 5 6 7 8 9 10 11 12 13 14 async function fn ( ){ return new Promise ((resolve, reject ) => { resolve ('成功的数据' ); }); } const result = fn ();result.then (value => { console .log (value) },reason => { console .log (reason) })
await
await 必须放在 async 函数中
await 右侧的表达式一般为 promise 对象
await 可以返回的是右侧 promise 成功的值
await 右侧的 promise 如果失败了,就会抛出异常,需要通过 try ... catch
捕获处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 const p = new Promise ((resolve, reject ) => { reject ("失败了" ) }) async function main ( ) { try { let res = await p; console .log (res); } catch (e) { console .log (e); } } main ()
对象方法扩展 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 const school = { name : '尚硅谷' , cities : ['北京' , '上海' , '深圳' ], xueke : ['前端' , 'Java' , '大数据' , '运维' ] }; console .log (Object .keys (school));console .log (Object .values (school));console .log (Object .entries (school));console .log (new Map (Object .entries (school)))console .log (Object .getOwnPropertyDescriptor (school))const obj = Object .create (null , { name :{ value : '尚硅谷' , writable :true , configurable :true , enumerable :true , } })
ES9 运算扩展符与 rest 参数 1 2 3 4 5 6 7 8 9 10 11 12 function connect ({host, port, ...user} ){ console .log (host); console .log (port); console .log (user) } connect ({ host : '127.0.0.1' , port : 3306 , username : 'root' , password : 'root' , type : 'master' })
1 2 3 4 5 6 7 8 9 10 11 const AA = { username : 'ran' } const BB = { password : 'lyyrhf' } const CC = { job : 'Java' } const D = {...AA , ...BB , ...CC };console .log (D)
ES10 对象扩展方法 1 2 3 4 5 6 7 8 9 10 11 12 const res = Object .fromEntries ([ ['name' , 'RHF' ], ['cities' , '成都' , '武汉' ] ]) console .log (res) const m = new Map ();m.set ('name' , 'ranhaifeng' ) const result = Object .fromEntries (m)console .log (result);
flat 与 flatMap 1 2 3 4 5 6 const arr = [1 , 2 , 3 , [4 , 5 , 6 , [7 , 8 , 9 ]]]console .log (arr.flat (2 )) const arr2=[1 , 2 , 3 , 4 ]const result = arr2.flatmap (item => [item * 10 ]);
私有属性 ES2022 正式为 class 添加了私有属性,方法是在属性名之前使用 #
表示
1 2 3 4 5 6 7 8 9 10 class IncreasingCounter { #count = 0 ; get value () { console .log ('Getting the current value!' ); return this .#count; } increment ( ) { this .#count++; } }
上面代码中,#count 就是私有属性,只能在类的内部使用(this.#count
)。如果在类的外部使用,就会报错
1 2 3 const counter = new IncreasingCounter ();counter.#count counter.#count = 42
注意,从 Chrome 111 开始 ,开发者工具里面可以读写私有属性,不会报错,原因是 Chrome 团队认为这样方便调试
另外,不管在类的内部或外部,读取一个不存在的私有属性,也都会报错。这跟公开属性的行为完全不同,如果读取一个不存在的公开属性,不会报错,只会返回 undefined
1 2 3 4 5 6 7 8 9 10 11 12 13 class IncreasingCounter { #count = 0 ; get value () { console .log ('Getting the current value!' ); return this .#myCount; } increment ( ) { this .#count++; } } const counter = new IncreasingCounter ();counter.#myCount
上面示例中,#myCount 是一个不存在的私有属性,不管在函数内部或外部,读取该属性都会导致报错
注意:私有属性的属性名必须包括 #
,如果不带 #
,会被当作另一个属性
可选链操作符 相当于一个判断符,如果前面的有,就进入下一层级
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 function main (config ){ const dbHost = config?.db ?.host console .log (dbHost) } main ({ db : { host :'192.168.1.100' , username :'root' }, cache : { host :'192.168.1.200' , username :'admin' } })
BigInt JavaScript 所有数字都保存成 64 位浮点数,这给数值的表示带来了两大限制。一是数值的精度只能到 53 个二进制位(相当于 16 个十进制位),大于这个范围的整数,JavaScript 是无法精确表示,这使得 JavaScript 不适合进行科学和金融方面的精确计算。二是大于或等于 2 的 1024 次方的数值,JavaScript 无法表示,会返回Infinity
1 2 3 4 5 Math .pow (2 , 53 ) === Math .pow (2 , 53 ) + 1 Math .pow (2 , 1024 )
ES2020 引入了一种新的数据类型 BigInt(大整数),来解决这个问题,这是 ECMAScript 的第八种数据类型。BigInt 只用来表示整数,没有位数的限制,任何位数的整数都可以精确表示
1 2 3 4 5 6 7 8 const a = 2172141653n ;const b = 15346349309n ;a * b Number (a) * Number (b)
为了与 Number 类型区别,BigInt 类型的数据必须添加后缀n
import ES2020 提案 引入 import() 函数,支持动态加载模块
上面代码中,import 函数的参数 specifier,指定所要加载的模块的位置。import 命令能够接受什么参数,import() 函数就能接受什么参数,两者区别主要是后者为动态加载。import() 返回一个 Promise 对象。下面是一个例子:
1 2 3 4 5 6 7 8 9 const main = document .querySelector ('main' );import (`./section-modules/${someVariable} .js` ) .then (module => { module .loadPageInto (main); }) .catch (err => { main.textContent = err.message ; });
import() 函数可以用在任何地方,不仅仅是模块,非模块的脚本也可以使用。它是运行时执行,也就是说,什么时候运行到这一句,就会加载指定的模块。另外,import() 函数与所加载的模块没有静态连接关系,这点也是与 import 语句不相同。import() 类似于 Node.js 的 require() 方法,区别主要是前者是异步加载,后者是同步加载
由于 import() 返回 Promise 对象,所以需要使用 then() 方法指定处理函数。考虑到代码的清晰,更推荐使用 await 命令
1 2 3 4 5 6 7 8 9 10 11 12 13 async function renderWidget ( ) { const container = document .getElementById ('widget' ); if (container !== null ) { const widget = await import ('./widget.js' ); widget.render (container); } } renderWidget ();