ECMAScript

(一)ECMAScript 相关介绍

1. 什么是 ECMA

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kPJrRpJz-1656942294724)(D:\JAVAWeb\WbeNotes\图片\Es6\ES6-1.4w4pcis4fva0.jpg)]

2. 什么是 ECMAScript]ECMAScript 是由 Ecma 国际通过ECMA-262 标准化的脚本程序设计语言。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NFghfdsZ-1656942294725)(D:\JAVAWeb\WbeNotes\图片\Es6\ES6-2.6aq6tvkonsc0.jpg)]

3. 什么是 ECMA-262

Ecma 国际制定了许多标准,而ECMA-262 只是其中的一个,所有标准列表查看:http://www.ecma-international.org/publications/standards/Standard.htm

ECMA-262 历史版本查看网址: http://www.ecma-international.org/publications/standards/Ecma-262-arch.htm

  • ES5 是 ECMAScript 第5版,2009年发布。
  • ES6 是 ECMAScript 第6版,2015年发布,也叫 ES2015。
  • 从 ES6 开始,每年发布一个版本,版本号比年份最后一位大 1。

4. 谁在维护 ECMA-262

TC39(Technical Committee 39)是推进ECMAScript 发展的委员会。其会员都是公司(其中主要是浏览器厂商,有苹果、谷歌、微软、因特尔等)。TC39 定期召开会议,会议由会员公司的代表与特邀专家出席。

5. 为什么要学习 ES6

  • ES6 的版本变动内容最多,具有里程碑意义
  • ES6 加入许多新的语法特性,编程实现更简单、高效
  • ES6 是前端发展趋势,就业必备技能

6. ES6 兼容性

可查看兼容性:http://kangax.github.io/compat-table/es6/


(二)ECMASript 6 新特性

1. let 关键字

let 关键字用来声明变量,使用 let 声明的变量有几个特点:

  • 不允许重复声明
  • 块级作用域
  • 不存在变量提升
  • 不影响作用域链

应用场景:以后声明变量使用let 就对了

案例1:给多个 div 循环注册点击事件

  • 错误示例:

    //三个盒子点击切换颜色
    
    // 错误示例,     divs.length === 3
    document.addEventListener('DOMContentLoaded', function () {
        let divs = document.querySelectorAll('.box div');
        for (var i = 0; i < divs.length; i++) {
            divs[i].addEventListener('click', function () {
                //this.style.backgroundColor = 'red'
                divs[i].style.backgroundColor = 'pink';
            });
        }
        console.log(i); // 3
    });
    /*
    i 为当前作用域下的共享变量。
    当每次点击 div 的时候,各个点击事件共享 i 的值。
    循环完毕后 i = 2 然后自增 i = 3  
    此时 window.i = 3,这将报错。
    */
    
  • 正确实例:将以上代码中的 var 改为 let

案例2:1s 后循环输出所有数字

  • 错误示例:

    for (var i = 1; i <= 5; i++) {
        setTimeout(() => {
            console.log(i);
        }, 1000);
    }
    /*
    输出:6 6 6 6 6
    循环从1-5的时间很短暂,远不及 1s。
    此时五个异步事件瞬间加入到异步事件队列中,等待 1s后依次执行。
    而此时i为6,故瞬间输出 5 个 6。
    异步事件队头
    (1) console.log(i);
    (2) console.log(i);
    (3) console.log(i);
    (4) console.log(i);
    (5) console.log(i);
    */
    
  • 正确示例:

    for (let j = 1; j <= 5; j++) {
        setTimeout(() => {
            console.log(j);
        }, 1000);
    }
    // 输出:1 2 3 4 5
    // let 有块级作用域,每个 j 都会形成一个自己的块级作用域,与相应的异步事件共享:
    // {j = 1;} {j = 2;} {j = 3;} {j = 4;} {j = 5;}Copy to clipboardErrorCopied
    
  • 解决方法2:

    // 给每一个 i 设置一个立即执行函数,会形成自己的块级作用域,不影响外部变量。
    for (var i = 1; i <= 5; i++) {
        (function (i) {
            setTimeout(() => {
                console.log(i);
            }, 1000);
        })(i);
    }
    

2. const 关键字

const 关键字用来声明常量,const 声明有以下特点:

  • 声明必须赋初始值
  • 标识符一般为大写
  • 不允许重复声明
  • 值不允许修改
  • 块级作用域

注意:对象属性修改和数组元素变化不会出发 const 错误
应用场景:声明对象类型使用 const,非对象类型声明选择 let

案例:

const arr = [1, 2, 3, 4];
arr.push(5, 6);
console.log(arr);
// 不报错Copy to clipboardErrorCopied
const obj = {
    uname: 'rick',
    age: 30
}
obj.age = 40;
// 只要不改变地址,就不报错Copy to clipboardErrorCopied

3. 变量的解构赋值

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

数组的解构赋值:

const arr = ['red', 'green', 'blue'];
let [r, g, b] = arr; 
// r = red g = green b = blue     如果数组 arr 只有三个元素则 b = underfined

对象的解构赋值:

const obj = {
    uname: 'rick',
    age: 30,
    sayHi: function () {
        console.log('hello');
    },
    sayBye() {
        console.log('Bye~');
    }
}
let {name, age, sayHi} = obj;
let {sayBye} = obj;   //单独取出来 方便使用 sayBye()   不用写 obj.sayBye

应用场景:频繁使用对象方法、数组元素,就可以使用解构赋值形式。

4. 模板字符串

模板字符串(template string)是增强版的字符串,用反引号 ` 标识,特点:

  • 字符串中可以出现换行符
  • 可以使用 ${xxx} 形式输出变量
let name = 'jack';
console.log(`hello, ${name}`);
let ul = `
    <ul>
    <li>apple</li>
    <li>banana</li>
    <li>peach</li>
    </ul>`
let div = '<div>'+ul+'</div>'   //老版
let div = `<div>${ul}</div>`    //新版

应用场景:当遇到字符串与变量拼接的情况使用模板字符串。

5. 简化对象写法

ES6 允许在大括号里面,直接写入变量和函数,作为对象的属性和方法。这样的书写更加简洁。

let uname = 'rick';
let age = 30;
let sayHi = function () {
    console.log('Hi');
}
// 组建对象
//老
const obj = {
    uname: uname,
    age: age,
    sayHi: sayHi
}
//新
const obj = {
    uname,
    age,
    sayHi,
    sayhi(){
        console.log('函数简写')
    }
    
}

应用场景:对象简写形式简化了代码,所以以后用简写就对了。

6. 箭头函数

ES6 允许使用「箭头」=> 定义函数。

  • function
    

    写法:

    function fn(param1, param2,, paramN) { 
        // 函数体
        return expression; 
    }
    
  • =>
    

    写法:

    let fn = (param1, param2,, paramN) => {
        // 函数体
        return expression;
    }
    

箭头函数的 注意点:

  • 如果形参只有一个,则小括号可以省略
  • 函数体如果只有一条语句,则花括号可以省略,函数的返回值为该条语句的执行结果
  • 箭头函数 this 始终指向声明时所在作用域下 this 的值
  • 箭头函数不能作为构造函数实例化
  • 不能使用 arguments 变量 arguments:保存实参 arguments[0] 就是第一个实参
  • 箭头函数适合与 this 无关的回调. 定时器, 数组的方法回调
  • 箭头函数不适合与 this 有关的回调. 事件回调, 对象的方法
// 省略小括号 
let fn1 = n => {
    return n * n;
}

// 省略花括号   return也必需省略
let fn2 = (a + b) => a + b;

// 箭头函数 this 始终指向声明时所在作用域下 this 的值
const obj = {
    a: 10,
    getA () {
        let fn3 = () => {
            console.log(this); // obj {...}
            console.log(this.a); // 10
        }
        fn3();
    }

}

案例1:箭头函数 this 始终指向声明时所在作用域下 this 的值,call 等方法无法改变其指向。

let obj = {
    uname: 'rick',
    age: 30
};
let foo = () => {
    console.log(this);
}
let bar = function () {
    console.log(this);
}
// call 对箭头函数无效
foo.call(obj); // window
bar.call(obj); // obj {...}

案例2:筛选偶数

let arr = [2, 4, 5, 10, 12, 13, 20];
let res = arr.filter(v => v % 2 === 0); //filter 对数组进行过滤  创建一个新数组
console.log(res); // [2, ,4 10, 12, 20]

案例3:点击 div两秒后变成粉色

  • 方案1:使用 _this 保存 div 下的 this,从而设置 divstyle 属性。

    div.addEventListener('click', function () {
        let _this = this;
        setTimeout(function () {
            console.log(_this); // <div>...<div>
            _this.style.backgroundColor = 'pink';
        }, 2000)
    });
    
  • 方案2:使用 => 箭头函数

    div.addEventListener('click', function () {
        setTimeout(() => {
            console.log(this); // <div>...</div>
            this.style.backgroundColor = 'pink';
        }, 2000);
    });
    

7. 函数参数默认值设定

ES6 允许给函数参数设置默认值,当调用函数时不给实参,则使用参数默认值。

具有默认值的形参,一般要靠后。

let add = (x, y, z=3) => x + y + z;
console.log(add(1, 2)); // 

可与解构赋值结合:

function connect({ host = '127.0.0.1', uesername, password, port }) {
    console.log(host); // 127.0.0.1
    console.log(uesername);
    console.log(password);
    console.log(port);
}
connect({
    // host: 'docs.mphy.top',
    uesername: 'root',
    password: 'root',
    port: 3306
})

8. rest 参数

ES6 引入 rest 参数,用于获取函数的实参,用来代替 arguments,作用与 arguments 类似。将接收的参数序列转换为一个数组对象。

用在函数形参中,语法格式:fn(a, b, ...args),写在参数列表最后面。

let fn = (a, b, ...args) => {
    console.log(a);
    console.log(b);
    console.log(args);
};
fn(1,2,3,4,5);
/*
1
2
[3, 4, 5]
*/

案例1:求不定个数数字的和

let add = (...args) => {
    let sum = args.reduce((pre, cur) => {
        pre + cur
    }, 0);    //pre 第一次指定的值是 0  所以 0+1  返回1 pre = 1 上次运算结果值 再次运算依次遍历  
    return sum;
}
console.log(add(1, 2, 3, 4, 5)); 
//reduce 方法
        const a = [1, 2, 3, 4, 5]
        a.reduce(function (
            accumulator,
            currentValue,
            currentIndex,
            array) {
            console.log(
                accumulator,
                currentValue,  //数组元素
                currentIndex,  //索引
                array);       //数组本身
        }, {});    //指定空对象
        /*   
        accumulator   值输出一次指定的值 这里指定了空对象
            {} 1 0 (5) [1, 2, 3, 4, 5]
     undefined 2 1 (5) [1, 2, 3, 4, 5]     因为没有运算的值返回所以是undefined
     undefined 3 2 (5) [1, 2, 3, 4, 5]
     undefined 4 3 (5) [1, 2, 3, 4, 5]
     undefined 5 4(5) [1, 2, 3, 4, 5]
        */

应用场景:rest 参数非常适合不定个数参数函数的场景

9. spread 扩展运算符

扩展运算符(spread)也是三个点(...)。它好比 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列,对数组进行解包。可用在调用函数时,传递的实参,将一个数组转换为参数序列。

扩展运算符也可以将对象解包。

展开数组:

function fn(a, b, c) {
    console.log(arguments);
}
let arr = ['red', 'green', 'blue']; 
fn(arr)
// {0: ['red','green','blue']}
fn(...arr)
//{0: 'red', 1: 'green', 2: 'blue'}

案例1:数组合并

let A = [1, 2, 3];
let B = [4, 5, 6];
let C = [...A, ...B];
console.log(C); // [1, 2, 3, 4, 5, 6]

案例2:数组克隆
这种数组克隆属于浅拷贝。

let arr1 = ['a', 'b', 'c'];
let arr2 = [...arr1];
console.log(arr2); // ['a', 'b', 'c']

案例3:将伪数组转换为真实数组

const divs = document.querySelectorAll('div');
let divArr = [...divs];
console.log(divArr);

案例4:对象合并

// 合并对象
let obj1 = {
    a: 123
};
let obj2 = {
    b: 456
};
let obj3 = {
    c: 789
};
let obj = { ...obj1, ...obj2, ...obj3 };
console.log(obj);
// { a: 123, b: 456, c: 789 }

10. Symbol

10.1 Symbol 基本介绍与使用

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

JavaScript 的七种基本数据类型:

  • 值类型(基本类型):string、number、boolean、undefined、null、symbol
  • 引用数据类型:object(包括了array、function)

Symbol 的特点:

  • Symbol 的值是唯一的,用来解决命名冲突的问题
  • Symbol 值不能与其他数据进行运算
  • Symbol 定义的对象属性不能使用 for...in 循环遍历,但是可以使用 Reflect.ownKeys 来获取对象的所有键名

Symbol 的创建:

  • 创建一个

    Symbol
    

    let s1 = Symbol();
    console.log(s1, typeof s1);
    // Symbol() 'symbol'
    
  • 添加具有标识的

    Symbol
    

    let s2 = Symbol('1');
    let s2_1 = Symbol('1');
    console.log(s2 === s2_1); 
    // false
    // Symbol 都是独一无二的
    
  • 使用

    Symbol.for()
    

    方法创建,名字相同的

    Symbol
    

    具有相同的实体。

    let s3 = Symbol.for('apple');
    let s3_1 = Symbol.for('apple');
    console.log(s3 === s3_1); // true
    
  • 输出

    Symbol
    

    变量的描述,使用

    description
    

    属性

    let s4 = Symbol('测试');
    console.log(s4.description); // 测试
    

10.2 对象添加 Symbol 类型的属性

案例:安全的向对象中添加属性和方法。
分析:如果直接向对象中添加属性或方法,则原来对象中可能已经存在了同名属性或方法,会覆盖掉原来的。所以使用 Symbol 生成唯一的属性或方法名,可以更加安全的添加。

代码实现:

// 这是一个 game 对象,假设我们不知道里面有什么属性和方法
const game = {
    uname: '俄罗斯方块',
    up: function () { },
    down: function () { }
}

// 通过 Symbol 生成唯一的属性名,然后给 game 添加方法
let [up, down] = [Symbol('up'), Symbol('down')];
game[up] = function () {
    console.log('up');
}   //不会覆盖原有的属性或方法
game[down] = function () {
    console.log('down');
}

// 调用刚刚创建的方法
game[up]();
game[down]();
//第二种方法
const data = {
    up: 'w',
    down: '11',
    [Symbol('up')]: function() {
        console.log(1)
    }
    [Symbol('down')]: function() {
        console.log(1)
    }
}

//调用Symbol 定义的属性或方法   这种方式无法调用   下面采用的是对象属性的索引调用 不推荐
data[Reflect.ownKeys(youxi)[2]]()

10.3 Symbol 内置值

除了定义自己使用的 Symbol 值以外,ES6 还提供了11 个内置的 Symbol 值,指向语言内部使用的方法。可以称这些方法为魔术方法,因为它们会在特定的场景下自动执行。

方法描述
Symbol.hasInstance当其他对象使用 instanceof 运算符,判断是否为该对象的实例时,会调用这个方法
Symbol.isConcatSpreadable 对象的 Symbol.isConcatSpreadable 属性等于的是一个布尔值,表示该对象用于Array.prototype.concat() 时,是否可以展开
Symbol.species创建衍生对象时,会使用该属性
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. unscopables 该对象指定了使用 with 关键字时,哪些属性会被 with 环境排除。
Object.getOwnPropertySymbols()方法可以获取指定对象的所有 Symbol 属性名。该方法返回一个数组,成员是当前对象的所有用作属性名的 Symbol 值。

案例1:Symbol.hasInstance 方法判断是否属于这个对象时被调用。

class MyClass {
  [Symbol.hasInstance](foo) {
    return foo instanceof Array;  // 判断是否为数组
  }
}

[1, 2, 3] instanceof new MyClass() // true
//上面代码中,MyClass是一个类,new MyClass()会返回一个实例。
//该实例的Symbol.hasInstance方法,会在进行instanceof运算时自动调用,判断左侧的运算子是否为Array的实例。
//比如,foo instanceof Foo在语言内部,实际调用的是Foo[Symbol.hasInstance](foo)。




class Even {
  static [Symbol.hasInstance](obj) {
    return Number(obj) % 2 === 0;
  }
}

// 等同于
const Even = {
  [Symbol.hasInstance](obj) {
    return Number(obj) % 2 === 0;
    //return (true)   //可以手动返回true
  }
};

1 instanceof Even // false
2 instanceof Even // true
12345 instanceof Even // false

案例2:数组使用 concat 方法时,是否可以展开。

let arr1 = [1, 2, 3];
let arr2 = [4, 5, 6];
let arr3 = [4, 5, 6];
arr2[Symbol.isConcatSpreadable] = false;
console.log(arr1.concat(arr2));
// [ 1, 2, 3, [ 4, 5, 6, [Symbol(Symbol.isConcatSpreadable)]: false ] ]
console.log(arr1.concat(arr3));
// [ 1, 2, 3, 4, 5, 6 ]


//类似数组的对象正好相反,默认不展开。它的Symbol.isConcatSpreadable属性设为true,才可以展开。
let obj = {length: 2, 0: 'c', 1: 'd'};
['a', 'b'].concat(obj, 'e') // ['a', 'b', obj, 'e']

obj[Symbol.isConcatSpreadable] = true;
['a', 'b'].concat(obj, 'e') // ['a', 'b', 'c', 'd', 'e']


//Symbol.isConcatSpreadable属性也可以定义在类里面。
class A1 extends Array {
  constructor(args) {
    super(args);
    this[Symbol.isConcatSpreadable] = true;
  }
}
class A2 extends Array {
  constructor(args) {
    super(args);
  }
  get [Symbol.isConcatSpreadable] () {
    return false;
  }
}
let a1 = new A1();
a1[0] = 3;
a1[1] = 4;
let a2 = new A2();
a2[0] = 5;
a2[1] = 6;
[1, 2].concat(a1).concat(a2)
// [1, 2, 3, 4, [5, 6]]

案例3:Symbol.species

对象的Symbol.species属性,指向一个构造函数。创建衍生对象时,会使用该属性。

class MyArray extends Array {
}

const a = new MyArray(1, 2, 3);
const b = a.map(x => x);
const c = a.filter(x => x > 1);

b instanceof MyArray // true
c instanceof MyArray // true
//上面代码中,子类MyArray继承了父类Array,a是MyArray的实例,b和c是a的衍生对象。
//你可能会认为,b和c都是调用数组方法生成的,所以应该是数组(Array的实例),但实际上它们也是MyArray的实例。
//Symbol.species属性就是为了解决这个问题而提供的。现在,我们可以为MyArray设置Symbol.species属性。

class MyArray extends Array {
  static get [Symbol.species]() { return Array; }
}
//面代码中,由于定义了Symbol.species属性,创建衍生对象时就会使用这个属性返回的函数,作为构造函数。
//这个例子也说明,定义Symbol.species属性要采用get取值器。默认的Symbol.species属性等同于下面的写法。

static get [Symbol.species]() {
  return this;
}
//现在,再来看前面的例子。
class MyArray extends Array {
  static get [Symbol.species]() { return Array; }
}

const a = new MyArray();
const b = a.map(x => x);

b instanceof MyArray // false
b instanceof Array // true

//上面代码中,a.map(x => x)生成的衍生对象,就不是MyArray的实例,而直接就是Array的实例。

class T1 extends Promise {
}

class T2 extends Promise {
  static get [Symbol.species]() {
    return Promise;
  }
}

new T1(r => r()).then(v => v) instanceof T1 // true
new T2(r => r()).then(v => v) instanceof T2 // false

//上面代码中,T2定义了Symbol.species属性,T1没有。
//结果就导致了创建衍生对象时(then方法),T1调用的是自身的构造方法,
//而T2调用的是Promise的构造方法。

//总之,Symbol.species的作用在于,实例对象在运行过程中,需要再次调用自身的构造函数时,会调用该属性指定的构造函数。
//它主要的用途是,有些类库是在基类的基础上修改的,那么子类使用继承的方法时,作者可能希望返回基类的实例,而不是子类的实例。

案例4: 对象的Symbol.match属性,指向一个函数。当执行str.match(myObject)时,如果该属性存在,会调用它,返回该方法的返回值。

String.prototype.match(regexp)
// 等同于
regexp[Symbol.match](this)

class MyMatcher {
  [Symbol.match](string) {
    return 'hello world'.indexOf(string);
  }
}

'e'.match(new MyMatcher()) // 1

案例5: 对象的Symbol.replace属性,指向一个方法,当该对象被String.prototype.replace方法调用时,会返回该方法的返回值。·

String.prototype.replace(searchValue, replaceValue)
// 等同于
searchValue[Symbol.replace](this, replaceValue)

const x = {};
x[Symbol.replace] = (...s) => console.log(s);

'Hello'.replace(x, 'World') // ["Hello", "World"]
//Symbol.replace方法会收到两个参数,第一个参数是replace方法正在作用的对象,
//上面例子是Hello,第二个参数是替换后的值,上面例子是World。

案例6:对象的Symbol.search属性,指向一个方法,当该对象被String.prototype.search方法调用时,会返回该方法的返回值。

String.prototype.search(regexp)
// 等同于
regexp[Symbol.search](this)

class MySearch {
  constructor(value) {
    this.value = value;
  }
  [Symbol.search](string) {
    return string.indexOf(this.value);
  }
}
'foobar'.search(new MySearch('foo')) // 0

案例7: 对象的Symbol.iterator属性,指向该对象的默认遍历器方法

const myIterable = {};
myIterable[Symbol.iterator] = function* () {
  yield 1;
  yield 2;
  yield 3;
};

[...myIterable] // [1, 2, 3]

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

class Collection {
  *[Symbol.iterator]() {
    let i = 0;
    while(this[i] !== undefined) {
      yield this[i];
      ++i;
    }
  }
}

let myCollection = new Collection();
myCollection[0] = 1;
myCollection[1] = 2;

for(let value of myCollection) {
  console.log(value);
}
// 1
// 2

11. 迭代器 for …of

11.1 定义

遍历器(Iterator)就是一种机制。它是一种接口,为各种不同的数据结构提 供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作。

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

  • 原生具备iterator接口的数据(可用for of 遍历)

    • Array
    • Arguments
    • Set
    • Map
    • String
    • TypedArray
    • NodeList

案例:使用 next() 方法遍历原生自带 iterator 接口的数据:

// 遍历 Map
const mp = new Map();
mp.set('a', 1);
mp.set('b', 2);
mp.set('c', 3);
let iter1 = mp[Symbol.iterator]();
// next() 方法每执行一次,指针自增
console.log(iter1.next()); // { value: [ 'a', 1 ], done: false }
console.log(iter1.next()); // { value: [ 'b', 2 ], done: false }
console.log(iter1.next()); // { value: [ 'c', 3 ], done: false }
console.log(iter1.next()); // { value: undefined, done: true }
// 遍历数组
let xiyou = ['唐僧','孙悟空','猪八戒','沙僧'];
let iter2 = xiyou[Symbol.iterator]();
console.log(iter2.next()); // { value: '唐僧', done: false }
console.log(iter2.next()); // { value: '孙悟空', done: false }
console.log(iter2.next()); // { value: '猪八戒', done: false }
console.log(iter2.next()); // { value: '沙僧', done: false }C

上面的案例只是为了证明他们自带 iterator 接口,实际上直接使用 for...of 方法遍历即可(iterator 接口为 for...of)服务。例如,可以使用 for [k, v] of map 来遍历 Map 数据结构中的键和值。

const mp = new Map();
mp.set('a', 1);
mp.set('b', 2);
mp.set('c', 3);
for (let [k, v] of mp) {
    console.log(k, v);
}
/*
a 1
b 2
c 3
*/

11.2 工作原理

  • 创建一个指针对象,指向当前数据结构的起始位置
  • 第一次调用对象的 next 方法,指针自动指向数据结构的第一个成员
  • 接下来不断调用 next 方法,指针一直往后移动,直到指向最后一个成员
  • 每调用 next 方法返回一个包含 valuedone 属性的对象

应用场景:需要自定义遍历数据的时候,要想到迭代器。

11.3 自定义遍历数据

我们可以通过给数据结构添加自定义 [Symbol.iterator]() 方法来使该数据结构能够直接被遍历,从而使 for...of 能够直接遍历指定数据,达到为 for...of 服务的功能。

// 需求:遍历对象中的数组
const xiaomi = {
    uname: '小明',
    course: [ '高数', '大物', '英语', '数据库' ],
    // 通过自定义 [Symbol.iterator]() 方法
    [Symbol.iterator]() {
        // 初始指针对象指向数组第一个
        let index = 0;
        // 保存 xiaomi 的 this 值
        let _this = this;
        return {
            next: function () {
                // 不断调用 next 方法,直到指向最后一个成员
                if (index < _this.course.length) {
                    const status = {value: _this.coures[index], done: false} ;//done: false表示遍历还没有结束
                    index++;
                    return status;
                    //简写
                    return { value: _this.course[index++], done: false }; 
                } else {
                    // 每调用next 方法返回一个包含value 和done 属性的对象
                    return { value: undefined, done: true };
                }
            }
        }
    }
}
// for...of直接遍历达到目的
for (let v of xiaomi) {
    console.log(v);
}

12. Generator 生成器函数

12.1 生成器函数的声明和调用

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

  • * 的位置没有限制
  • 使用 function * gen()yield 可以声明一个生成器函数。生成器函数返回的结果是迭代器对象,调用迭代器对象的 next 方法可以得到 yield 语句后的值。
  • 每一个 yield 相当于函数的暂停标记,也可以认为是一个分隔符,每调用一次 next(),生成器函数就往下执行一段。
  • next 方法可以传递实参,作为 yield 语句的返回值

例如以下生成器函数中,3 个 yield 语句将函数内部分成了 4 段。

function* generator() {
    console.log('before 111'); // 生成器第 1 段
    yield '分隔1';
    console.log('before 222'); // 生成器第 1 段
    yield '分隔2';
    console.log('before 333'); // 生成器第 1 段
    yield '分隔3';
    console.log('after 333'); // 生成器第 1 段
}
let iter = generator();
console.log(iter.next());
console.log(iter.next());
console.log(iter.next());
console.log(iter.next());
/*
before 111
{ value: "分隔1", done: false }
before 222
{ value: "分隔2", done: false }
before 333
{ value: "分隔3", done: false }
after 333
{ value: undefined, done: true }
*/


 for (let v of generator()) {
            console.log(v);
}
/*
before 111
分隔1
before 222
分隔2
before 333
分隔3
 after 333
*/

12.2 生成器函数的参数传递

function* generator(arg) {
    console.log(arg); // 生成器第 1 段
    let one = yield '分隔1';
    console.log(one); // 生成器第 2 段
    let two = yield '分隔2';
    console.log(two); // 生成器第 3 段
    let three = yield '分隔3'; 
    console.log(three); // 生成器第 4 段
}

let iter = generator('aaa'); // 传给生成器第 1 段
console.log(iter.next());
console.log(iter.next('bbb')); // 传给生成器第 2 段,作为这一段开始的 yield 语句返回值
console.log(iter.next('ccc')); // 传给生成器第 3 段,作为这一段开始的 yield 语句返回值
console.log(iter.next('ddd')); // 传给生成器第 4 段,作为这一段开始的 yield 语句返回值
/*
aaa
{ value: "分隔1", done: false }
bbb
{ value: "分隔2", done: false }
ccc
{ value: "分隔3", done: false }
ddd
{ value: undefined, done: true }
*/

[12.3 生成器函数案例

案例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);
            iter.next();
        }, 1000);
    }
    
    function two() {
        setTimeout(() => {
            console.log(222);
            iter.next();
        }, 2000);
    }
    
    function three() {
        setTimeout(() => {
            console.log(333);
        }, 3000);
    }
    
    function* generator() {
        yield one();
        yield two();
        yield three();
    }
    
    let iter = generator();
    iter.next();
    

案例2:生成器函数模拟每隔1s获取商品数据

function getUsers() {
    setTimeout(() => {
        let data = '用户数据';
        iter.next(data); // 传参给生成器函数的第 2 段,后面类似
    }, 1000);
}

function getOrders() {
    setTimeout(() => {
        let data = '订单数据';
        iter.next(data);
    }, 1000);
}

function getGoods() {
    setTimeout(() => {
        let data = '商品数据';
        iter.next(data);
    }, 1000);
}

function* generator() {
    let users = yield getUsers();  //接收数据
    console.log(users);
    let orders = yield getOrders();
    console.log(orders);
    let goods = yield getGoods();
    console.log(goods);
}

let iter = generator();
iter.next();

13. Promise

13.1 Promise 的定义和使用

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

一个 Promise 必然处于以下几种状态之一:

  • 待定(pending):初始状态,既没有被兑现,也没有被拒绝。
  • 已兑现(fulfilled):意味着操作成功完成。
  • 已拒绝(rejected):意味着操作失败。

Promise 的使用:

  • Promise 构造函数:new Promise((resolve, reject)=>{})
  • Promise.prototype.then 方法
  • Promise.prototype.catch 方法

一个简单案例:

let p = new Promise(function (resolve, reject) {
    //resolve:成功  reject:失败  改变 promise 对象的状态
    // 使用 setTimeout 模拟请求数据库数据操作
    setTimeout(function () {
        // 这个异步请求数据库数据操作是否正确返回数据
        let isRight = true;
        if (isRight) {
            let data = '数据库中的数据';
            // 设置 Promise 对象的状态为操作成功
            resolve(data);
        } else {
            let err = '数据读取失败!'
            // 设置 Promise 对象的状态为操作失败
            reject(err);
        }
    }, 1000);
});

p.then(function (value) {  //调用成功返回的值   
    console.log(value);   // 数据库中的数据
}, function (reason) {   //调用失败返回的值
    console.error(reason);  //数据读取失败
})

13.2 Promise 封装读取文件

// 使用 nodejs 的 fs 读取文件模块
const fs = require('fs');

const p = new Promise(function (resolve, reject) {
    fs.readFile('./resources/为学.txt', (err, data) => {
        // err 是一个异常对象
        if (err) reject(err);
        resolve(data);
    })
})

p.then(function (value) {
    // 转为字符串输出
    console.log(value.toString());
}, function (reason) {
    console.log('读取失败!!');
})

13.3 Promise 封装 Ajax 请求

const p = new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest();
    xhr.open('get', 'https://api.apiopen.top/getJoke');
    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) {
    console.error(reason);
})

13.4 Promise.prototype.then 方法

先复习一下一个 Promise 的三种状态:

  • 待定(pending):初始状态,既没有被兑现,也没有被拒绝。
  • 已兑现(fulfilled):意味着操作成功完成。
  • 已拒绝(rejected):意味着操作失败。

Promise.prototype.then 方法返回的结果依然是 Promise 对象,对象状态由回调函数的执行结果决定

具体情况如下:

  • 若then方法没有返回值,则then方法返回的对象的状态值为成功fulfilled,返回结果值为undefined。

    const p = new Promise((resolve, reject) => {
        setTimeout(() => {
            // resolve('用户数据')
            reject('出错了');
        }, 1000);
    })
    // 未设定返回值
    const res = p.then((value) => {
        console.log(value);
    }, (reason) => {
        console.warn(reason);
    })
    // 打印 then 方法的返回值
    console.log(res); //Promise 对象
    
  • 如果回调函数中返回的结果是非Promise类型的属性,则then方法返回的对象,其状态为成功(fulfilled),返回结果值取决于

    then方法所执行的是哪个函数(resolve或reject)。

    const p = new Promise((resolve, reject) => {
        setTimeout(() => {
            // resolve('用户数据')
            reject('出错了');
        }, 1000);
    })
     // 返回的非 Promise 对象
    const res = p.then((value) => {
        console.log(value);
        return '成功了!!';
    }, (reason) => {
        console.warn(reason);
        return '出错啦!!'
    })
    // 打印 then 方法的返回值
    console.log(res);  
    /*
      16行输出  Promise {...[[PromiseValue]]: '出错啦'}   
      12行输出   出错了
    */
    

    打印结果:

  • 如果回调函数中返回的结果是Promise类型(eturn new Promise()),则then方法返回的Promise对象状态与该返回结果的状态相同,返回值也相同。

    const p = new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve('用户数据')
            // reject('出错了');
        }, 1000);
    })
    const res = p.then((value) => {
        console.log(value);
        // 返回 Promise 对象
        return new Promise((resolve, reject) => {
            resolve('(1)成功了!!!');
            // reject('(1)出错了!!!')
        })
    }, (reason) => {
        console.warn(reason);
        return new Promise((resolve, reject) => {
            // resolve('(2)成功了!!!');
            reject('(2)出错了!!!')
        })
    })
    // 打印 then 方法的返回值
    console.log(res);
    /*
     Promise {...[[PromiseValue]]: '(1)成功了!!!'}  
     用户数据
    */
    
  • 如果回调函数中返回的结果是throw语句抛出异常,则then方法的对象的状态值为rejected,返回结果值为throw抛出的字面量或Error对象。

    const p = new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve('用户数据');
        }, 1000);
    });
    const res = p.then((value) => {
      console.log(value);
        return new Promise((resolve, reject) => {
          throw new Error('错误了!!');
        })
    });
    // 打印结果
    console.log(res);
    

13.4 链式调用

Promise.prototype.then 方法返回的结果还是 Promise 对象,这意味着我们可以继续在该结果上使用 then 方法,也就是链式调用。

const p = new Promise(resolve=>{}, reject=>{});
p.then(value=>{}, reason=>{})
.then(value=>{}, reason=>{})
.then(value=>{}, reason=>{})
...C

13.5 链式调用练习-多个文件读取

const fs = require('fs');

let p = new Promise((resolve, reject) => {
    fs.readFile('./resources/users.md', (err, data) => {
        // 传给下一轮文件读取操作
        resolve(data);
    })
});

p.then(value => {
    return new Promise((resolve, reject) => {
        // value 为第一次读取的文件数据,data 为第二次(当前)读取的数据
        fs.readFile('./resources/orders.md', (err, data) => {
            // 将上轮读取结果和本轮合并传到下一轮轮读取操作
            resolve([value, data]);
        });
    });
}).then(value => {
    return new Promise((resolve, reject) => {
        fs.readFile('./resources/goods.md', (err, data) => {
            // value 为上一轮传递过来的文件数据数组
            value.push(data);   // 数组类型用 push 追加新的 data
            // 传给下一轮操作
            resolve(value);
        });
    });
}).then(value => {
    // 合并数组元素,输出
    console.log(value.join('\r\n'));
});

13.6 Promise.prototype.catch

catch() 方法返回一个 Promise,并且处理拒绝的情况。它的行为与调用 Promise.prototype.then(undefined, onRejected) 相同。

即:

obj.catch(onRejected);]

等同于:

obj.then(undefined, onRejected);

语法:

p.catch(onRejected);

p.catch(function(reason) {
   // 拒绝
});

举例:

var p1 = new Promise(function (resolve, reject) {
    resolve('Success');
});

p1.then(function (value) {
    console.log(value); // "Success!"
    throw 'oh, no!';
}).catch(function (e) {
    console.log(e); // "oh, no!"
}).then(function () {
    console.log('有 catch 捕获异常,所以这句输出');
}, function () {
    console.log('没有 catch 捕获异常,这句将不会输出');
});

输出结果:

Success
oh, no!
有 catch 捕获异常,所以这句输出Copy to clipboardErrorCopied

以上只是 Promise 的入门,更多还要进一步深入学习。

14. Set

14.1 Set 的定义和使用

ES6 提供了新的数据结构 Set(集合)。它类似于数组,但 成员的值都是唯一的,集合实现了 iterator 接口,所以可以使用『扩展运算符』和『for...of』进行遍历。

定义一个 Set 集合:

let st1 = new Set();
let st2 = new Set([可迭代对象]);

集合(这里假设有一个集合 st)的属性和方法:

  • st.size:返回集合个数
  • st.add(item):往集合中添加一个新元素 item,返回当前集合
  • st.delete(item):删除集合中的元素,返回 boolean
  • st.has(item):检测集合中是否包含某个元素,返回 boolean
  • st.clear():清空集合
  • 集合转为数组:[...st]
  • 合并两个集合:[...st1, ...st2]

14.2 集合案例

案例1: 数组去重

let arr1 = [1, 2, 2, 3, 3, 3, 4, 1, 2];
let res1 = [...new Set(arr1)];
console.log(res1); // [ 1, 2, 3, 4 ]

案例2:数组求交集

let arr2_1 = [1, 2, 2, 3, 4, 5];
let arr2_2 = [3, 6, 6, 7, 1, 4];
let res2 = arr2_1.filter((v) => {   //创建一个新数组  符合条件的返回一个新数组
    new Set(arr2_2).has(v);
2})
console.log(res2); // [ 1, 3, 4 ]

案例3:数组求并集

let arr3_1 = [1, 2, 2, 3, 4, 5];
let arr3_2 = [3, 6, 6, 7, 1, 4];
let res3 = [...new Set([...arr3_1, ...arr3_2])];
console.log(res3); // [ 1, 2, 3, 4, 5, 6, 7 ]

案例4:数组求差集

let arr4_1 = [1, 2, 2, 3, 4, 5];
let arr4_2 = [3, 6, 6, 7, 1, 4];
let res4 = [...new Set(arr4_1)].filter((v) => {
    !(new Set(arr4_2).has(v));
})
console.log(res4); // [ 2, 5 ]   1 和 2的差集 

15. Map

ES6 提供了 Map 数据结构。它类似于对象,也是键值对的集合。但是 “键” 的范围不限于字符串,各种类型的值(包括对象)都可以当作键。Map 也实现了 iterator 接口,所以可以使用『扩展运算符』和『for…of』进行遍历。

定义一个 Map:

let mp1 = new Map();
mp1.set('aaa', 111);
mp1.set('bbb', 222);
mp1.set('ccc', 333);

let mp2 = new Map([
    ['aaa', 111],
    ['bbb', 222],
    ['ccc', 333]
]);

console.log(mp1['aaa']); // undefined
console.log(mp2.get('bbb')); // 222

Map 的属性和方法:(k 为键,v为值)

  • size:返回 Map 的元素(键值对)个数
  • set(k, v):增加一个键值对,返回当前 Map
  • get(k):返回键值对的键值
  • has():检测 Map 中是否包含某个元素
  • clear():清空集合,返回 undefined

16. class 类

这部分在 JS 高级也涉及,故可以前往 JS 高阶 class 学习。,那部分的笔记更加详细,有原理。所以这一节后面部分只给出例子。

ES6 提供了更接近传统语言的写法,引入了 Class(类)这个概念,作为对象的模板。通过class 关键字,可以定义类。基本上,ES6 的 class 可以看作只是一个语法糖,它的绝大部分功能,ES5 都可以做到,新的 class 写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。

16.1 复习 ES5 function 构造函数 的继承

具体知识点可以前往:JS高级

//手机
function Phone(brand, price){
    this.brand = brand;
    this.price = price;
}

Phone.prototype.call = function(){
    console.log("我可以打电话");
}

//智能手机
function SmartPhone(brand, price, color, size){
    Phone.call(this, brand, price);
    this.color = color;
    this.size = size;
}

//设置子级构造函数的原型
SmartPhone.prototype = new Phone;
// 矫正 constructor 指向
SmartPhone.prototype.constructor = SmartPhone;

//声明子类的方法
SmartPhone.prototype.photo = function(){
    console.log("我可以拍照")
}

SmartPhone.prototype.playGame = function(){
    console.log("我可以玩游戏");
}

const chuizi = new SmartPhone('锤子',2499,'黑色','5.5inch');

console.log(chuizi);

16.2 extends 类继承和方法的重写

ES6 中直接使用 extends 语法糖(更简洁高级的实现方式)来实现继承,同时可以重写父类的方法,直接在子类中重新写一次要重写的方法即可覆盖父类方法。

class Phone{
    //构造方法
    constructor(brand, price){
        this.brand = brand;
        this.price = price;
    }
    //父类的成员属性
    call(){
        console.log("我可以打电话!!");
    }
}

class SmartPhone extends Phone {
    //构造方法
    constructor(brand, price, color, size){
        super(brand, price);// Phone.call(this, brand, price)
        this.color = color;
        this.size = size;
    }

    photo(){
        console.log("拍照");
    }

    // 方法的重写
    call(){
        console.log('我可以进行视频通话');
    }
}

const xiaomi = new SmartPhone('小米',799,'黑色','4.7inch');
xiaomi.call();
xiaomi.photo();

16.3 getter 和 setter

实际上,gettersetter 是 ES5(ES2009)提出的特性,这里不做详细说明,只是配合 class 使用举个例子。
当属性拥有 get/set 特性时,属性就是访问器属性。代表着在访问属性或者写入属性值时,对返回值做附加的操作。而这个操作就是 getter/setter 函数。

使用场景: getter 是一种语法,这种 get 将对象属性绑定到 查询该属性时将被调用的函数。适用于某个需要动态计算的成员属性值的获取。setter 则是在修改某一属性时所给出的相关提示。

class Test {
    constructor(log) {
        this.log = log;
    }
    get latest() {
        console.log('latest 被调用了');
        return this.log;
    }
    set latest(e) {
        console.log('latest 被修改了');
        this.log.push(e);
    }
}

let test = new Test(['a', 'b', 'c']);
// 每次 log 被修改都会给出提示
test.latest = 'd';
// 每次获取 log 的最后一个元素 latest,都能得到最新数据。
console.log(test.latest);

以上输出:

latest 被修改了
latest 被调用了
[ 'a', 'b', 'c', 'd' ]

17. 数值扩展

  1. Number.EPSILON 是 JavaScript 表示的最小精度,一般用来处理浮点数运算。例如可以用于两个浮点数的比较。

    let equal = (x, y) => Math.abs(x - y) < Number.EPSILON;
    console.log(0.1 + 0.2 === 0.3); // false
    console.log(equal(0.1 + 0.2, 0.3)); // true
    
  2. 二进制和八进制:二进制以 0b 开头,八进制以 0o 开头。

    let b = 0b1010;
    let o = 0o777;
    let d = 100;
    let x = 0xff;
    console.log(x);
    
  3. Number.isFinite 检测一个数值是否为有限数。

    console.log(Number.isFinite(100)); // false
    console.log(Number.isFinite(100 / 0)); // true
    console.log(Number.isFinite(Infinity)); // false
    
  4. Number.parseInt 和 Number.parseFloat
    ES6 给 Number 添加了 parseInt 方法,Number.parseInt 完全等同于 parseInt。将字符串转为整数,或者进行进制转换。Number.parseFloat 则等同于 parseFloat()

    Number.parseInt === parseInt; // true
    Number.parseFloat === parseFloat; // true
    

    Number.parseInt(s, base);

    s:待转换的字符串

    base:进位制的基数

    console.log(Number.parseInt('5211314love')); // 5211314
    console.log(Number.parseFloat('3.1415926神奇')); // 3.1415926
    
  5. Number.isInteger() 判断一个数是否为整数。

    console.log(Number.isInteger(5)); // true
    console.log(Number.isInteger(2.5)); // false
    
  6. Math.trunc() 将数字的小数部分抹掉。

    console.log(Math.trunc(3.5)); // 3
    
  7. Math.sign 判断一个数到底为正数 负数 还是零

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

18. 对象方法扩展

ES6 新增了一些 Object 对象的方法。

  • Object.is 比较两个值是否严格相等,与『===』行为 基本一致
  • Object.assign 对象的合并,将源对象的所有可枚举属性,复制到目标对象
  • __proto__setPrototypeOfgetPrototypeOf 可以直接设置对象的原型

18.1 Object.is()

Object.is() 方法判断两个值是否完全相同。Object.is 比较两个值是否严格相等,与 === 行为 基本一致。返回一个 Boolean 类型。

Object.is(value1, value2);

Object.is() 方法判断两个值是否为同一个值。如果满足以下条件则两个值相等:

  • 都是 undefined
  • 都是 null
  • 都是 truefalse
  • 都是相同长度的字符串且相同字符按相同顺序排列
  • 都是相同对象(意味着每个对象有同一个引用)
  • 都是数字且
    • 都是 +0
    • 都是 -0
    • 都是 NaN
    • 或都是非零而且非 NaN 且为同一个值

== 运算不同。 == 运算符在判断相等前对两边的变量(如果它们不是同一类型)进行强制转换 (这种行为的结果会将 "" == false 判断为 true),而 Object.is 不会强制转换两边的值。

===算也不相同。 === 运算符 (也包括 == 运算符) 将数字 -0+0 视为相等,而将 Number.NaNNaN 视为不相等。

18.2 Object.assign

Object.assign 对象的合并,相当于浅拷贝。

const config1 = {
    host: 'localhost',
    port: 3306,
    name: 'root',
    pass: 'root',
    test: 'test'
};
const config2 = {
    host: 'http://atguigu.com',
    port: 33060,
    name: 'atguigu.com',
    pass: 'iloveyou',
    test2: 'test2'
}
console.log(Object.assign(config1, config2));

18.3 Object.setPrototypeOf 和 Object.getPrototypeof

Object.setPrototypeOf 用于设置对象的原型对象,Object.getPrototypeof 用于获取对象的原型对象,相当于 __proto__

const school = {
    name: '尚硅谷'
}
const cities = {
    xiaoqu: ['北京','上海','深圳']
}
Object.setPrototypeOf(school, cities);
console.log(Object.getPrototypeOf(school));
console.log(school);

19. ES6 模块化

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

19.1 模块化的好处

模块化的优势有以下几点:

  • 防止命名冲突
  • 代码复用
  • 高维护性

19.2 模块化规范产品

ES6 之前的模块化规范有:

  • CommonJS => NodeJS、Browserify
  • AMD => requireJS
  • CMD => seaJS

19.3 ES6 模块化语法

模块功能主要由两个命令构成:exportimport

  • export 命令用于规定模块的对外接口
  • import 命令用于输入其他模块提供的功能
19.3.1 模块导出数据语法
  1. 单个导出

    // 单个导出
    export let uname = 'Rick';
    export let sayHello = function () {
        console.log('Hi, bro!');
    }
    
  2. 合并导出

    let uname = 'Rick';
    let sayHello = function () {
        console.log('Hi, bro!');
    }
    // 合并导出
    export { uname, sayHello };
    
  3. 默认导出

    // 默认导出
    export default {
        uname: 'Rick',
        sayHello: function () {
            console.log('Hi, bro!');
        }
    }
    
    //引入后调用要多一层   m3.default.uname
    
19.3.2 模块导入数据语法
  1. 通用导入方式

    import * as m1 from './js/m1.js';   // m1变量名自定义  在HTML内直接写入时 用 <script type="module"></script>包括
    import * as m2 from './js/m2.js';
    import * as m3 from './js/m3.js';
    
  2. 解构赋值导入

    import { uname, sayHello } from './js/m1.js';
    // 有重复名可以设置别名  加as后就不会重名
    import { uname as uname2, sayHello as sayHello2 } from './js/m2.js';
    console.log(uname);
    // 配合默认导出  必需设置别名
    import {default as m3} from "./src/js/m3.js";
    
  3. 简便方式导入,针对默认暴露

    import m3 from "./src/js/m3.js";
    
19.3.3 ES6 使用模块化方式二

将文件导入都写进一个 app.js 文件中,然后在里面写入要导入的模块。app.js 中的内容如下:

import * as m1 from './js/m1.js';
import * as m2 from './js/m2.js';
import * as m3 from './js/m3.js';

console.log(m1);
console.log(m2);
console.log(m3);

在 index.html 中引入 app.js 文件内容:

<script src="./app.js" type="module"></script>

19.4 使用 babel 对模块化代码转换

有的浏览器不支持 ES6 语法,这时候就需要使用 babel 来将其转换成 ES5 等价语法。

  1. 安装工具

    npm i babel-cli babel-preset-env browserify 或者 (webpack) -D
    
  2. 编译

    src / js文件目录 dist / js 转换结果存放路径

    npx babel src/js -d dist/js --presets=babel-preset-env
    
  3. 打包

    转换后的文件 dist/js/app.js 入口文件 (所有模块的集合文件) dist/bundle.js 输出位置

    npx browserify dist/js/app.js -o dist/bundle.js
    
  4. 引入模块

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

19.5 ES6 模块化引入 npm 安装的包

npm install jquery

再通过 import 导入即可。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LezV77NK-1656942294726)(D:\JAVAWeb\WbeNotes\图片\Es6\es6-7.216iomdsx9mo.png)]


(三)ECMASript 7 新特性

1. Array.prototype.includes

includes 方法用来检测数组中是否包含某个元素,返回布尔类型值。

2. 指数运算符

在 ES7 中引入指数运算符 **,用来实现幂运算,功能与 Math.pow(a, b) 结果相同。

2 ** 3 // 8
Math.pow(2, 3) // 8

(四)ECMAScript 8 新特性

1. async 和 await

asyncawait 两种语法结合可以让异步代码像同步代码一样。(即:看起来是同步的,实质上是异步的。)

先从字面意思理解,async 意为异步,可以用于声明一个函数前,该函数是异步的。await 意为等待,即等待一个异步方法完成。

1.1 async

async 声明(function)的函数成为 async 函数,语法:

async function funcName() {
    //statements 
}

async 内部可以使用 await,也可以不使用。 async 函数的返回值是一个 Promise 对象,因此执行这个函数时,可以使用 thencatch 方法。 根据 函数体内部 的返回值, async 函数返回值具体情况如下:

  • 函数体内不返回任何值,则async函数返回值为一个成功(fulfilled)的Promise对象,状态值为 undefined 。

    let a = async function() {}
    let res = a()
    console.log(res)
    // Promise{<fullfilled>: undefined}
    
  • 返回结果不是一个Promise,则async函数返回值为一个成功(fulfilled)的Promise对象,状态值为这个内部返回值。

    let a = async function () {
      return 'hello'
    }
    let res = a()
    console.log(res) 
    // Promise{<fullfilled>: 'hello'}
    
  • 内部抛出错误,则async函数返回值为一个失败的Promise对象。

    let a = async function foo() {
      throw new Error('出错了')
    }
    a().catch(reason => {
      console.log(reason)
    })
    
  • 若函数内部返回值是一个Promise对象,则async函数返回值的状态取决于这个Promise对象。

    let a = async function () {
      return new Promise((resolve, reject) => {
        resolve("成功")
      })
    }
    a().then(value => {
    console.log(value)
    })
    

### 1.2 await

`await` 相当于一个运算符,右边接一个值。一般为一个 `Promise` 对象,也可以是一个非 `Promise` 类型。当右接一个非 `Promise` 类型,`await` 表达式返回的值就是这个值;当右接一个 `Promise` 对象,则 `await` 表达式会阻塞后面的代码,等待当前 `Promise` 对象 `resolve` 的值。

综合 `async` 和 `await` 而言。`await` 必须结合 `async` 使用,而 `async` 则不一定需要 `await`。 `async` 会将其后的函数的返回值封装成一个 `Promise` 对象,而 `await` 会等待这个 `Promise` 完成,然后返回 `resolve` 的结果。当这个 `Promise` 失败或者抛出异常时,需要时使用 `try-catch` 捕获处理。

`Promise` 使用链式调用解决了传统方式回调地狱的问题,而 `async-await` 又进一步优化了代码的可读性。

```js
const p = new Promise((resolve, reject)=>{
  resolve('成功')
})
async function main() {
  let res = await p
  console.log(res)
}
main()
// '成功'
const p = new Promise((resolve, reject)=>{
  reject('失败')
})
async function main() {
  try {
    let res = await p
    console.log(res)
  } catch(e) {
    console.log(e)
  }
}
main()
// '失败'

1.3 综合应用-读取文件

需求:先读取用户数据 user,然后读取订单数据 order,最后读取商品数据 goods。

对于这种异步操作很容易想到使用 Promise,代码如下:

const fs = require('fs')

let p = new Promise((resolve, reject) => {
  fs.readFile('./files/user.md', (err, data) => {
    if (err) reject(err)
    resolve(data)
  })
})

p.then(value => {
  return new Promise((resolve, rejecet) => {
    fs.readFile('./files/order.md', (err, data) => {
      if (err) rejecet(err)
      resolve([value, data])
    })
  })
}, reason => {
  console.log(reason)
}).then(value => {
  return new Promise((resolve, reject) => {
    fs.readFile('./files/goods.md', (err, data) => {
      if (err) reject(err)
      value.push(data)
      resolve(value)
    })
  })
}, reason => {
  console.log(reason)
}).then(value => {
  console.log(value.join('\n'))
}, reason => {
  console.log(reason)
})

但是,使用 Promise 链式调用虽然避免了回调地狱,但这种链式调用过多难免引起代码复杂,看起来不直观。可以使用 asyncawait 方法优化,代码如下:

const fs = require('fs')

function readUser() {
  return new Promise((resolve, reject) => {
    fs.readFile('./files/user.md', (err, data) => {
      if (err) reject(err)
      resolve(data)
    })
  })
}

function readOrder() {
  return new Promise((resolve, reject) => {
    fs.readFile('./files/order.md', (err, data) => {
      if (err) reject(err)
      resolve(data)
    })
  })
}

function readGoods() {
  return new Promise((resolve, reject) => {
    fs.readFile('./files/goods.md', (err, data) => {
      if (err) reject(err)
      resolve(data)
    })
  })
}

async function read() {
  let user = await readUser()
  let order = await readOrder()
  let goods = await readGoods()
  console.log([user, order, goods].join('\n'))
}

read()

这样,代码看起来很直观,就好像是同步代码一样,实际上是异步操作。

1.4 综合应用-封装ajax

function sendAjax(url) {
  return new Promise((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(JSON.parse(xhr.response))   //JSON.pares字符串转换为JS对象
        }
        reject(xhr.status)
      }
    }
  })
}

async function main() {
  let res = await sendAjax('http://poetry.apiopen.top/sentences')
  let poem = res.result.name + '——' + res.result.from
  document.body.innerText = poem
}

main()

这里封装的ajax还不能体现 async-await 的作用所在,因为没有出现多个 ajax 请求。在又多个 ajax 请求并且后续的请求依赖于前一个请求的结果的时候,async-await 的优点就体现出来了。

2. Object.values 和 Object.entries

  • Object.values()方法返回一个给定对象的所有可枚举属性值的数组,类似于Object.keys(),只是前者返回属性值,后者返回键值组合的数组。
let obj = {
  a: 1,
  b: {1:2},
  c: [1,2,3]
}
console.log(Object.values(obj))
// [1, {1: 2}, [1,2,3]]
console.log(Object.keys(obj))
// ['a', 'b', 'c']
  • Object.entries()方法返回一个给定对象自身可遍历属性[key,value]的数组(数组元素也是一个个的数组的数组)

    const obj = {a: 1, b: 2, c: 3};
    console.log(Object.entries(obj))
    // [ [ 'a', 1 ], [ 'b', 2 ], [ 'c', 3 ] ]
    

    返回的是一个数组,这样就可以使用for…of遍历了。

    const obj = { a: 1, b: 2, c: 3 };
    for (let [k, v] of Object.entries(obj)) {
      console.log(k, v)
    }
    

(五)ECMAScript 9 新特性

1.Rest参数spread扩展运算符

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

 //rest 参数
function connect({host, port, ...user}){   //加了  ... 后多余的键值对都存到user里
    console.log(host);
    console.log(port);
    console.log(user);
}

connect({
    host: '127.0.0.1',
    port: 3306,
    username: 'root',
    password: 'root',
    type: 'master'
});


//对象合并
const skillOne = {
    q: '天音波'
}

const skillTwo = {
    w: '金钟罩'
}

const skillThree = {
             e: '天雷破'
}
const skillFour = {
    r: '猛龙摆尾'
}

const mangseng = {...skillOne, ...skillTwo, ...skillThree, ...skillFour};

console.log(mangseng)

// ...skillOne   =>  q: '天音波', w: '金钟罩'

2.正则扩展

  • 正则命名
//声明一个字符串
// let str = '<a href="http://www.atguigu.com">及你太美</a>';

// //提取 url 与 『标签文本』
// const reg = /<a href="(.*)">(.*)<\/a>/;

// //执行
// const result = reg.exec(str);

// console.log(result);
// // console.log(result[1]);  //<a href="http://www.atguigu.com">及你太美</a>
// // console.log(result[2]);  //"http://www.atguigu.com


let str = '<a href="http://www.atguigu.com">及你太美</a>';
//分组命名            //?<url>命名
const reg = /<a href="(?<url>.*)">(?<text>.*)<\/a>/;

const result = reg.exec(str);

console.log(result.groups.url); //<a href="http://www.atguigu.com">及你太美</a>
console.log(result.groups.text); //"http://www.atguigu.com
  • 正则断言
  //声明字符串
let str = 'JS5211314你知么555啦啦';
//正向断言
const reg = /\d+(?=啦)/;   //(?=啦)判断是否为  啦 true才执行 \d  表示一个或多个数字
const result = reg.exec(str);
console.log(result);    //

//反向断言
const regs = /(?<=么)\d+/;   //判断数字前是否是 ‘么’
const results = regs.exec(str);
console.log(results);
  • 正则dotAll模式
   //dot  .  元字符  除换行符以外的任意单个字符
let str = `
<ul>
    <li>
        <a>肖生克的救赎</a>
        <p>上映日期: 1994-09-10</p>
    </li>
    <li>
        <a>阿甘正传</a>
        <p>上映日期: 1994-07-06</p>
    </li>
</ul>`;
//声明正则
// const reg = /<li>\s+<a>(.*?)<\/a>\s+<p>(.*?)<\/p>/;
const reg = /<li>.*?<a>(.*?)<\/a>.*?<p>(.*?)<\/p>/gs;
//执行匹配
// const result = reg.exec(str);
let result;
let data = [];
while(result = reg.exec(str)){
    data.push({title: result[1], time: result[2]});
}
//输出结果
console.log(data);


(六)ECMAScript 10 新特性

1. Object.fromEntries

Object.fromEntries() 方法把可迭代对象的键值对列表转换为一个对象。
语法:

Object.fromEntries(iterable)
  • iterable:类似 Array 、 Map 或者其它实现了可迭代协议的可迭代对象。
  • 返回值:一个由该迭代对象条目提供对应属性的新对象。
  • 相当于 Object.entries (ES8)的逆运算。
const mp = new Map([
  [1, 2],
  [3, 4]
])
const obj = Object.fromEntries(mp)
console.log(obj)
// { '1': 2, '3': 4 }
const arr = [[1, 2]]
console.log(Object.fromEntries(arr))
// {'1': 2}

2. trimStart() 和 trimEnd()

  • trimStart() 去除字符串开头连续的空格(trimLeft 是此方法的别名)
  • trimEnd() 去除字符串末尾连续的空格(trimRight 是此方法的别名)

3. Array.prototype.flat 和 Array.prototype.flatMap

  • Array.prototype.flat(i):展平一个多维数,i 为要展开的层数,默认为1,即展开一层。

    let arr1 = [1, [2, 3], [4, 5]]
    console.log(arr1.flat(1)) 
    // [1,2,3,4,5]
    let arr2 = [1, [2, 3, [4, 5]]]
    console.log(arr2.flat(2))
    // [1,2,3,4,5]
    

    使用 Infinity 作为深度,展开任意深度的嵌套数组

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

    也可以使用 flat 来去除数组空项

    let arr = [1,2,3,,4]
    arr.flat() // [1,2,3,4]
    
  • Array.prototype.flatMap:相当于 map 和 flat 的结合,方法首先使用映射函数映射每个元素,然后将结果压缩成一个新数组。

    let arr = [1,2,3,4]
    let res1 = arr.map((x) => {
        return [x * 2]   //乘以2    去掉 [] 和flatMap结果一样 
    })
    console.log(res1)  
    //  [Array(1), Array(1), Array(1), Array(1)]
    //0: [2]
    //1: [4]
    //2: [6]
    //3: [8]
    //返回二维数组 [[2],[4],[6],[8]]
    let res2 = arr.flatMap(x => [x * 2])   
    console.log(res2) 
    //[2, 4, 6, 8]
    

4. Symbol.prototype.description

使用 Symbol() 创建的 Symbol 字面量,可以直接使用 description 获取该字面量的描述。

let sym = Symbol('hello')
console.log(sym.description)
// hello

(七)ECMAScript 11 新特性

1. 类的私有属性

ES11 提供了类的私有属性,在类的外部无法访问该属性。只有再类的内部能访问。

class Person{
  //公有属性
  name;
  //私有属性
  #age;
  #weight;
  //构造方法
  constructor(name, age, weight){
    this.name = name;
    this.#age = age;
    this.#weight = weight;
  }

  intro(){
    console.log(this.name);
    console.log(this.#age);
    console.log(this.#weight);
  }
}

//实例化
const girl = new Person('晓红', 18, '45kg');

// 外部无法直接访问
// console.log(girl.name);
// console.log(girl.#age);
// console.log(girl.#weight);

girl.intro();

2. allSettled

Promise.allSettled() 方法返回一个在所有给定的 promise 都已经 fulfilledrejected 后的 promise,并带有一个对象数组,每个对象表示对应的 promise 结果。allSettled 方法返回的 Promise 对象始终是成功(fulfilled)的。
使用场景:

  • 有多个彼此不依赖的异步任务成功完成时使用。
  • 想得到每个 promise 的结果时使用。

对比于 Promise.all()all() 也接受一个 Promise 对象数组参数,只要有一个失败(rejected),那么返回的 Promise 对象就是失败(rejected)的。
使用场景:

  • 传进去的 Promise 对象彼此依赖,且需要在其中任何一个失败的时候停止。

两个 Promise 都是成功的情况:

let p1 = new Promise((resolve, reject) => {
  resolve('用户数据-1')
})

let p2 = new Promise((resolve, reject) => {
  resolve('订单数据-2')
})

let res1 = Promise.allSettled([p1, p2])
let res2 = Promise.all([p1, p2])
console.log(res1)
console.log(res2)

输出结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-X01Q3Bkb-1656942294727)(D:\JAVAWeb\WbeNotes\图片\Es6\屏幕截图 2022-07-02 164947.png)]

一个成功,一个失败:

let p1 = new Promise((resolve, reject) => {
  resolve('用户数据-1')
})

let p2 = new Promise((resolve, reject) => {
  reject('失败了')
})

let res1 = Promise.allSettled([p1, p2])
let res2 = Promise.all([p1, p2])
console.log(res1)
console.log(res2)

打印结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vyN8rIcG-1656942294728)(D:\JAVAWeb\WbeNotes\图片\Es6\屏幕截图 2022-07-02 165340.png)]

3. matchAll

matchAll() 方法返回一个包含所有匹配正则表达式的结果及分组捕获组的迭代器。

const regexp = /t(e)(st(\d?))/g;
const str = 'test1test2';

const array = [...str.matchAll(regexp)];

console.log(array[0]);
// expected output: Array ["test1", "e", "st1", "1"]

console.log(array[1]);
// expected output: Array ["test2", "e", "st2", "2"]

4. 可选链

4.1 定义

可选链 ?. 是一种访问嵌套对象属性的安全的方式。即使中间的属性不存在,也不会出现错误。
原则:如果可选链 ?. 前面的部分是 undefined 或者 null,它会停止运算并返回该部分。

let user = {
  address: {
  }
}
console.log( user?.address?.street ); // undefined(不报错)

4.2 短路效应

短路效应:正如前面所说的,如果 ?. 左边部分不存在,就会立即停止运算(“短路效应”)。所以,如果后面有任何函数调用或者副作用,它们均不会执行。
这有和 && 的作用类似,但上述改用 && 会显得代码冗余度高:

console.log(user && user.address && user.address.street)

4.3 其它变体:?.(),?.[]

可选链 ?. 不是一个运算符,而是一个特殊的语法结构。它还 可以与函数和方括号一起使用
例如,将 ?.() 用于调用一个可能不存在的函数(即使不存在也不报错)。

function foo() {
  console.log('hello')
}
foo?.()
// hello

?.[] 允许从一个可能不存在的对象上安全地读取属性。(即使不存在也不报错)。

let obj = {
  key: 123
}
console.log(obj?.['key'])
// 123

5. 动态 import 导入

const btn = document.getElementById('btn');

btn.onclick = function(){
  import('./hello.js').then(module => {
    module.hello();
}

6. BigInt

BigInt 是一种特殊的数字类型,它提供了对任意长度整数的支持。

创建 bigint 的方式有两种:在一个整数字面量后面加 n 或者调用 BigInt 函数,该函数从字符串、数字等中生成 bigint

let n1 = 123n
let n2 = 456n
let n3 = BigInt(789)
console.log(typeof n1) // bigint
console.log(n1+n2) // 579n
console.log(n2+n3) // 1245n

比较运算符:

  • 例如< 和 >,使用它们来对 bigint 和 nu`mber 类型的数字进行比较没有问题:

    alert( 2n > 1n ); // true
    alert( 2n > 1 ); // true
    
  • 但是请注意,由于 number 和 bigint 属于不同类型,它们可能在进行 == 比较时相等,但在进行 ===(严格相等)比较时不相等:

    alert( 1 == 1n ); // true
    alert( 1 === 1n ); // false
    

7. globalThis

全局对象提供可在任何地方使用的变量和函数。默认情况下,这些全局变量内置于语言或环境中。
在浏览器中,它的名字是 window,对 Node.js 而言,它的名字是 global,其它环境可能用的是别的名字。
ES11中 globalThis 被作为全局对象的标准名称加入到了 JavaScript 中,所有环境都应该支持该名称。所有主流浏览器都支持它。
ve, reject) => {
resolve(‘用户数据-1’)
})

let p2 = new Promise((resolve, reject) => {
resolve(‘订单数据-2’)
})

let res1 = Promise.allSettled([p1, p2])
let res2 = Promise.all([p1, p2])
console.log(res1)
console.log(res2)


输出结果:

[外链图片转存中...(img-X01Q3Bkb-1656942294727)]

一个成功,一个失败:

```js
let p1 = new Promise((resolve, reject) => {
  resolve('用户数据-1')
})

let p2 = new Promise((resolve, reject) => {
  reject('失败了')
})

let res1 = Promise.allSettled([p1, p2])
let res2 = Promise.all([p1, p2])
console.log(res1)
console.log(res2)

打印结果:

[外链图片转存中…(img-vyN8rIcG-1656942294728)]

3. matchAll

matchAll() 方法返回一个包含所有匹配正则表达式的结果及分组捕获组的迭代器。

const regexp = /t(e)(st(\d?))/g;
const str = 'test1test2';

const array = [...str.matchAll(regexp)];

console.log(array[0]);
// expected output: Array ["test1", "e", "st1", "1"]

console.log(array[1]);
// expected output: Array ["test2", "e", "st2", "2"]

4. 可选链

4.1 定义

可选链 ?. 是一种访问嵌套对象属性的安全的方式。即使中间的属性不存在,也不会出现错误。
原则:如果可选链 ?. 前面的部分是 undefined 或者 null,它会停止运算并返回该部分。

let user = {
  address: {
  }
}
console.log( user?.address?.street ); // undefined(不报错)

4.2 短路效应

短路效应:正如前面所说的,如果 ?. 左边部分不存在,就会立即停止运算(“短路效应”)。所以,如果后面有任何函数调用或者副作用,它们均不会执行。
这有和 && 的作用类似,但上述改用 && 会显得代码冗余度高:

console.log(user && user.address && user.address.street)

4.3 其它变体:?.(),?.[]

可选链 ?. 不是一个运算符,而是一个特殊的语法结构。它还 可以与函数和方括号一起使用
例如,将 ?.() 用于调用一个可能不存在的函数(即使不存在也不报错)。

function foo() {
  console.log('hello')
}
foo?.()
// hello

?.[] 允许从一个可能不存在的对象上安全地读取属性。(即使不存在也不报错)。

let obj = {
  key: 123
}
console.log(obj?.['key'])
// 123

5. 动态 import 导入

const btn = document.getElementById('btn');

btn.onclick = function(){
  import('./hello.js').then(module => {
    module.hello();
}

6. BigInt

BigInt 是一种特殊的数字类型,它提供了对任意长度整数的支持。

创建 bigint 的方式有两种:在一个整数字面量后面加 n 或者调用 BigInt 函数,该函数从字符串、数字等中生成 bigint

let n1 = 123n
let n2 = 456n
let n3 = BigInt(789)
console.log(typeof n1) // bigint
console.log(n1+n2) // 579n
console.log(n2+n3) // 1245n

比较运算符:

  • 例如< 和 >,使用它们来对 bigint 和 nu`mber 类型的数字进行比较没有问题:

    alert( 2n > 1n ); // true
    alert( 2n > 1 ); // true
    
  • 但是请注意,由于 number 和 bigint 属于不同类型,它们可能在进行 == 比较时相等,但在进行 ===(严格相等)比较时不相等:

    alert( 1 == 1n ); // true
    alert( 1 === 1n ); // false
    

7. globalThis

全局对象提供可在任何地方使用的变量和函数。默认情况下,这些全局变量内置于语言或环境中。
在浏览器中,它的名字是 window,对 Node.js 而言,它的名字是 global,其它环境可能用的是别的名字。
ES11中 globalThis 被作为全局对象的标准名称加入到了 JavaScript 中,所有环境都应该支持该名称。所有主流浏览器都支持它。
使用场景: 假设我们的环境是浏览器,我们将使用 window。如果你的脚本可能会用来在其他环境中运行,则最好使用 globalThis

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值