ES新特性学习笔记

一、ES 一些背景

ECMA(European Computer Manufactures Association)欧洲计算机制造商协会,该组织的目标是评估、开发和认可电信和计算机标准,1994 年后该组织改名为 Ecma 国际

ES(EcmaScript)是脚本语言的规范,是由 Ecma 国际通过 ECMA-262 标准化的脚本程序设计语言。 ECMA-262 由 TC39(Technical Committee 39)委员会推进和维护

JavaScript 是 EcmaScript 的一种实现,ES 新特性其实指的是 JavaScript 新特性

ES 新特性语法简洁,功能丰富,框架开发应用(如 Vue、React、Angular)开发过程中需要用到大量的 ES6 新特性语法

二、ES6 语法

1、let

特性:

(1)变量不能重复声明,而使用 var 可以重复声明

(2)块级作用域

ES5 中作用域分为全局作用域、函数作用域、eval 作用域(在 ES5 的严格模式中才会出现)

ES6 中引入块级作用域,即变量只在代码块中有效,出代码块就无效了,let 声明变量就是块级作用域

(3)不存在变量提升

不能在变量声明之前使用

(4)不影响作用域链

如函数作用域中没有找到某变量依然会向上一级作用域中找

let items = document.getElementsByClassName('item');
for(var i = 0; i < items.length; i++){
    items[i].onclick = function(){
        items[i].style.background = 'red';
    }
}

上述代码在点击时会报错 items[i] undefined,因为在点击时已经执行完 for 循环,执行点击的回调函数时在函数作用域中没有 i,则去上一级找,在全局下找到 i,并且 i 为 3,因此 items[i] 为 undefined

但若改成

let items = document.getElementsByClassName('item');
for(var i = 0; i < items.length; i++){
    items[i].onclick = function(){
        this.style.background = 'red';
    }
}

上述代码会出现预期效果

或者使用 let 声明变量 i

let items = document.getElementsByClassName('item');
for(let i = 0; i < items.length; i++){
    items[i].onclick = function(){
        items[i].style.background = 'red';
    }
}

此时代码也会出现预期效果,行点击的回调函数时在函数作用域中没有 i,则去上一级找,在代码块中找到相应的 i

2、const 声明常量

常量就是值不能修改的量

特点:

(1)一定要赋初始值

(2)一般常量名使用大写

(3)常量的值不能修改

(4)块级作用域

{
    const A = 'XX';
}
console.log(A);   //此时会报错

(5)对于数组和对象的元素进行修改不算对常量的修改,不会报错

因为仅对其元素修改并不会影响它保存的地址值

3、变量的解构赋值

ES6 允许按照一定模式从数组和对象中提取值,对变量进行赋值,这称为解构赋值

数组的解构

const arr = ['12','23','34','45'];
let [a,b,c,d] = arr;  //则 a 为 12,b 为 23,c 为 34,d 为45

对象的解构

const obj = {
    name: 'xxx';
    age: 12;
    fun: function(){}
};
let {name,age,fun} = obj;

4、模板字符串

ES6 中引入新的声明字符串的方式:使用反引号

特点:

(1)内容中可以直接出现换行符

(2)变量拼接

let a = '123';
let b = `${a}456`;  //b 为 123456

5、简化对象写法

ES6 允许在大括号里直接写入变量和函数作为对象的属性和方法

let name = 'xxx';
let fun = function(){}
const obj = {
    name,
    fun,
    fun1(){}  //在 ES5 中是 fun1: function(){}
}

6、箭头函数

ES6 允许使用箭头(=>)定义函数

//ES5
let fn = function(a,b){
    return a + b;
}
//ES6
let fn = (a,b) => {
    return a + b;
}

特性:

(1)this 是静态的,this始终指向函数声明时所在作用域下的 this 值

function getName(){
    console.log(this.name);
}
let getName1 = () => {
    console.log(this.name);
}
window.name = 'aaa';
const obj = {
    name: 'bbb';
}
getName();  //输出 aaa
getName1();  //输出 aaa
getName.call(obj);  //输出 bbb
getName1.call(obj);  //输出 aaa

(2)不能作为构造函数实例化对象

let Person = (name,age) => {
    this.name = name;
    this.age = age;
}
let p = new Person('xx',12);
console.log(p);    //报错 Person is not a constructor

(3)不能使用 arguments 变量

let fn = () => {
    console.log(arguments);
}
fn(1,2,3);  //报错 arguments is not defined

(4)箭头函数的简写

省略小括号 ———— 当形参只有一个的时候

let add = n => {return n+n;}
console.log(add(1));

省略大括号 ———— 当代码体只有一条语句时可省略大括号,此时 return 语句必须省略,语句的执行结果就是函数的返回值

let pow = n => n * n;
console.log(pow(2));

箭头函数的使用例子

例子1

let ele = document.getElementById('div1');
ele.addEventListener("click",function(){
    setTimeout(function(){
        this.style.background = 'red';
    },2000)
})

上述代码执行无效,因为定时器回调函数里的 this 指向 window,可改成如下

let ele = document.getElementById('div1');
ele.addEventListener("click",function(){
    let _this = this;
    setTimeout(function(){
        _this.style.background = 'red';
    },2000)
})

上述代码可实现相应效果,也可通过箭头函数来解决

let ele = document.getElementById('div1');
ele.addEventListener("click",function(){
    setTimeout(() => {
        this.style.background = 'red';
    },2000)
})

因为箭头函数中的 this 始终指向 函数声明时作用域下(addEventListener 的回调函数作用域)的 this,因此定时器回调函数中的 this 指向事件元素

例子2

返回数组中的偶数元素

const arr = [1,2,3,4,5,6]
//不使用箭头函数
const result = arr.filter(function(item){
    if(item % 2 === 0){
        return true;
    }else{
        return false;
    }
});
//使用箭头函数
const result = arr.filter(item => item % 2 === 0)

总结:箭头函数适合与 this 无关的回调,如定时器中回调函数、数组的方法回调;箭头函数不适合与 this 有关的回调,如事件回调(this 一般需要指向事件元素)、对象的方法

var obj = {
    name: 'xx';
    getName: function(){
        this.name;  //这里 this 指向该对象,若使用箭头函数,则 this 指向外层作用域的 this(如 window)
    }
}

7、函数参数的默认值

ES6 允许给函数参数赋值初始值

(1)形参初始值

具有默认值的参数一般位置要靠后

(2)与解构赋值结合

function connect({host="127.0.0.1",username,password,port}){}
connect({
    username:'root',
    password:'root',
    port:3306
})

8、rest 参数

ES6 引入 rest 参数,形参中通过 ...标识符 表示,用于获取函数的实参,返回数组,用来代替 arguments(返回的是对象),并且 rest 参数必须要放到参数最后

function fn(a,b,...args){
    console.log(args);
}
fn(1,2,3,4,5,6);  //输出数组[3,4,5,6]

9、扩展运算符

扩展运算符 ... 能将数组转换为逗号分隔的参数序列

const arr = ['a','b','c'];
function fn(){
    console.log(arguments);
}
fn(arr);  //输出实参数组中只有一个元素,该元素是个数组
fn(...arr);  //输出实参数组中有三个元素,三个元素分别对应 arr 中三个元素

注意 rest 参数是将 ... 用在函数形参中,而扩展运算符是用在函数调用的实参中

扩展运算符的应用:

(1)数组的合并

const arr1 = [1,2,3];
const arr2 = [4,5,6];
const arr = [...arr1,...arr2];

(2)数组的克隆

const arr1 = [1,2,3];
const arr2 = [...arr1];  

注意:若元素中有引用类型数据是浅拷贝

(3)将伪数组转为真正的数组

const divs = document.querySelectorAll('div');
const divarr = [...divs];

(4)在函数中的使用

function sun(...numbers){
    return numbers.reduce((preValue,currentValue)=>{
        return preValue + currentValue
    })
}
console.log(sum(1,2,3,4)); //可求和

(5)构造字面量对象时

let per1 = {name:'xx',age:18}
let per2 = {...per1}
console.log(...per1); //注意:这样会报错,对象不能展开,但只有外面加花括号{...对象}时可以展开里面的属性进行深拷贝,原对象中属性改变时拷贝的新对象中属性不变
per1.name = 'xxx1';
console.log(per2.name); //per2中 name 属性还是 xx
let per3 = {...per1,name:'xxx2',sex:'male'}; //这时得到的 per3 是 {name:'xxx2',age:18,sex:'male'}

10、Symbol

ES6 引入了新的原始数据类型 Symbol 表示独一无二的值,是一种类似于字符串的数据类型

特点:

(1)Symbol 的值是唯一的,用来解决命名冲突的问题

(2)Symbol 值不能与其他数据进行运算(如加减乘除、拼接、比较等)

(3)Symbol 定义的对象属性不能使用 for…in 循环遍历,但可以使用 Reflect.ownKeys 来获取对象的所有键名

let s1 = Symbol();       //这里把 Symbol 当成函数使用
let s2 = Symbol('aaa');  //其中传入的字符串为描述字符串
let s3 = Symbol('aaa');
console.log(s2 === s3);  //false
let s4 = Symbol.for('bbb');   //这里把 Symbol 当成函数对象,通过该方式创建,可通过描述字符串得出唯一的 Symbol 值
let s5 = Symbol.for('bbb'); 
console.log(s4 === s5);  //true 

通过 Symbol 给对象添加属性/方法

方式一:

let game = {......}
let methods = {
    up: Symbol(),
    down: Symbol()
};
game[methods.up] = function(){}
game[methods.down] = function(){}

通过 Symbol 来添加属性或方法更安全,避免与对象可能已存在的属性或方法冲突

方式二:

let game1 = {
    name:'xxx',
    [Symbol('up')]:function(){},
    [Symbol('down')]:function(){}
}

Symbol 内置值

除了定义自己使用的 Symbol 值以外,ES6 还提供了 11 个内置的 Symbol 值,指向语言内部使用的方法

Symbol.hasInstance

当其他对象使用 instanceof 运算符判断是否为该对象实例时会调用这个方法

Symbol.isConcatSpreadable

对象的 Symbol.isConcatSpreadable 属性是一个布尔值,表示该对象使用 Array.prototype.concat() 时是否可以展开

Symbol.unscopables

该对象指定了使用 with 关键字时,哪些属性会被 with 环境排除

Symbol.match

当执行 str.match(myObject) 时,若该属性存在,会调用它,返回该方法的返回值

Symbol.replace

当该对象被 str.replace(myObject) 方法调用时,会返回该方法的返回值

Symbol.search

当该对象被 str.search(myObject) 方法调用时,会返回该方法的返回值

Symbol.split

当该对象被 str.split(myObject) 方法调用时,会返回该方法的返回值

Symbol.iterator

对象进行 for…of 循环时,会调用 Symbol.iterator 方法,返回该对象的默认遍历器

Symbol.toPrimitive

该对象被转为原始类型的值,会调用该方法,返回该对象对应的的原始类型值

Symbol.toStringTag

在该对象上调用 toString 方法时,返回该方法的返回值

Symbol.species

创建衍生对象时,会使用该属性

Symbol.hasInstance

Symbol.hasInstance 这一个整体将作为其他普通对象的方法,可扩展对象功能,通过对它的设置可以改变对象在特定场景下的表现结果(其他内置值也是类似道理),当其他对象使用 instanceof 运算符判断是否为该对象实例时会调用这个方法

class Person{
    static [Symbol.hasInstance](param){
        console.log(param);  //输出 {}
        //return true;
    }
}
let o = {}
console.log(o instanceof Person);  //false,这个返回值会根据 Person 中 [Symbol.hasInstance](param){} 方法的返回结果改变,若返回 true 则这里会输出 true

Symbol.isConcatSpreadable

对象的 Symbol.isConcatSpreadable 属性是一个布尔值,表示该对象使用 Array.prototype.concat() 时是否可以展开

const arr = [1,2,3];
const arr2 = [4,5,6];
console.log(arr.concat(arr2));  //输出[1,2,3,4,5,6]
arr2[Symbol.isConcatSpreadable] = false;
console.log(arr.concat(arr2));  //输出[1,2,3,[4,5,6]]

11、迭代器

迭代器(Iterator)是一种接口(就是对象里的一个属性 Symbol.iterator,这是一个函数),为各种不同的数据结构提供统一的访问机制,任何数据结构只要部署 Iterator 接口就可以完成遍历操作

ES6 创造了一种新的遍历命令 for...of 循环,Iterator 接口主要提供 for...of 消费

原生具备 iterator 接口(可用 for of 遍历)的数据有 Array、Arguments、Set、Map、String、TypedArray、NodeList

注意:需要自定义遍历数据时要想到迭代器

工作原理

(1)创建一个指针对象,指向当前数据结构的起始位置

(2)第一次调用对象的 next 方法,指针自动指向数据结构的第一个成员

(3)接下来不断调用 next 方法,指针一直向后移动,直到指向最后一个成员

(4)每调用 next 方法返回一个包含 value 和 done(布尔值,为 true 表示遍历完成) 属性的对象

const arr = [1,2,3,4]
let iterator = arr[Symbol.iterator]();  //第(1)步
console.log(iterator.next());  //第(2)步,返回{value:1,done:false}
console.log(iterator.next());  //返回{value:2,done:false}
console.log(iterator.next());  //返回{value:3,done:false}
console.log(iterator.next());  //返回{value:4,done:false}
console.log(iterator.next());  //返回{value:undefined,done:true}

for…in 和 for…of区别

for(let i in items){} 遍历过程中这里的 i 表示键(数组中就是下标 0,1,2,…)

for(let i of items){} 遍历过程中这里的 i 表示值

自定义遍历数据

要遍历对象 obj 对象内的数组arr

const obj = {
    name:'xx',
    arr:[1,2,3,4],
    [Symbol.iterator](){
        let index = 0;
        let _this = this;
        return {  //因为 iterator 工作原理中第一步创建一个指针对象,所以返回需要是一个对象
            next:function(){  //因为 iterator 工作原理中第二步调用对象的 next 方法,所以需要一个 next 方法
                if(index < _this.arr.length){
                    const result = {value: _this.arr[index], done:false}
                    index++;
                    return result;
                }else{
                    return {value: undefined, done:false};
                }
            }
        }
    }
}
for(let o of obj){
    console.log(o)
}

12、生成器

生成器函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同

异步操作有如文件操作、网络操作(ajax、request)、数据库操作等

在 ES6 之前异步编程是通过纯回调函数实现

function * gen(){
    console.log('hello')
}
let iterator = gen();  //返回的是一个迭代器对象
console.log(iterator);   //此处并没有输出 hello
iterator.next();  //输出 hello

在生成器中可以出现 yield 语句

function * gen(){
    console.log('111');
    yield 'aa';
    console.log('222');
    yield 'bb';
    console.log('333');
    yield 'cc';
    console.log('444');
}
et iterator = gen();
iterator.next();  //输出 111
iterator.next();  //输出 222
iterator.next();  //输出 333
iterator.next();  //输出 444

function * gen(){
    console.log('111');
    yield 'aa';
    console.log('222');
    yield 'bb';
    console.log('333');
    yield 'cc';
    console.log('444');
}
et iterator = gen();
console.log(iterator.next());  //输出{value:'aa',done:false}
console.log(iterator.next());  //输出{value:'bb',done:false}
console.log(iterator.next());  //输出{value:'cc',done:false}
console.log(iterator.next());  //输出{value:'undefined',done:true}

for(let g of gen()){
    console.log(g);  //会分别输出aa bb cc
}

next 方法可以传入实参,该实参将作为上一个 yield 语句的返回结果

function * gen(arg){
    console.log(arg);
    let one = yield 111;
    console.log(one);
    let two = yield 222;
    console.log(two);
    let three = yield 333;
    console.log(three);
}
et iterator = gen('AAA');
console.log(iterator.next());  //输出 AAA {value:111,done:false}
console.log(iterator.next('BBB'));  //输出 BBB {value:222,done:false}
console.log(iterator.next('CCC'));  //输出 CCC{value:333,done:false}
console.log(iterator.next('DDD'));  //输出 DDD{value:'undefined',done:true}

生成器实例

例子1

1s 后控制台输出 111,2s 后输出 222,3s 后输出 333

方式一:使用定时器

setTimeout(() => {
    console.log(111);
    setTimeout(() => {
        console.log(222);
        setTimeout(() => {
            console.log(333);
        },3000);
    },2000);
},1000);

若使用定时器实现会出现回调地狱,一层层不停回调,阅读、调试不方便,且容易重名

方式二:使用生成器

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();

例子2

第一秒获取用户数据,下一秒获取订单数据,下一秒获取商品数据

function getUsers(){
    setTimeout(() => {
        let data = '用户数据';
        iterator.next(data);
    },1000)
}
function getOrders(){
    setTimeout(() => {
        let data = '订单数据';
        iterator.next(data);
    },1000)
}
function getGoods(){
    setTimeout(() => {
        let data = '商品数据';
        iterator.next(data);
    },1000)
}
function * gen(){
    let users = yield getUsers();
    let orders = yield getOrders();
    let goods = yield getGoods();
}
let iterator = gen();
iterator.next();

13、Promise

Promise 是 ES6 引入的异步编程的新解决方案,语法上 Promise 是一个构造函数,用来封装异步操作并可以获取其成功或失败的结果

(1)Promise 构造函数

(2)Promise.prototype.then 方法

(3)Promise.prototype.catch 方法

//实例化 Promise 对象
const p = new Promise(function(resolve,reject){  //通过 resolve 和 reject 函数改变 Promise 实例对象的状态
    setTimeout(function(){  //把异步任务封装在 Promise 对象中
        let data = 'xxx';
        resolve(data); //调用 resolve 方法后 Promise 实例对象的状态就变为成功
        let err = '错误';        
        reject(err);  //调用 resolve 方法后 Promise 实例对象的状态就变为失败
    },1000);
});
//Promise 实例对象状态改变后调用 then 方法
p.then(function(value){  //状态为成功时调用
    console.log(value);
},function(reason){     //状态为失败时调用
    consle.error(reason);
})

Promise 封装读取文件

Promise 读取文件会使用到 Node.js 的 API

使用 fs 模块读取文件的原始方法:

const fs = require('fs');
fs.readFile('./xxx.md', (err, data) => {
    if(err) throw err;  //若失败抛出错误
    console.log(data.toString());  //若没有出错输出内容,其中 data 是一个 Buffer
});

使用 Promise 封装

const p = new Promise(function(resolve,reject){
    fs.readFile('./xxx.md', (err, data) => {
        if(err) reject(err);
        resolve(data)
    });
});
p.then(function(value){  //状态为成功时调用
    console.log(value.toString());
},function(reason){     //状态为失败时调用
    consle.log("读取失败");
})

Promise 封装 AJAX 请求

const p = new Promise(function(resolve,reject){
    const xhr = new XMLHttpRequest(); 
    xhr.open('POST','http://127.0.0.1:8000/server);
    xhr.send();
    xhr.onreadystatechange = function(){
        if(xhr.readyState === 4){
            if(xhr.status >= 200 && xhr.status < 300){
                resolve(xhr.response)
            }else{
                reject(xhr.status)
            }
        }
    }
});
p.then(function(value){  //状态为成功时调用
    console.log(value);
},function(reason){     //状态为失败时调用
    consle.error(reason);
})

Promise.prototype.then 方法

Promise.prototype.then 方法返回值也是一个 Promise 对象,该返回对象的状态由 then 里的回调函数的执行结果决定

若回调函数中返回的结果是非 Promise 类型的属性,则状态为成功,返回的值为成功时回调函数的返回值

若回调函数中返回 Promise 类型的对象,则内部的该 Promise 对象的返回状态就是外部 then 方法返回的 Promise 对象的状态

若回调函数中抛出错误,则 then 方法返回的 Promise 对象状态也为失败,值为出错的值

const p = new Promise(function(resolve,reject){
    setTimeout(function(){
        let data = 'xxx';
        resolve(data);
    },1000);
});
const result = p.then(value => {
    console.log(value);
    //1、返回非 Promise 类型
    //return 123; //或者直接没有 return 语句返回 undefined
    //2、返回 Promise 对象,则内部的该 Promise 对象的返回状态就是外部 then 方法返回的 Promise 对象的状态
    //return new Promise((resolve,reject)=>{
    //    resolve('ok'); //外部 then 方法返回的 Promise 对象状态也为成功,且值为 ok
    //})
    //3、抛出错误,外部 then 方法返回的 Promise 对象状态也为失败,值为出错的值
    //throw new Error('出错');
}, reason => {
    consle.error(reason);
})

因为 then 方法返回值也是一个 Promise 对象,所以可以进行链式调用,从而杜绝回调地狱

p.then(value => {

}, reason => {

}).then(value =>{

}, reason =>{

})

then 方法的链式调用

例子:按顺序读三个文件,并把三个文件内容合在一起后做输出

可通过 Promise 读取多个文件,Promise 可解决回调地狱

const p = new Promise((resolve,reject) => {
    fs.readFile('./文件1.md', (err, data) => {
        resolve(data)
    });
});
p.then(value => {  //状态为成功时调用
    return new Promise((resolve,reject) => {
        fs.readFile('./文件2.md', (err, data) => {
            resolve([value,data])
        });
    });
}).then(value => {
    return new Promise((resolve,reject) => {
        fs.readFile('./文件3.md', (err, data) => {
            value.push(data);
            resolve(value);
        });
    });
}).then(value => {
    console.log(value.join('\r\n'));
})

Promise.prototype.catch 方法

Promise.prototype.catch 方法其实算是一个语法糖,其实由 then 方法不指定第一个参数结果和 catch 方法一样

const p = new Promise(function(resolve,reject){
    setTimeout(function(){
        let err = '错误';        
        reject(err); 
    },1000);
});
p.catch(function(reason){  //只需指定失败的回调
    console.warn(reason);
})

14、Set

ES6 提供了新的数据结构 Set(集合),它类似于数组,但成员的值都是唯一的

集合实现了 iterator 接口,所以可使用扩展运算符和 for…of 进行遍历

集合的属性和方法:

size 属性返回会集合元素个数

add(xxx) 增加一个新元素,返回当前集合

delete(xxx) 删除元素,返回 boolean 值

has(xxx) 检测集合中是否包含某个元素,返回 boolean 值

clear() 清空集合中元素

let s1 = new Set();
let s2 = new Set([1,2,3,1])  //会自动去重
console.loge(s2.size);  //输出3
for(let s of s2){
    console.log(s); //输出 1,2,3
}

集合的使用

数组去重

let arr = [1,2,3,4,5,4,3,2,1];
let result = [...new Set(arr)]

交集

let arr = [1,2,3,4,5,4,3,2,1];
let arr1 = [4,5,6,5,6];
let result = [...new Set(arr)].filter(item => {
    let s = new Set(arr1);  //4 5 6
    if(s.has(item)){
        return true;
    }else{
        return false;
    }
})
//上述代码可简化为let result = [...new Set(arr)].filter(item => new Set(arr1).has(item));

并集

let arr = [1,2,3,4,5,4,3,2,1];
let arr1 = [4,5,6,5,6];
let union = [...new Set([...arr,...arr2])]

差集

差集即对交集取反

//arr - arr1
let arr = [1,2,3,4,5,4,3,2,1];
let arr1 = [4,5,6,5,6];
let diff = [...new Set(arr)].filter(item => !(new Set(arr1).has(item)));

15、Map

ES6 提供了 Map 数据结构,它类似于对象,也是键值对的集合,但是键的范围不限于字符串,各种类型的值(包括对象)都可以当作键

Map 也实现了 iterator 接口,所以可使用扩展运算符和 for…of 进行遍历

Map 的属性和方法:

size 属性返回会 Map 元素个数

set(键,值) 增加一个新元素,返回当前 Map

delete(键) 删除元素

get(键) 返回键名对象的键值

has(xxx) 检测 Map 中是否包含某个元素,返回 boolean 值

clear() 清空集合中,返回 undefined

使用 iterator 遍历返回的结果每个元素是个数组 [键,值]

16、class 类

ES6 提供了更接近传统语言的写法,引入了 Class(类)作为对象的模板。通过 class 关键字可以定义类

基本上 ES6 的 class 可以看作只是一个语法糖,它绝大部分功能 ES5 都能做到,新的 class 写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已

class 声明类

constructor 定义构造函数初始化

extends 继承父类

super 调用父级构造方法

static 定义静态方法和属性

父类方法可以重写

//ES5 构造类
function Phone (brand,price){
    this.brand = brand;
    this.price = price;
}
Phone.prototype.call = function(){}
let huawei = new Phone('华为',6000);
huawei.call()

//ES6
class Phone{
    constructor(brand,price){  //构造方法,new 创建实例对象时会自动执行
        this.brand = brand;
        this.price = price;
    }
    call(){  //方法必须使用该语法,不能使用 ES5 中的 call: function(){}
    }
}
let oneplus = new Phone('1+',2000);

class 的静态成员

函数对象和实例对象的属性是不相通的,属于类的属性但不属于类实例的属性这种属性称为静态成员

ES5 中

function Phone (brand,price){
    this.brand = brand;
    this.price = price;
}
Phone.name = name; //静态成员
Phone.size = size; //静态成员
Phone.prototype.change = function(){}
let nokia = new Phone();
conosle.log(nokia.name);  //报错
console.log(nokia.size);  //报错
nokia.change();  //不报错

ES6 中,通过 static 标注的属性或方法属于类而不属于类实例对象

class Phone{
    //静态属性
    static name = "手机";
    static change(){}
}
let nokia = new Phone();
conosle.log(nokia.name);  //输出 undefined
console.log(Phone.name);  //输出 手机

class 类对象继承

ES5 中使用原型链 + 借用构造函数的组合继承,可参考之前的博客中 3.2 部分

在 ES6 的 class 中使用 extends 继承父类

//父类
class Phone{
    constructor(brand,price){
        this.brand = brand;
        this.price = price;
    }
    //父类的成员属性
    call(){
    }
}
//子类
class SmartPhone extends Phone{
    //构造方法
    constructor(brand,price,color,size){
        super(brand,price);//super 就是父类中 constrctor 方法,通过 super 完成调用,相当于 ES5 中 Phone.call(this,brand,price);
        this.color = color;
        this.size = size;
    }
    photo(){}
}
const xiaomi = new SmartPhone('xx',1000,'黑色','4.7inch');
xiaoomi.call();
xiaomi.photo();

子类对父类方法的重写

//父类
class Phone{
    constructor(brand,price){
        this.brand = brand;
        this.price = price;
    }
    //父类的成员属性
    call(){
        console.log('call')
    }
}
//子类
class SmartPhone extends Phone{
    //构造方法
    constructor(brand,price,color,size){
        super(brand,price);//super 就是父类中 constrctor 方法,通过 super 完成调用,相当于 ES5 中 Phone.call(this,brand,price);
        this.color = color;
        this.size = size;
    }
    photo(){}
    call(){  //重写父类中 call 方法
        console.log('call1');
    }
}
const xiaomi = new SmartPhone('xx',1000,'黑色','4.7inch');
xiaomi.call();  //输出 call1

注意:在 js 的 class 中子类不能直接调用父类的同名方法,普通成员方法中也不能使用 super() 去调用父类的同名方法

class 中的 getter 和 setter

class Phone{
    get price(){  //对 price 属性的读取绑定了一个函数,只要读取实例对象里的 price 属性就会执行该函数,函数的返回值就是 price 属性的值
        console.log('price属性被读取了')
        return 'xxx'
    }
    get price(newVal){  //对 price 属性的进行赋值时就会调用该函数,且 setter 中必须有一个参数
        console.log('price属性被修改了')
    }
}
let s = new Phone();
console.log(s.price);  //,输出 “price属性被读取了” 以及 “xxx”
s.price = 'free';  //输出 “price属性被修改了”

get 的使用场景:get 常用来对对象的动态属性(即属性值是变化的)做封装,如求整数、求平均数时

set:通过 set 可以添加更多的控制和判断,如判断给属性设置的值是否合法(如得是数字,结果传了字符串)若成功则赋值否则不赋值

17、数值扩展

Number.EPSILON

Number.EPSILON 属性值为 2.2204460492503130808472633361816-16 是 JavaScript 表示的最小精度

若两个数的差值小于 Number.EPSILON 则相等

console.log(0.1 + 0.2 === 0.3)  //false
//0.1 + 0.2 的结果为 0.30000000000000004

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))  //true

进制

二进制 0b 开头,八进制 0o 开头,十六进制 0x 开头

Number.isFinite

Number.isFinite 检测一个数值是否为有限数

console.log(Number.isFinite(100/0))  //false
console.log(Number.isFinite(Infinity))  //false

Number.isNaN

Number.isNaN 检测一个数值是否为 NaN

在 ES5 中 isNaN() 是个单独的函数,在 ES6 中把它作为 Number 中的一个方法

console.log(Number.isNaN(10))  //false

Number.parseInt 和 Number.parseFloat

Number.parseInt 和 Number.parseFloat 把字符串转为整数、浮点数

在 ES5 中 parseInt() 和 parseFloat() 是个单独的函数,在 ES6 中也把它们作为 Number 中的一个方法

console.log(Number.parseInt('10hello'))  //输出 10

Number.isInteger

Number.isInteger 判断一个数是否为整数

console.log(Number.isInteger(10))  //true

Math.trunc

Math.trunc 将数字的小数部分抹掉

console.log(Math.trunc(3.5))  //输出 3

Math.sign

Math.sign 判断一个数为正数、负数还是零,分别输出1、0、-1

console.log(Math.sign(100))  //输出 1
console.log(Math.sign(0))  //输出 0
console.log(Math.sign(-200))  //输出 -1

18、ES6 的对象方法扩展

Object.is

Object.is 判断两个值是否完全相等

console.log(Object.is(10,10))  //true
console.log(Object.is(NaN,NaN))  //true
console.log(NaN === NaN)  //false

Object.assign

Object.assign(被覆盖的对象,覆盖的对象) 对象的合并,后者会覆盖前者的同名属性,若前者中有后者没有的属性则覆盖后依然存在,若后者有前者没有的属性则不会出现在合并后新对象中

const config1 = {
    host:'localhost',
    port: 3306,
    name: 'root',
    pass: 'root',
    test: 'test'
}
const config2 = {
    host:'http://127.0.0.1',
    port: 33060,
    name: 'root1',
    pass: 'root1'
}
console.log(Object.assign(config1,config2))

Object.setPrototypeOf

Object.setPrototypeOf(对象实例, 原型对象) 设置原型对象

Object.getPrototypeOf(对象实例) 获取原型对象

const obj1 = {
    name:'xx1'
}
const obj2 = {
    arr: [1,2,3]
}
Object.setPrototypeOf(obj1, obj2);
console.log(Object.getPrototypeOf(obj1)) //会输出 obj2

19、模块化

模块化是指将一个大的程序文件拆分成许多小的文件,然后将小文件组合起来实现功能

ES6 之前的模块化规范有

1)CommonJS => NodeJS、Browserify(浏览器端打包工具)

2)AMD(针对浏览器) => requireJS

  1. CMD(针对浏览器) => seaJS

(左边是规范,右边是实现/产品)

模块化好处

(1)防止命名冲突

(2)代码复用

(3)高维护性(如不同人员间的修改不冲突,升级只需对某个或某些模块升级)

模块化语法

export 命令用于规定模块的对外接口,在模块文件中只需在需要暴露的数据或函数前添加 export 即可

暴露模块

方式一:分别暴露

export let school = 'xx';
export function teach(){}

方式二:统一暴露

let school = 'xx';
function teach(){}
export {school,teach};

方式三:默认暴露

export default {
    school: 'xx',
    teach: function(){}
}

引入模块

import 命令用于输入其他模块提供的功能

通过 script 标签引入模块

方式一:通用的导入方式

//对于分别暴露和统一暴露
<script type="module">
    import * as m1 from "./m1.js"
    console.log(m1)
</script>

//对于默认暴露
<script type="module">
    import * as m3 from "./m3.js"
    m3.default.teach();  //注意使用方式
</script>

方式二:解构赋值形式

//对于分别暴露和统一暴露
<script type="module">
    import {school,teach} from "./m1.js"
    import {school as xx,teach} from "./m2.js"
</script>

//对于默认暴露
<script type="module">
    import {default as m3} from "./m3.js"
    m3.default.teach();
</script>

方式三:简便形式(只能针对默认暴露)

<script type="module">
    import m3 from "./m3.js"
</script>
通过入口文件引入模块

新建一个入口文件 xxx.js,在该文件中对各模块进行引入和做一些操作

import * as m1 from "./m1.js";
import * as m2 from "./m2.js";
import * as m3 from "./m3.js";
console.log(m1);
m2.teach();
m3.default.change();

然后在 html 文件中引入该入口文件

<script src="./xxx.js" type="module"></script>
引入 npm 安装的模块

和上面一样使用入口文件的方式引入模块,并在入口文件中编写相关操作的代码

如通过 npm i jquery 安装 jquery 包,在入口文件中引入并使用:

import $ from 'jquery'; //ES6 中的引入方式,相当于 CommonJS 中 const $ = require("jquery");
$('body').css('background','red');

ES6 模块化代码转换

因为并不是所有浏览器都兼容 ES6 新特性,所以需要对代码进行转换

babel 是一个 JavaScript 编译器,可以把 ES6 代码转换为 ES5

步骤:

(1)安装工具 babel-cli(babel 的命令行工具)、babel-preset-env(预设包,能把最新的 ECMAScript 特性转换为 ES5 语法)、browserify(打包工具)

npm init --yes
npm i babel-cli babel-preset-env browserify -D

-D 为开发依赖,为局部安装

(2)编译

前提:入口js文件、其他js源码放在一个目录下

npx babel js代码文件夹 -d dist/js --presets=babel-preset-env

其中 dist/js 为输出的文件夹

因为这里 babel 是局部安装,所以使用 npx babel 命令,若全局安装可直接使用 babel…

(3)打包

npx browserify dist/js/入口文件.js -o dist/bundle.js

(4)引入编译打包好的文件

<script src="dist/bundle.js"></script>

三、ES7 新特性

1、Array.prototype.includes

数组.includes(xxx) 方法用来检测数组中是否包含某个元素,返回布尔值

2、指数操作符

在 ES7 中引入指数运算符 ** 实现幂运算,相当于 Math.pow

console.log(2 ** 10);  //1024

四、ES8 新特性

1、async 和 await

async 和 await 两种语法结合可以让异步代码像同步代码一样

async

async 函数的返回值为 promise 对象,promise 对象的结果由 async 函数执行的返回值决定

async 函数里 return 一个非 Promise 类型的值结果都是一个 Promise 对象,对象的值为 return 的相应的值

并且只要 async 函数 return 的不是一个 Promise 类型对象,都会返回成功的状态,即返回一个成功的 Promise 对象

若在 async 函数中抛出错误,则会返回一个失败的 Promise 对象

若在 async 函数中返回一个 Promise 对象,若 Promise 对象是成功的,则 async 函数也是成功的,且返回的 Promise 中 resolve 的值即为 async 函数返回对象成功的值,若 Promise 对象是失败的,则 async 函数也是失败的

//return 一个非 Promise 类型的对象
async function fn1(){
    return; //只要这里 return 的不是一个 Promise 类型对象,最后函数执行完都会返回一个成功的 Promise 对象
}
//抛出错误
async function fn2(){
    throw new Error('出错'); //函数执行完会返回一个失败的 Promise 对象
}
//return一个 Promise 对象
async function fn3(){
    return new Promise((resolve,reject)=>{
        resolve('成功');  //fn3 返回的结果也是成功的
    })
}
//return一个 Promise 对象
async function fn4(){
    return new Promise((resolve,reject)=>{
        reject('失败');  //fn3 返回的结果也是失败的
    })
}
const result = fn3();
result.then(value=>{
    console.log(value);  //输出 “成功”
},reason=>{
    console.log(reason);
})

await

await 必须写在 async 函数中,但 async 中可以没有 await

await 右侧的表达式一般为 promise 对象,await 返回的是 promise 成功的值,await 的 promise 失败了就会抛出异常,需要通过 try…catch 捕获处理

const p = new Promise((resolve, reject) => {
    resolve("xxxx")
})
async function main() {
    try{
        let result = await p;
        console.log(result);  //输出的就是 p 成功时的 xxx
    }catch(e){
        console.log(e)
    }
}
main();

async 和 await 结合读取文件

分别读取 3 个文件并输出

const fs = require('fs');
function fn1(){
    return new Promise(function(resolve,reject){
        fs.readFile('./文件1.md', (err, data) => {
            if(err) reject(err);
            resolve(data)
        });
    });
}

function fn2(){
    return new Promise(function(resolve,reject){
        fs.readFile('./文件2.md', (err, data) => {
            if(err) reject(err);
            resolve(data)
        });
    });
}

function fn3(){
    return new Promise(function(resolve,reject){
        fs.readFile('./文件3.md', (err, data) => {
            if(err) reject(err);
            resolve(data)
        });
    });
}

async function main(){
    let data1 = await fn1();
    let data2 = await fn2();
    let data3 = await fn3();
    console.log(data1.toString());
    console.log(data2.toString());
    console.log(data3.toString());
}
main();

async 和 await 结合发送 AJAX 请求

function fn(url){
    return new Promise(function(resolve,reject){
        const xhr = new XMLHttpRequest(); 
        xhr.open('GET',url);
        xhr.send();
        xhr.onreadystatechange = function(){
            if(xhr.readyState === 4){
                if(xhr.status >= 200 && xhr.status < 300){
                    resolve(xhr.response)
                }else{
                    reject(xhr.status)
                }
            }
        }
    });
}

async function main(){
    let result = await fn('http://127.0.0.1/server');
    console.log(result);
}
main();

注意:axios 发送 AJAX 请求的返回结果就是一个 Promise 对象,所以通过 axios 发送请求,并利用 await 接收结果非常方便

2、ES8 中对象方法的扩展

Object.value 和 Object.entries

Object.values() 方法返回一个给定对象的所有可枚举属性值的数组

Object.entries() 方法返回一个给定对象的自身可遍历属性的数组,数组中每个元素又是一个数组 [key,value]

const obj = {
    name: 'xx',
    arr1:[1,2,3]
}
console.log(Object.entries(obj));  //输出 [['name', 'xx'],['arr',[1,2,3]]]
const m = new Map(Object.entries(obj));
conosle.log(m.get('arr1'));  //输出 [1,2,3]

Object.getOwnPropertyDescriptors

Object.getOwnPropertyDescriptors 返回指定对象所有自身属性的描述对象,通过描述对象可以对对象进行深拷贝

五、ES9 新特性

1、ES9 的扩展运算符与 rest 参数

rest 参数与 spread 扩展运算符在 ES6 中已经引入,但是 ES6 中只针对数组,在 ES9 中为对象提供了像数组一样的 rest 参数和扩展运算符

function connect({host,port,...user}){
    console.log(user)  //除了 host 和 port 以外的参数都会存到 user 中,user 是个对象
}
connect({
    host:'127.0.0.1',
    port:3306,
    username: 'root',
    password:'root',
    type:'master'
})


const obj1 = {att1:'1'}
const obj2 = {att2:'2'}
const obj3 = {att3:'3'}
const obj = {...obj1,...obj2,...obj3} //把三个对象中的属性都合并到对象 obj 中

2、ES9 的正则扩展

命名捕获分组

命名捕获分组就是可以给分组匹配的结果命名,方便对结果进行处理

let str = '<a href="http://127.0.0.1">标签文本</a>'
//没有命名捕获分组时
const reg = /<a href="(.*)">(.*)<\/a>/;
const result = reg.exec(str);
console.log(result);  //result 是个数组,result[0]是str,result[1]是 “http://127.0.0.1”,result[2]是 “标签文本”

//有命名捕获分组时
const reg = /<a href="(?<url>.*)">(?<text>.*)<\/a>/;
const result = reg.exec(str);
console.log(result);  //result 中有个 groups 对象属性,该属性中有 url 和 text
console.log(result.groups.url);  //http://127.0.0.1
console.log(result.groups.text); //标签文本

反向断言

断言:可以根据目标内容的前边和后边来做唯一性识别

正向断言:根据当前匹配的后边的内容来判断匹配的内容是否合法

let str = 'JS12345哈哈哈789啦啦啦'
//正向断言:提取789
const reg = /\d+(?=啦)/;  //通过789后边跟着 “啦” 判断
const result = reg.exec(str);

反向断言:根据当前匹配的前边的内容来判断匹配的内容是否合法

let str = 'JS12345哈哈哈789啦啦啦'
//反向断言:提取789
const reg = /(?<=哈)\d+/;  //通过789前边跟着 “哈” 判断
const result = reg.exec(str);

dotAll 模式

dot即 .. 是元字符,表示除换行符以外的任意单个字符

通过添加模式修正符 \\s 可使 . 匹配任意字符,包括空格和换行,若用模式修正符 \\gs 则会全局匹配,这样方便通配可能出现多个空格多个换行的情况

例子:提取 a 标签和 p 标签中标签文本

let str = `<ul>
                <li>
                    <a>xxx</a>
                    <p>xxx</p>
                </li>
                <li>
                    <a>xxx</a>
                    <p>xxx</p>
                </li>
            </ul>`;
//没有使用dotAll
const reg = /<li>\s+<a>(.*?)<\/a>\s+<p>(.*?)<\/p>/;
const result = reg.exec(str);
console.log(result);
//使用dotAll
const reg = /<li>.*?<a>(.*?)<\/a>.*?<p>(.*?)<\/p>/gs;  //加 ? 是为了禁止贪婪
let result;
let data = [];
while(result = reg.exec(str)){
    console.log(result);  //当匹配不到时是 NaN
    data.push({a:result[1],p:result[2]})
}
console.log(data)

六、ES10 新特性

1、ES10 的对象扩展方法 ———— Object.fromEntries

Object.fromEntries 用于创建对象,参数为二维数组或 Map

const result = Object.fromEntries([
    ['name','xx'],
    ['arr','1,2,3,4']
])
const m = new Map()
m.set('name','xxx')
const result = Object.fromEntries(m);

ES8 中 Object.entries 可将对象转化为二维数组

cosnt arr = Object.entries({
    name: 'xxx'
});  //返回一个数组,第一个元素为键名,第二个元素为键值

因此 Object.fromEntries 和 Object.entries 相当于逆运算,前者将二维数组转化为对象,后者将对象转化为二维数组

2、ES10 的字符串扩展方法 ———— trimStart 和 trimEnd

trimStart 和 trimEnd 用于指定清除字符串左侧或右侧空白字符

let str = '   hello   '
console.log(str.trimStart());
console.log(str.trimEnd());

3、ES10 的数组扩展方法 ———— flat 和 flatMap

flat(深度) 能将多维数组转化为低维数组,深度默认是 1

const arr = [1,2,3,[4,5,6]];  //二维数组
console.log(arr.flat());  //输出[1,2,3,4,5,6]
const arr = [1,2,3,[4,5,6,[7,8,9]]];  //三维数组
console.log(arr.flat()); //输出二维数组[1,2,3,4,5,6,[7,8,9]]
console.log(arr.flat(2)); //输出一维数组[1,2,3,4,5,6,7,8,9]

flatMap 是对 Map 结果作维度降低

const arr1 = [1,2,3,4];
const result = arr1.map(item => item * 10);
console.log(result); //输出[10,20,30,40]

const result = arr1.map(item => [item * 10]);
console.log(result); //输出二维数组[[10],[20],[30],[40]]

const result = arr1.flatMap(item => [item * 10]);
console.log(result); //输出一维数组[10,20,30,40]

4、ES10 的 Symbol 扩展方法 ———— Symbol.prototype.description

let s = Symbol('hello');
console.log(s.description);  //输出 hello

七、ES11 新特性

1、私有属性

私有属性在类的外部无法得到其结果,只能通过类内的方法来访问

class Person{
    //公有属性
    name;
    //私有属性
    #age;
    #weight;
    //构造方法
    constructor(name,age,weight){
        this.name = name;
        this.#age = age;
        this.#weight = weight;    
    }
    intor(){
        console.log(this.name);
        console.log(this.#age);
        console.log(this.#weight);
    }
}
const girl = new Person('xxx',18,'50kg');
console.log(girl.#age);  //报错 Private field '#age' must be declared in an enclosing class
girl.intro();  //不报错

2、Promise.allSettled

Promise.allSettled 接收 Promise 数组,总是返回成功的 Promise 对象,成功的值是个数组,数组中每个元素是对象{status,value 或 reason}

const p1 = new Promise((resolve,reject) => {
    setTimeout(() => {
        resolve('商品数据1');
    },1000)
});
const p2 = new Promise((resolve,reject) => {
    setTimeout(() => {
        resolve('商品数据2');
    },1000)
});
const result = Promise.allSettled([p1,p2]);
console.log(result);

类似的方法有 Promise.all,也是接收 Promise 数组,返回 Promise 对象,但返回的 Promise 对象是否为成功由数组中每个 Promise 对象的状态决定,只要有一个 Promise 对象失败则 all 方法返回的 Promise 对象也是失败

all 方法成功的值为数组,数组中的元素是接收的 Promise 对象成功的值,all 方法失败的值为接收的 Promise 对象中失败的那个的失败的值

const p1 = new Promise((resolve,reject) => {
    setTimeout(() => {
        resolve('商品数据1');
    },1000)
});
const p2 = new Promise((resolve,reject) => {
    setTimeout(() => {
        resolve('商品数据2');
    },1000)
});
const result = Promise.all([p1,p2]);

3、ES11 的字符串扩展方法 ———— Sting.prototype.matchAll

Sting.prototype.matchAll 用于得到正则批量匹配的结果

let str = `<ul>
                <li>
                    <a>xxx</a>
                    <p>xxx</p>
                </li>
                <li>
                    <a>xxx</a>
                    <p>xxx</p>
                </li>
            </ul>`;
const reg = /<li>.*?<a>(.*?)<\/a>.*?<p>(.*?)<\/p>/gs;
const result = str.matchAll(reg); //返回的结果是一个可迭代对象
for(let v of result){  //或者使用扩展运算符 const arr = [...result];
    console.log(v);
}

4、可选链操作符

可选链操作符 ?.,当面对对象类型的参数时,若对象深度较深,则通过可选链操作符后无需作层级判断

例子:获取 db 中的 host

function main(config){
    //原先的方法
    const dbHost = config && config.db && config.db.host; //但是当没有db对象或 db 中没有 host 属性时会报错
    //使用可选链操作符
    const dbHost = config?.db?.host;  //此时若没有db对象或 db 中没有 host 属性时也不会报错会输出 undefined,只有当前的属性存在时才会读后面的属性
}
main({
    db: {
        host:'192.168.1.100'
        username: 'root'
    },
    cache: {
        host:'192.168.1.200'
        username: 'admin'
    }
})

5、动态 import

通过动态 import 可实现按需加载,提高加载效率,import 方法返回的是 Promise 对象,语法:import(文件).then(module => {})

//文件hello.js
export function hello(){
    alert('hello');
}
//入口文件app.js
//在 ES11 之前使用 import * as m1 from './hello.js' 静态导入模块
cosnt btn = document.getElementById('#btn');
btn.onclick = function(){
    //ES11 中动态导入模块
    import('./hello.js').then(module => {
        module.hello();
    })
}

6、ES11 引入的数据类型 ———— BigInt

大整型通过 整数n 表示

通过 BigInt() 函数可将整数转为大整型

let n = 123;
console.log(BigInt(n));  //输出 123n

BigInt() 函数可用于大数值运算,注意 BigInt 类型数据不能直接和普通 int 作运算

let max = NUMBER.MAX_SAFE_INTEGER;
console.log(max);  //9007199254740991
console.log(max + 1);  //9007199254740992
console.log(max + 2);  //9007199254740992

console.log(BinInt(max));  //9007199254740991n
console.log(BinInt(max) + BigInt(1));  //9007199254740992n
console.log(BinInt(max) + BigInt(2));  //9007199254740993n

7、绝对全局对象 globalThis

globalThis 始终指向全局对象,无论执行环境是什么(如浏览器、Nodejs 等)

//在浏览器中
console.log(globalThis);  //window 对象
//在 Nodejs 中
console.log(globalThis);  //输出 Object [global]