字符串的扩展 ES6 加强了对 Unicode 的支持,并且扩展了 String 字符串对象,解决了 ES5 在字符串功能上的痛点。
最佳实践:静态字符串一律使用单引号,不使用双引号。动态字符串使用模板字符串。
模板字符串 模板字符串第一个用途:字符串拼接。将表达式嵌入字符串中进行拼接,用 ``和\${}
来界定。这使得事情变得更简单,代码更容易阅读。你可以在花括号内放置任何东西:变量、方程式或函数调用。
1 2 3 4 5 6 7 // es5 var name1 = "bai"; console.log('hello ' + name1); // es6 const name2 = "ming"; console.log(`hello ${name2}`);
模板字符串第二个用途:在 ES5 时我们通过反斜杠来做多行字符串拼接。ES6 模板字符串直接搞定。
1 2 3 4 5 6 7 8 // es5 var msg = "Hi \ man!"; // es6 const template = `<div> <span>hello world</span> </div>`;
模板字符串之中还能调用函数:
1 2 3 4 5 function fn() { return "Hello World"; } console.log(`输出值为:${fn()}`) // "输出值为:Hello World"
字符串扩展方法 传统上,JavaScript 只有 indexOf 方法,可以用来确定一个字符串是否包含在另一个字符串中。ES6 又提供了三种新方法。
includes():返回布尔值,判断数组、字符串是否包含某个元素。
startsWith():返回布尔值,表示参数字符串是否在原字符串的头部。
endsWith():返回布尔值,表示参数字符串是否在原字符串的尾部。
includes() 可以代替 indexOf(),endsWith() 可以用来检测文件后缀
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 // includes: 判断是否包含然后返回布尔值 'hahah'.includes('y') // false ['1','y'].includes('y') // true // ES5 indexOf let arr = ['react', 'angular', 'vue']; if (arr.indexOf('react') !== -1) { console.log('React存在'); } // ES6 includes let arr = ['react', 'angular', 'vue']; if (arr.includes('react')) { console.log('React存在'); }
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 // repeat: 字符串重复n次 console.log('he'.repeat(3)); // 'hehehe' // 开始字符 console.log('abcd'.startsWith('aa')); // false // 结束字符 console.log('abcd'.endsWith('cd')); // true // padStart、padEnd 长度不足就补 let date = new Date(); let month = date.getMonth() + 1; console.log(month.toString().padStart(2, '0')); // 06 console.log('123'.padStart(11, '0')); // 00000000123 console.log('123'.padEnd(11, '0')); // 12300000000 // padStart ES5实现 padStart(num, n = 2, str = '0') { let len = num.toString().length while (len < n) { num = str + num len++ } return num }
正则的扩展 y 修饰符 y 修饰符的作用与 g 修饰符类似,也是全局匹配,后一次匹配都从上一次匹配成功的下一个位置开始。不同之处在于,g 修饰符只要剩余位置中存在匹配就可,而 y 修饰符确保匹配必须从剩余的第一个位置开始
1 2 3 4 5 let s= 'bbb_bb_b'; let reg1 = /b+/g; let reg2 = /b+/y; console.log(reg1.exec(s), reg2.exec(s)); // ["bbb", index: 0, input: "bbb_bb_b"] ["bbb", index: 0, input: "bbb_bb_b"] console.log(reg1.exec(s), reg2.exec(s)); // ["bb", index: 4, input: "bbb_bb_b"] null
u 修饰符 含义为“Unicode 模式”,用来正确处理大于\uFFFF 的 Unicode 字符。也就是说,会正确处理四个字节的 UTF-16 编码。
1 2 /^\uD83D/u.test('\uD83D\uDC2A') // false /^\uD83D/.test('\uD83D\uDC2A') // true
s 修饰符 dotAll 模式
数值的扩展 Number 新增数值方法 注意 Number.isFinite() 和 Number.isNaN() 与传统的全局方法 isFinite() 和 isNaN() 的区别。
1 2 3 4 5 6 7 8 9 // isFinite: 判断一个数值是否是有尽的 console.log(Number.isFinite(100)); // true console.log(Number.isFinite(100 / 0)); // false console.log(Number.isFinite(NaN)); // false // isNaN: 判断一个数值是否是一个非数值 console.log(Number.isNaN(NaN)); // true console.log(Number.isNaN(100)); // false console.log(Number.isNaN(100 / 0)); // false
ES6 将全局方法 parseInt() 和 parseFloat(),移植到 Number 对象上面,行为完全保持不变。
1 2 3 4 // isInteger: 判断一个数值是否是一个整数 console.log(Number.isInteger(100)); // true console.log(Number.isInteger(100.0)); // true console.log(Number.isInteger(100.5)); // false
Math 新增数学方法 ES6 在 Math 对象上新增了 17 个与数学相关的方法。所有这些方法都是静态方法,只能在 Math 对象上调用。
Math.trunc 方法用于去除一个数的小数部分,返回整数部分。
Math.sign 方法用来判断一个数到底是正数、负数、还是零。
Math.cbrt 方法用于计算一个数的立方根。
Math.hypot 方法返回所有参数的平方和的平方根。
ES6 新增了 4 个对数相关方法。
ES6 新增了 6 个双曲函数方法。
1 2 3 4 5 6 7 8 9 10 // trunc: 返回整数部分 console.log(Math.trunc(4)); // 4 console.log(Math.trunc(4.1)); // 4 console.log(Math.trunc(-1.9)); // -1 // 返回数值符号 console.log(Math.sign(-5)); // -1 console.log(Math.sign(0)); // 0 console.log(Math.sign(5)); // 1 console.log(Math.sign('abc')); // NaN
指数运算符 ES2016 新增了一个指数运算符(**
),类似 Math.pow
1 2 3 4 5 // ES5 Math.pow(2, 3) // 8 // ES6 2 ** 3 // 8
指数运算符可以与赋值运算符结合,形成一个新的赋值运算符(**=)。
1 2 3 4 5 6 7 let a = 1.5; a **= 2; // 等同于 a = a * a; let b = 4; b **= 3; // 等同于 b = b * b * b;
数组的扩展 扩展运算符 扩展运算符是三个点(...
)。它好比 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列。
==rest 参数可以理解为聚拢,扩展运算符可以理解为“展开”==。
1 2 3 4 5 function foo(...args) { console.log(...args); } foo(1, 2, 3); // 1, 2, 3
如果将扩展运算符用于数组赋值,只能放在参数的最后一位,否则会报错。
1 const [first, ...rest] = [1, 2, 3, 4, 5];
用途:数组合并的新写法
1 2 3 4 5 6 7 8 9 const arr1 = ['a' , 'b' ]const arr2 = ['c' ]const arr3 = ['d' , 'e' ]arr1.concat(arr2, arr3) ;[...arr1, ...arr2, ...arr3]
用途:复制数组的新写法
1 2 3 4 5 6 7 8 9 10 11 const a1 = [1, 2]; // 写法一 const a2 = [...a1]; a2[0] = 2; a1 // [1, 2] // 写法二 const [...a2] = a1; a2[0] = 2; a1 // [1, 2]
用途:获取数组除了某几项的其他项
1 2 3 let num = [1, 3, 5, 7, 9]; let [first, second, ...rest] = num; rest; // [5, 7, 9]
用途:函数调用
1 2 3 4 5 6 function add(x, y) { return x + y; } var numbers = [4, 38]; add(...numbers) // 42
数组扩展方法 Array.from 方法用于将两类对象转为真正的数组:
类似数组的对象(如 String、NodeList、arguments),所谓类似数组的对象,本质特征只有一点,即必须有 length 属性。
可遍历(iterable)的对象(包括 ES6 新增的数据结构 Set 和 Map)。
1 2 3 4 5 6 7 8 9 10 11 12 let arrayLike = { 0 : 'a' , 1 : 'b' , 2 : 'c' , length: 3 , } var arr1 = [].slice.call(arrayLike) let arr2 = Array .from(arrayLike)
Array.of 方法用于将一组值,转换为数组。Array.of 基本上可以用来替代 Array()或 new Array(),并且不存在由于参数不同而导致的重载。它的行为非常统一。
1 2 console.log(Array.of()); // [] console.log(Array.of(1, 3, 5, 7, 9)); // [1, 3, 5, 7, 9]
数组实例的 find 方法,用于找出第一个符合条件的数组成员。它的参数是一个回调函数,所有数组成员依次执行该回调函数,直到找出第一个返回值为 true 的成员,然后返回该成员。如果没有符合条件的成员,则返回 undefined
数组实例的 findIndex 方法的用法与 find 方法非常类似,返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回-1。
1 2 console.log([1, 4, -5, -1, 10].find((n) => n < 0)); // -5 console.log([1, 4, -5, -1, 10].findIndex((n) => n < 0)); // 2
fill 方法使用给定值,填充一个数组。常用于空数组的初始化。
1 2 console.log(['a', 'b', 'c'].fill(7)); // [7, 7, 7] console.log(new Array(3).fill(7)); // [7, 7, 7]
entries(),keys()和 values()——用于遍历数组,它们都返回一个遍历器对象。keys()是对键名(索引)的遍历、values()是对键值的遍历,entries()是对键值对的遍历。可以配合 for…of 循环进行遍历。
1 2 3 4 5 6 7 8 9 10 11 for (let index of ['a', 'b'].keys()) { console.log(index); // 0 1 } for (let elem of ['a', 'b'].values()) { console.log(elem); // a b } for (let [index, elem] of ['a', 'b'].entries()) { console.log(index, elem); // 0 "a" 1 "b" }
Array.prototype.includes 方法返回一个布尔值,表示某个数组是否包含给定的值,与字符串的 includes 方法类似。
1 2 3 console.log([1, 2, 3].includes(2)); // true console.log([1, 2, 3].includes(4)); // false console.log([1, 2, NaN].includes(NaN)); // true
Array.prototype.flat 方法用于将嵌套的数组“拉平”,变成一维的数组。
对象的扩展 对象属性的简洁表示法 ES6 允许直接写入变量和函数,作为对象的属性和方法。这样的书写更加简洁。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 // ES5 var o = 1, k = 2; var obj1 = { o: o, k: k, name: 'bxm', getName: function() { console.log(this.name); } }; // ES6 let c = 1, n = 2; let obj2 = { c, // 等同于 c: c n, name: 'bai', getName() { // 等同于 getName: function() {} console.log(this.name); } };
用途:
用于函数的返回值
CommonJS 模块、ES6 模块输出一组变量
1 2 3 4 5 function getPoint() { const x = 1; const y = 10; return {x, y}; }
1 module.exports = { getItem, setItem, clear };
属性表达式 现在,对象的属性可以是一个变量或表达式
1 2 3 4 5 6 7 8 9 10 11 for (let i = 0 ; i < 5 ; i++) { let obj = { [i]: 'value' , } console .log(obj) }
如果对象的属性名是动态的,可以在创造对象的时候,使用属性表达式定义。
1 2 3 4 5 6 7 8 9 10 11 12 13 const obj = { id: 5 , name: 'San Francisco' , } obj[getKey('enabled' )] = true const obj = { id: 5 , name: 'San Francisco' , [getKey('enabled' )]: true , }
对象遍历 Object.keys(),Object.values(),Object.entries()
Object.assign() Object.assign() 又叫做==浅拷贝==,用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)。Object.assign() 的第一个参数是目标对象,后面的参数都是源对象。
1 Object .assign({ a : '1' }, { b : '2' })
注意,如果目标对象与源对象有同名属性,或多个源对象有同名属性,则后面的属性会覆盖前面的属性。
1 2 3 4 5 6 const target = { a : 1 }const source1 = { b : 2 }const source2 = { c : 3 }Object .assign(target, source1, source2)target
最佳实践:在实际项目中,我们为了不改变源对象,一般会把目标对象传为{}
1 2 3 4 const obj = Object .assign({}, objA, objB)this .seller = Object .assign({}, this .seller, response.data)
小技巧:扩展运算符也可以实现 Object.assign() 的功能:
1 2 3 4 5 6 7 let aClone = { ...a }let aClone = Object .assign({}, a)let ab = { ...a, ...b }let ab = Object .assign({}, a, b)
用途:
==为属性指定默认值,然后用来覆盖默认的配置选项(config)==
为对象添加属性和方法
合并多个对象
对象浅克隆
1 options = Object .assign({}, defaultConfig, options)
Object.getOwnPropertyDescriptors() ES2017
返回指定对象的某个指定属性的属性描述符,该属性必须是对象自己定义而不是继承自原型链
Object.getOwnPropertyDescriptors()
相当于Object.getOwnPropertyDescriptor()
的复数形式,可以获取对象的所有自身属性的描述符
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 [object Object] { configurable:当该值为true时,该属性描述符才能够被改变,同时该属性也能从对应的对象上被删除。默认为 false。 enumerable:当且仅当该属性的 enumerable 为 true时,该属性才能够出现在对象的枚举属性中。默认为 false。 value writable get set } [object Object] { configurable: true, enumerable: true, value: ["React Quickly"], writable: }
函数的扩展 函数参数的默认值 ES5 给函数定义参数默认值,使用的是短路运算,有一定缺陷和冗余。
1 2 3 4 5 6 7 8 9 10 // ES5 function foo(num) { num = num || 200; return num; } // ES6 function foo(num = 200) { return num; }
参数默认值可以与解构赋值的默认值,结合起来使用。
1 2 3 4 5 function foo({x, y = 5} = {}) { console.log(x, y); } foo() // undefined 5
通常情况下,默认值后面不能再有没有默认值的参数,即定义了默认值的参数,应该是函数的尾参数。因为这样比较容易看出来,到底省略了哪些参数。如果非尾部的参数设置默认值,实际上这个参数是没法省略的。
另外,可以将参数默认值设为 undefined,表明这个参数是可以省略的。
1 function foo(optional = undefined) { ··· }
rest 参数 ES6 引入 rest 参数(形式为...变量名
),用于获取函数的多余参数,这样就不需要使用 arguments 对象了。rest 参数搭配的变量是一个数组,该变量将多余的参数放入数组中。
注意,rest 参数之后不能再有其他参数(即只能是最后一个参数),否则会报错。
1 2 3 4 function rest(...args) { console.log(args); } rest(); // [] rest(1); // [1] rest(1,2); // [1, 2]
1 2 3 4 function foo(x, y, ...rest) { return ((x + y) * rest.length); } foo(1, 2, 'hello', true, 7); // 9
最佳实践:不要在函数体内使用 arguments 变量,使用 rest 运算符(…)代替。因为 rest 运算符显式表明你想要获取参数,而且 arguments 是一个类似数组的对象,而 rest 运算符可以提供一个真正的数组。
1 2 3 4 5 6 7 8 9 10 // bad function concatenateAll() { const args = Array.prototype.slice.call(arguments); return args.join(''); } // good function concatenateAll(...args) { return args.join(''); }
箭头函数 ES6 允许使用“箭头”(=>
)定义函数,箭头函数是函数的快捷写法,使得表达更加简洁,有以下特性。
不需要 function 关键字来创建函数
如果箭头函数不需要参数或需要多个参数,就使用一个圆括号代表参数部分;
而当你的函数有且仅有一个参数的时候,是可以省略掉括号()
的
当你函数中有且仅有一个表达式的时候,可以省略return
语句和大括号{}
;
而如果箭头函数的代码块部分多于一条语句,就要使用大括号将它们括起来,并且使用 return 语句返回。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 // ES5 var arr1 = [1, 2, 3]; var newArr1 = arr1.map(function(x) { return x + 1; }); // ES6 let arr2 = [1, 2, 3]; let newArr2 = arr2.map((x) => { x + 1 }); // 箭头函数使得表达更加简洁 let isEven = n => n % 2 == 0; let square = n => n * n; let f = () => 5; // 等同于 let f = function () { return 5 }; let f = v => v; // 等同于 let f = function (v) { return v; }; let sum = (num1, num2) => num1 + num2; // 等同于 let sum = function(num1, num2) { return num1 + num2; }; [1,2,3].map(x => x * x); // 等同于 [1,2,3].map(function (x) { return x * x; });
立即执行函数可以写成箭头函数的形式。
1 2 3 (() => { console.log('Welcome to the Internet.'); })();
箭头函数中的 this 绑定 箭头函数让复杂的 this 变得清晰:
==箭头函数内的 this 对象,就是定义时所在的对象,而不是使用时所在的对象。即,this 对象的指向是可变的,但是在箭头函数中,它是固定的。==
箭头函数中的 this 继承当前上下文的 this 关键字,即箭头函数中 this 的作用域和外面是一样的,==并不产生新的作用域==。
本质上,箭头函数里面根本没有自己的 this,而是引用外层的 this。
箭头函数可以绑定 this 对象,大大减少了显式绑定 this 对象的写法(call、apply、bind)。
箭头函数取代 Function.prototype.bind,不应再用 self/_this/that 绑定 this。
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 // 。运行下面的代码会报错,这是因为setTimeout中的this指向的是全局对象。 class Animal { constructor() { this.type = 'animal'; } says(say) { setTimeout(function() { console.log(this.type + ' says ' + say); }, 1000); } } var animal = new Animal(); animal.says('hi'); //undefined says hi // 解决办法: // 传统方法1: 将this传给self,再用self来指代this says(say) { var self = this; setTimeout(function() { console.log(self.type + ' says ' + say); }, 1000); } // 传统方法2: 用bind(this),即 says(say) { setTimeout(function() { console.log(this.type + ' says ' + say); }.bind(this), 1000); } // ES6: 箭头函数 says(say) { setTimeout(() => { console.log(this.type + ' says ' + say); }, 1000); }
函数参数的尾逗号 现在,函数参数与数组和对象的尾逗号规则,保持一致了。函数定义和调用时,尾部允许有一个逗号。这样可以避免一些不必要的报错。
1 2 3 4 5 6 7 8 9 function clownsEverywhere( param1, param2 ) { /* ... */ } clownsEverywhere( 'foo', 'bar' );
编程风格:单行定义的对象,最后一个成员不以逗号结尾。多行定义的对象,最后一个成员以逗号结尾,方便扩展。
1 2 3 4 5 6 7 8 9 10 11 12 13 const a = { k1 : v1, k2 : v2 }const b = { k1: v1, k2: v2, } const a = { k1 : v1, k2 : v2 }const b = { k1: v1, k2: v2, }
尾调用 优化递归性能