【复习资料】ES6/ES7/ES8/ES9资料整理(个人整理) 上

简介: 【复习资料】ES6/ES7/ES8/ES9资料整理(个人整理) 上

一、介绍

现在的网络上已经有各样关于 ECMAScript 规范介绍和分析的文章,而我自己重新学习一遍这些规范,整理出这么一份笔记,比较精简,主要内容涵盖ES6ES7ES8ES9,后续会增加面试题框架入门等笔记,欢迎吐槽交流。

这份资料的ES6部分将会参考阮一峰老师的 ECMAScript6入门 ,精简和整理出快速实用的内容。

另外ES7/ES8/ES9则会从网络综合参考和整理。

ES全称ECMAScript:

目前JavaScript使用的ECMAScript版本为ECMAScript-262

ECMAScript版本 发布时间 新增特性
ECMAScript 2009(ES5) 2009年11月 扩展了Object、Array、Function的功能等
ECMAScript 2015(ES6) 2015年6月 类,模块化,箭头函数,函数参数默认值等
ECMAScript 2016(ES7) 2016年3月 includes,指数操作符
ECMAScript 2017(ES8) 2017年6月 async/await,Object.values(),Object.entries(),St123 ; b['myfun'] => 'hi'ring padding等

本文博客 CuteECMAScript

本文开源地址 CuteECMAScript

个人博客 ping'anの博客

二、正文

1. ES6

1.1 let 和 const命令

在ES6中,我们通常实用 let 表示变量const 表示常量,并且 letconst 都是块级作用域,且在当前作用域有效不能重复声明。

1.1.1 let 命令

let 命令的用法和 var 相似,但是 let 只在所在代码块内有效。

基础用法

{
    let a = 1;
    let b = 2;
}

并且 let 有以下特点:

  • 不存在变量提升:
    在ES6之前,我们 var 声明一个变量一个函数,都会伴随着变量提升的问题,导致实际开发过程经常出现一些逻辑上的疑惑,按照一般思维习惯,变量都是需要先声明后使用。
// var 
console.log(v1); // undefined
var v1 = 2;
// 由于变量提升 代码实际如下
var v1;
console.log(v1)
v1 = 2;
// let 
console.log(v2); // ReferenceError
let v2 = 2;
  • 不允许重复声明:
    letconst相同作用域下,都不能重复声明同一变量,并且不能在函数内重新声明参数
// 1. 不能重复声明同一变量
// 报错
function f1 (){
    let a = 1;
    var a = 2;
}
// 报错
function f2 (){
    let a = 1;
    let a = 2;
}
// 2. 不能在函数内重新声明参数
// 报错
function f3 (a1){
    let a1; 
}
// 不报错
function f4 (a2){
    {
        let a2
    }
}

1.1.2 const 命令

const 声明一个只读常量

基础用法

const PI = 3.1415926;
console.log(PI);  // 3.1415926

注意点

  • const 声明后,无法修改值;
const PI = 3.1415926;
PI = 3; 
// TypeError: Assignment to constant variable.
  • const 声明时,必须赋值;
const a ; 
// SyntaxError: Missing initializer in const declaration.
  • const 声明的常量,let 不能重复声明;
const PI = 3.1415926;
let PI = 0;  
// Uncaught SyntaxError: Identifier 'PI' has already been declared

1.2 变量的解构赋值

解构赋值概念:在ES6中,直接从数组和对象中取值,按照对应位置,赋值给变量的操作。

1.2.1 数组

基础用法

// ES6 之前
let a = 1;
let b = 2;
// ES6 之后
let [a, b] = [1, 2];

本质上,只要等号两边模式一致,左边变量即可获取右边对应位置的值,更多用法:

let [a, [[b], c]] = [1, [[2], 3]];
console.log(a, b, c); // 1, 2, 3
let [ , , c] = [1, 2, 3];
console.log(c);       // 3
let [a, , c] = [1, 2, 3];
console.log(a,c);     // 1, 3
let [a, ...b] = [1, 2, 3];
console.log(a,b);     // 1, [2,3]
let [a, b, ..c.] = [1];
console.log(a, b, c); // 1, undefined, []

注意点

  • 如果解构不成功,变量的值就等于undefined
let [a] = [];     // a => undefined
let [a, b] = [1]; // a => 1 , b => undefined
  • 当左边模式多于右边,也可以解构成功。
let [a, b] = [1, 2, 3];
console.log(a, b); // 1, 2
  • 两边模式不同,报错。
let [a] = 1;
let [a] = false;
let [a] = NaN;
let [a] = undefined;
let [a] = null;
let [a] = {};

指定解构的默认值

基础用法

let [a = 1] = [];      // a => 1
let [a, b = 2] = [a];  // a => 1 , b => 2

特殊情况:

let [a = 1] = [undefined]; // a => 1
let [a = 1] = [null];      // a => null

右边模式对应的值,必须严格等于undefined,默认值才能生效,而null不严格等于undefined

1.2.2 对象的解构赋值

与数组解构不同的是,对象解构不需要严格按照顺序取值,而只要按照变量名去取对应属性名的值,若取不到对应属性名的值,则为undefined

基础用法

let {a, b} = {a:1, b:2};  // a => 1 , b => 2
let {a, b} = {a:2, b:1};  // a => 2 , b => 1
let {a} = {a:3, b:2, c:1};// a => 3
let {a} = {b:2, c:1};     // a => undefined

注意点

  • 变量名属性名不一致,则需要修改名称。
let {a:b} = {a:1, c:2}; 
// error: a is not defined
// b => 1

对象的解构赋值的内部机制,是先找到同名属性,然后再赋给对应的变量。真正被赋值的是后者,而不是前者。

上面代码中,a 是匹配的模式,b才是变量。真正被赋值的是变量b,而不是模式a

  • 对象解构也支持嵌套解构
let obj = {
    a:[ 1, { b: 2}]
};
let {a, a: [c, {b}]} = obj;
// a=>[1, {b: 2}], b => 2, c => 1

指定解构的默认值

let {a=1} = {};        // a => 1
let {a, b=1} = {a:2};  // a => 2, b => 1
let {a:b=3} = {};      // b => 3
let {a:b=3} = {a:4};   // b = >4
// a是模式,b是变量 牢记
let {a=1} = {a:undefined};  // a => 1
let {a=1} = {a:null};   // a => null
// 因为null与undefined不严格相等,所以赋值有效
// 导致默认值1不会生效。

1.2.3 字符串的解构赋值

字符串的解构赋值中,字符串被转换成了一个类似数组的对象基础用法

const [a, b, c, d, e] = 'hello';
a // "h"
b // "e"
c // "l"
d // "l"
e // "o"
let {length:len} = 'hello';// len => 5

1.2.4 数值和布尔值的解构赋值

解构赋值的规则是,只要等号右边的值不是对象或数组,就先将其转为对象。由于undefinednull无法转为对象,所以对它们进行解构赋值,都会报错。

// 数值和布尔值的包装对象都有toString属性
let {toString: s} = 123;
s === Number.prototype.toString // true
let {toString: s} = true;
s === Boolean.prototype.toString // true
let { prop: x } = undefined; // TypeError
let { prop: y } = null;      // TypeError

1.2.5 函数参数的解构赋值

基础用法

function fun ([a, b]){
    return a + b;
}
fun ([1, 2]); // 3

指定默认值的解构:

function fun ({a=0, b=0} = {}){
    return [a, b];
}
fun ({a:1, b:2}); // [1, 2]
fun ({a:1});      // [1, 0]
fun ({});         // [0, 0]
fun ();           // [0, 0]
function fun ({a, b} = {a:0, b:0}){
    return [a, b];
}
fun ({a:1, b:2}); // [1, 2]
fun ({a:1});      // [1, undefined]
fun ({});         // [undefined, undefined]
fun ();           // [0, 0]

1.2.6 应用

  • 交换变量的值:
let a = 1,b = 2;
[a, b] = [b, a]; // a =>2 , b => 1 
  • 函数返回多个值:
// 返回一个数组
function f (){
    return [1, 2, 3];
}
let [a, b, c] = f(); // a=>1, b=>2, c=>3
// 返回一个对象
function f (){
    return {a:1, b:2};
}
let {a, b} = f();    // a=>1, b=>2
  • 快速对应参数: 快速的将一组参数与变量名对应。
function f([a, b, c]) {...}
f([1, 2, 3]);
function f({a, b, c}) {...}
f({b:2, c:3, a:1});
  • 提取JSON数据
let json = {
    name : 'leo',
    age: 18
}
let {name, age} = json;
console.log(name,age); // leo, 18
  • 遍历Map结构:
const m = new Map();
m.set('a',1);
m.set('b',2);
for (let [k, v] of m){
    console.log(k + ' : ' + v);
}
// 获取键名
for (let [k] of m){...}
// 获取键值
for (let [,k] of m){...}
  • 输入模块的指定方法: 用于按需加载模块中需要用到的方法。
const {log, sin, cos} = require('math');

1.3 字符串的拓展

1.3.1 includes(),startsWith(),endsWith()

在我们判断字符串是否包含另一个字符串时,ES6之前,我们只有typeof方法,ES6之后我们又多了三种方法:

  • includes():返回布尔值,表示是否找到参数字符串
  • startsWith():返回布尔值,表示参数字符串是否在原字符串的头部
  • endsWith():返回布尔值,表示参数字符串是否在原字符串的尾部
let a = 'hello leo';
a.startsWith('leo');   // false
a.endsWith('o');       // true
a.includes('lo');      // true

并且这三个方法都支持第二个参数,表示起始搜索的位置。

let a = 'hello leo';
a.startsWith('leo',1);   // false
a.endsWith('o',5);       // true
a.includes('lo',6);      // false

endsWith 是针对前 n 个字符,而其他两个是针对从第n个位置直到结束。

1.3.2 repeat()

repeat方法返回一个新字符串,表示将原字符串重复n次。

基础用法

'ab'.repeat(3);        // 'ababab'
'ab'.repeat(0);        // ''

特殊用法:

  • 参数为小数,则取整
'ab'.repeat(2.3);      // 'abab'
  • 参数为负数Infinity,则报错
'ab'.repeat(-1);       // RangeError
'ab'.repeat(Infinity); // RangeError
  • 参数为0到-1的小数NaN,则取0
'ab'.repeat(-0.5);     // ''
'ab'.repeat(NaN);      // ''
  • 参数为字符串,则转成数字
'ab'.repeat('ab');     // ''
'ab'.repeat('3');      // 'ababab'

1.3.3 padStart(),padEnd()

用于将字符串头部尾部补全长度,padStart()头部补全padEnd()尾部补全

这两个方法接收2个参数,第一个指定字符串最小长度,第二个用于补全的字符串

基础用法

'x'.padStart(5, 'ab');   // 'ababx'
'x'.padEnd(5, 'ab');     // 'xabab'

特殊用法:

  • 原字符串长度,大于或等于指定最小长度,则返回原字符串。
'xyzabc'.padStart(5, 'ab'); // 'xyzabc'
  • 用来补全的字符串长度和原字符串长度之和,超过指定最小长度,则截去超出部分的补全字符串。
'ab'.padStart(5,'012345'); // "012ab"
  • 省略第二个参数,则用空格补全。
'x'.padStart(4);           // '    x'
'x'.padEnd(4);           // 'x    '

1.3.4 模版字符串

用于拼接字符串,ES6之前:

let a = 'abc' + 
    'def' + 
    'ghi';

ES6之后:

let a = `
    abc
    def
    ghi
`

拼接变量: 在**反引号(`)**中使用${}包裹变量或方法。

// ES6之前
let a = 'abc' + v1 + 'def';
// ES6之后
let a = `abc${v1}def`

1.4 正则的拓展

1.4.1 介绍

在ES5中有两种情况。

  • 参数是字符串,则第二个参数为正则表达式的修饰符。
let a = new RegExp('abc', 'i');
// 等价于
let a = /abx/i;
  • 参数是正则表达式,返回一个原表达式的拷贝,且不能有第二个参数,否则报错。
let a = new RegExp(/abc/i);
//等价于
let a = /abx/i;
let a = new RegExp(/abc/, 'i');
//  Uncaught TypeError

ES6中使用:

第一个参数是正则对象,第二个是指定修饰符,如果第一个参数已经有修饰符,则会被第二个参数覆盖。

new RegExp(/abc/ig, 'i');

1.4.2 字符串的正则方法

常用的四种方法:match()replace()search()split()

1.4.3 u修饰符

添加u修饰符,是为了处理大于uFFFF的Unicode字符,即正确处理四个字节的UTF-16编码。

/^\uD83D/u.test('\uD83D\uDC2A'); // false
/^\uD83D/.test('\uD83D\uDC2A');  // true

由于ES5之前不支持四个字节UTF-16编码,会识别为两个字符,导致第二行输出true,加入u修饰符后ES6就会识别为一个字符,所以输出false

注意:

加上u修饰符后,会改变下面正则表达式的行为:

  • (1)点字符 点字符(.)在正则中表示除了换行符以外的任意单个字符。对于码点大于0xFFFF的Unicode字符,点字符不能识别,必须加上u修饰符。
var a = "𠮷";
/^.$/.test(a);  // false
/^.$/u.test(a); // true
  • (2)Unicode字符表示法 使用ES6新增的大括号表示Unicode字符时,必须在表达式添加u修饰符,才能识别大括号。
/\u{61}/.test('a');      // false
/\u{61}/u.test('a');     // true
/\u{20BB7}/u.test('𠮷'); // true
  • (3)量词 使用u修饰符后,所有量词都会正确识别码点大于0xFFFF的 Unicode 字符。
/a{2}/.test('aa');    // true
/a{2}/u.test('aa');   // true
/𠮷{2}/.test('𠮷𠮷');  // false
/𠮷{2}/u.test('𠮷𠮷'); // true
  • (4)i修饰符 不加u修饰符,就无法识别非规范的K字符。
/[a-z]/i.test('\u212A') // false
/[a-z]/iu.test('\u212A') // true

检查是否设置u修饰符: 使用unicode属性。

const a = /hello/;
const b = /hello/u;
a.unicode // false
b.unicode // true

1.4.4 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"]  剩余 '_aa_a'
r2.exec(s) // null

lastIndex属性: 指定匹配的开始位置:

const a = /a/y;
a.lastIndex = 2;  // 从2号位置开始匹配
a.exec('wahaha'); // null
a.lastIndex = 3;  // 从3号位置开始匹配
let c = a.exec('wahaha');
c.index;          // 3
a.lastIndex;      // 4

返回多个匹配

一个y修饰符对match方法只能返回第一个匹配,与g修饰符搭配能返回所有匹配。

'a1a2a3'.match(/a\d/y);  // ["a1"]
'a1a2a3'.match(/a\d/gy); // ["a1", "a2", "a3"]

检查是否使用y修饰符

使用sticky属性检查。

const a = /hello\d/y;
a.sticky;     // true

1.4.5 flags属性

flags属性返回所有正则表达式的修饰符。

/abc/ig.flags;  // 'gi'

1.5 数值的拓展

1.5.1 Number.isFinite(), Number.isNaN()

Number.isFinite() 用于检查一个数值是否是有限的,即不是Infinity,若参数不是Number类型,则一律返回false

Number.isFinite(10);            // true
Number.isFinite(0.5);           // true
Number.isFinite(NaN);           // false
Number.isFinite(Infinity);      // false
Number.isFinite(-Infinity);     // false
Number.isFinite('leo');         // false
Number.isFinite('15');          // false
Number.isFinite(true);          // false
Number.isFinite(Math.random()); // true

Number.isNaN()用于检查是否是NaN,若参数不是NaN,则一律返回false

Number.isNaN(NaN);      // true
Number.isNaN(10);       // false
Number.isNaN('10');     // false
Number.isNaN(true);     // false
Number.isNaN(5/NaN);    // true
Number.isNaN('true' / 0);      // true
Number.isNaN('true' / 'true'); // true

区别

与传统全局的isFinite()isNaN()方法的区别,传统的这两个方法,是先将参数转换成数值,再判断。

而ES6新增的这两个方法则只对数值有效, Number.isFinite()对于非数值一律返回false,Number.isNaN()只有对于NaN才返回true,其他一律返回false

isFinite(25);          // true
isFinite("25");        // true
Number.isFinite(25);   // true
Number.isFinite("25"); // false
isNaN(NaN);            // true
isNaN("NaN");          // true
Number.isNaN(NaN);     // true
Number.isNaN("NaN");   // false

1.5.2 Number.parseInt(), Number.parseFloat()

这两个方法与全局方法parseInt()parseFloat()一致,目的是逐步减少全局性的方法,让语言更模块化

parseInt('12.34');     // 12
parseFloat('123.45#'); // 123.45
Number.parseInt('12.34');     // 12
Number.parseFloat('123.45#'); // 123.45
Number.parseInt === parseInt;     // true
Number.parseFloat === parseFloat; // true

1.5.3 Number.isInteger()

用来判断一个数值是否是整数,若参数不是数值,则返回false

Number.isInteger(10);   // true
Number.isInteger(10.0); // true
Number.isInteger(10.1); // false

1.5.4 Math对象的拓展

ES6新增17个数学相关的静态方法,只能在Math对象上调用。

  • Math.trunc:
    用来去除小数的小数部分,返回整数部分
    若参数为非数值,则先转为数值
    若参数为空值无法截取整数的值,则返回NaN
// 正常使用
Math.trunc(1.1);     // 1
Math.trunc(1.9);     // 1
Math.trunc(-1.1);    // -1
Math.trunc(-1.9);    // -1
Math.trunc(-0.1234); // -0
// 参数为非数值
Math.trunc('11.22'); // 11
Math.trunc(true);    // 1
Math.trunc(false);   // 0
Math.trunc(null);    // 0
// 参数为空和无法取整
Math.trunc(NaN);       // NaN
Math.trunc('leo');     // NaN
Math.trunc();          // NaN
Math.trunc(undefined); // NaN

ES5实现

Math.trunc = Math.trunc || function(x){
    return x < 0 ? Math.ceil(x) : Math.floor(x);
}
  • Math.sign():判断一个数是正数负数是零,对于非数值,会先转成数值。返回值:
  • 参数为正数, 返回 +1
  • 参数为负数, 返回 -1
  • 参数为0, 返回 0
  • 参数为-0, 返回 -0
  • 参数为其他值, 返回 NaN
Math.sign(-1);   // -1
Math.sign(1);    // +1
Math.sign(0);    // 0
Math.sign(-0);   // -0
Math.sign(NaN);  // NaN
Math.sign('');   // 0
Math.sign(true); // +1
Math.sign(false);// 0
Math.sign(null); // 0
Math.sign('9');  // +1
Math.sign('leo');// NaN
Math.sign();     // NaN
Math.sign(undefined); // NaN

ES5实现

Math.sign = Math.sign || function (x){
    x = +x;
    if (x === 0 || isNaN(x)){
        return x;
    }
    return x > 0 ? 1: -1;
}
  • Math.cbrt():
    用来计算一个数的立方根,若参数为非数值则先转成数值。
Math.cbrt(-1); // -1
Math.cbrt(0);  // 0
Math.cbrt(1);  // 1
Math.cbrt(2);  // 1.2599210498
Math.cbrt('1');   // 1
Math.cbrt('leo'); // NaN

ES5实现

Math.cbrt = Math.cbrt || function (x){
    var a = Math.pow(Math.abs(x), 1/3);
    return x < 0 ? -y : y;
}
  • Math.clz32():
    用于返回一个数的 32 位无符号整数形式有多少个前导 0。
Math.clz32(0) // 32
Math.clz32(1) // 31
Math.clz32(1000) // 22
Math.clz32(0b01000000000000000000000000000000) // 1
Math.clz32(0b00100000000000000000000000000000) // 2
  • Math.imul():
    用于返回两个数以 32 位带符号整数形式相乘的结果,返回的也是一个 32 位的带符号整数。
Math.imul(2, 4)   // 8
Math.imul(-1, 8)  // -8
Math.imul(-2, -2) // 4
  • Math.fround():
    用来返回一个数的2位单精度浮点数形式。
Math.fround(0)   // 0
Math.fround(1)   // 1
Math.fround(2 ** 24 - 1)   // 16777215
  • Math.hypot():
    用来返回所有参数的平方和的平方根
Math.hypot(3, 4);        // 5
Math.hypot(3, 4, 5);     // 7.0710678118654755
Math.hypot();            // 0
Math.hypot(NaN);         // NaN
Math.hypot(3, 4, 'foo'); // NaN
Math.hypot(3, 4, '5');   // 7.0710678118654755
Math.hypot(-3);          // 3
  • Math.expm1():
    用来返回ex - 1,即Math.exp(x) - 1
Math.expm1(-1) // -0.6321205588285577
Math.expm1(0)  // 0
Math.expm1(1)  // 1.718281828459045

ES5实现

Math.expm1 = Math.expm1 || function(x) {
  return Math.exp(x) - 1;
};
  • Math.log1p():
    用来返回1 + x的自然对数,即Math.log(1 + x)。如果x小于-1,返回NaN
Math.log1p(1)  // 0.6931471805599453
Math.log1p(0)  // 0
Math.log1p(-1) // -Infinity
Math.log1p(-2) // NaN

ES5实现

Math.log1p = Math.log1p || function(x) {
  return Math.log(1 + x);
};
  • Math.log10():
    用来返回以 10为底的x的对数。如果x小于 0,则返回 NaN
Math.log10(2)      // 0.3010299956639812
Math.log10(1)      // 0
Math.log10(0)      // -Infinity
Math.log10(-2)     // NaN
Math.log10(100000) // 5

ES5实现

Math.log10 = Math.log10 || function(x) {
  return Math.log(x) / Math.LN10;
};
  • Math.log2():
    用来返回以 2 为底的x的对数。如果x小于0,则返回 NaN
Math.log2(3)       // 1.584962500721156
Math.log2(2)       // 1
Math.log2(1)       // 0
Math.log2(0)       // -Infinity
Math.log2(-2)      // NaN
Math.log2(1024)    // 10
Math.log2(1 << 29) // 29

ES5实现

Math.log2 = Math.log2 || function(x) {
  return Math.log(x) / Math.LN2;
};
  • 双曲函数方法:
  • Math.sinh(x) 返回x的双曲正弦(hyperbolic sine)
  • Math.cosh(x) 返回x的双曲余弦(hyperbolic cosine)
  • Math.tanh(x) 返回x的双曲正切(hyperbolic tangent)
  • Math.asinh(x) 返回x的反双曲正弦(inverse hyperbolic sine)
  • Math.acosh(x) 返回x的反双曲余弦(inverse hyperbolic cosine)
  • Math.atanh(x) 返回x的反双曲正切(inverse hyperbolic tangent)

1.5.5 指数运算符

新增的指数运算符(**):

2 ** 2; // 4
2 ** 3; // 8 
2 ** 3 ** 2; // 相当于 2 ** (3 ** 2); 返回 512

指数运算符(**)与Math.pow的实现不相同,对于特别大的运算结果,两者会有细微的差异。

Math.pow(99, 99)
// 3.697296376497263e+197
99 ** 99
// 3.697296376497268e+197

1.6 函数的拓展

1.6.1 参数默认值

// ES6 之前
function f(a, b){
    b = b || 'leo';
    console.log(a, b);
}
// ES6 之后
function f(a, b='leo'){
    console.log(a, b);
}
f('hi');          // hi leo
f('hi', 'jack');  // hi jack
f('hi', '');      // hi leo

注意:

  • 参数变量是默认声明的,不能用letconst再次声明:
function f (a = 1){
    let a = 2; // error
}
  • 使用参数默认值时,参数名不能相同:
function f (a, a, b){ ... };     // 不报错
function f (a, a, b = 1){ ... }; // 报错

与解构赋值默认值结合使用

function f ({a, b=1}){
    console.log(a,b)
};
f({});         // undefined 1
f({a:2});      // 2 1
f({a:2, b:3}); // 2 3
f();           // 报错
function f ({a, b = 1} = {}){
    console.log(a, b)
}
f();  // undefined 1

尾参数定义默认值:

通常在尾参数定义默认值,便于观察参数,并且非尾参数无法省略。

function f (a=1,b){
    return [a, b];
}
f();    // [1, undefined]
f(2);   // [2, undefined]
f(,2);  // 报错
f(undefined, 2);  // [1, 2]
function f (a, b=1, c){
    return [a, b, c];
}
f();        // [undefined, 1, undefined]
f(1);       // [1,1,undefined]
f(1, ,2);   // 报错
f(1,undefined,2); // [1,1,2]

在给参数传递默认值时,传入undefined会触发默认值,传入null不会触发。

function f (a = 1, b = 2){
    console.log(a, b);
}
f(undefined, null); // 1 null

函数的length属性:

length属性将返回,没有指定默认值的参数数量,并且rest参数不计入length属性。

function f1 (a){...};
function f2 (a=1){...};
function f3 (a, b=2){...};
function f4 (...a){...};
function f5 (a,b,...c){...};
f1.length; // 1
f2.length; // 0
f3.length; // 1
f4.length; // 0
f5.length; // 2

1.6.2 rest 参数

rest参数形式为(...变量名),其值为一个数组,用于获取函数多余参数。

function f (a, ...b){
    console.log(a, b);
}
f(1,2,3,4); // 1 [2, 3, 4]

注意

  • rest参数只能放在最后一个,否则报错:
function f(a, ...b, c){...}; // 报错 
  • 函数的length属性不包含rest参数。
function f1 (a){...};
function f2 (a,...b){...};
f1(1);   // 1
f2(1,2); // 1

1.6.3 name 属性

用于返回该函数的函数名。

function f (){...};
f.name;    // f
const f = function g(){...};
f.name;    // g

1.6.4 箭头函数

使用“箭头”(=>)定义函数。

基础使用

// 有1个参数
let f = v => v;
// 等同于
let f = function (v){return v};
// 有多个参数
let f = (v, i) => {return v + i};
// 等同于
let f = function (v, i){return v + i};
// 没参数
let f = () => 1;
// 等同于
let f = function (){return 1};

箭头函数与变量结构结合使用

// 正常函数写法
function f (p) {
    return p.a + ':' + p.b;
}
// 箭头函数写法
let f = ({a, b}) => a + ':' + b;

简化回调函数

// 正常函数写法
[1, 2, 3].map(function (x){
    return x * x;
})
// 箭头函数写法
[1, 2, 3].map(x => x * x);

箭头函数与rest参数结合

let f = (...n) => n;
f(1, 2, 3); // [1, 2, 3]

注意点

  • 1.箭头函数内的this总是指向定义时所在的对象,而不是调用时。
  • 2.箭头函数不能当做构造函数,即不能用new命令,否则报错。
  • 3.箭头函数不存在arguments对象,即不能使用,可以使用rest参数代替。
  • 4.箭头函数不能使用yield命令,即不能用作Generator函数。

不适用场景

  • 1.在定义函数方法,且该方法内部包含this
const obj = {
    a:9,
    b: () => {
        this.a --;
    }
}

上述b如果是普通函数,函数内部的this指向obj,但是如果是箭头函数,则this会指向全局,不是预期结果。

  • 2.需要动态this时。
let b = document.getElementById('myID');
b.addEventListener('click', ()=>{
    this.classList.toggle('on');
})

上诉按钮点击会报错,因为b监听的箭头函数中,this是全局对象,若改成普通函数this就会指向被点击的按钮对象。

1.6.5 双冒号运算符

双冒号暂时是一个提案,用于解决一些不适用的场合,取代callapplybind调用。

双冒号运算符(::)的左边是一个对象,右边是一个函数。该运算符会自动将左边的对象,作为上下文环境(即this对象),绑定到右边函数上。

f::b;
// 等同于
b.bind(f);
f::b(...arguments);
// 等同于
b.apply(f, arguments);

若双冒号左边为空,右边是一个对象的方法,则等于将该方法绑定到该对象上。

let f = a::a.b;
// 等同于
let f = ::a.b;

1.7 数组的拓展

1.7.1 拓展运算符

拓展运算符使用(...),类似rest参数的逆运算,将数组转为用(,)分隔的参数序列。

console.log(...[1, 2, 3]);   // 1 2 3 
console.log(1, ...[2,3], 4); // 1 2 3 4

拓展运算符主要使用在函数调用。

function f (a, b){
    console.log(a, b);
}
f(...[1, 2]); // 1 2
function g (a, b, c, d, e){
    console.log(a, b, c, d, e);
}
g(0, ...[1, 2], 3, ...[4]); // 0 1 2 3 4

若拓展运算符后面是个空数组,则不产生效果

[...[], 1]; // [1]

替代apply方法

// ES6之前
function f(a, b, c){...};
var a = [1, 2, 3];
f.apply(null, a);
// ES6之后
function f(a, b, c){...};
let a = [1, 2, 3];
f(...a);
// ES6之前
Math.max.apply(null, [3,2,6]);
// ES6之后
Math.max(...[3,2,6]);

拓展运算符的运用

  • (1)复制数组
    通常我们直接复制数组时,只是浅拷贝,如果要实现深拷贝,可以使用拓展运算符。
// 通常情况 浅拷贝
let a1 = [1, 2];
let a2 = a1; 
a2[0] = 3;
console.log(a1,a2); // [3,2] [3,2]
// 拓展运算符 深拷贝
let a1 = [1, 2];
let a2 = [...a1];
// let [...a2] = a1; // 作用相同
a2[0] = 3;
console.log(a1,a2); // [1,2] [3,2]
  • (2)合并数组
    注意,这里合并数组,只是浅拷贝。
let a1 = [1,2];
let a2 = [3];
let a3 = [4,5];
// ES5 
let a4 = a1.concat(a2, a3);
// ES6
let a5 = [...a1, ...a2, ...a3];
a4[0] === a1[0]; // true
a5[0] === a1[0]; // true
  • (3)与解构赋值结合
    与解构赋值结合生成数组,但是使用拓展运算符需要放到参数最后一个,否则报错。
let [a, ...b] = [1, 2, 3, 4]; 
// a => 1  b => [2,3,4]
let [a, ...b] = [];
// a => undefined b => []
let [a, ...b] = ["abc"];
// a => "abc"  b => []

1.7.2 Array.from()

类数组对象可遍历的对象,转换成真正的数组。

// 类数组对象
let a = {
    '0':'a',
    '1':'b',
    length:2
}
let arr = Array.from(a);
// 可遍历的对象
let a = Array.from([1,2,3]);
let b = Array.from({length: 3});
let c = Array.from([1,2,3]).map(x => x * x);
let d = Array.from([1,2,3].map(x => x * x));

1.7.3 Array.of()

将一组数值,转换成数组,弥补Array方法参数不同导致的差异。

Array.of(1,2,3);    // [1,2,3]
Array.of(1).length; // 1
Array();       // []
Array(2);      // [,] 1个参数时,为指定数组长度
Array(1,2,3);  // [1,2,3] 多于2个参数,组成新数组

1.7.4 find()和findIndex()

find()方法用于找出第一个符合条件的数组成员,参数为一个回调函数,所有成员依次执行该回调函数,返回第一个返回值为true的成员,如果没有一个符合则返回undefined

[1,2,3,4,5].find( a => a < 3 ); // 1

回调函数接收三个参数,当前值、当前位置和原数组。

[1,2,3,4,5].find((value, index, arr) => {
    // ...
});

findIndex()方法与find()类似,返回第一个符合条件的数组成员的位置,如果都不符合则返回-1

[1,2,3,4].findIndex((v,i,a)=>{
    return v>2;
}); // 2

1.7.5 fill()

用于用指定值填充一个数组,通常用来初始化空数组,并抹去数组中已有的元素。

new Array(3).fill('a');   // ['a','a','a']
[1,2,3].fill('a');        // ['a','a','a']

并且fill()的第二个和第三个参数指定填充的起始位置结束位置

[1,2,3].fill('a',1,2); //  [1, "a", 3]

1.7.6 entries(),keys(),values()

主要用于遍历数组,entries()对键值对遍历,keys()对键名遍历,values()对键值遍历。

for (let i of ['a', 'b'].keys()){
    console.log(i)
}
// 0
// 1
for (let e of ['a', 'b'].values()){
    console.log(e)
}
// 'a'
// 'b'
for (let e of ['a', 'b'].entries()){
    console.log(e)
}
// [0, "a"] 
// [1, "b"]

1.7.7 includes()

用于表示数组是否包含给定的值,与字符串的includes方法类似。

[1,2,3].includes(2);     // true
[1,2,3].includes(4);     // false
[1,2,NaN].includes(NaN); // true

第二个参数为起始位置,默认为0,如果负数,则表示倒数的位置,如果大于数组长度,则重置为0开始。

[1,2,3].includes(3,3);    // false
[1,2,3].includes(3,4);    // false
[1,2,3].includes(3,-1);   // true
[1,2,3].includes(3,-4);   // true

1.7.8 flat(),flatMap()

flat()用于将数组一维化,返回一个新数组,不影响原数组。

默认一次只一维化一层数组,若需多层,则传入一个整数参数指定层数。

若要一维化所有层的数组,则传入Infinity作为参数。

[1, 2, [2,3]].flat();        // [1,2,2,3]
[1,2,[3,[4,[5,6]]]].flat(3); // [1,2,3,4,5,6]
[1,2,[3,[4,[5,6]]]].flat('Infinity'); // [1,2,3,4,5,6]

flatMap()是将原数组每个对象先执行一个函数,在对返回值组成的数组执行flat()方法,返回一个新数组,不改变原数组。

flatMap()只能展开一层。

[2, 3, 4].flatMap((x) => [x, x * 2]); 
// [2, 4, 3, 6, 4, 8] 

1.8 对象的拓展

1.8.1 属性的简洁表示

let a = 'a1';
let b = { a };  // b => { a : 'a1' }
// 等同于
let b = { a : a };
function f(a, b){
    return {a, b}; 
}
// 等同于
function f (a, b){
    return {a:a ,b:b};
}
let a = {
    fun () {
        return 'leo';
    }
}
// 等同于
let a = {
    fun : function(){
        return 'leo';
    }
}

1.8.2 属性名表达式

JavaScript提供2种方法定义对象的属性

// 方法1 标识符作为属性名
a.f = true;
// 方法2 字符串作为属性名
a['f' + 'un'] = true;

延伸出来的还有:

let a = 'hi leo';
let b = {
    [a]: true,
    ['a'+'bc']: 123,
    ['my' + 'fun'] (){
        return 'hi';
    }
};
// b.a => undefined ; b.abc => 123 ; b.myfun() => 'hi'
// b[a] => true ; b['abc'] => 123 ; b['myfun'] => ƒ ['my' + 'fun'] (){ return 'hi'; }

注意

属性名表达式不能与简洁表示法同时使用,否则报错。

// 报错
let a1 = 'aa';
let a2 = 'bb';
let b1 = {[a1]};
// 正确
let a1 = 'aa';
let b1 = { [a1] : 'bb'};

1.8.3 Object.is()

Object.is() 用于比较两个值是否严格相等,在ES5时候只要使用相等运算符(==)和严格相等运算符(===)就可以做比较,但是它们都有缺点,前者会自动转换数据类型,后者的NaN不等于自身,以及+0等于-0

Object.is('a','a');   // true
Object.is({}, {});    // false
// ES5
+0 === -0 ;           // true
NaN === NaN;          // false
// ES6
Object.is(+0,-0);     // false
Object.is(NaN,NaN);   // true

1.8.4 Object.assign()

Object.assign()方法用于对象的合并,将原对象的所有可枚举属性复制到目标对象。

基础用法

第一个参数是目标对象,后面参数都是源对象

let a = {a:1};
let b = {b:2};
Object.assign(a,b);  // a=> {a:1,b:2}

注意

  • 若目标对象与源对象有同名属性,则后面属性会覆盖前面属性。
let a = {a:1, b:2};
let b = {b:3, c:4};
Object.assign(a, b); // a => {a:1, b:3, c:4}
  • 若只有一个参数,则返回该参数。
let a = {a:1};
Object.assign(a) === a;  // true
  • 若参数不是对象,则先转成对象后返回。
typeof Object.assign(2); // 'object'
  • 由于undefinedNaN无法转成对象,所以做为参数会报错。
Object.assign(undefined) // 报错
Object.assign(NaN);      // 报错
  • Object.assign()实现的是浅拷贝。

Object.assign()拷贝得到的是这个对象的引用。这个对象的任何变化,都会反映到目标对象上面。

let a = {a: {b:1}};
let b = Object.assign({},a);
a.a.b = 2;
console.log(b.a.b);  // 2
  • 将数组当做对象处理,键名为数组下标,键值为数组下标对应的值。
Object.assign([1, 2, 3], [4, 5]); // [4, 5, 3]

1.9 Symbol

1.9.1 介绍

ES6引入Symbol作为一种新的原始数据类型,表示独一无二的值,主要是为了防止属性名冲突

ES6之后,JavaScript一共有其中数据类型:SymbolundefinednullBooleanStringNumberObject

简单实用:

let a = Symbol();
typeof a; // "symbol"

注意:

  • Symbol函数不能用new,会报错。由于Symbol是一个原始类型,不是对象,所以不能添加属性,它是类似于字符串的数据类型。
  • Symbol都是不相等的,即使参数相同。
// 没有参数
let a1 = Symbol();
let a2 = Symbol();
a1 === a2; // false 
// 有参数
let a1 = Symbol('abc');
let a2 = Symbol('abc');
a1 === a2; // false 
  • Symbol不能与其他类型的值计算,会报错。
let a = Symbol('hello');
a + " world!";  // 报错
`${a} world!`;  // 报错

Symbol可以显式转换为字符串:

let a1 = Symbol('hello');
String(a1);    // "Symbol(hello)"
a1.toString(); // "Symbol(hello)"

Symbol可以转换为布尔值,但不能转为数值:

let a1 = Symbol();
Boolean(a1);
!a1;        // false
Number(a1); // TypeError
a1 + 1 ;    // TypeError

1.9.2 Symbol作为属性名

好处:防止同名属性,还有防止键被改写或覆盖。

let a1 = Symbol();
// 写法1
let b = {};
b[a1] = 'hello';
// 写法2
let b = {
    [a1] : 'hello'
} 
// 写法3
let b = {};
Object.defineProperty(b, a1, {value : 'hello' });
// 3种写法 结果相同
b[a1]; // 'hello'

需要注意: Symbol作为对象属性名时,不能用点运算符,并且必须放在方括号内。

let a = Symbol();
let b = {};
// 不能用点运算
b.a = 'hello';
b[a] ; // undefined
b['a'] ; // 'hello'
// 必须放在方括号内
let c = {
    [a] : function (text){
        console.log(text);
    }
}
c[a]('leo'); // 'leo'
// 上面等价于 更简洁
let c = {
    [a](text){
        console.log(text);
    }
}

常常还用于创建一组常量,保证所有值不相等:

let a = {};
a.a1 = {
    AAA: Symbol('aaa'),
    BBB: Symbol('bbb'),
    CCC: Symbol('ccc')
}

1.9.3 应用:消除魔术字符串

魔术字符串:指代码中多次出现,强耦合的字符串或数值,应该避免,而使用含义清晰的变量代替。

function f(a){
    if(a == 'leo') {
        console.log('hello');
    }
}
f('leo');   // 'leo' 为魔术字符串

常使用变量,消除魔术字符串:

let obj = {
    name: 'leo'
};
function f (a){
    if(a == obj.name){
        console.log('hello');
    }
}
f(obj.name); // 'leo'

使用Symbol消除强耦合,使得不需关系具体的值:

let obj = {
    name: Symbol()
};
function f (a){
    if(a == obj.name){
        console.log('hello');
    }
}
f(obj.name);

1.9.4 属性名遍历

Symbol作为属性名遍历,不出现在for...infor...of循环,也不被Object.keys()Object.getOwnPropertyNames()JSON.stringify()返回。

let a = Symbol('aa'),b= Symbol('bb');
let obj = {
    [a]:'11', [b]:'22'
}
for(let k of Object.values(obj)){console.log(k)}
// 无输出
let obj = {};
let aa = Symbol('leo');
Object.defineProperty(obj, aa, {value: 'hi'});
for(let k in obj){
    console.log(k); // 无输出
}
Object.getOwnPropertyNames(obj);   // []
Object.getOwnPropertySymbols(obj); // [Symbol(leo)]

Object.getOwnPropertySymbols方法返回一个数组,包含当前对象所有用做属性名的Symbol值。

let a = {};
let a1 = Symbol('a');
let a2 = Symbol('b');
a[a1] = 'hi';
a[a2] = 'oi';
let obj = Object.getOwnPropertySymbols(a);
obj; //  [Symbol(a), Symbol(b)]

另外可以使用Reflect.ownKeys方法可以返回所有类型的键名,包括常规键名和 Symbol 键名。

let a = {
    [Symbol('leo')]: 1,
    aa : 2, 
    bb : 3,
}
Reflect.ownKeys(a); // ['aa', 'bb',Symbol('leo')]

由于Symbol值作为名称的属性不被常规方法遍历获取,因此常用于定义对象的一些非私有,且内部使用的方法。

1.9.5 Symbol.for()、Symbol.keyFor()

  • Symbol.for()
    用于重复使用一个Symbol值,接收一个字符串作为参数,若存在用此参数作为名称的Symbol值,返回这个Symbol,否则新建并返回以这个参数为名称的Symbol值。
let a = Symbol.for('aaa');
let b = Symbol.for('aaa');
a === b;  // true

Symbol()Symbol.for()区别:

Symbol.for('aa') === Symbol.for('aa'); // true
Symbol('aa') === Symbol('aa');         // false
  • Symbol.keyFor()
    用于返回一个已使用的Symbol类型的key:
let a = Symbol.for('aa');
Symbol.keyFor(a);   //  'aa'
let b = Symbol('aa');
Symbol.keyFor(b);   //  undefined

1.9.6 内置的Symbol值

ES6提供11个内置的Symbol值,指向语言内部使用的方法:

  • 1.Symbol.hasInstance
    当其他对象使用instanceof运算符,判断是否为该对象的实例时,会调用这个方法。比如,foo instanceof Foo在语言内部,实际调用的是Foo[Symbol.hasInstance](foo)
class P {
    [Symbol.hasInstance](a){
        return a instanceof Array;
    }
}
[1, 2, 3] instanceof new P(); // true

P是一个类,new P()会返回一个实例,该实例的Symbol.hasInstance方法,会在进行instanceof运算时自动调用,判断左侧的运算子是否为Array的实例。

  • 2.Symbol.isConcatSpreadable
    值为布尔值,表示该对象用于Array.prototype.concat()时,是否可以展开。
let a = ['aa','bb'];
['cc','dd'].concat(a, 'ee'); 
// ['cc', 'dd', 'aa', 'bb', 'ee']
a[Symbol.isConcatSpreadable]; // undefined
let b = ['aa','bb']; 
b[Symbol.isConcatSpreadable] = false; 
['cc','dd'].concat(b, 'ee'); 
// ['cc', 'dd',[ 'aa', 'bb'], 'ee']
  • 3.Symbol.species
    指向一个构造函数,在创建衍生对象时会使用,使用时需要用get取值器。
class P extends Array {
    static get [Symbol.species](){
        return this;
    }
}

解决下面问题:

// 问题:  b应该是 Array 的实例,实际上是 P 的实例
class P extends Array{}
let a = new P(1,2,3);
let b = a.map(x => x);
b instanceof Array; // true
b instanceof P; // true
// 解决:  通过使用 Symbol.species
class P extends Array {
  static get [Symbol.species]() { return Array; }
}
let a = new P();
let b = a.map(x => x);
b instanceof P;     // false
b instanceof Array; // true
  • 4.Symbol.match
    当执行str.match(myObject),传入的属性存在时会调用,并返回该方法的返回值。
class P {
    [Symbol.match](string){
        return 'hello world'.indexOf(string);
    }
}
'h'.match(new P());   // 0
  • 5.Symbol.replace 当该对象被String.prototype.replace方法调用时,会返回该方法的返回值。
let a = {};
a[Symbol.replace] = (...s) => console.log(s);
'Hello'.replace(a , 'World') // ["Hello", "World"]
  • 6.Symbol.hasInstance
    当该对象被String.prototype.search方法调用时,会返回该方法的返回值。
class P {
    constructor(val) {
        this.val = val;
    }
    [Symbol.search](s){
        return s.indexOf(this.val);
    }
}
'hileo'.search(new P('leo')); // 2
  • 7.Symbol.split
    当该对象被String.prototype.split方法调用时,会返回该方法的返回值。
// 重新定义了字符串对象的split方法的行为
class P {
    constructor(val) {
        this.val = val;
    }
    [Symbol.split](s) {
        let i = s.indexOf(this.val);
        if(i == -1) return s;
        return [
            s.substr(0, i),
            s.substr(i + this.val.length)
        ]
    }
}
'helloworld'.split(new P('hello')); // ["hello", ""]
'helloworld'.split(new P('world')); // ["", "world"] 
'helloworld'.split(new P('leo'));   // "helloworld"
  • 8.Symbol.iterator
    对象进行for...of循环时,会调用Symbol.iterator方法,返回该对象的默认遍历器。
class P {
    *[Symbol.interator]() {
        let i = 0;
        while(this[i] !== undefined ) {
            yield this[i];
            ++i;
        }
    }
}
let a = new P();
a[0] = 1;
a[1] = 2;
for (let k of a){
    console.log(k);
}
  • 9.Symbol.toPrimitive该对象被转为原始类型的值时,会调用这个方法,返回该对象对应的原始类型值。调用时,需要接收一个字符串参数,表示当前运算模式,运算模式有:
  • Number : 此时需要转换成数值
  • String : 此时需要转换成字符串
  • Default : 此时可以转换成数值或字符串
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 // 246
3 + obj // '3default'
obj == 'default' // true
String(obj) // 'str'
  • 10.Symbol.toStringTag
    在该对象上面调用Object.prototype.toString方法时,如果这个属性存在,它的返回值会出现在toString方法返回的字符串之中,表示对象的类型。也就是说,这个属性可以用来定制[object Object]或[object Array]object后面的那个字符串。
// 例一
({[Symbol.toStringTag]: 'Foo'}.toString())
// "[object Foo]"
// 例二
class Collection {
  get [Symbol.toStringTag]() {
    return 'xxx';
  }
}
let x = new Collection();
Object.prototype.toString.call(x) // "[object xxx]"
  • 11.Symbol.unscopables
    该对象指定了使用with关键字时,哪些属性会被with环境排除。
// 没有 unscopables 时
class MyClass {
  foo() { return 1; }
}
var foo = function () { return 2; };
with (MyClass.prototype) {
  foo(); // 1
}
// 有 unscopables 时
class MyClass {
  foo() { return 1; }
  get [Symbol.unscopables]() {
    return { foo: true };
  }
}
var foo = function () { return 2; };
with (MyClass.prototype) {
  foo(); // 2
}

上面代码通过指定Symbol.unscopables属性,使得with语法块不会在当前作用域寻找foo属性,即foo将指向外层作用域的变量。


目录
相关文章
|
6月前
|
JavaScript 小程序 Java
ES6使用实践总结
ES6使用实践总结
45 0
|
2月前
|
JavaScript 前端开发
ES6学习(6)
ES6学习(6)
|
2月前
|
网络架构
ES6学习(5)
ES6学习(5)
|
自然语言处理 前端开发 Java
【ES系列八】——问题整理与总结
前端时间在团队中进行了ES的分享活动,只是将大概的内容进行了宏观的分享,以及自己在做的过程中的一些思路,但是在最后的答疑环节,伙伴们提出了一些问题,今天就针对于这些问题进行了一个简单的总结,希望能够带给读者朋友一些帮助。
|
6月前
|
JavaScript 前端开发
|
前端开发 JavaScript
每天3分钟,重学ES6-ES12系列文章汇总
每天3分钟,重学ES6-ES12系列文章汇总
66 0
|
JavaScript 前端开发
每天3分钟,重学ES6-ES12(十八)ES Module(一)
每天3分钟,重学ES6-ES12(十八)ES Module
83 0
|
JavaScript 前端开发
每天3分钟,重学ES6-ES12(十八)ES Module(二)
每天3分钟,重学ES6-ES12(十八)ES Module
81 0
|
缓存 JavaScript 算法
每天3分钟,重学ES6-ES12(十八) CJS
每天3分钟,重学ES6-ES12(十八) CJS
90 0
|
存储 监控 前端开发