ES特性

目录

ES

ECMA: European Computer Manufacturers Association 欧洲计算机制造商协会
ECMAScript: 是由ecma通过ecma-262标准化的脚本语言规范

ES6

循环体中不能使用var关键字

如下代码var i为全局变量,当循环体结束时i的值变为3,而elements没有脚标3,因此undefined

for (var i = 0; i < 3; i++) {
    elements[i].addEventListener('click', function () {
        console.log(elements[i]); // undefined
    })
}

const对于对象和数组来说,不能修改引用,但是可以修改值

const a = 2;
// a = 3; // 报错: Assignment to constant variable.
// console.log(a); 
const b = { name: '张三' };
b.name = '李四';
console.log(b); // { name: '李四' }
const arr = [1, 2, 3];
arr.push(4);
console.log(arr); // [1, 2, 3, 4]

箭头函数

不可改变箭头函数中的this值

箭头函数没有自己的this,它会捕获箭头函数外层作用域中的this
普通函数可以通过call和apply改变this的值,但是箭头函数中的this值不能改变

箭头函数不能作为构造函数使用

let Person = function (name) {
    this.name = '张三';
}
console.log(new Person('张三'));
let Person2 = (name) => {
    this.name = '李四';
}
console.log(new Person2('李四'));

img

箭头函数中没有arguments对象


定时器回调函数中的this

定时器(如 setTimeout 和 setInterval)的回调函数中的 this 默认指向 window 对象
由于定时器的回调函数无法指定自定义调用者,默认情况下,其中的this就是window对象

document.querySelector('.div').onclick = function () {
    console.log(this); // 事件中的this指向触发事件的元素对象
    setTimeout(function () {
        console.log(this); // setTimeout中的this指向window对象
    }, 1000);
}

在定时器中使用箭头函数操作事件源对象

因此我们可以通过定时器使箭头函数作为回调函数,从而解决定时器中的this指向window对象的问题
我们就可以触发事件事件源进行操作,如设置样式等

document.querySelector('.div').onclick = function () {
    console.log(this); // 事件中的this指向触发事件的元素对象 div对象
    setTimeout(() => {
        console.log(this); // 箭头函数中的this指向其外层作用域的this,即事件回调中的this,就是触发事件的元素对象
    }, 1000);
}

箭头函数适合场景

箭头函数的使用适合和this无关的场景,比如定时器回调函数、数组回调函数、对象方法等
对象方法

const obj = {
    name: '张三',
    getName: () => {
        console.log(this); // window对象
        console.log(this.name); // undefined
    }
}

形参结构

function fn({name='张三', age'}){
    console.log(name);
};
fn({age: 18});

...rest参数

形参使用...rest参数: 将实参剩余部分收集到一个数组中
而且rest参数必须放在形参的最后一个位置

function fn(a, b, ...args){
    console.log(args); //args是一个数组,
};

...扩展运算符

let arr = [1, 2, 3];
let arr2 = [...arr, 4, 5]; // [1, 2, 3, 4, 5]

实现原理:
将实现了Iterator 接口的对象中的每个元素一个个迭代取出单独使用


Symbol不能和其他数据进行运算和比较

let s = Symbol('a');
let s2 = Symbol('a');
console.log(s + 'a'); // 报错
console.log(s > 'a'); // 报错
console.log(s === s2); // false

symbol的作用

1.安全的为对象添加方法或属性

假如有一个复杂的对象,我并不知道里面有什么属性,假如我想添加一个name属性,使用obj.name = '李四',那么就破坏了对象的原来的name属性

let obj = {name: '张三'};
obj.name = '李四'; // 破坏了obj对象原有的name属性

使用symbol添加唯一的属性,不会破坏原有的属性

let obj = {name: '张三'};
let methods = {
    name: Symbol(),
    age: Symbol()
}
obj[methods.name] = '李四';
obj[methods.age] = 18;
console.log(obj); // { name: '张三', [Symbol()]: '李四', [Symbol()]: 18 }
// 使用
console.log(obj[methods.name]); // 李四
console.log(obj[methods.age]); // 18

Symbol内置属性

Symbol内置属性可以扩展对象方法的功能

Symbol.hasInstance: 用于判断某对象是否为某构造函数的实例
相当于在类中重写了instanceof的实现,在使用x instanceof 类时,会调用类中的[Symbol.hasInstance]方法

class Person {
    static [Symbol.hasInstance](param) {
        console.log(param);
        return true;
    }
}
let obj = { name: '张三' }
console.log(obj instanceof Person); // 输出: { name: '张三' } true

Symbol.isConcatSpreadable: 用于判断对象在使用concat方法时,是否展开对象

let arr = [1, 2, 3];
let arr2 = [4, 5, 6];
let arr3 = [7, 8, 9];
arr3[Symbol.isConcatSpreadable] = false;
let arr4 = arr.concat(arr2, arr3);
console.log(arr4); // [1, 2, 3, 4, 5, 6, [7, 8, 9]]

for...of工作原理

for...of循环用于遍历可迭代对象(如数组、字符串、Map、Set等)

Array.prototype[Symbol.iterator](): 返回一个数组迭代器对象,通过迭代器对象获取数组的元素

for...of循环的工作原理如下:
1.调用可迭代对象的Symbol.iterator方法,返回该对象的迭代器对象
2.使用迭代器对象的next方法,获取下一个迭代项
3.将迭代项的值赋给循环变量,并执行循环体
4.重复步骤2和3,直到迭代器对象的next方法返回的迭代项的done属性为true

let arr = [1, 2, 3];
let iterator = arr[Symbol.iterator](); // 获取迭代器对象
console.log(iterator.next()); // { 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: undefined, done: true } 

根据for...of循环的工作原理,我们可以自定义可迭代对象

通常情况下我们使用for...in循环来遍历对象的属性名,然后通过obj[item]获取属性值
我们可以自定义迭代器对象遍历对象中的属性值
1.在对象中创建一个[Symbol.iterator]方法,返回一个迭代器对象
2.该迭代器对象中包含一个next方法
3.next方法中返回一个对象,对象中包含一个value属性和一个done属性

自定义的对象迭代器后,使用for...of时,对象就会自动调用[Symbol.iterator]方法,

let obj = {
    name: '张三',
    age: 18,
    gender: '男',
    arr: [1, 2, 3],
    [Symbol.iterator]() {
        let index = 0;
        let keys = Object.keys(this);
        return {
            next: () => {
                if (index < keys.length) {
                    let key = keys[index++];
                    return {
                        value: this[key],
                        done: false
                    }
                } else {
                    return {
                        value: undefined,
                        done: true
                    }
                }
            }
        }
    }
}
for (const element of obj) {
    console.log(element);
}

img


生成器函数

生成器函数是一种特殊的函数,它可以暂停执行并返回一个值,然后在需要时恢复执行
生成器函数使用function*定义,函数体内部使用yield语句定义一个或多个暂停点

调用生成器函数会返回一个带迭代器的对象,可调用该迭代器对象的next方法

function* gen() {
    console.log(111);
    yield 1;
    console.log(222);
    yield 2;
    console.log(333);
    yield 3;
}
let iterator = gen();
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());

在调用生成器函数时不会执行生成器函数中的代码,只有调用next方法时才会执行生成器函数中的代码
next()方法:
1.每次调用生成器函数的next方法,就会执行生成器函数中yield之前的语句和yield之后的表达式语句,并返回一个对象
2.对象中包含一个value属性和一个done属性
3.value值是yield语句后面的表达式的值,done属性表示生成器函数是否执行完毕
img


next执行片段

注意:
yield语句后面表达式最后会自动添加分号,作为本次分段函数结尾,并不会执行下一个yield前的语句
第一个next只输出111和yield1

function* gen() {
    console.log(111);
    console.log(1111);
    yield console.log('yield1')
    console.log(222);
}
let iterator = gen();
iterator.next();

执行对应次数的yield之前的语句和yield之后当前行的表达式
img

img


生成器函数传参

生成器函数返回的迭代器对象
第一次调用next(传参无效)方式时,即使传入了参数,生成器函数也不会接受到参数,而是将调用生成器函数时的参数作为实参
如果调用生成器函数gen()时没有传参,那么第一次next()也不会传参
在调用next方式时才会执行gen的代码逻辑,并且以调用gen时的实参作为第一次调用next方式的参数,因此第一次next即使没有传参,但在调用gen时
传入了AAA,因此next()的参数就是AAA

第二次next时,才会把传入的参数作为第一个yield语句的返回值,同时next也会返回一个对象,该对象value值就是第二个yield后面表达式的值
第三次next时,才会把传入的参数作为第二个yield语句的返回值,同时next也会返回一个对象,该对象value值就是第三个yield后面表达式的值

function* gen(args) {
    console.log('执行了生成器函数', args);
    let one = yield 1;
    console.log('第一个yield返回的值是:' + one);
    let two = yield 2;
    console.log('第二个yield返回的值是:' + one);
    let three = yield 3;
    console.log('第三个yield返回的值是:' + one);
}
let iterator = gen('AAA');
iterator.next(); // 输出: 执行了生成器函数 AAA
let nextTwoResult = iterator.next('BBB'); // 输出: 第一个yield返回的值是:BBB
console.log(nextTwoResult); // 输出: { value: 2, done: false }
let nextThreeReuslt = iterator.next('CCC'); // 输出: 第二个yield返回的值是:CCC
console.log(nextThreeReuslt); // 输出: { value: 3, done: false }
let nextFourResult = iterator.next('DDD'); // 输出: 第三个yield返回的值是:CCC
console.log(nextFourResult); // 输出: { value: undefined, done: true }

总结

1.在调用生成器函数时,不会执行生成器中代码逻辑,但会返回一个迭代器对象
2.调用迭代器对象的next方法,才会执行生成器函数中对应的代码逻辑,并返回一个对象,该对象中value值是yield语句后面的表达式的值,done属性表示生成器函数是否执行完毕
3.调用next会执行对应次数的yield语句前面的代码和yeild后面当前行的语句
4.第一次next传参无效,并将调用生成器函数时的参数,作为第一次其迭代器对象调用next方法时的参数
5.从第二次迭代器调用开始,第n次调用next传入的参数做为n - 1次yield语句的返回值,并且yeild语句前可使用变量接收
6.生成器函数可以指定某段特定函数执行,并且可以暂停执行,等待调用next方法时才会继续执行

使用场景

解决回调地狱问题

需求: 异步实现,1秒后输出1,而后2s后输出2,而后3s后输出3
通过setTimeout定时实现,如果嵌套很深就会出现回调地狱

setTimeout(() => {
    console.log(1)
    setTimeout(() => {
        console.log(2)
        setTimeout(() => {
            console.log(3)
        }, 3000)
    }, 2000)
}, 1000)

通过生成器函数异步执行对应的任务,解决回调地狱问题

function one() {
    setTimeout(() => {
        console.log(1);
        g.next();
    }, 1000)
}
function two() {
    setTimeout(() => {
        console.log(2);
        g.next();
    }, 2000)
}
function three() {
    setTimeout(() => {
        console.log(3);
        g.next();
    }, 3000)
}
function* gen() {
    yield one();
    yield two();
    yield three(); b
}
let g = gen();
g.next();

由于g已经初始化了,因此在任务函数中可以使用g.next()方法,执行第一个任务one,而后在one中执行g.next()方法,执行第二个任务two,以此类推
img


异步解决数据依赖

需求: 异步获取用户数据,当获取到数据后,再根据数据获取其他数据,再根据其他数据获取其他数据,以此类推

function getuser() {
    console.log('获取用户信息');
    setTimeout(() => {
        user = {
            userName: '张三'
        }
        g.next(user);
    }, 1000)
}
function getorder(user) {
    console.log('获取订单信息入参', user);
    setTimeout(() => {
        let order = {
            orderId: '123456'
        }
        g.next(order);
    }, 1000)
}
function getprouct(order) {
    console.log('获取商品信息入参', order);
    setTimeout(() => {
        let product = {
            productId: '7890'
        }
        g.next(product);
    }, 1000)
}
function* gen() {
    let user = yield getuser();
    let order = yield getorder(user);
    let product = yield getprouct(order);
    return product;
}
let g = gen();
g.next();

img


Promise

Promise是一个构造函数,是用于处理异步操作的对象,Promise对象代表了一个异步操作,该操作有三种状态:
1.pending: 初始状态,不是成功或失败状态
2.fulfilled: 意味着操作成功完成
3.rejected: 意味着操作失败

创建一个Promise对象,入参为一个函数,该函数有两个参数resolve函数和reject函数,在调用resolve方法时会改变Promise对象的状态为fulfilled,调用reject方方时会改变Promise对象的状态为rejected
创建Promise语法: let promise = new Promise(fn(resolve, reject))
异步执行resolveFn或rejectFn: promise.then(resolveFn, rejectFn)

当执行resolve(data)时,promise状态变为fulfilled,其PromiseResult属性值为调用回调函数时入参,并且执行then方法中第一个函数,并将data作为参数传入
也就是说resolve变量指向then方法中第一个函数,reject指向then方法中第二个函数

let promise = new Promise((resolve, reject) => {
    // 请求接口获取数据
    let data = "接口返回数据";
    resolve(data);
    // reject('接口请求失败');
});
console.log(promise); // 输出: Promise {<fulfilled>: '接口返回数据'}
promise.then((value) => {
    console.log(value); // 输出: 接口返回数据
}, (reason) => {
    console.log(reason); //  输出: 接口请求失败
});

resolve(入参)决定了promise的状态为fulfilled,reject(入参)决定了promise的状态为rejected,其PromiseResult的值为调用回调函数的入参
img


new Promise中的回调函数会立即执行,不会因为调用resolve或reject而终止后面代码执行

new Promise(fn(resolve, reject)): new Promise就会执行fn代码中的代码,不会因为调用resolve或reject而终止后面代码执行
log(123)仍会执行

let promise = new Promise((resolve, reject) => {
    reject('err');
    console.log(123);
});

Promise.then()方法可以只指定一个resolve回调函数,如果没有reject回调函数,而promise中又调用了reject函数则会报错

then方法中可以只有一个resolve回调函数
promise.then(res => {})

let promise = new Promise((resolve, reject) => {
    reject('错误内容'); // Uncaught (in promise) 错误内容
});
promise.then((value) => {
});

指定了一个reject回调函数,则不会报错

let promise = new Promise((resolve, reject) => {
    reject('错误内容');
});
promise.then((value) => {
}, (reason) => {
    console.log(reason); //  输出: 错误内容
});

Promise读取文件内容

安装node环境,js文件中可直接导入fs模块使用

普通方式:

import fs from 'fs';
fs.readFile('../static/test.txt', 'utf-8', (err, data) => {
    if (err) {
        console.log(err);
    } else {
        console.log(data);
    }
})

文件内容:
img
执行js,输出内容

➜  ES git:(master) ✗ node 13_promise_读取文件内容.js
这是txt文本中内容

promise方式:

import fs from 'fs';
let promise = new Promise((resolve, reject) => {
    fs.readFile('../static/test.txt', 'utf-8', (err, data) => {
        if (err) {
            reject(err);
        }
        resolve(data);
    })
})
promise.then(data => {
    console.log(data);
}).catch(err => {
    console.log(err);
})
➜  ES git:(master) ✗ node 13_promise_promise方式读取文件内容.js 
这是txt文本中内容

promise异步行为

js是单线程运行,和java相比并不是真正意义上的异步执行,java中可以开启线程执行一段逻辑,该逻辑不会阻塞主线程执行
而js的异步更像是触发一个事件,指定回调函数执行的时机

let promise = new Promise((resolve, reject) => {
    console.log('执行promise逻辑');
    // 阻塞5s
    let currentTime = Date.now();
    while(Date.now() - currentTime < 5000) {}
    resolve('接口返回数据');
});
function after() {
    console.log('执行后续代码');
}
promise.then((value) => {
    console.log(value);
});
after();

在promise中阻塞,那么后续的代码after函数仍然不会先执行,而是等待promise执行结束后继续
img


promise.then()返回值

promise.then()返回值为一个新的Promise对象

1.then方法始终返回一个新的Promise对象,无论执行resolve还是reject,都会返回一个新的Promise对象

let promise = new Promise((resolve, reject) => {
    let data = "接口返回数据";
    // resolve(data);
    let err = "接口返回错误";
    reject(err);
});
let p = promise.then((value) => {
    console.log(value);
}, (reason) => {
    console.log(reason);
    return 123;
});
console.log(p === promise); // false

2.then的回调函数如果没有return Promise类型对象,那么自动返回Promise对象,其的PromiseState状态为fulfilled,PromiseResult为then回调函数的返回值,如果then无返回值,那么返回的Promise对象的PromiseResult为undefined,如果then有返回值,那么返回的Promise对象的PromiseResult为then回调函数的返回值

let promise = new Promise((resolve, reject) => {
    let data = "接口返回数据";
    reject('err');
});
let p = promise.then((value) => {
    console.log(value); 
}, (reason) => {
    console.log(reason);
});
console.log(p);

img


then回调函数有返回值,但不是Promise对象,那么then返回的Promise对象中PromiseResult属性值为then回调函数的返回值

let promise = new Promise((resolve, reject) => {
  let data = "接口返回数据";
  reject("err");
});
let p = promise.then(
  (value) => {
    console.log(value);
  },
  (reason) => {
    console.log(reason);
    return 'then回调返回内容'
  }
);
console.log(p);

then回调函数有return值,且不是promise对象,那么then方法返回的promise对象中PromiseResult属性值就为then回调函数的返回值
img


3.then的回调函数如果没有return Promise类型对象,那么自动返回Promise对象,其状态根据then回调函数中有误错误决定,如果回调函数中无错误,那么返回的promise状态为fulfilled,如果回调函数中有错误,那么返回的promise状态为rejected,并把错误内容作为PromiseResult的值

let promise = new Promise((resolve, reject) => {
    reject('错误内容');
});
let p = promise.then((value) => {
    console.log(value);
}, (reason) => {
    console.log(reason);
    throw new Error('错误');
});
console.log(p);

then回调函数没有返回Promise类型对象,并且其中抛出错误,因此then返回的新的promise对象的状态为rejected,并把错误内容作为PromiseResult的值
img


4.then的回调函数如果返回的是Promise类型对象,那么返回的promise对象的状态和PromiseResult由返回的Promise对象决定

let promise = new Promise((resolve, reject) => {
    reject('错误内容');
});
let p = promise.then((value) => {
    console.log(value);
}, (reason) => {
    console.log(reason);
    return new Promise((resolve, reject) => {
        resolve('成功');
    });
});
console.log(p);

回调函数返回了Promise类型对象,因此then方法就会返回该对象,并且p对象中的状态受回调函数返回的Promise对象状态决定
由于返回的Promise对象中调用了resolve方法,因此new Promise的状态为fullfilled,PromiseResult为resolve的入参'成功'
img


5.then方法可以链式调用

let promise = new Promise((resolve, reject) => {
    console.log('逻辑1');
    resolve();
});
let p = promise.then((value) => {
    return new Promise((resolve, reject) => {
        console.log('逻辑2');
        resolve();
    });
}).then((value) => {
    console.log('逻辑3');
    return new Promise((resolve, reject) => { })
});

img


promise.catch()方法

catch()方法用于指定Promise对象失败的回调函数
then的第二个函数和catch()方法作用一样,如果Promise指定的回调函数出错或执行reject回调函数,都会执行then的第二个函数和promise.catch()方法

let promise = new Promise((resolve, reject) => {
    reject('错误内容');
});
promise.then(() => {
}, (reason) => {
    console.log(reason); // 输出: 错误内容
});
promise.catch((reason) => {
    console.log(reason); // 输出: 错误内容
});

reject后,then第二个函数和catch将都会执行


Set

交集

var arr1 = [1, 2, 3, 4, 5];
var arr2 = [4, 5, 6, 7, 8];
let result = [...new Set(arr1)].filter(item => new Set(arr2).has(item));
console.log(result); // [4, 5]

并集

var arr1 = [1, 2, 3, 4, 5];
var arr2 = [4, 5, 6, 7, 8];
let result = [...new Set([...arr1, ...arr2])];
console.log(result); // [1, 2, 3, 4, 5, 6, 7, 8]

差集

求arr1数组与arr2数组之差

var arr1 = [1, 2, 3, 4, 5];
var arr2 = [4, 5, 6, 7, 8];
let result = [...new Set(arr1)].filter(item => !new Set(arr2).has(item));
console.log(result); // [1, 2, 3]

覆盖父类方法

子类可以覆盖父类方法,使用super.methodName()调用父类方法

class Person {
    say() {
        console.log('父类say方法');
    }
}
class Student extends Person {
    say() {
        super.say();
        console.log('子类say方法');
    }
}
new Student().say(); // 输出: 父类say方法 子类say方法

数值扩展

Number.EPSILON解决小数判断不准确问题

Number.EPSILON是js中一个极小的数,用于浮点数计算精度问题

console.log(0.1 + 0.2 === 0.3); // 输出: false
// 使用最小精度来比较两个小数是否相等
function equal(a, b) {
    return Math.abs(a - b) < Number.EPSILON;
}
console.log(equal(0.1 + 0.2, 0.3)); // 输出: true

各个进制字面量

// 二进制、八进制、十进制、十六进制字面量语法
console.log(0b1011); // 输出: 11
console.log(0o13); // 输出: 11
console.log(11); // 输出: 11
console.log(0xB); // 输出: 11

Number.isFinite()判断是否为有限数值

console.log(Number.isFinite(Infinity)); // 输出: false
console.log(Number.isFinite(0)); // 输出: true
console.log(Number.isFinite(100/0)); // 输出: false

parseInt()和parseFloat()方法

parseInt()和parseFloat()方法用于将字符串转换为数值,如果转换失败,则返回NaN

console.log(parseInt('123abc')); // 输出: 123
console.log(parseFloat('123.45abc')); // 输出: 123.45
console.log(parseInt('a123bc')); // 输出: NaN
console.log(parseFloat('.1abc')); // 输出: 0.1

Number.isInteger()方法

Number.isInteger()方法用于判断一个数值是否为整数,返回一个布尔值

console.log(Number.isInteger(0.2)); // 输出: false
console.log(Number.isInteger('0.2')); // 输出: false

对象方法扩展

Object.is()方法用于判断两个值是否相等,返回一个布尔值

console.log(Object.is(0, -0)); // 输出: false
console.log(0 === -0); // 输出: true
console.log(Object.is(NaN, NaN)); // 输出: true
console.log(NaN === NaN); // 输出: false
console.log(Object.is(1, 1)); // 输出: true
console.log(Object.is('a', 'a')); // 输出: true
let obj = { name: 1 };
let obj2 = { name: 1 };
console.log(Object.is(obj, obj2)); // 输出: false

Object.is()和===的区别:

  1. is(): NaN和NaN相等,====: NaN和NaN不相等
  2. is(): +0和-0不相等,====: +0和-0相等

模块化

分别暴露

export let name = '张三';
export let obj = {
    name: '李四',
    age: 18
};
export function say() {
    console.log('hello');
}

统一暴露

let name = '张三';
let obj = {
    name: '李四',
    age: 18
};
export function say() {
    console.log('hello');
}
export { name, obj };

默认暴露

export default {
    name: '张三',
    age: 18,
    say() {
        console.log('hello');
    }
}

通用导入模块方式

<script type="module">
    // 分别暴露
    import * as module from './js/分别暴露.js';
    console.log(module);
    // 统一暴露
    import * as module2 from './js/统一暴露.js';
    console.log(module2.say()); // 输出: hello
    // 默认暴露
    import * as module3 from './js/默认暴露.js';
    console.log(module3.default.say()); // 输出: hello
</script>

解构赋值导入模块方法

<script type="module">
    import { name, obj, say } from './js/分别暴露.js';
    console.log(name, obj, say());
</script>

引入模块指定数据使用别名

如果引入多个模块中具有相同变量名时,执行时,会报错Identifier 'name' has already been declared,此时可以别名

{变量 as 别名}

<script type="module">
    import { name, obj } from './js/分别暴露.js';
    import { name as name1, obj as obj1 } from './js/统一暴露.js'; // Identifier 'name' has already been declared
    console.log(name, name1); // 输出: 张三 张三
</script>

默认暴露使用别名

{default as 别名}: 将default对象命名为别名

<script type="module">
    import { default as default1 } from './js/默认暴露.js';
    console.log(default1.name); // 输出: 张三
</script>

简便形式导入模块方法

简便形式只能用于默认暴露的导入,不能用于分别暴露和统一暴露

import 模块名 from '模块路径'

<script type="module">
    import module from './js/默认暴露.js';
    console.log(module.name); // 输出: 张三
</script>

业务逻辑单独抽离出一个js文件,页面再引入该js文件,由于app.js使用了模块化,所以需要使用模块化导入,type="module"

把业务逻辑抽离到app.js

import * as m1 from './统一暴露.js';
import * as m2 from './默认暴露.js';
import * as m3 from './分别暴露.js';

// 业务逻辑
console.log(m1);
console.log(m2);
console.log(m3);
// 业务逻辑

页面中引入app.js,加载页面后立即执行app.js中的代码

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

小结

1.分别暴露和统一暴露可以同时使用,但是默认暴露只能使用一次
2.默认暴露其实就是以default命名的对象类型
3.使用模块时需要指定script标签的type属性为module
4.使用通用导入模块形式时,需要加default,module.default.数据
5.简易形式导入模块可以直接引入模块名,不需要加default,如: module.数据


ES8

async函数

async function 声明创建一个绑定到给定名称的新异步函数。函数体内允许使用 await 关键字,这使得我们可以更简洁地编写基于 promise 的异步代码,并且避免了显式地配置 promise 链的需要

async关键字标记的函数为异步函数,总是返回一个promise对象。

1.如果该异步函数返回值不是promise对象,那么返回的promise对象的状态为fullfilled,PromiseResult的值返回值为该异步函数的返回值

async function fn() {
    return 'hello';
}
let result = fn();
console.log(result); //  输出: Promise {<fulfilled>: 'hello'}

2.如果异步函数中出错,则会返回一个失败的promise,状态为reject,PromiseResult的值为出错信息

3.如果返回值是一个promise对象,则返回的promise对象的状态和值与返回的promise对象一致

async function fn() {
    return new Promise((resolve, reject) => {
        reject('error');
    });
}
let result = fn();
console.log(result); //  输出: Promise {<rejected>: 'error'}

asyncnew Promise区别

new Promise的返回promise状态受函数体执行的结果决定,不受函数是否返回promise对象的影响,因此new Promise函数中返回的值无效
async函数返回的promise对象状态受async函数体执行结果影响,同时也受返回值影响

let promise = new Promise((resolve, reject) => {
    resolve('成功内容');
    return new Promise((resolve, reject) => {
        throw new Error('错误内容');
    });
});
console.log(promise); // 输出: Promise {<resolved>: '成功内容'}

await关键字

1.await关键字必须写在async函数中才能生效

2.一般情况下await后面是一个promise对象

3.await后面promise执行完成后如果返回的promise状态为成功会把promise对象中的result值返回给await的左侧变量

成功:

let promise = new Promise((resolve, reject) => {
    resolve('用户数据');
});
async function fn() {
    let result = await promise;
    console.log(result); // 输出: 用户数据
}
fn();

4.如果返回的promise状态为失败,会抛出异常,需要通过try...catch捕获异常,catch中的参数值为promise对象中result的值

失败:

let promise = new Promise((resolve, reject) => {
    reject('错误信息');
});
async function fn() {
    try {
        let result = await promise;
        console.log(result);
    } catch (e) {
        console.log(e); // 输出: 错误信息
    }
}
fn();

5.await修饰的语句也会阻塞线程,等待await后面的逻辑执行完毕,才会执行后续代码

let promise = new Promise((resolve, reject) => {
    let currentTime  = Date.now();
    console.log('睡眠5s');
    while(Date.now() - currentTime < 5000){}
    resolve('成功');
})
async function fn() {
    let p1 = await promise;
    console.log(1);
}
fn();

img


async和await异步特点

let promise = new Promise((resolve, reject) => {
  // 睡眠5s
  console.log("promise开始睡眠");
  let date = new Date();
  while (new Date() - date < 5000) {}
  console.log("promise睡眠结束");
  console.log("promise开始执行");
  // 请求api
  let data = { code: 200, msg: "请求成功", data: {} };
  if (data.code === 200) {
    resolve(data);
  } else {
    reject(data.msg);
  }
  console.log("promise结束执行");
});
async function getData() {
  console.log("getDate执行");
  console.log("promise状态:", promise);
  console.log("getData开始睡眠");
  // 睡眠5s
  let date = new Date();
  while (new Date() - date < 5000) {}
  console.log("getData睡眠结束");
  // 一旦遇到await关键字当前函数就会异步等待promise状态,在等待过程中,当前函数返回新的promise对象,外层代码将继续执行,直到promise状态改变,当前函数才会继续执行
  let result = await promise;
  console.log("异步-promise状态:", promise);
  console.log('异步-', result); // 输出: { code: 200, msg: "请求成功", data: {} }
}
function doOther() {
  console.log("doOther执行");
}
console.log(getData()); // 输出: Promise { <pending> }
doOther();

img
img


async和await的作用

await作用: 直接将异步函数返回的promise对象中的结果赋值给左侧变量
async作用: 标记为异步函数将异步函数转为同步函数,在执行async函数时,函数体如果有await关键字,则需要等待await的结果,而后续执行下一行代码
标记为async函数意味着函数内部有异步操作,遇到await时,该函数立即返回一个promise对象,继续执行外层逻辑,等待await后的promise状态改变时,则继续执行该异步函数中await后面的逻辑

let promise = new Promise((resolve, reject) => {
  // 请求api
  let data = { code: 200, msg: "请求成功", data: {} };
  if (data.code === 200) {
    resolve(data);
  } else {
    reject(data.msg);
  }
});
// 通过then方法获取promise的结果
let p = promise.then((res) => {
  // res: api返回的数据
  console.log(res); // 输出: { code: 200, msg: "请求成功", data: {} }
});
async function getData() {
  // 通过await获取promise的结果
  let result = await promise;
  console.log(result); // 输出: { code: 200, msg: "请求成功", data: {} }
}
console.log(getData()); // 输出: Promise { <pending> }

then回调函数和await可同时使用来获取promise结果
img


对象方法扩展

Object.entries(): 返回一个数组,数组的元素是当前对象的可枚举属性的键值对数组。

let obj = {
    name: '张三',
    age: 18
    [Symbol(1)]: 1
}
console.log(Object.entries(obj)); // [ [ 'name', '张三' ], [ 'age', 18 ] ]

对象转map

let obj = {
    name: '张三',
    age: 18
}
let map = new Map(Object.entries(obj));
console.log(map); // Map(2) { 'name' => '张三', 'age' => 18 }

ES9

ES6已经引入了扩展运算符,但只适用于数组,在ES9中引入了扩展运算符,用于扩展数组或对象

...扩展运算符

rest参数: 将多个元素或属性值,转为一个数组或一个对象

function fn([a, b, ...c]) {
    console.log(a, b, c);
}
fn([1, 2, 3, 4]); // 输出: 1 2 [3, 4]
function fnObj({ name, age, ...c }) {
    console.log(name, age, c);
}
fnObj({ name: '张三', age: 18, gender: '男', hobby: '篮球' }); // 输出: 张三 18 { gender: '男', hobby: '篮球' }

...展开元素或对象属性

let arr = [1, 2, 3];
let arr1 = [...arr, 4, 5, 6];
console.log(arr1); // 输出: [1, 2, 3, 4, 5, 6]
let obj = { name: '张三', age: 18 };
let obj1 = { ...obj, gender: '男', hobby: '篮球' };
console.log(obj1); // 输出: { name: '张三', age: 18, gender: '男', hobby: '篮球' }

正则表达式命名命名捕获分组

常规提取字符串正则表达式:

let a = '<a href="https://www.baidu.com">百度</a>';
let reg = /<a href="(.*)">(.*)<\/a>/;
let res = reg.exec(a);
console.log(res);
console.log(res[1]);
console.log(res[2]);

img

命名捕获:

let a = '<a href="https://www.baidu.com">百度</a>';
let reg = /<a href="(?<url>.*)">(?<name>.*)<\/a>/;
let res = reg.exec(a);
console.log(res);
console.log(res.groups.url);
console.log(res.groups.name);

img


正则表达式dotAll模式

dotAll: .可以匹配任意字符,包括换行符

let str = `
            <ul>

                <li>1</li>
                <li>2</li>
                <li>3</li>
            </ul>
        `;
// 提取li标签中文字
let result;
let reg = /<li>(.*?)<\/li>\s/gs;
while (result = reg.exec(str)) {
    console.log(result);
}

img


ES10

Object.fromEntries()

将一个键值对数组或可迭代对象转换为对象
它和Object.entries()是相反的操作,Object.entries()是将对象转换为键值对数组

let arr = [
    ['name', '张三'],
    ['age', 18]
];
let obj = Object.fromEntries(arr);
console.log(obj); // 输出: { name: '张三', age: 18 }

数组扩展方法flat()和flatMap()

flat(深度): 创建一个新的数组,并根据指定深度递归地将所有子数组元素拼接到新的数组中
深度: 默认值为1,表示只扁平化到第2层,如果值为Infinity,表示扁平化所有层

let arr = [1, 2, [3, 4, [5, 6, [7, 8]]]];
let arr1 = arr.flat(2); // 扁平化到第3层  
console.log(arr1); // 输出: [1, 2, 3, 4, 5, 6, [7, 8]]

flatMap(): 先对数组中的每个元素执行一个函数,然后对结果进行flat()操作

将函数所有返回结果进行flat()操作

let arr = [1, 2, 3, 4];
let arr1 = arr.flatMap(item => [item, item * 2]);
console.log(arr1); // 输出: [1, 2, 2, 4, 3, 6, 4, 8]

Symbol.prototype.description

获取Symbol的描述

let s = Symbol('张三');
console.log(s.description); // 输出: 张三

ES11

可选链操作符

可选链操作符: 变量?.属性名
作用: 当属性不存在时,不会报错,而是返回undefined

通常情况下获取对象中不存在的属性会返回undefined
再获取不存在属性的属性时,会报错
在可能不存在的属性后添加?,那么再获取该属性的属性时也不会报错,而是返回undefined

let obj = {
  name: "张三",
};
console.log(obj.age); // undefined
console.log(obj.age?.name); // undefined
console.log(obj.age.name); // 报错: Uncaught TypeError: Cannot read properties of undefined (reading 'name')

动态导入模块

静态导入: 无论后续逻辑中有没有使用模块,模块都会被导入,影响页面加载速度

动态导入模块: import(),返回一个promise对象,并把导入的模块作为promise对象的result值,因此可以直接使用await import()来导入模块
作用: 按需加载模块,提高页面加载速度

// 静态导入
// import * as module from './module.js';
// 动态导入
let btn = document.querySelector('button');
btn.addEventListener('click', async () => {
    let module = await import('./module.js');
    module.fn();
})

Bigint大整形类型

超过js中最大安全正数时则出现运算错误,使用BigInt可以解决这个问题

let maxint = Number.MAX_SAFE_INTEGER;
console.log(maxint + 1); // 输出: 9007199254740992
console.log(maxint + 1); // 输出: 9007199254740992
// 使用bigint来处理大整数
let bigint = BigInt(maxint);
console.log(bigint + 1n); // 输出: 9007199254740993n
console.log(bigint + 1n); // 输出: 9007199254740994n

globalThis

globalThis: 不同环境中全局this指向,浏览器中指向window,node中指向global

在浏览器中,globalThis指向window对象

<script>
    console.log(globalThis); // 输出: Window {window: Window, self: Window, document: document, name: "", location: Location, …}
</script>

在node中,全局globalThis指向global对象
globalThis.js

console.log(globalThis);

img

posted @ 2025-05-23 18:28  ethanx3  阅读(25)  评论(0)    收藏  举报