整理自阮一峰 ECMAScript 入门html
<ul>
标签前面会有一个换行。若是你不想要这个换行,可使用trim方法消除它。$('#list').html(` <ul> <li>first</li> <li>second</li> </ul> `.trim()); 复制代码
let s = 'Hello world!'; s.startsWith('world', 6) // true s.endsWith('Hello', 5) // true s.includes('Hello', 6) // false 复制代码
上面代码表示,使用第二个参数n时,endsWith的行为与其余两个方法有所不一样。它针对前n个字符,而其余两个方法针对从第n个位置直到字符串结束。node
repeat方法返回一个新字符串,表示将原字符串重复n次。es6
'x'.repeat(3) // "xxx" 'hello'.repeat(2) // "hellohello" 'na'.repeat(0) // "" 复制代码
ES2017 引入了字符串补全长度的功能。若是某个字符串不够指定长度,会在头部或尾部补全。padStart()用于头部补全,padEnd()用于尾部补全。 padStart()和padEnd()一共接受两个参数,第一个参数是字符串补全生效的最大长度,第二个参数是用来补全的字符串。正则表达式
'x'.padStart(4, 'ab') // 'abax' 'x'.padEnd(5, 'ab') // 'xabab' 复制代码
padStart()的常见用途是为数值补全指定位数。下面代码生成 10 位的数值字符串。算法
'1'.padStart(10, '0') // "0000000001" '12'.padStart(10, '0') // "0000000012" '123456'.padStart(10, '0') // "0000123456" 复制代码
ES2019 对字符串实例新增了trimStart()和trimEnd()这两个方法。它们的行为与trim()一致,trimStart()消除字符串头部的空格,trimEnd()消除尾部的空格。它们返回的都是新字符串,不会修改原始字符串。编程
const s = ' abc '; s.trim() // "abc" s.trimStart() // "abc " s.trimEnd() // " abc" 复制代码
matchAll()方法返回一个正则表达式在当前字符串的全部匹配。 若是一个正则表达式在字符串里面有多个匹配,如今通常使用g修饰符或y修饰符,在循环里面逐一取出。ES6增长了String.prototype.matchAll()方法,能够一次性取出全部匹配。不过,它返回的是一个遍历器(Iterator),而不是数组。数组
const string = 'test1test2test3'; // g 修饰符加不加均可以 const regex = /t(e)(st(\d?))/g; for (const match of string.matchAll(regex)) { console.log(match); } // ["test1", "e", "st1", "1", index: 0, input: "test1test2test3"] // ["test2", "e", "st2", "2", index: 5, input: "test1test2test3"] // ["test3", "e", "st3", "3", index: 10, input: "test1test2test3"] 复制代码
遍历器转为数组是很是简单的,使用...运算符和Array.from()方法就能够了。安全
// 转为数组方法一
[...string.matchAll(regex)]
// 转为数组方法二
Array.from(string.matchAll(regex))
复制代码
ES6 提供了二进制和八进制数值的新的写法,分别用前缀0b(或0B)和0o(或0O)表示。bash
0b111110111 === 503 // true 0o767 === 503 // true 复制代码
若是要将0b和0o前缀的字符串数值转为十进制,要使用Number方法。markdown
Number('0b111') // 7 Number('0o10') // 8 复制代码
Number对象能够将任何数据类型转换成数值,包括 布尔值,日期,二进制数,八进制数,数字字符串(parseInt,parseFloat只能够转化数字字符串)。
Number 对象的方法
指数运算符(**
) 这个运算符的一个特色是右结合,而不是常见的左结合。多个指数运算符连用时,是从最右边开始计算的。 例如:
// 至关于 3 ** (3 ** 2)
3 ** 3 ** 2
// 19683
复制代码
指数运算符能够与等号结合,造成一个新的赋值运算符(**=)
let a = 1.5; a **= 2; // 等同于 a = a * a; let b = 4; b **= 3; // 等同于 b = b * b * b; 复制代码
ES6 容许为函数的参数设置默认值,即直接写在参数定义的后面。
function log(x, y = 'World') { console.log(x, y); } 复制代码
tip:一般状况下,定义了默认值的参数,应该是函数的尾参数。由于这样比较容易看出来,到底省略了哪些参数。若是非尾部的参数设置默认值,实际上这个参数是无法省略的。
rest 参数与扩展运算符实际上是同一种东西,就是... 只不过使用场景不一样,他们的名字也不同。
function sumRest (...m) {
{a,b,...m}
console.log(...array);
var f = v => v;
若是箭头函数的代码块部分多于一条语句,就要使用大括号将它们括起来,而且使用return语句返回。 因为大括号被解释为代码块,因此若是箭头函数直接返回一个对象,必须在对象外面加上括号,不然会报错。
let getTempItem = id => ({ id: id, name: "Temp" }); 复制代码
注意点 箭头函数体内的this对象,就是定义时所在的对象,这个this是固定的,注意它没有本身的this。
function foo() { setTimeout(() => { console.log('id:', this.id); }, 100); } var id = 21; foo.call({ id: 42 }); // id: 42 复制代码
上面代码中,setTimeout的参数是一个箭头函数,这个箭头函数的定义生效是在foo函数生成时,而它的真正执行要等到 100 毫秒后。若是是普通函数,执行时this应该指向全局对象window,这时应该输出21。可是,箭头函数致使this老是指向函数定义生效时所在的对象(本例是{id: 42}),因此输出的是42。 箭头函数转成 ES5 的代码以下:
// ES6 function foo() { setTimeout(() => { console.log('id:', this.id); }, 100); } // ES5 function foo() { var _this = this; setTimeout(function () { console.log('id:', _this.id); }, 100); } 复制代码
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' ] 复制代码
须要注意的是:这两种方法都是浅拷贝,使用的时候须要注意。合并而成的新数组,它们的成员都是对原数组成员的引用,这就是浅拷贝。若是修改了引用指向的值,会同步反映到新数组。
任何定义了遍历器(Iterator)接口的对象(参阅 Iterator 一章),均可以用扩展运算符转为真正的数组。
let nodeList = document.querySelectorAll('div'); let array = [...nodeList]; 复制代码
上面代码中,querySelectorAll方法返回的是一个NodeList对象。它不是数组,而是一个相似数组的对象。这时,扩展运算符能够将其转为真正的数组,缘由就在于NodeList对象实现了 Iterator 。
数组的成员有时仍是数组,Array.prototype.flat()用于将嵌套的数组“拉平”,变成一维的数组。该方法返回一个新数组,对原数据没有影响。
[1, 2, [3, 4]].flat()
// [1, 2, 3, 4]
复制代码
上面代码中,原数组的成员里面有一个数组,flat()方法将子数组的成员取出来,添加在原来的位置。
flat()默认只会“拉平”一层,若是想要“拉平”多层的嵌套数组,能够将flat()方法的参数写成一个整数,表示想要拉平的层数,默认为1。 若是flat()的参数为2,表示要“拉平”两层的嵌套数组。 若是无论有多少层嵌套,都要转成一维数组,能够用Infinity关键字做为参数。
[1, 2, [3, [4, 5]]].flat(2)
// [1, 2, 3, 4, 5]
[1, [2, [3]]].flat(Infinity)
// [1, 2, 3]
复制代码
编程实务中,若是读取对象内部的某个属性,每每须要判断一下该对象是否存在。好比,要读取message.body.user.firstName,安全的写法是写成下面这样。
const firstName = (message && message.body && message.body.user && message.body.user.firstName) || 'default'; 或 const fooInput = myForm.querySelector('input[name=foo]') const fooValue = fooInput ? fooInput.value : undefined 复制代码
这样的层层判断很是麻烦,所以 ES2020 引入了“链判断运算符”(optional chaining operator)?.,简化上面的写法。
const firstName = message?.body?.user?.firstName || 'default'; const fooValue = myForm.querySelector('input[name=foo]')?.value 复制代码
上面代码使用了?.运算符,直接在链式调用的时候判断,左侧的对象是否为null或undefined。若是是的,就再也不往下运算,而是返回undefined。
链判断运算符有三种用法。
obj?.prop // 对象属性
obj?.[expr] // 同上
func?.(...args) // 函数或对象方法的调用
复制代码
下面是判断对象方法是否存在,若是存在就当即执行的例子。
iterator.return?.()
复制代码
上面代码中,iterator.return若是有定义,就会调用该方法,不然直接返回undefined。
对于那些可能没有实现的方法,这个运算符尤为有用。
if (myForm.checkValidity?.() === false) { // 表单校验失败 return; } 复制代码
(1). 短路机制
a?.[++x]
// 等同于
a == null ? undefined : a[++x]
复制代码
上面代码中,若是a是undefined或null,那么x不会进行递增运算。也就是说,链判断运算符一旦为真,右侧的表达式就再也不求值。 (2). delete 运算符
delete a?.b
// 等同于
a == null ? undefined : delete a.b
复制代码
(3). 括号的影响
若是属性链有圆括号,链判断运算符对圆括号外部没有影响,只对圆括号内部有影响。
(a?.b).c // 等价于 (a == null ? undefined : a.b).c 上面代码中,?.对圆括号外部没有影响,无论a对象是否存在,圆括号后面的.c老是会执行。
通常来讲,使用?.运算符的场合,不该该使用圆括号。
(4).报错场合
如下写法是禁止的,会报错。
// 构造函数
new a?.()
new a?.b()
// 链判断运算符的右侧有模板字符串
a?.`{b}`
a?.b`{c}`
// 链判断运算符的左侧是 super
super?.()
super?.foo
// 链运算符用于赋值运算符左侧
a?.b = c
复制代码
(5).右侧不得为十进制数值
为了保证兼容之前的代码,容许foo?.3:0被解析成foo ? .3 : 0,所以规定若是?.后面紧跟一个十进制数字,那么?.再也不被当作是一个完整的运算符,而会按照三元运算符进行处理,也就是说,那个小数点会归属于后面的十进制数字,造成一个小数。
读取对象属性的时候,若是某个属性的值是null或undefined,有时候须要为它们指定默认值。常见作法是经过||运算符指定默认值。
const headerText = response.settings.headerText || 'Hello, world!'; const animationDuration = response.settings.animationDuration || 300; const showSplashScreen = response.settings.showSplashScreen || true; 复制代码
上面的三行代码都经过||运算符指定默认值,可是这样写是错的。开发者的原意是,只要属性的值为null或undefined,默认值就会生效,可是属性的值若是为空字符串或false或0,默认值也会生效。
为了不这种状况,ES2020 引入了一个新的 Null 判断运算符??。它的行为相似||,可是只有运算符左侧的值为null或undefined时,才会返回右侧的值。
const headerText = response.settings.headerText ?? 'Hello, world!'; const animationDuration = response.settings.animationDuration ?? 300; const showSplashScreen = response.settings.showSplashScreen ?? true; 复制代码
上面代码中,默认值只有在属性值为null或undefined时,才会生效。
这个运算符的一个目的,就是跟链判断运算符?.配合使用,为null或undefined的值设置默认值。
const animationDuration = response.settings?.animationDuration ?? 300;
复制代码
上面代码中,response.settings若是是null或undefined,就会返回默认值300。
ES5 比较两个值是否相等,只有两个运算符:相等运算符(==)和严格相等运算符(===)。它们都有缺点,前者会自动转换数据类型,后者的NaN不等于自身,以及+0等于-0。JavaScript 缺少一种运算,在全部环境中,只要两个值是同样的,它们就应该相等。
ES6 提出“Same-value equality”(同值相等)算法,用来解决这个问题。Object.is就是部署这个算法的新方法。它用来比较两个值是否严格相等,与严格比较运算符(===)的行为基本一致。
Object.is('foo', 'foo') // true Object.is({}, {}) // false 复制代码
不一样之处只有两个:一是+0不等于-0,二是NaN等于自身。
+0 === -0 //true NaN === NaN // false Object.is(+0, -0) // false Object.is(NaN, NaN) // true 复制代码
ES5 引入了Object.keys方法,返回一个数组,成员是参数对象自身的(不含继承的)全部可遍历(enumerable)属性的键名。
var obj = { foo: 'bar', baz: 42 }; Object.keys(obj) // ["foo", "baz"] 复制代码
Object.values方法返回一个数组,成员是参数对象自身的(不含继承的)全部可遍历(enumerable)属性的键值。
const obj = { foo: 'bar', baz: 42 }; Object.values(obj) // ["bar", 42] 复制代码
返回数组的成员顺序为:
例如:
const obj = { 100: 'a', 2: 'b', 7: 'c' }; Object.values(obj) // ["b", "c", "a"] 复制代码
上面代码中,属性名为数值的属性,是按照数值大小,从小到大遍历的,所以返回的顺序是b、c、a。
规则以下:
Object.values('foo') // ['f', 'o', 'o'] 复制代码
Object.values(42) // [] Object.values(true) // [] 复制代码
Object.entries()方法返回一个数组,成员是参数对象自身的(不含继承的)全部可遍历(enumerable)属性的键值对数组。
const obj = { foo: 'bar', baz: 42 }; Object.entries(obj) // [ ["foo", "bar"], ["baz", 42] ] 复制代码
除了返回值不同,该方法的行为与Object.values基本一致。
let obj = { one: 1, two: 2 }; for (let [k, v] of Object.entries(obj)) { console.log( `${JSON.stringify(k)}: ${JSON.stringify(v)}` ); } // "one": 1 // "two": 2 复制代码
const obj = { foo: 'bar', baz: 42 }; const map = new Map(Object.entries(obj)); map // Map { foo: "bar", baz: 42 } 复制代码
Object.fromEntries()方法是Object.entries()的逆操做,用于将一个键值对数组转为对象。
Object.fromEntries([ ['foo', 'bar'], ['baz', 42] ]) // { foo: "bar", baz: 42 } 复制代码
该方法的主要目的,是将键值对的数据结构还原为对象,所以特别适合将 Map 结构转为对象。
// 例一 const entries = new Map([ ['foo', 'bar'], ['baz', 42] ]); Object.fromEntries(entries) // { foo: "bar", baz: 42 } // 例二 const map = new Map().set('foo', true).set('bar', false); Object.fromEntries(map) // { foo: true, bar: false } 复制代码
该方法的一个用处是配合URLSearchParams对象,将查询字符串转为对象。
Object.fromEntries(new URLSearchParams('foo=bar&baz=qux')) // { foo: "bar", baz: "qux" } 复制代码
ES6 提供了新的数据结构 Set。它相似于数组,可是成员的值都是惟一的,没有重复的值。 Set自己是一个构造函数,用来生成 Set 数据结构。 Set 接收一个数组或者类数组做为参数,用来初始化。
// 例一 const set = new Set([1, 2, 3, 4, 4]); [...set] // [1, 2, 3, 4] // 可使用 Set 来给数组去重 // 例三 const set = new Set(document.querySelectorAll('div')); set.size // 56 复制代码
上面的代码也展现了去除数组重复成员的方法
// 去除数组的重复成员
[...new Set(array)]
复制代码
也能够用来去除字符串里面的重复字符串
[...new Set('ababbc')].join('') // "abc" 复制代码
Array.from 方法也能够将 Set结构转换为数组,这就提供了去重的另外一种方法
const items = new Set([1, 1, 2, 3, 3, 4, 5]);
const array = Array.from(items);
复制代码
Set 结构的四个遍历方法
let set = new Set(['red', 'green', 'blue']); for (let item of set.keys()) { console.log(item); } // red // green // blue for (let item of set.values()) { console.log(item); } // red // green // blue for (let item of set.entries()) { console.log(item); } // ["red", "red"] // ["green", "green"] // ["blue", "blue"] 复制代码
Map 数据结构,它相似于对象,也是键值对的集合,可是“键”的范围不限于字符串,各类类型的值(包括对象)均可以看成键。也就是说,Object 结构提供了“字符串—值”的对应,Map 结构提供了“值—值”的对应,是一种更完善的 Hash 结构实现。若是你须要“键值对”的数据结构,Map 比 Object 更合适。
Map 函数接收数组,或者类数组(每一个成员都是一个双元素的数组,看成Map构造函数的参数,这就是说,Set 和 Map 均可以用来生成新的 Map。
const set = new Set([ ['foo', 1], ['bar', 2] ]); const m1 = new Map(set); m1.get('foo') // 1 const m2 = new Map([['baz', 3]]); const m3 = new Map(m2); m3.get('baz') // 3 复制代码
若是对同一个键屡次赋值,后面的值将覆盖前面的值。
const map = new Map(); map .set(1, 'aaa') .set(1, 'bbb'); map.get(1) // "bbb" 复制代码
注意,只有对同一个对象的引用,Map 结构才将其视为同一个键。这一点要很是当心。
const map = new Map(); map.set(['a'], 555); map.get(['a']) // undefined 复制代码
上面代码的set和get方法,表面是针对同一个键,但实际上这是两个不一样的数组实例,内存地址是不同的,所以get方法没法读取该键,返回undefined。
const map = new Map(); const list = ['a'] map.set(list, 555); map.get(list) // 555 复制代码
将数组赋值给一个变量后,就能够获得想象中的结果了。
若是 Map 的键是一个简单类型的值(数字、字符串、布尔值),则只要两个值严格相等,Map 将其视为一个键,好比0和-0就是一个键,布尔值true和字符串true则是两个不一样的键。另外,undefined和null也是两个不一样的键。虽然NaN不严格相等于自身,但 Map 将其视为同一个键
let map = new Map(); map.set(-0, 123); map.get(+0) // 123 map.set(true, 1); map.set('true', 2); map.get(true) // 1 map.set(undefined, 3); map.set(null, 4); map.get(undefined) // 3 map.set(NaN, 123); map.get(NaN) // 123 复制代码