《ECMAScript6入门》作者:阮一峰 这本书写的非常详细,有兴趣可以去看一下这本书
目录
5.实例方法:includes(),startsWith(),endsWith()
13.String.prototype.matchAll()
3.Number.isFinite(),Number.isNaN()
4.Number.parseInt(),Number.parseFloat()
7.Function.prototype.toString()
5.实例方法:find(),findIndex(),findLast(),findLastIndex()
7.实例方法:entries(),keys()和values()
11.实例方法:toReversed(),toSorted(),toSpliced(),with()
14.Array.prototype.sort()的排序稳定性
3.Object.getOwnPropertyDescriptors()
4.__proto__属性,Object.setPrototypeOf(),Object.getPrototypeOf()
5.Object.keys(),Object.values(),Object.entries()
3.Symbol.for(),Symbol.keyFor()
1、ES6简介
ECMAScript6.0(简称ES6)是JavaScript语言的下一代标准,已经在2015年6月正式发布。它的目标是使得JavaScript语言可以用来编写复杂的大型应用程序,成为企业级开发语言。
2、let和const命令
1.let命令
-
基本用法:ES6新增 let 命令,用来声明变量,用法类似于 var,但是所声明的变量,只在 let 命令所在的代码块内有效。
-
使用var命令时会出现变量可以在声明之前使用,值为undefined,而let命令改变了这种语法行为,它所声明的变量一定要在声明后使用,否则会报错。
-
只要块级作用域内存在let命令,它所声明的变量就“绑定”这个区域,不再受外部的影响
var tmp = 123;//全局声明一个变量tmp if(true){ tmp = 'abc';//ReferenceError let tmp; }
上述代码中,存在全局变量tmp,但是在块级作用域内let又声明了一个局部变量tmp,导致后者绑定这个块级作用域,所以在let声明变量前,对tmp赋值会报错。
这里ES6明确规定:如果区块中存在 let 和 const命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。在语法上,成为“暂时性死区”(TDZ)。
if(true){ //TDZ开始 tmp = 'abc';//ReferenceError console.log(tmp);//ReferenceError let tmp;//TDZ结束 console.log(tmp);//undefined tmp = 123; console.log(tmp);//123 }
正是因为如此,typeof 也不再是一个百分之百安全的操作。
typeof x;//ReferenceError let x; typeof undeclared_variable;//undefined
变量x在声明之前都属于“死区”,所以使用就会报错。作为对比undeclared_variable 本就是一个不存在的变量名,结果返回“undefined”。
-
let不允许在相同作用域内,重复声明一个变量。
2.块级作用域
-
let实际上为JavaScript新增了块级作用域。
-
ES6中引入块级作用域,明确允许在块级作用域中声明函数。并且ES6规定,块级作用域之中,函数声明语句的行为类似于let,在块级作用域之外不可引用。但是根据ES6附录B的规定,浏览器的实现可以不遵守上诉规定,有自己的行为方式。
-
允许在块级作用域内声明函数。
-
函数声明类似于var,即会提升到全局作用域或函数作用域的头部。
-
同时,函数声明还会提升到所在的块级作用域的头部。
-
3.const命令
-
const声明一个只读的常量。一旦声明,常量的值就不能改变。
-
const声明的变量不得改变值,也就是说一旦声明变量,就必须立即初始化。
-
与let命令相同,只在声明所在的块级作用域内有效,同样还存在暂时性死区。
-
const实际上保证的并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动。对于简单类型的数据,值就保存在变量指向的那个内存地址,等同于常量。对于复合类型的数据(对象和数组),变量指向的内存地址,保存的只是一个指向实际数据的指针,const只能保证这个指针是固定的(总是指向另一个固定的地址)。
3、变量的解构赋值
1.数组的解构赋值
ES6允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构。
以前,为变量赋值,只能指定值。
let a = 1;
let b = 2;
let c = 3;
ES6中允许写成下面这样。
let [a,b,c] = [1,2,3];
本质上,这种写法属于“模式匹配”,只要等到两边的模式相同,左边的变量就会被赋予对应的值。解构不成功的话,变量的值就等于undefined。
上述的情况属于完全解构,还有一种情况属于不完全解构,也就是说等号左边的模式只能匹配一部分的等号右边的数组。
let [x,y] = [1,2,3];
x //1
y //2
这个例子就属于不完全解构,但是仍然可以成功。
如果等号右边不是可遍历的结构,那么就会报错。
let [foo] = 1;
let [foo] = NaN;
let [foo] = {};
上述的语句都会报错,前两个例子转为对象以后不具备Iterator接口,最后一个例子本身就不具备Iterator接口。
解构赋值允许指定默认值。
let [foo = true] = [];
foo //true
let[x,y = 'b'] = ['a',undefined];
//x = 'a',y = 'b'
这里需要注意的是 ES6内部严格使用相等运算符(===),判断一个位置是否有值,只有当一个数组成员严格等于undefined,默认值才会生效。但是如果一个数组成员是null,默认值就不会生效,因为 null不严格等于undefined。
let [x = 1] = [undefined];
x //1
let [x = 1] = [null];
x //null
如果默认值是一个表达式,那么这个表达式是惰性求值,即只有在用到的时候,才会求值。
function f(){
console.log('aaa');
}
let [x = f()] = [1];
x //1
2.对象的解构赋值
解构不仅可以用于数组,还可以用于对象。其和数组有一个重要的不同,数组的元素是按次序排列的,变量的取值由它的位置决定,而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。
let {bar,foo} = {foo: 'aaa',bar: 'bbb'};
foo //"aaa"
bar //"bbb"
如果变量名和属性名不同,变量的值等于undefined。
let {foo} = {bar: 'baz'};
foo //undefined
对象的解构赋值,还有一个妙用就是将现有的对象方法赋值到某个变量。
let {log,sin,cos} = Math;
const {log} = console;
log('hello') //hello
第一个例子讲Math对象的对数、正弦、余弦三个方法赋值到对应的变量上,第二个例子将 console.log赋值到log变量,使用起来就会方便很多。
如果变量名和属性名不一致,就必须写成下面的格式。
let {foo:baz} = {foo:'aaa',bar:'bbb'};
baz //"aaa"
对象的解构也可以指定默认值。
var {x,y = 5} = {x:1}
x //1
y //5
注意点
-
将一个已经声明的变量用于解构赋值
let x; {x} = {x:1}; //SyntaxError: syntax error
这是错误的写法,因为JavaScript引擎会将{x}理解成一个代码块,从而发生语法错误。解决方法就是不将大括号放在行首,避免JavaScript将其解释为代码块。下面是正确的写法
let x; ({x} = {x:1});
-
由于数组本质是特殊的对象,因此可以对数组进行对象属性的解构。
let [arr] = [1,2,3]; let {0: first,[arr.length - 1: last] = arr; first //1 last //3
3.字符串的解构赋值
字符串也可以解构赋值,因为字符串被转换为一个类似数组的对象。
const [a,b,c,d,e] = 'hello';
a //"h"
b //"e"
c //"l"
d //"l"
e //"o"
类似数组的对象都有一个length属性,因此还会对这个属性解构赋值。
let {length : len} = 'hello';
len //5
4.数值和布尔值的解构赋值
解构赋值时,如果等号右边是数值和布尔值,则会先转换为对象。
let {toString : s} = 123;
s === Number.prototype.toString //true
let {toString : s} = true;
s === Boolean.prototype.toString //true
上述代码中,数值和布尔值的包装对象都有toString属性
let {prop : x} = undefined;//TypeError
let {prop : y} = null;//TypeError
解构赋值的规则就是,只要等号右边的值不是对象或数组,就先将其转为对象。上述代码中,由于undefined和null无法转为对象,所以无法进行解构赋值。
5.函数参数的解构赋值
函数的参数也可以使用解构赋值。
function add([x,y]){
return x + y;
}
add([1,2]);//3
上述代码中,数组参数被解构成变量x和y。
函数的参数也可以使用默认值。
function move({x = 0,y = 0} = {}){
return [x,y];
}
move({x:3,y:8}); //[3,8]
move({x:3}); //[3,0]
6.圆括号问题
模式中出现圆括号怎么处理?ES6的规则是 只要有可能导致解构的歧义,就不得使用圆括号。但是这条规则实际上不那么容易辨别,处理起来相当麻烦。因此,建议只要有可能,就不要在模式中放置圆括号。
以下是三种解构赋值不得使用圆括号的三种情况:
-
变量声明语句
let [(a)] = [1]; let {x:(c)} = {}; let {(x:c)} = {};
-
函数参数
function f([(z)]) {return z;} function f([z,(x)]) {return x;}
-
赋值语句的模式
({p:a}) = {p:42}; ([a]) = [5];
4、字符串的扩展
1.字符的Unicode表示法
ES6加强了对Unicode的支持,允许采用 \uxxxx 形式来表示一个字符,其中 xxxx 表示字符的Unicode码点。但是这种表示法只限于码点在 \u0000 ~ \uFFFF 之间的字符,超出这个范围的字符就必须用两个双字节的形式表示。
"\u0061"
// "a"
"\uD842\uDFB7"
//"𠮷"
"\u20BB7"
//" 7"
上述代码中的 "\u20BB7" 在\u后面跟上超过 0xFFFF 的数值,JavaScript会理解成 \u20BB+7,由于 \u20BB 是一个不可打印的字符,所以只会显示一个空格,后边跟着一个7。
对于上述的这种问题,ES6对此做出了改进,只要把码点放入大括号,就能正确解读该字符。
"\u{20BB7}"
//𠮷"
2.字符串的遍历器接口
ES6为字符串添加了遍历器接口,使得字符串可以被 for...of 循环遍历。
for(let c of 'foo'){
console.log(c)
}
//"f"
//"o"
//"o"
除了遍历字符串,这个遍历器最大的优点就是可以识别大于 0xFFFF的码点,传统的for循环是无法识别这样的码点。
let text = String.fromCodePoint(0x20BB7);
for(let i = 0;i < text.length;i++){
console.log(text[i]);
}
//" "
//" "
for(let i of text){
console.log(i);
}
//"𠮷"
使用传统的for循环认为text会包含两个字符,两个字符都无法打印。
3.直接输入U+2028 和 U+2029
JavaScript中字符串允许直接输入字符以及输入字符的转义形式。但是,JavaScript规定有5个字符不能在字符串里直接使用,只能使用转义形式。
-
U+005C:反斜杠
-
U+000D:回车
-
U+2028: 行分隔符
-
U+2029: 段分隔符
-
U+000A:换行符
比如说 字符串中不能直接包含反斜杠,一定要转义写成\\或者\u005c.
但是Json格式允许字符串中直接使用U+2028和U+2029,所以服务器输出的JSON被JSON.parse解析,就有可能直接报错。
为了消除这一问题,ES2019允许JavaScript字符串中直接输入U+2028和U+2029。
4.JSON.stringify()的改造
根据标准来说,JSON数据必须是UTF-8编码,但是JSON.stringify()方法可能返回不符合UTF-8标准的字符串。
具体来说,UTF-8标准规定,0xD800到0xDFFF之间的码点,不能单独使用,必须配比使用。就比如说,\uD834\uDF06 是两个码点,但是必须放在一起配比使用,代表字符 𝌆。这种形式也是一种解决码点大于 0xFFFF的字符的方法。
JSON.stringify()问题就在于,它可能返回0xD800到0xDFFF之间的单个码点。
为了确保返回的是合法的UTF-8字符,ES2019改变了JSON.stringify()的行为。如果遇到 oxD800 到 oxDFFF之间的单个码点,或者不存在的配对形式,它会返回转义字符串,留给应用自己决定下一步的处理。
JSON.stringify('\u{D834}')
// ""\\uD834""
5.模板字符串
模板字符串是增强版的字符串,用反引号(`)标识。他可以当做普通字符串使用,也可以用来定义多行字符串,或者在字符串中嵌入变量。
//普通字符串
`In JavaScript `\n` is a line-feed.`
//多行字符串
`In JavaScript this is
not legal.`
//字符串中嵌入变量
let name = "Bob",time = "today";
`Hello ${name},howa are you ${time}?`
如果在模板字符串中需要使用反引号,则前面要用反斜杠转义。
let greeting = `\`Yo\` World!`;
如果使用模板字符串表示多行字符串,所有的空格和缩进都会保留在输出之中。要是想要取消换行,就可以在 ` 后边加上trim()方法消除它。
6.标签模板
模板字符串可以紧跟在一个函数名后面,该函数将被调用来处理这个模板字符串,这被称为 标签模板。
标签模板其实不是模板,而是函数调用的一种特殊形式。“标签”指的就是函数,紧跟在后边的模板字符串就是它的参数。
alert`hello`
//等同于
alert([`hello`])
7.模板字符串的限制
标签模板中可以内嵌其他语言,但是模板字符串默认会将字符串转义,导致无法嵌入其他语言。
5、字符串的新增方法
1.String.fromCodePoint()
ES5中提供String.fromCharCode()方法,用于从Unicode码点返回对应字符,但是这个方法有个很大的缺陷,就是不能识别码点大于0xFFFF的字符。
String.fromCharCode(0x20BB7)
//"ஷ"
因为String.fromCharCode()这个方法不能识别大于0xFFFF的码点,所以0x20BB7就发生了溢出,最高位2就被舍弃了,最后返回的是码点U+0BB7对应的字符,而不是码点U+20BB7对应的字符。
而ES6新增的String.fromCodePoint()方法可以识别大于0xFFFF的字符,弥补了String.fromCharCode()方法的不足。
String.fromCodePoint(0x20BB7)
// "𠮷"
String.fromCodePoint(0x78,0x1f680,0x79) === 'x\uD83D\uDE80y'
如果String.fromCodePoint()方法有多个参数,那么它们会被合并成一个字符串返回。
2.String.raw()
ES6还为原生的String对象,提供了一个raw()方法,该方法返回一个斜杠都被转义的字符串,往往用于模板字符串的处理方法。
String.raw`Hi\n${2+3}!`;
//实际返回 "Hi\\n5!",显示的是转义后的结果 "Hi\n5!"
String.raw`Hi\u000A!`;
//实际返回 "Hi\\u000A!",显示的是转义后的结果 "Hi\u000A!"
如果原字符串的斜杠已经转义,那么String.raw()会进行再次转义。
String.raw()方法可以作为处理模板字符串的基本方法,它会将所有变量替换,而且对斜杠进行转义,方便下一步作为字符串来使用。
3.实例方法:codePointAt()
JavaScript内部,字符是以UTF-16的格式储存,每个字符固定为2个字节,而码点大于0xFFFF的字符需要4个字节储存,所以JavaScript会认为它们是两个字符。
ES6提供的 codePointAt() 方法能够正确的处理4个字节储存的字符,返回一个字符的码点。
这里需要注意的是,codePointAt()方法返回的是码点的十进制值,而不是十六进制的值,如果需要十六进制的值,可以使用toString()方法转换一下。
4.实例方法:normalize()
ES6提供字符串实例的normalize()方法,用来将字符串的不同方法统一为同样的形式,成为Unicode正规化。
5.实例方法:includes(),startsWith(),endsWith()
以前,JavaScript中只有indexOf方法可以确定一个字符串中是否包含在另一个字符串中,而Es6又提供了三种新方法。
-
includes():返回布尔值,表示是否找到了参数字符串。
-
startsWith():返回布尔值,表示参数字符串是否在原字符串的头部。
-
endsWith():返回布尔值,表示参数字符串是否在原字符串的尾部。
let s = 'Hello world!';
s.startsWith('Hello');//true
s.endsWith('!');//true
s.includes('o');//true
并且,这三个方法都支持第二个参数,表示开始搜索的位置。
let s = 'Hello world!';
s.startsWith('world',5);//true
s.endsWith('Hello',5);//true
s.includes('Hello',6);//false
这里endsWith()方法第二个参数n指的是它适用于前n个字符,其他两个方法指的是从第n个位置直到字符串结束。
6.实例方法:repeat()
repeat()方法就是返回一个新的字符串,表示将原字符串重复n次。
'x'.repeat(3) //"xxx"
如果参数是小数,会被取整;如果是负数或者Infinity,会报错,但是参数如果为0到-1的小数,会先进行取整运算为-0,视同为0;如果参数为Nan,等同于0;如果参数是字符串,JavaScript会尝试将字符串转换为数字,作为重复的次数,但是如果无法转换为数字,就会返回一个空字符串。
'na'.repeat(2.9) //"nana"
'na'.repeat(Infinity) //RangeError
'na'.repeat(-1) //RangeError
'na'.repeat(-0.9) //""
'na'.repeat(NaN) //""
'na'.repeat('na') //""
7.实例方法:padStart(),padEnd()
ES2017引入了字符串补全长度的功能。如果某个字符串不够指定长度,会在头部或尾部补全。padStart()用于头部补全,padEnd()用于尾部补全。
这两个方法中共有两个参数,第一个参数是字符串补全的最大长度,第二个参数为用来补全的字符串。
如果原字符串的长度等于或者大于最大长度,则字符串的补全不生效,返回原字符串。
如果用来补全的字符串与原字符串,连两者之和超过了最大长度,则会截去超出位数的补全字符串。
'abc'.padStart(10,'0123456789') //'0123456abc'
如果省略第二个参数,默认使用空格补全长度。
padStart()方法常见用途就是为数值补全指定位数,还有就是提示字符串格式。
'1'.padStart(10,'0') //"0000000001"
'12'.padStart(10,'YYYY-MM-DD') //"YYYY-MM-12"
8.实例方法:trimStart(),trimEnd()
ES2019对字符串实例新增了trimStart()和trimEnd()这两个方法,和trim()方法的行为一致,只不过是消除空格位置不同。它们返回的都是新的字符串,不会修改原来的字符串。
9.实例方法:matchAll()
matchAll()方法返回一个正则表达式在当前字符串的所有匹配。
10.实例方法:replaceAll()
ES2021引入了replaceAll()方法,可以一次性替换所有匹配。它的用法与replace()方法相同,返回的是一个新字符串,不会改变原字符串。
11.实例方法:at()
at()方法接受一个整数作为参数,返回参数指定位置的字符,支持负索引(倒数位置)。
如果参数位置超过了字符串范围,at()返回undefined。
6、正则的扩展
1.RegExp构造函数
在ES5中,RegExp构造函数存在以下两种情况:
-
第一种情况:第一个参数为字符串,第二个参数为正则表达式的修饰符
var regex = new RegExp('xyz','i');
-
第二种情况:参数是一个正则表达式,会返回一个原有正则表达式的拷贝。
var regex = new RegExp(/xyz/i);
但是,ES5中不允许此时使用第二个参数添加修饰符,否则会报错。ES6中修改了这个行为,返回的正则表达式会忽略原有的正则表达式的修饰符,只使用新指定的修饰符。
new RegExp(/abc/ig,'i').flags
原有正则对象的修饰符为 ig,但是他会被第二个参数i覆盖。
2.字符串的正则方法
ES6中将match()、replace()、search()、split(),在语言内部全部调用RegExp的实例方法,从而做到所有与正则相关的方法,全部定义在RegExp上。
- String.prototype.match 调用 RegExp.prototype[Symbol.match]
- String.prototype.replace 调用 RegExp.prototype[Symbol.replace]
- String.prototype.search 调用 RegExp.prototype[Symbol.search]
- String.prototype.split 调用 RegExp.prototype[Symbol.split]
3.u修饰符
ES6中对正则表达式添加了 u 修饰符,用来正确处理大于\uFFFF的Unicode字符。
/^\uD83D/u.test('\uD83D\uDC2A') //false
/^\uD83D/.test('\uD83D\uDC2A') //true
\uD83D\uDC2A代表一个字符, ES6 加了u字符 会识别其为一个字符,所以第一行代码为false;ES5中会将其识别为两个字符,导致第二行代码为true
4.RegExp.prototype.unicode属性
正则实例对象新增 unicode属性,表示是否设置了u修饰符
const r1 = /hello/;
const r2 = /hello/u;
r1.unicode //false
r2.unicode //true
上述代码中,正则表达式是否设置了u修饰符,可以从unicode属性看出来
5.y修饰符
ES6还为正则表达式添加了y修饰符,叫做“粘连”修饰符。
y修饰符的作用与g修饰符类似,也是全局匹配,后一次匹配都从上一次匹配成功的下一个位置开始。不同之处在于,g修饰符只要剩余位置中存在匹配就可,而y修饰符确保匹配必须从剩余的第一个位置开始。
var s = 'aaa_aa_a';
var r1 = /a+/g;
var r2 = /a+/y;
r1.exec(s);//["aaa"]
r2.exec(s);//["aaa"]
r1.exec(s);//["aa"]
r2.exec(s);//null
6.RegExp.prototype.sticky属性
ES6的正则对象中多了 sticky 属性,表示是否设置了y修饰符。
var r = /hello\d/y;
r.sticky //true
7.RegExp.prototype.flags属性
ES6中为正则表达式新增了flags属性,会返回正则表达式的修饰符。
//ES5的 source属性返回正则表达式的正文
/abc/ig.source
//"abc"
//ES6的 flags属性返回正则表达式的修饰符
/abc/ig.flags
//'gi'
8.s修饰符:dotAll模式
正则表达式中,点(.)是一个特殊字符,代表任意的单个字符,除了四个字节的UTF-16字符,另一个就是行终止符。
/foo.bar/s.test('foo\nbar') //true
这被称为dotAll模式,即点代表一切字符。正则表达式还引入了一个dotAll属性,返回一个布尔值,表示该正则表达式是否处在dotAll模式。
const re = /foo.bar/s;
re.test('foo\nbar')//true
re.dotAll//true
re.flags //'s'
9.Unicode属性类
ES2018引入了Unicode属性类,允许使用\p(...)和\P{...}(\P是\p的否定形式)代表一类Unicode字符,匹配满足条件的所有字符。
const regexGreekSymbol = /\p{Script=Greek}/u;
regexGreekSymbol.test('Π') //true
10.v修饰符:Unicode属性类的运算
现在增加了Unicode属性类的运算功能,它提供两种形式的运算,分别是差集运算,另一种是交集运算。这种运算的前提就是正则表达式必须使用新引入的v修饰符。
//十进制字符去除ASCII码的 0到9
[\p{Decimal_Number}--[0-9]]
11.具名组匹配
ES2018引入了具名组匹配,允许为每一个组匹配指定一个名字(之前只能使用数字序号)。
const RE_DATE = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const matchObj = RE_DATE.exec('1999-12-31');
const year = matchObj.groups.year;//"1999"
const month = matchObj.groups.month;//"12"
const day = matchObj.groups.day;//"31"
"具名组匹配"在圆括号内部,模式的头部添加“问号+尖括号+组名”(?<year>),然后就可以在exec方法返回结果的groups属性上引用该组名,同时数字序号依然有效。
如果具名组没有匹配,那么对应的groups对象属性会是undefined。
const RE_OPT_A = /^(?<as>a+)?$/;
const matchObj = RE_OPT_A.exec('');
matchObj.groups.as //undefined
12.d修饰符:正则匹配索引
组匹配的结果,在原始字符串里面的开始位置和结束位置,目标获取并不是很方便。ES2022中新增了d修饰符,这个修饰符可以让exec()、match()的返回结果添加 indices属性,在该属性上可以拿到匹配的开始位置和结束位置。
const text = 'zabbcdef';
const re = /ab/d;
const result = re.exec(text);
result.index //1
result.indices //[1,3]
如果正则表达式包含组匹配,那么indices属性对应的数组就会包含多个成员,提供每个组匹配的开始位置和结束位置。
const text = 'zabbcdef';
const re = /ab+(cd)/d;
const result = re.exec(text);
result.indices //[[1,6],[4,6]]
如果获取组匹配不成功,indices属性数组的对应成员为undefined,indices.groups属性对象的对应成员也是undefined。
13.String.prototype.matchAll()
如果一个正则表达式在字符串里面有多个匹配,现在一般使用g修饰符或者y修饰符,在循环里面逐一取出。ES2020增加了String.prototype.matchAll()方法可以一次性取出所有匹配。不过返回的是一个遍历器,而不是数组。相比于返回数组,返回遍历器的好处就在于,如果匹配结果是一个很大的数组,那么遍历器比较节省资源。
遍历器转为数组有两种方式:
-
使用 ... 运算符
-
Array.from()
7、数值的扩展
1.二进制和八进制表示法
ES6提供了二进制和八进制数值新的写法,分别是前缀0b(或0B)和0o(或0O)表示。
如果需要将0b和0o前缀的字符串数值转为十进制,要使用Number方法。
Number('0b111')
2.数值分隔符
ES2021允许JavaScript的数值使用下划线作为分隔符。
使用的时候有以下几个需要注意的点:
-
不能放在数值的最前面或最后面
-
不能两个或两个以上的分隔符连在一起
-
小数点的前后不能有分隔符
-
科学计数法里,表示指数的e或E前后不能有分隔符
-
分隔符不能紧跟着0b、0B、0o、0O、0x、0X
3.Number.isFinite(),Number.isNaN()
ES6在Number对象上,新提供了Number.isFinite()和Number.isNaN()两个方法。
Number.isFinite()用来检查一个数值是否是有限的。
只要参数类型不是数值,一律返回false。
Number.isNaN()用来检查一个值是否为NaN。
如果参数类型不是NaN,Number.isNaN一律返回false。
4.Number.parseInt(),Number.parseFloat()
ES6将全局方法parseInt()和parseFloat()移植到Number对象上面,行为完全保持不变。
这样做的目的,是逐步减少全局性方法,使得语言逐步模块化。
5.Number.isInteger()
Number.isInteger()用来判断一个数值是否为整数。
如果参数不是数值,Number.isInteger()返回false。
6.Number.EPSILON
ES6在Number对象上面,新增了一个极小的常量Number.EPSILON。根据规格,他表示1与大于1的最小浮点数之间的差。
7.安全整数和Number.isSafeInteger()
ES6中引入了Number.MAX_SAFE_INTEGER
和Number.MIN_SAFE_INTEGER
这两个常量,用来表示-2^53到2^53这个范围的上下限。
Number.isSafeInteger()则用来判断一个整数是否落在这个范围之内。
8.Math对象的扩展
ES6中在Math对象上新增了17个与数学相关的方法,并且这些方法都是静态方法,只能在Math对象上调用。
Math.trunc()
该方法用于去除一个数的小数部分,返回整数部分。
对于非数值,Math.trunc内部使用Number方法将其先转为数值。
对于空值和无法截取整数的值,返回NaN
Math.sign()
该方法用来判断一个数到底是正数、负数、还是零。对于非数值,会将其转换为数值。
-
参数为整数,返回+1;
-
参数为负数,返回-1;
-
参数为0,返回0;
-
参数为-0,返回-0;
-
其他值,返回NaN;
Math.cbrt()
Math.cbrt()方法用于计算一个数的立方根。
对于非数值,Math.cbrt()内部也是先使用Number()方法将其转为数值。
Math.clz32()
该方法将参数转为32位无符号整数的形式,然后返回这个32位值里面有多少个前导0。
Math.imul()
该方法返回两个数以32位带符号整数形式相乘的结果,返回的也是一个32位的带符号的整数。
Math.fround()
该方法返回一个数的32位单精度浮点数形式。
Math.hypot()
该方法返回所有参数的平方和的平方根。
如参数不是数值,会将其转为数值,只要有一个参数无法转换,就会返回NaN。
对数方法
Math.expm1()
Math.expm1(x)返回eˣ - 1
Math.log1p()
Math.log1p(x)返回1+x的自然对数。
如x小于-1,返回NaN。
Math.log10()
Math.log10(x)返回以10为底的对数。
如x小于0,则返回NaN。
Math.log2()
Math.log2(x)返回以2为底的x的对数。
如x小于0,则返回NaN。
双曲函数方法
ES6中新增6个双曲函数方法。
-
Math.sinh(x)
返回x
的双曲正弦 -
Math.cosh(x)
返回x
的双曲余弦 -
Math.tanh(x)
返回x
的双曲正切 -
Math.asinh(x)
返回x
的反双曲正弦 -
Math.acosh(x)
返回x
的反双曲余弦 -
Math.atanh(x)
返回x
的反双曲正切
9.BigInt数据类型
JavaScript中对于数值精度大于53个二进制位的数或大于等于2的1024次方的数值无法表示。所以ES2020引入了一种新的数据类型BigInt来解决相关问题。
BigInt只能用来表示整数,没有位数的限制,任何位数的整数都可以精确表示。
为了与Number类型区别,BigInt类型的数据必须添加后缀n。
BigInt函数
JavaScript原生提供BigInt函数,可以用它生成BigInt类型的数值。
BigInt()函数必须有参数,并且参数必须可以正常转为数值,同时参数也不能为小数。
8、函数的扩展
1.函数参数的默认值
ES6中允许为函数的参数设置默认值,即直接写在参数定义的后面。(ES6前不能这么使用)
参数变量是默认声明的,所以不能用let或const再次声明。
function foo(x = 5){
let x = 1;//error
const x = 2;//error
}
使用参数默认值时,函数不能有同名参数。
function foo(x,x,y){
//...
}
function foo(x,x,y=1){
//...
}
//SyntaxError: Duplicate parameter name not allowed in this context
而且参数默认值是惰性求值。
let x = 99;
function foo(p = x + 1) {
console.log(p);
}
foo() // 100
x = 100;
foo() // 101
参数默认值还可以与解构赋值默认值结合起来使用。
function fetch(url, { body = '', method = 'GET', headers = {} }) {
console.log(method);
}
fetch('http://example.com', {})
// "GET"
fetch('http://example.com')
// 报错
函数的length属性
指定默认值之后,函数的length属性将返回没有指定默认值的参数个数。
(function (a){}).length //1
(function (a,b,c = 5){}).length //2
此时length属性的含义就变成了该函数预期传入的参数个数。
如果设置了默认值的参数不是尾参数,那么length属性也不会再计入后面的参数了。
(function (a = 0,b,c){}).length //0
作用域
一旦设置了参数的默认值,函数进行声明初始化时,参数会形成一个单独的作用域。等到初始化结束,这个作用域就会消失。
let x = 1;
function f(y = x) {
let x = 2;
console.log(y);
}
f() // 1
2.rest函数
ES6中引入rest参数(形式为 …变量名),用于获取函数的多余参数,这样就不需要使用arguments对象了。rest参数搭配的变量是一个数组,该变量将多余的参数放入数组中。
function add(...values) {
let sum = 0;
for (var val of values) {
sum += val;
}
return sum;
}
add(2, 5, 3) // 10
注意,rest参数之后不能再有其它参数(即只能是最后一个参数),否则会报错。
并且函数的length属性,不包括rest参数。
3.严格模式
从ES5开始,函数内部可以设定为严格模式。
function doSomething(a,b){
'use strict';
//code
}
ES2016做了一点修改,规定只要函数参数使用了默认值、解构赋值、或者扩展运算符,那么函数内部就不能显示设定为严格模式,否则会报错。
两种方法可以规避这种限制。
第一种就是设定全局性的严格模式。
'use strict';
function doSomething(a,b = a){
//code
}
第二种就是把函数包在一个无参数的立即执行函数里面。
const doSomething = (function (){
'use strict';
return function(value = 42){
return value;
};
}());
4.name属性
函数的name属性,返回该函数的函数名。
function foo(){}
foo.name //"foo"
ES6中对这个属性的行为作出了一点修改,如果将一个匿名函数赋值给一个变量,name属性会返回实际函数名。(ES5中会返回空字符串)
var f = function () {};
// ES5
f.name // ""
// ES6
f.name // "f"
5.箭头函数
ES6中允许使用箭头(=>)定义函数。
如果箭头函数不需要参数或需要多个参数,就使用一个圆括号代表参数部分。
并且如果代码块部分多于一条语句,就要使用大括号将它们括起来,并且使用return语句返回。
如果箭头函数直接返回一个对象,必须在对象外面加上括号,否则会报错。
使用注意点
-
箭头函数没有自己的this对象。
-
不可以当作构造函数,也就是说,不可以对箭头函数使用new命令,否则会抛出一个错误。
-
不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用rest参数代替。
-
不可以使用yield命令。
6.尾调用优化
尾调用指某个函数的最后一步是调用另一个函数。
尾调用优化指的就是只保留内层函数的调用帧。
function f(){
let m = 1;
let n = 2;
return g(m + n);
}
上述代码中,如果函数g 不是尾调用,函数f 就需要保存内部变量m和n的值、g的调用位置等信息。但是由于调用g之后,函数f就结束了,所以执行到最后一步,完全可以删除f(x)的调用帧,只保留g(3)的调用帧。
这里需要注意的是,只有不再用到外层函数的内部变量,内层函数的调用帧才会取代外层函数的调用帧,否则就无法进行尾调用优化。
function addOne(a){
var one = 1;
function inner(b){
return b + one;
}
return inner(a);
}
尾递归
函数尾调用自身,就称为尾递归。
一般来说,递归非常消耗内存,因为需要同时保存成千上百个调用帧,很容易发生栈溢出错误,但对于尾递归来说,由于只存在一个调用帧,所以永远不会发生栈溢出错误。
ES6中的尾调用优化只会在严格模式下开启,正常模式是无效的。
这时因为在正常模式下,函数内部有两个变量,可以跟踪函数的调用栈。
-
func.arguments
:返回调用时函数的参数。 -
func.caller
:返回调用当前函数的那个函数。
尾调用优化发生时,函数的调用栈会改写,因此上面两个变量会失真。严格模式禁止用这两个变量,所以尾调用仅在严格模式下生效。
7.Function.prototype.toString()
ES2019中规定返回一模一样的原始代码(包括注释和空格)。
8.catch命令的参数省略
ES2019中允许使用try…catch块时,省略catch语句中的参数。
9、数组的扩展
1.扩展运算符
扩展运算符三个点(…),将一个数组转为逗号分隔的参数序列,主要用于函数调用。
应用
-
复制数组
扩展运算符提供了复制数组的简便写法。
const a1 = [1,2]; //写法一 const a2 = [...a1]; //写法二 const [...a2] = a1;
-
合并数组
扩展运算符提供了数组合并的新写法
const arr1 = ['a','b']; const arr2 = ['c']; const arr3 = ['d','e']; //ES5的合并数组 arr1.concat(arr2,arr3); //['a','b','c','d','e'] //ES6的合并数组 [...arr1,...arr2,...arr3] //['a','b','c','d','e']
不过这两种方法都是浅拷贝
-
与解构赋值结合
扩展运算符可以与解构赋值结合起来,用于生成数组。
const [first,...rest] = [1,2,3,4,5]; first //1 rest //[2,3,4,5]
如果将扩展运算符用于数组赋值,只能放在参数的最后一位,否则会报错。
-
字符串
扩展运算符还可以将字符串转为真正的数组。
[...'hello'] //["h","e","l","l","o"]
-
实现了Iterator接口的对象
任何定义了遍历器接口的对象,都可以用扩展运算符转为真正的数组。
let nodeList = document.querySelectorAll('div'); let array = [...nodeList];
-
Map和Set结构,Generator函数
扩展运算符内部调用的是数据结构的Iterator接口,因此只要具有Iterator接口的对象,都可以使用扩展看运算符。
let map = new Map([ [1,'one'], [2,'two'], [3,'three'], ]); let arr = [...map.keys()];//[1,2,3] const go = function*(){ yield 1; yield 2; yield 3; }; [...go()] //[1,2,3]
2.Array.from()
Array.from()方法用于将两类对象转为真正的数组:类似数组的对象和可遍历的对象。
//NodeList对象
let ps = document.querySelectorAll('p');
Array.from(ps).filter(p => {
return p.textContent.length > 100;
});
扩展运算符也可以将某些数据结构转为数组,背后调用的是遍历器接口,如果一个对象没有部署这个接口,就无法转换。Array.from()还支持类似数组的对象(必须有length属性)。
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()的另一个应用可以将字符串转为数字,返回字符串的长度。(这样可以有效避免JavaScript将大于\uFFFF的Unicode字符算作两个字符)
function countSymbols(string){
return Array.from(string).length;
}
3.Array.of()
Array.of()方法用于将一组值,转换为数组。
Array.of()总是返回参数值组成的数组,如果没有参数,就返回一个空数组。基本上可以替代Array()或new Array()。
4.实例方法: copyWithin()
数组实例的copyWithin()
方法,在当前数组内部,将指定位置的成员复制到其他位置(会覆盖原有成员),然后返回当前数组。也就是说,使用这个方法会修改当前数组。
Array.prototype.copyWithin(target,start = 0,end = this.length)
方法接受三个参数:
-
target(必需): 从该位置开始替换数据。如果为负数,表示倒数。
-
start(可选):从该位置开始读取数据,默认为0。如果为负值,表示从末尾开始计算。
-
end(可选):到该位置前停止读取数据,默认等于数组长度。如果为负值,表示从末尾开始计算。
[1,2,3,4,5].copyWithin(0,3,4)
//表示将3号位复制到0号位
//[4,2,3,4,5]
[1,2,3,4,5].copyWithin(0,-2,-1)
//还是表示将3号位复制到0号位
//[4,2,3,4,5]
5.实例方法:find(),findIndex(),findLast(),findLastIndex()
数组实例的find()方法,用于找出第一个符合条件的数组成员。它的参数是一个回调函数,所有数组成员依次执行该回调函数,直到找出第一个返回值为true的成员,然后返回该成员。如果没有符合条件的成员,则返回undefined。
数组实例的findIndex()方法与find()方法非常类似,返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回-1。
并且这两个方法都可以接受第二个参数,用来绑定回调函数的this对象。
function f(v){
return v > this.age;
}
let person = {name:'John',age:20};
[10,12,26,15].find(f,person);//26
另外这两个方法都可以发现NaN,弥补数组的indexOf()方法的不足。
findLast()和findLastIndex()这两个方法就是从数组的最后一个成员开始,依次向前检查,其他和上述两个方法一样。
6.实例方法:fill()
fill方法使用给定值,填充一个数组。
fill方法用于空数组的初始化非常方便。数组中已有的元素,会被全部抹去。
fill方法还可以接受第二个和第三个参数,用于指定填充的起始位置和结束位置。
7.实例方法:entries(),keys()和values()
ES6中提供三个新的方法用于遍历数组,分别是entries(),keys()和values(),唯一的区别就是keys()是对键名的遍历、values()是对键值的遍历,entries()是对键值对的遍历。三者都可以使用for...of循环进行遍历,或者手动调用遍历器对象的next方法进行遍历。
8.实例方法:includes()
Array.prototype.includes方法返回一个布尔值,表示某个数组是否包含给定的值,与字符串的includes方法类似。
第二个参数表示搜索的起始位置,默认为0,如果为负数,表示为倒数位置。
9.实例方法:flat(),flatMap()
数组的成员有时还是数组,Array.prototype.flat()用于将嵌套的数组“拉平”,变成一维的数组。该方法返回一个新数组,对原数据没有影响。
flat()默认只会“拉平”一层,如果想要“拉平”多层的嵌套数组,可以将flat()方法的参数写成一个整数,表示想要拉平的层数,默认为1。
如果不管有多少层嵌套,都要转为一维数组,可以用Infinity关键字作为参数。
[1,[2,[3]]].flat(Infinity)
//[1,2,3]
并且原数组有空位,flat()方法会跳过空位。
flatMap()方法对原数组的每个成员执行一个函数(相当于执行Array.prototype.map()),然后对返回值组成的数组执行flat()方法。该方法返回一个新数组,不改变原数组。
[2,3,4].flatMap((x) => [x,x * 2])
//相当于[[2,4],[3,6],[4,8]].flat()
//[2,4,3,6,4,8]
flatMap()只能展开一层数组。flatMap()方法的参数是一个遍历函数,该函数可以接受三个参数,分别是当前数组成员、当前数组成员的位置(从零开始)、原数组。flatMap()方法还可以接受第二个参数,用来绑定遍历函数里面的this。
10.实例方法:at()
ES2022为数组实例增加了at()方法,接受一个整数作为参数,返回对应位置的成员,并支持负索引。这个方法不仅可用于数组,也可以用于字符串和类型数组。如果参数位置超出了数组范围,at()返回undefined。
11.实例方法:toReversed(),toSorted(),toSpliced(),with()
很多数组的传统方法会改变原数组,现在有一个提案,允许对数组进行操作时,不改变原数组,而返回一个原数组的拷贝。
这样的方法有四个:
-
Array.prototype.toReversed() -> Array
-
Array.prototype.toSorted(compareFn) -> Array
-
Array.prototype.toSpliced(start, deleteCount, ...items) -> Array
-
Array.prototype.with(index, value) -> Array
它们分别对应数组的原有方法:
-
toReversed()
对应reverse()
,用来颠倒数组成员的位置。 -
toSorted()
对应sort()
,用来对数组成员排序。 -
toSpliced()
对应splice()
,用来在指定位置,删除指定数量的成员,并插入新成员。 -
with(index, value)
对应splice(index, 1, value)
,用来将指定位置的成员替换为新的值。
12.实例方法:group(),groupToMap()
group()和groupMap()这两个方法可以根据分组函数的运行结果将数组成员分组。
group()的参数是一个分组函数,原数组的每个成员都会依次执行这个函数,确认自己是哪一个组。
const array = [1, 2, 3, 4, 5];
array.group((num, index, array) => {
return num % 2 === 0 ? 'even': 'odd';
});
// { odd: [1, 3, 5], even: [2, 4] }
group()的分组函数接受三个参数,依次是数组的当前成员、该成员的位置序号、原数组。分组函数的返回值应该是字符串以作为分组后的组名。
group()的返回值是一个对象,该对象的键名就是每一组的组名,键值是一个数组,包括所有产生当前键名的原数组的成员。
groupToMap()的作用和用法与group()完全一致,唯一的区别是返回值是一个Map结构,而不是对象。Map结构的键名可以是各种值。
总之,按照字符串分组就使用group(),按照对象分组就使用groupToMap()。
13.数组的空位
ES6明确将空位转为undefined,但是空位的处理规则非常不统一,所以建议避免出现空位。
14.Array.prototype.sort()的排序稳定性
排序稳定性是排序算法的重要属性,指的是排序关键字相同的项目,排序前后的顺序不变。
ES2019中明确规定Array.prototype.sort()的默认排序算法必须稳定。
10、对象的扩展
1.属性的简洁表示法
ES6允许在大括号里面,直接写入变量和函数,作为对象的属性和方法。除了属性简写,方法也可以简写。
2.属性名表达式
JavaScript定义对象的属性,有两种方法。
//方法一
obj.foo = true;
//方法二
obj['a' + 'bc'] = 123;
ES5中使用字面量方式定义对象(使用大括号),只能使用方法一(标识符)定义属性。
ES6允许字面量定义对象时,用方法二(表达式)作为对象的属性名,即把表达式放在方括号内。
3.方法的name属性
函数的name属性,返回函数名。对象方法也是函数,因此也有name属性。
如果对象的方法使用了取值函数(getter)和存值函数(setter),则name属性不是在该方法上面,而是该方法的属性的描述对象的get和set属性上面,返回值是方法名前加上get和set。
有两种特殊情况:bind方法创造的函数,name属性返回bound加上原函数的名字;Function构造函数创造的函数,name属性返回anonymous。
4.属性的可枚举性和遍历
可枚举性
对象的每个属性都有一个描述对象,用来控制该属性的行为。Object.getOwnPropertyDescriptor
方法可以获取该属性的描述对象。
描述对象的enumerable
属性,称为“可枚举性”,如果该属性为false,就表示某些操作会忽略当前属性。
目前,有四个操作会忽略enumerable
为false的属性。
-
for...in
循环:只遍历对象自身的和继承的可枚举的属性。 -
Object.keys()
:返回对象自身的所有可枚举的属性的键名。 -
JSON.stringify()
:只串行化对象自身的可枚举的属性。 -
Object.assign()
:忽略enumerable为false的属性,只拷贝对象自身的可枚举的属性
属性的遍历
ES6一共有5种方法可以遍历对象的属性。
-
for...in
循环遍历对象自身和继承的可枚举属性(不含Symbol属性)。
-
Object.keys(obj)
返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含Symbol属性)的键名。
-
Object.getOwnPropertyNames(obj)
返回一个数组,包含对象自身的所有属性(不含Symbol属性,但是包括不可枚举属性)的键名。
-
Object.getOwnPropertySymbols(obj)
返回一个数组,包含对象自身的所有Symbol属性的键名。
-
Reflect.ownKeys(obj)
返回一个数组,包含对象自身的(不含继承的)所有键名。
5.super关键字
ES6中新增了另一个关键字super
,指向当前对象的原型对象。
super关键字表示原型对象时,只能在对象的方法之中,用在其他地方都会报错。
6.对象的扩展运算符
解构赋值
对象的解构赋值用于从一个对象取值,相当于将目标对象自身的所有可遍历的(enumerable)、但尚未被读取的属性,分配到指定的对象上面。所有的键和它们的值,都会拷贝到新对象上面。
如果等号右边是undefined或null,就会报错,因为它们无法转为对象。而且解构函数必须是最后一个参数,否则会报错。
扩展运算符
对象的扩展运算符(...)用于取出参数对象的所有可遍历属性,拷贝到当前对象之中。
7.AggregateError错误对象
AggregateError在一个错误对象里面,封装了多个错误。如果某个单一操作,同时引发了多个错误,需要同时抛出这些错误,那么就可以抛出一个AggregateError错误对象,把各种错误都放在这个对象里面。
AggregateError本身就是一个构造函数,用来生成AggregateError实例对象。其构造函数可以接受两个参数。
-
errors:数组,它的每个成员都是一个错误对象,该参数是必须的。
-
message:字符串,表示AggregateError抛出时的提示信息,该参数是可选的。
8.Error对象的cause属性
ES2022为Error对象添加了一个cause属性,可以在生成错误时,添加报错原因的描述。
它的用法是 new Error()
生成Error实例时,给出一个描述对象,该对象可以设置cause属性。
const actual = new Error('an error!',{cause: 'Error cause'});
actual.cause;//'Error cause'
cause属性可以放置任意内容,不必一定是字符串。
11、对象的新增方法
1.Object.is()
ES5中比较两个值是否相等,只有两个运算符:相等运算符(==)和严格相等运算符(===)。这两种都有缺点,前者会自动转换数据类型,后者的NaN不等于自身,以及+0等于-0。
ES6中提出的Object.is
这个新方法,它用来比较两个值是否严格相等,与严格比较运算符的行为基本一致。(解决了严格相等运算符之前存在的问题)
2.Object.assign()
Object.assign()
方法用于对象的合并,将源对象的所有可枚举属性,复制到目标对象。
其第一个参数是目标对象,后面的参数都是源对象。
如果目标对象与源对象有同名属性,或多个源对象有同名属性,则后面的属性会覆盖前面的属性。
注意点
-
浅拷贝
Object.assign()
方法实行的是浅拷贝,而不是深拷贝。如果源对象某个属性的值是对象,那么目标对象拷贝得到的是这个对象的引用。 -
同名属性的替换
一旦遇到同时属性,Object.assign()的处理方法是替换,而不是添加。
-
数组的处理
Object.assign()可以用来处理数组,但是会把数组视为对象。
-
取值函数的处理
Object.assign()只能进行值的复制,如果要复制的值是一个取值函数,那么将求值后再复制。
常见用途
-
为对象添加属性
-
为对象添加方法
-
克隆对象
-
合并多个对象
const merge = (target,...sources) => Object.assign(target,...sources);
如果希望合并返回一个新对象,可以对一个空对象合并。
-
为属性指定默认值
const DEFAULTS = { logLevel: 0, outputFormat: 'html' }; function processContent(options) { options = Object.assign({}, DEFAULTS, options); console.log(options); // ... }
3.Object.getOwnPropertyDescriptors()
ES2017引入了 Object.getOwnPropertyDescriptors
方法,返回指定对象所有自身属性(非继承属性)的描述对象。
const obj = {
foo: 123,
get bar() { return 'abc' }
};
Object.getOwnPropertyDescriptors(obj)
// { foo:
// { value: 123,
// writable: true,
// enumerable: true,
// configurable: true },
// bar:
// { get: [Function: get bar],
// set: undefined,
// enumerable: true,
// configurable: true } }
该方法引入的目的,主要是为了解决Object.assign()
无法正确拷贝get属性和set属性的问题。
const source = {
set foo(value) {
console.log(value);
}
};
const target1 = {};
Object.assign(target1, source);
Object.getOwnPropertyDescriptor(target1, 'foo')
// { value: undefined,
// writable: true,
// enumerable: true,
// configurable: true }
source对象的foo属性的值是一个赋值函数,Object.assign方法将这个属性拷贝给target1对象,结果该属性的值变成了undefined。
这时,Object.getOwnPropertyDescriptors()
方法配合Object.defineProperties()
方法就可以实现正确的拷贝。
const source = {
set foo(value) {
console.log(value);
}
};
const target2 = {};
Object.defineProperties(target2, Object.getOwnPropertyDescriptors(source));
Object.getOwnPropertyDescriptor(target2, 'foo')
// { get: undefined,
// set: [Function: set foo],
// enumerable: true,
// configurable: true }
该方法的另一个用处就是配合 Object.create()
方法,将对象属性克隆到一个新对象。(浅拷贝)
const clone = Object.create(Object.getPrototypeOf(obj),
Object.getOwnPropertyDescriptors(obj));
另外,该方法可以实现一个对象继承另一个对象。
const obj = Object.create(
prot,
Object.getOwnPropertyDescriptors({
foo: 123,
})
);
4.__proto__
属性,Object.setPrototypeOf(),Object.getPrototypeOf()
__proto__
属性
__proto__
属性用来读取或设置当前对象的原型对象。
Object.setPrototypeOf()
该方法的作用与 __proto__
相同,用来设置一个对象的原型对象,返回参数对象本身。
Object.getPrototypeOf()
该方法与Object.setPrototypeOf
方法配套使用,用于读取一个对象的原型对象。
5.Object.keys(),Object.values(),Object.entries()
Object.keys()
该方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历属性的键名。
ES2017引入了跟Object.keys
配套的Object.values
和Object.entries
,作为遍历一个对象的补充手段,供for...of循环使用。
Object.values()
Object.values
方法返回一个数组,成员是参数对象自身的(不含继承的)属性的键值。
Object.entries()
Object.entries()
方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历属性的键值对数组。
6.Object.fromEntries()
Object.fromEntries()
方法是Object.entries()
的逆操作,用于对一个键值对数组转为对象。
7.Object.hasOwn()
ES2022在Object对象上新增了一个静态方法Object.hasOwn()判断是否为自身的属性。
其可以接受两个参数,第一个是所要判断的对象,第二个是属性名。
12、运算符的扩展
1.指数运算符
ES2016新增了一个指数运算符(**)。
这个运算符的特点就是右结合,而不是左结合。
2.链判断运算符
ES2020中引入了“链判断运算符” ?.
,直接在链式调用的时候判断,左侧的对象是否为null或undefined。如果是的,就不再往下运算,而是返回undefined。
链判断运算符 ?.
有三种写法。
-
obj?.
判断对象属性是否存在 -
obj?.[expr]
判断对象属性是否存在 -
func?.(...args)
函数或对象方法是否存在
3.Null判断运算符
ES2020引入了一个新的Null判断运算符??
,它的行为类似||,但是只有运算符左侧的值为null或undefined时,才会返回右侧的值。
这个运算符的一个目的就是跟链判断运算符?.
配合使用,为null或者undefined的值设置默认值。
const text = response.settings?.headerText ?? 3;
4.逻辑赋值运算符
ES2021引入了三个新的逻辑赋值运算符,将逻辑运算符与赋值运算符进行结合。
||=
或赋值运算符、&&=
与赋值运算符、??=
Null赋值运算符,这三个运算符相当于先进行逻辑运算,然后根据运算结果,再视情况进行赋值运算。
它们的一个用途就是为变量或属性设置默认值。
//老的写法
user.id = user.id || 1;
//新的写法
user.id ||= 1;
13、Symbol
1.概述
ES6中引入了一种新的原始数据类型Symbol
,表示独一无二的值。它属于JavaScript语言的原生数据类型之一。
Symbol值通过Symbol() 函数生成,其可以接受一个字符串作为参数,表示对Symbol实例的描述。
如果想要读取这个描述就需要将Symbol显示转为字符串,即
const sym = Symbol('foo');
String(sym)//"Symbol(foo)"
sym.toString()//"Symbol(foo)"
ES2019提供了一个Symbol值的实例属性description,直接返回Symbol值的描述。
const sym = Symbol('foo');
sym.description//"foo"
2.属性名的遍历
Object.getOwnPropertySymbols()
方法可以获取指定对象的所有Symbol属性名。该方法返回一个数组,成员是当前对象的所有用作属性名的Symbol值。
3.Symbol.for(),Symbol.keyFor()
Symbol.for()
与Symbol()
z这两种写法,都会生成新的Symbol。它们的区别就是前者会被登记在全局环境中供搜索,后者不会。并且Symbol.for()不会每次调用就返回一个新的Symbol类型的值,而是会先检查给定的key是否存在,如果不存在才会新建一个值;而Symbol()每次调用都会返回一个不同的值。
Symbol.keyFor()
方法返回一个已登记的Symbol类型值的key。
*14、Set和Map数据结构
1.Set
ES6提供了新的数据结构Set,类似于数组,但是成员的值都是唯一的,没有重复的值。Set函数可以接受一个数组(或者具有iterable接口的其他数据结构)作为参数,用来初始化。
const set = new Set([1,2,3,4,4]);
[...set]
//[1,2,3,4]
上面代码展示了一种去除数组重复成员的办法。
//去除数组的重复成员
[...new Set(array)]
//去除字符串里面的重复字符
[...new Set('ababbc')].join('')
//"abc"
向Set加入值的时候,不会发生类型转换,Set内部会判断两个值是否不同。
Set的实例的属性和方法
属性:
-
Set.prototype.constructor
:构造函数,默认就是Set函数。 -
Set.prototype.size
:返回Set实例函数的成员总数。
方法(操作方法和遍历方法):
-
Set.prototype.add(value)
:添加某个值,返回Set结构本身。 -
Set.prototype.delete(value)
:删除某个值,返回一个布尔值,表示删除是否成功。 -
Set.prototype.has(value)
:返回一个布尔值,表示该值是否为Set的成员。 -
Set.prototype.clear()
:清除所有成员,没有返回值。
-
Set.prototype.keys()
:返回键名的遍历器 -
Set.prototype.values()
:返回键值的遍历器 -
Set.prototype.entries()
:返回键值对的遍历器 -
Set.prototype.forEach()
:使用回调函数遍历每个成员
如果想要在遍历操作中,同步改变原来的Set结构,没有直接的办法,有两种变通的方法:
//方法一
let set = new Set([1,2,3]);
set = new Set([...set].map(val => val * 2));
//set的值是2,4,6
//方法二
let set = new Set([1,2,3]);
set = new Set(Array.from(set,val => val * 2));
2.Map
实例的属性和操作方法
-
size属性:返回Map结构的成员总数
-
Map.prototype.set(key,value)
:set方法设置键名对应的值,返回整个Map结构。 -
Map.prototype.get(key)
:get方法读取key对应的键值。 -
Map.prototype.has(key)
: has方法返回一个布尔值,表示某个值是否在当前Map对象之中。 -
Map.prototype.delete(key)
:delete方法删除某个键,返回true。 -
Map.prototype.clear()
:clear方法清楚所有成员,没有返回值。
遍历方法
-
Map.prototype.keys()
:返回键名的遍历器。 -
Map.prototype.values()
:返回键值的遍历器。 -
Map.prototype.entries()
:返回所有成员的遍历器。 -
Map.prototype.forEach()
:遍历 Map 的所有成员。