ECMAScript(简称 ES)是 JavaScript 脚本语言的下一代标准,已经在 2015 年 6 月正式发布了。它的目标,是使得 JavaScript 语言可以用来编写复杂的大型应用程序,成为企业级开发语言。
ECMAScript 和 JavaScript 的关系:ECMAScript是一种实现的规范,对其约束和定义,而JavaScript正是这套规范的实现。所以ES的新特性就是JavaScript的新特性。 详细可参考:https://es6.ruanyifeng.com/ ## let 和 const命令 ### let let是用来生成变量的,类似于我们之前学习的var。区别是let所声明的变量,只在let命令所在的代码块内有效。例如:
  1. //示例一: 代码块
  2. {
  3. let a = 10;
  4. var b = 1;
  5. }
  6. a // 报错:ReferenceError: a is not defined.
  7. b // 1,var可以正常获取
  8. //示例二: 循环外使用
  9. for (let i = 0; i < 10; i++) {
  10. // ...
  11. }
  12. console.log(i);
  13. // ReferenceError: i is not defined

const

声明一个不可变的常量,一旦生命后续不可修改否则报错。所以我们声明时就必须要给初始值,否则将没有任何意义。

  1. const PI = 3.1415;
  2. PI // 3.1415
  3. PI = 3;
  4. // 修改PI报错——TypeError: Assignment to constant variable.
  5. //不给初始值报错
  6. const xc;
  7. // SyntaxError: Missing initializer in const declaration

const的作用域与let命令相同:只在声明所在的块级作用域内有效。

:::info const实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动。对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。但对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指向实际数据的指针,const只能保证这个指针是固定的(即总是指向另一个固定的地址),至于它指向的数据结构是不是可变的,就完全不能控制了。因此,将一个对象声明为常量必须非常小心。

:::

let&const不存在变量提升

var命令会发生“变量提升”现象,即变量可以在声明之前使用,值为undefined。这种现象多多少少是有些奇怪的,按照一般的逻辑,变量应该在声明语句之后才可以使用。

let命令改变了语法行为,它所声明的变量一定要在声明后使用,否则报错。

const命令声明的常量也是不提升,同样存在暂时性死区,只能在声明的位置后面使用。

  1. // var 的情况
  2. console.log(foo); // 输出undefined
  3. var foo = 2;
  4. // let 的情况
  5. console.log(bar); // 报错ReferenceError
  6. let bar = 2;
  7. // const 的情况
  8. if (true) {
  9. console.log(MAX); // ReferenceError
  10. const MAX = 5;
  11. }

let&const不允许重复声明

const声明的常量,和let声明的变量在相同作用域内,不允许重复声明同一个变量。

  1. //案例一
  2. // 报错
  3. function func() {
  4. let a = 10;
  5. var a = 1;
  6. }
  7. // 报错
  8. function func() {
  9. let a = 10;
  10. let a = 1;
  11. }
  12. //案例二
  13. function func(arg) {
  14. let arg;
  15. }
  16. func() // 报错
  17. function func(arg) {
  18. {
  19. let arg;
  20. }
  21. }
  22. func() // 不报错
  23. //案例三
  24. {
  25. var message = "Hello!";
  26. let age = 21;
  27. // 以下两行都会报错
  28. const message = "Goodbye!";
  29. const age = 15;
  30. }

总结

let

  1. let作用——声明变量,变量且不能重复声明。
  2. 块级作用域,声明的变量只在当前块级({})下生效
  3. 不能变量提升,在使用let声明变量之前,一定要先声明变量
  4. 不影响作用域链,如下
  1. {
  2. let xc = '小陈';
  3. function fn (){
  4. console.log(xc);
  5. }
  6. fn()//小陈
  7. }

const

  1. const作用——声明常量,一定要赋初始值
  2. 一般常量使用大写(这不是语法要求,是一个潜规则,用于区分变量)
  3. 常量的值不能修改
  4. 块级作用域
  5. 对于数组或对象的修改,不算对常量的修改,不会报错。因为他们指向的是一个地址,只要地址不变,可以任意操作对象或数组

块级作用域

ES5 只有全局作用域和函数作用域,没有块级作用域,这带来很多不合理的场景。

为啥需要块级作用域?

常见不使用块级作用域的两种缺陷场景。

  1. 第一种场景,内层变量可能会覆盖外层变量。
  1. var tmp = new Date();
  2. function f() {
  3. console.log(tmp);
  4. if (false) {
  5. var tmp = 'hello world';
  6. }
  7. }
  8. f(); // undefined

上面运行逻辑是,if代码块的外部使用外层的tmp变量,内部使用内层的tmp变量。但是,函数f执行后,输出结果为undefined,原因在于变量提升,导致内层的tmp变量覆盖了外层的tmp变量。

  1. 第二种场景,用来计数的循环变量泄露为全局变量。
var s = 'hello';
for (var i = 0; i < s.length; i++) {
    console.log(s[i]);
}
console.log(i); // 5 ,正常来说循环外我们应该访问不到的

上面代码中,变量i只用来控制循环,但是循环结束后,它并没有消失,泄露成了全局变量。

ES6块级作用域

  1. 解决内层变量可能会覆盖外层变量
function f1() {
    let n = 5;
    if (true) {
        let n = 10;
    }
    console.log(n); // 5
}
f1()

上面的函数有两个代码块,都声明了变量n,运行后输出 5。这表示外层代码块不受内层代码块的影响。如果两次都使用var定义变量n,最后输出的值才是 10

  1. 任意作用域的嵌套
{
    {
        {
            {
                { let insane = 'Hello World' }
                console.log(insane); // 报错
            }
        }
    }
};

上方报错是因为不在同一个块级作用域上,第四层级的作用域无法读取第五层级的作用域

  1. 内层作用域可以定义外层作用域的同名变量
{
    {
        {
            {
                { let insane = 'Hello World' }
                let insane = 'Hello World'
            }
        }
    }
};

块级作用域——函数生命使用

ES6之前是不支持在块级作用域声明的,只能在顶层作用域和函数作用域之中声明。函数生命语句行为类似<font style="color:rgb(74, 74, 74);">let</font>的使用,在块级作用域之外不可引用。

考虑到环境导致的行为差异太大,应该避免在块级作用域内声明函数。如果确实需要,也应该写成函数表达式,而不是函数声明语句。
// 块级作用域内部的函数声明语句,建议不要使用
{
    let a = 'secret';
    function f() {
        return a;
    }
}

// 块级作用域内部,优先使用函数表达式
{
    let a = 'secret';
    let f = function () {
        return a;
    };
}
ES6 的块级作用域必须有大括号,如果没有大括号,JavaScript 引擎就认为不存在块级作用域。函数声明也是如此,严格模式下,函数只能声明在当前作用域的顶层。

变量的解构赋值

按照一定模式从数组或对象中提取值,对变量赋值一些列操作被称为解构赋值。

//数组解构
const NAME = ['小陈','小周','小张'];
let [chen,zhou,zhang] = NAME;
console.log(chen,zhou,zhang)

//对象解构
const xiaoZhang = {
    name: '小张',
    age: '21',
    zhiye : function(){
        console.log('摄影师')
    }
}
let {name,age,zhiye} = xiaoZhang;
zhiye()

通常方法结构我们用的比较多,类似于zheye()这种。如果一个方法频繁调用我们可以使用方法结构

模板字符串

ES6之前生命字符串的方式有两种第一种单引号【’’】、第二种双引号【””】,ES6之后引入了模板字符串【``】

区别:

  1. 字符串内容中可以直接出现换行符。其他两种不可以,编译报错
  2. 变量拼接
let xc = `小陈,超级超级超级
          帅!!!`
let name = `小陈`;
let xiangxi = `${name},迷死我了`
console.log(xiangxi) //小陈,迷死我了

简化开发

  1. ES6允许在大括号里面,直接写入变量和函数,作为对象的属性和方法
let book = '西游记';
let readBook = function(){
    console.log('读西游记');
}
const arr = {
    book,
    readBook
}
console.log(arr)

//ES6之前写法
const arr1 = {
    book: book,
    readBook: readBook
}
console.log(arr1)

箭头函数 【=>

=>作用:用来简化函数的定义

//箭头函数
let fn = function(){
    console.log('普通声明明方式')
}
//省略了function关键字
let fn1 = () =>{
    console.log('箭头声明方式')
}

fn()
fn1()

特点:

  1. this的静态特性,this始终指向函数声明时所在作用域下的this的值
// 箭头函数的额this是静态的,this始终指向函数声明时所在作用域下的this的值
function A() {
    console.log(this.name)
}
//而这个箭头函数是在全局作用域下声明的,所以this也是指向window
let B = () => {
    console.log(this.name);
}

window.name = 'this的静态特性';
const school = {
    name: 'xiaochen'
}
//直接调用
A()  //this的静态特性
B()  //this的静态特性
//call
A.call(school); //xiaochen
B.call(school);  //this的静态特性
  1. 不能作为构造函数实例对象
  2. 不能使用 arguments 变量
  3. 箭头函数的缩写
    1. 省略小括号,当形参有且只有一个的时候
let add = n =>{
    return n+1;
}
console.log(add(2))
2. <font style="color:rgb(77, 77, 77);">省略花括号,当代码体只有一条语句的时候,此时</font><font style="color:rgb(0, 0, 0);background-color:rgb(248, 248, 64);">return</font><font style="color:rgb(77, 77, 77);">也必须省略</font>
let add = n => n+1;
console.log(add(2))

注意事项:

  • 箭头函数适合与this无关的回调,比如定时器,数组的方法回调。
  • 箭头函数不适合与this有关的回调,比如DOM元素的事件回调、对象的方法。
btn.addEventListener("click",function(){
  //此时普通函数的this指向事件缘
  //如果使用箭头函数,事件源将变成外部作用域的this值,即这个函数所在的作用域
})

函数参数默认值

可以给形参赋初始值,一般位置要靠后(潜规则)


function number(a,b,c=3) {
    console.log(a,b,c)
}
number(1) //1 undefined 3

也可以与解构赋值中运用

//函数参数初始值与结构赋值结合使用
function data({name,age='22'}){
    console.log(name);
    console.log(age);
}
data({
    name='zzl',
    // age='23'
})

rest参数

rest参数:用于获取函数的实参,可以代替arguments。

function data(...args) {
  console.log(args); //输出的是一个数组,可以使用filter some...
}
data('xc', 'xz', 'xj', 'xl');
data('xc')

注意:rest参数必须放在参数最后

function data(a,b,...args) {
    console.log(a);
    console.log(b);
    console.log(args);
}
data('x','c','xc','xz','xl')

扩展运算符

扩展运算符是能将数组转换为逗号分隔的参数序列
const names = ['xc', 'xz', 'xl'];
  function data() {
      console.log(arguments);
  }
  //不用扩展运算符
  data(names);//只输出一个结果,是一个数组
  // 用扩展运算符
  data(...names);//输出3个结果,等价于:data('xc', 'xz', 'xl'),即参数序列

常见应用:

// 数组的合并
const A = ['aa', 'bb'];
const B = ['cc', 'dd'];
const C = [...A, ...B];
console.log(C)   //[aa,bb,cc,dd]

// 数组的克隆
const A = ['a', 'b', 'c'];
const B = [...A];
console.log(B)   //[a,b,c]

// 将伪数组转换为真正的数组
const A = documents.querySelectorAll('div');
const B = [...A];
console.log(B) // [div,div,div]

Symbol

symbol是Es6中新增的数据类型。

作用:

  • 防止属性名发生冲突,表示一个独一无二的值。

注意点:

  • Symbol值不能与其他数据进行运算
  • Symbol定义的对象属性不能使用for…in循环遍历,但是可以使用Reflect.ownKeys来获取对象的所有键名
//1. 创建symbol类型值
let x = Symbol('xc');
let c = Symbol('xc');


console.log(typeof x)//查看Symbol声明出的值,是什么类型
//2. 判断Symbol声明两个名称一样的值是否相等
console.log(x == c);

//3. symbol声明的值不能进行计算,下面会报错
let result = c + x;

symbol添加描述:Symbol()函数创建 Symbol 值时,可以用参数添加一个描述。

let c = Symbol('xc');
console.log(c.description); //xc

迭代器

任何数据结构只要部署了Iterator接口,就可以使用for…of来遍历.

//for in保存的是键名,for of保存的是键值
const xiyou = ['AA', 'BB', 'CC', 'DD'];
for (let v of xiyou) {
    console.log(v)  // 'AA','BB','CC','DD'  
}
for (let v in xiyou) {
    console.log(v);
}
let iterator = xiyou[Symbol.iterator]();
console.log(iterator.next());

迭代过程如下:

  1. 通过 Symbol.iterator 创建一个迭代器,指向当前数据结构的起始位置;
  2. 随后通过 next 方法进行向下迭代指向下一个位置,next 方法会返回当前位置的对象,对象包含了 value 和 done 两个属性,value 是当前属性的值,done 用于判断是否遍历结束;
  3. 当 done 为 true 时则遍历结束。
const xiyou = ['AA', 'BB', 'CC', 'DD'];
let iterator = xiyou[Symbol.iterator]();
//{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: 'DD', done: false}
console.log(iterator.next());
//{value: undefined, done: true}
console.log(iterator.next());

创建一个数组,然后通过 Symbol.iterator 方法创建一个迭代器,之后不断地调用 next 方法对数组内部项进行访问,当属性 done 为 true 时访问结束。

Promise

promise三种状态

  • pending(进行中)
  • fulfilled(已成功)
  • rejected(已失败)
只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。 一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)

基本用法

//异步请求
const promise = new Promise(function (resolve, reject) {
    // ... some code

    if (/* 异步操作成功 */) {
        resolve(value);
    } else {
        reject(error);
    }
});
Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolvereject。它们是两个函数
  • resolve作用是:将Promise对象的状态从“未完成”变为“成功”(即从 pending 变为 resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去
  • reject作用是:将Promise对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
通过then方法分别指定resolved状态和rejected状态的回调函数。
promise.then(function(value) {
  // success
}, function(error) {
  // failure
});

then

作用是为 Promise 实例添加状态改变时的回调函数。then方法的第一个参数是resolved状态的回调函数,第二个参数是rejected状态的回调函数,它们都是可选的。
then方法返回的是一个新的Promise实例(注意,不是原来那个Promise实例)。因此可以采用链式写法,即then方法后面再调用另一个then方法。
getJSON("/posts.json").then(function(json) {
  return json.post;
}).then(function(post) {
  // ...
});

catch

Promise.prototype.catch()方法是.then(null, rejection).then(undefined, rejection)的别名,用于指定发生错误时的回调函数。

const promise = new Promise(function(resolve, reject) {
  throw new Error('test');
});
promise.catch(function(error) {
  console.log(error);
});
// Error: test

then中也可以实现同等效果。相比不要在then()方法里面定义 Reject 状态的回调函数(即then的第二个参数),推荐使用catch方法捕获处理。

这种方式跟类似于写法(try/catch),不管catch前存在多少<font style="color:rgb(13, 20, 30);">then</font>他都会进行捕获。
// 不推荐
promise.then(function(data) {
    // success
  }, function(err) {
    // error
  });

// 推荐
promise.then(function(data) { //cb
    // success
  })
  .catch(function(err) {
    // error
  });

finally

finally()方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。

promise
.then(result => {···})
.catch(error => {···})
.finally(() => {···});

finally方法的回调函数不接受任何参数,这意味着没有办法知道,前面的 Promise 状态到底是fulfilled还是rejected。这表明,finally方法里面的操作,应该是与状态无关的,不依赖于 Promise 的执行结果。

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

promise
.finally(() => {
  // 语句
});

// 等同于
promise
.then(
  result => {
    // 语句
    return result;
  },
  error => {
    // 语句
    throw error;
  }
);

如果不使用finally方法,同样的语句需要为成功和失败两种情况各写一次。有了finally方法,则只需要写一次。

Set和Map数据结构

Set

Set集合是ES6新增的数据结构。类似于数组,但成员的值都是唯一的。set集合实现了iterator接口,所以可以使用「扩展运算符』或「 for…of…』进行遍历集合的属性和方法。

基本使用

//生命Set集合
let s = new Set();
let s2 = new Set(['A', 'B', 'C', 'D'])

//元素个数
console.log(s2.size);

//添加新的元素
s2.add('E');

//删除元素
s2.delete('A')

//检测
console.log(s2.has('C'));

//清空
s2.clear()
实际应用
let arr = [1, 2, 3, 4, 5, 4, 3, 2, 1]

//1.数组去重
let result = [...new Set(arr)]
console.log(result);
//2.交集
let arr2 = [4, 5, 6, 5, 6]
let result2 = [...new Set(arr)].filter(item => new Set(arr2).has(item))
console.log(result2);
//3.并集
let result3 = [new Set([...arr, ...arr2])]
console.log(result3);
//4.差集
let result4 = [...new Set(arr)].filter(item => !(new Set(arr2).has(item)))
console.log(result4);

Map

Map数据结构。类似于对象,也是键值对的集合。但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。Map也实现了iterator接口,所以可以使用『扩展运算符』和「for…of…』进行遍历
let m = new Map();
m.set('name', 'xc');
m.set('change', () => {
    console.log('好酷!')
})
let key = {
    school: '清华大学'
}
m.set(key, ['北京', '西安']);

//size
console.log(m.size);

//删除
m.delete('name');

//获取
console.log(m.get('change'));

// //清空
// m.clear()

//遍历
for (let v of m) {
    console.log(v);
}

Map 结构原生提供三个遍历器生成函数和一个遍历方法。
  • Map.prototype.keys():返回键名的遍历器。
  • Map.prototype.values():返回键值的遍历器。
  • Map.prototype.entries():返回所有成员的遍历器。
  • Map.prototype.forEach():遍历 Map 的所有成员。

const map = new Map([
    ['F', 'no'],
    ['T', 'yes'],
]);

for (let key of map.keys()) {
    console.log(key);
}
// "F"
// "T"

for (let value of map.values()) {
    console.log(value);
}
// "no"
// "yes"

for (let item of map.entries()) {
    console.log(item[0], item[1]);
}
// "F" "no"
// "T" "yes"

// 或者
for (let [key, value] of map.entries()) {
    console.log(key, value);
}
// "F" "no"
// "T" "yes"

// 等同于使用map.entries()
for (let [key, value] of map) {
    console.log(key, value);
}
// "F" "no"
// "T" "yes"

如果我们想把Map转换为数组,最简单的办法就是使用扩展运算符

const map = new Map([
  [1, 'one'],
  [2, 'two'],
  [3, 'three'],
]);

[...map.keys()]
// [1, 2, 3]

[...map.values()]
// ['one', 'two', 'three']

[...map.entries()]
// [[1,'one'], [2, 'two'], [3, 'three']]

[...map]
// [[1,'one'], [2, 'two'], [3, 'three']]

Class

ES6后新引入了Class(类)这个概念,作为对象的模板。通过class关键字,可以定义类。

定义class

class students{
    constructor(name,age){
        this.name = name;
        this.age = age;
    }

    //定义一个方法。不需要加上function这个关键字,直接把函数定义放进去了就可以
    toString(){
        return  '(' + this.name + ', ' + this.age + ')';
    }
}

let student = new students('喜洋洋','9')
console.log(student.toString()) //(喜洋洋, 9)

constructor()

<font style="color:rgb(13, 20, 30);">constructor()</font>方法是类的默认方法,通过new命令生成对象实例时,自动调用该方法。一个类必须有constructor()方法,如果没有显式定义,一个空的constructor()方法会被默认添加。

//显示生命构造
class students{
    constructor(name,age){
        this.name = name;
        this.age = age;
    }
}

//隐式生命构造,使用默认的
class students{

}

        ↑等同于↓

class students{
    constructor(){}
}

类的实例

定义好类,怎么调用呢,就要实例化对象才能使用。实例对象的写法使用new关键字完成如下:

class students{
    //……
}

//实例化对象
let student = new students()

实例属性的新写法

ES2022 为类的实例属性,又规定了一种新写法。实例属性现在除了可以定义在constructor()方法里面的this上面,也可以定义在类内部的最顶层。

// 原来的写法
class IncreasingCounter {
  constructor() {
    this._count = 0;
  }
  get value() {
    return this._count;
  }
  increment() {
    this._count++;
  }
}
现在的新写法是,这个属性也可以定义在类的最顶层,其他都不变。
class IncreasingCounter {
  _count = 0; 
  get value() {
    return this._count;
  }
  increment() {
    this._count++;
  }
}
注意,新写法定义的属性是实例对象自身的属性,而不是定义在实例对象的原型上面。好处是,所有实例对象自身的属性都定义在类的头部,看上去比较整齐,一眼就能看出这个类有哪些实例属性。

取值函数(getter)和存值函数(setter)

在“类”的内部可以使用getset关键字,对某个属性设置存值函数和取值函数,拦截该属性的存取行为
class students{
    constructor(name,age){
        this.name = name;
        this.age = age;
    }

    get name(){
        return 'name';
    }
    set name(value){
        console.log('set'+value);
    }
}

let student = new students('喜洋洋','9')
console.log('*'+student.name)

静态方法

静态方法需要在方法前加上static关键字 ,可以直接通过的方式去调用。静态方法不会创建的实例对象继承,也就是说我们创建的实例对象无法调用。

class students{
    static testStatic(){
        console.log('测试静态方法');
    }
}

let student = new students()
students.testStatic() //测试静态方法
student.testStatic() //student.testStatic is not a function
注意,如果静态方法包含this关键字,这个this指的是类,而不是实例。同一个类中,静态方法名和非静态方法名可以重复。

私有化属性

ES2022正式为class添加了私有属性,方法是在属性名之前使用#表示。

class IncreasingCounter {
    #count = 0;
    get value() {
        console.log('Getting the current value!');
        return this.#count;
    }
    increment() {
        this.#count++;
    }
}

const counter = new IncreasingCounter();
counter.#count // 报错
counter.#count = 42 // 报错

Class继承

class通过extends关键字实现继承,让子类继承父类的属性和方法。

class person{

    introduce(){
        console.log("测试继承效果");
    }
}
class students extends person{

}

let student = new students()
student.introduce()//测试继承效果

子类继承父类,如果显示生命构造函数(不使用默认的),子类必须在constructor()方法中调用super(),否则就会报错。子类没有定义constructor()方法,这个方法会默认生成

class person {

    introduce() {
        console.log("测试继承效果");
    }
}
class students extends person {
    constructor() { }
}

let student = new students()
student.introduce() //Must call super constructor in derived class before accessing 'this' or returning from derived constructor

这是因为子类自己的this对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再对其进行加工,添加子类自己的实例属性和方法。如果不调用super()方法,子类就得不到自己的this对象。

ES6 的继承机制,则是先将父类的属性和方法,加到一个空的对象上面,然后再将该对象作为子类的实例,即“继承在前,实例在后”。这就是为什么 ES6 的继承必须先调用super()方法,因为这一步会生成一个继承父类的this对象,没有这一步就无法继承父类。 注意,这意味着新建子类实例时,父类的构造函数必定会先运行一次。
class person {
    constructor() {console.log(1); }
}
class students extends person {
    constructor() {
        super();
        console.log(2); 
    }
}

let student = new students()
//1
//2

私有属性和私有方法的继承

子类可以继承父类私有属性和方法外的所有属性和方法。私有属性和方法只能在当前类中使用。

class Foo {
    #p = 1;
    #m() {
        console.log('hello');
    }
}

class Bar extends Foo {
    constructor() {
        super();
        console.log(this.#p); // 报错
        this.#m(); // 报错
    }
}

模块化

模块化:将一个大的程序文件,拆分成许多小的文件,最终将小文件组合打包起来运行。好处如下:

  • 防止命名冲突
  • 代码复用
  • 高维护性
在 ES6 之前,社区制定了一些模块加载方案,最主要的有 CommonJS 和 AMD 两种。前者用于服务器,后者用于浏览器。ES6 在语言标准的层面上,实现了模块功能,而且实现得相当简单,完全可以取代 CommonJS 和 AMD 规范,成为浏览器和服务器通用的模块解决方案。

ES6后通过export命令显式指定输出的代码,再通过import命令输入。

import { stat, exists, readFile } from 'fs';//从fs模块,加载三个方法

模块开发主要通过export和import实现

  • export命令用于规定模块的对外接口
  • import命令用于输入其他模块提供的功能

export

export关键字可以输出该变量、方法

export var firstName = 'Michael';
export function getName(x,y){
    return x+y;
}

//统一不暴露接口
var firstName = 'Michael';
function getName(x, y) {
    return x + y;
}
export { firstName, getName };


//默认暴露
export default {
    firstName:'清华大学',
    getName:function(){
        console.log('加油!!!')
    }
}

import

import命令可以加载export的模块,使用总结
//1. 通用导入方式
import * as m1 from "./main.js"
import * as m2 from "./main.js"

//2. 解构赋值方式
import {school,teach} from "./main.js"
import {school as guigu,findJob} from "./main.js"
import {default as m3 } from "./main.js"

//3. 简便形式(只针对默认暴露)
import m3 from "./main.js"

export default

export default命令,为模块指定默认输出。

// export-default.js
export default function () {
  console.log('foo');
}
//----------------------------------
//其他模块加载该模块时,import命令可以为该匿名函数指定任意名字。
// import-default.js
import customName from './export-default';
customName(); // 'foo'
上面代码的import命令,可以用任意名称指向export-default.js输出的方法,这时就不需要知道原模块输出的函数名。需要注意的是,这时import命令后面,不使用大括号。 正常输出和默认输出对比
// 第一组
export default function crc32() { // 输出
  // ...
}
import crc32 from 'crc32'; // 输入


// 第二组
export function crc32() { // 输出
  // ...
};
import {crc32} from 'crc32'; // 输入

数组扩展

Array.prototype.includes:用来检测数组中是否包含某个元素,返回布尔类型值

let arr = ['清华','北大','哈弗','耶鲁'];
console.log(arr.includes('清华'));//true
console.log(arr.includes('浙大'));//false

async&await函数

async

async函数返回一个 Promise 对象。async函数内部return语句返回的值,会成为then方法回调函数的参数。

async function fn() {
    return 'hello async';
}
fn().then(v => console.log(v));

fn方法内返回的值会被then方法接收到。

async函数返回的 Promise 对象,必须等到内部所有await命令后面的 Promise 对象执行完,才会发生状态改变,除非遇到return语句或者抛出错误。也就是说,只有async函数内部的异步操作执行完,才会执行then方法指定的回调函数。
function timeout(ms) {
  return new Promise((resolve) => {
      setTimeout(resolve, ms);
  });
}

async function asyncPrint(value, ms) {
  await timeout(ms);
  await timeout(ms);
  return value;
}

asyncPrint('hello world', 3000).then(v => console.log(v));

await

正常情况下,await命令后面是一个 Promise 对象,返回该对象的结果。如果不是 Promise 对象,就直接返回对应的值。

async function f() {
  // 等同于
  // return 123;
  return await 123;
}

f().then(v => console.log(v))
// 123
  • await必须放在async函数
  • await右侧的表达式一般为promise对象
  • await可以返回的是右侧promise成功的值
  • await右侧的promise如果失败了,就会抛出异常,需要通过try…catch捕获处理

开发使用

/ajax请求返回一个promise
  function sendAjax(url) {
      return new Promise((resolve, reject) => {

          //创建对象
          const x = new XMLHttpRequest();

          //初始化
          x.open('GET', url,true);

          //发送
          x.send();

          //时间绑定
          x.onreadystatechange = () => {
              if (x.readyState === 4) {
                  if (x.status >= 200 && x.status < 300) {
                      //成功
                      resolve(x.response)
                  } else {
                      //失败
                      reject(x.status)
                  }
              }
          }
      })
  }

  //async 与 await 测试
  async function main() {
      let result = await sendAjax("https://api.apiopen.top/api/getTime")
      console.log(result);
  }
  main()