「课件」原创 => ES6 - ES15【转载请标明出处】

Zero 章·何为ES

ES表示 ECMAScript,它是 JavaScript 的标准规范。ECMAScript 定义了 JavaScript 的核心语言特性,包括语法、数据类型、控制结构、函数等。JavaScript 实现了 ECMAScript 标准,并在此基础上添加了浏览器和其他宿主环境提供的额外功能,比如 DOM、BOM 等。

ES 的版本通常以 ECMAScript 规范的版本号命名。例如,ES6 表示 ECMAScript 6,也称为 ECMAScript 2015。随着时间的推移,ECMAScript 规范不断更新,引入新的语言特性和功能。每个新版本都会带来一些改进和增强,使得 JavaScript 语言更加强大和灵活。

对于 JavaScript 开发者,理应通常关注 ECMAScript 标准的发展,并使用最新的语言特性来提高代码质量和开发效率。

我们之前学习的 JS 入门,默认就是 ES6 之下的版本,也就是 ES5 标准的。

在 ES6 + 中,每行代码是没有必要都用 分号进行分割的!但是这里还是建议大家养成 有分号的好习惯。

以下是 ES6(ECMAScript 2015)到 ES13(ECMAScript 2020)中一些常用的新特性:

  1. ES6(ECMAScript 2015)
    • Arrow Functions(箭头函数)
    • Classes(类)
    • Template Literals(模板字面量)
    • Destructuring(解构赋值)
    • Default Parameters(默认参数)
    • Rest Parameters(剩余参数)
    • Spread Operator(扩展运算符)
    • Let 和 Const(块级作用域变量声明)
    • Promises(Promise)
    • Modules(模块)
    • Map 和 Set(映射和集合)
    • Symbol(符号)
    • Generators(生成器)
    • Iterators(迭代器)
  2. ES7(ECMAScript 2016)
    • Includes() 方法
    • 指数操作符(**)
  3. ES8(ECMAScript 2017): => 更新至此 ing...
    • Async/Await(异步/等待)
    • SharedArrayBuffer 和 Atomics(共享内存和原子操作)不建议去学(如果你是学后端的,没必要学这么早这个东西)
    • Object.entries() 和 Object.values() 方法
    • 字符串填充方法(padStart() 和 padEnd())
  4. ES9(ECMAScript 2018)
    • Asynchronous Iterators(异步迭代器)
    • Rest/Spread 属性
    • Promise.finally() 方法
    • 正则表达式命名捕获组
    • 扩展对象属性描述符
  5. ES10(ECMAScript 2019)
    • Array.prototype.flat() 和 Array.prototype.flatMap() 方法
    • Object.fromEntries() 方法
    • String.prototype.trimStart() 和 String.prototype.trimEnd() 方法
    • Symbol.prototype.description 属性
    • Optional Catch Binding(可选的 catch 绑定)
  6. ES11(ECMAScript 2020)
    • Nullish Coalescing Operator(空值合并运算符 ??)
    • Optional Chaining Operator(可选链运算符 ?.)
    • BigInt 类型
    • Promise.allSettled() 方法
    • String.prototype.matchAll() 方法
  7. ES12(ECMAScript 2021)
    • 数值分隔符(Numeric Separators)
    • Promise.any() 方法
    • String.prototype.replaceAll() 方法
    • WeakRef 和 FinalizationRegistry
  8. ES13(ECMAScript 2022)
    • Array.prototype.{reduceRight, findLast, findLastIndex}()
    • String.prototype.replaceAll() 在 Node.js 中的支持
  9. ES14(ECMAScript 2023)
    • Async Data Sources(ADS)API
    • AggregateError 类
    • ArrayBuffer.prototype.transfer() 方法
  10. ES15(ECMAScript 2024)
    • 标准中还未确定,可能会在未来版本中添加。

第一章·变量与常量

1.1 let 与 var

我们之前声明变量 使用的就是 var,而 ES6 引入了一个新的关键字 let。也是用来声明变量的。

let 其实就是为了避免 var 可能会出现的一些问题,而被发明出来的。现在已经不推荐使用 var 关键字

它们有一些重要的区别,你需要清楚地知道:

  1. 作用域
    • var 声明的变量具有函数作用域(function scope),即在声明它的函数内部可见。大家要注意,仅仅只支持函数内部,而并不是支持 { } 花括号这种代码块。
    • let 声明的变量具有块级作用域(block scope),即在声明它的块(例如 {})内部可见。
  2. 变量提升变量都会被提升的,只不过看它何时会被初始化。
    • 使用 var 声明的变量会被提升到其所在函数的顶部(但初始化的值不会提升)。
    • 使用 let 声明的变量也会被提升,但在变量提升阶段不会被赋予初始值,直到执行到声明语句才会被初始化。
  3. 重复声明
    • 使用 var 可以重复声明同一个变量,不会报错,后面的声明会覆盖前面的声明。(即使 JS 开发者其实也不是特别能接受这种特性(缺点)。。
    • 使用 let 重复声明同一个变量会导致 SyntaxError。
  4. 全局对象属性
    • 使用 var 声明的全局变量会成为全局对象的属性(在浏览器中是 window 对象)。这其实是不太合理的。。
    • 使用 let 声明的全局变量不会成为全局对象的属性。保留了变量就是变量本身的性质。
  5. 临时死区(Temporal Dead Zone,TDZ)
    • 使用 let 声明的变量在声明语句之前访问会引发 ReferenceError,这个阶段被称为临时死区。这其实也是变量必须具有的特性。

总的来说,官方、实际开发,现目前都推荐使用 let 来声明变量,因为它具有更好的作用域规则,更容易控制变量的作用范围,避免了一些 var 存在的各种问题。

1.2 常量

  • 格式 = const 常量名 = xxx

常量就是无法改变的量,初始化之后就不会再允许更改!

const IDcode = 123
IDcode = 456 // 虽然没报错,但是运行时绝对会报错。

第二章·对象、类class

2.1 对象字面量

对象字面量 {}: 之前学习过的创建对象的方法

let obj = { // 键值对格式
    name: "mqy",
    age: 22
};
  • 这种方式创建的对象是一个普通的 JavaScript 对象,也称为对象字面量或对象字面量语法。它是一种简洁创建对象的方式。
  • 这种方式创建的对象没有原型链或构造函数,它们仅仅是简单的键值对集合。
  • 适合用于创建临时的简单对象作为数据结构

2.2 类 class

类 class:

class A {}
let a = new A();
console.log(typeof a); // typeof 能够获取目标的类型
  • 使用类和构造函数创建的对象具有更多的功能性。类可以有构造函数和方法,允许面向对象的编程方式。
  • 类可以被继承,可以拥有属性和方法,可以实现接口等。
  • 使用类创建的对象通常用于更复杂的应用程序,需要更多的功能和结构化的代码组织。

因此,选择使用对象字面量还是类取决于你的需求。如果你只是需要一个简单的数据容器或者临时存储一些键值对,那么对象字面量可能是更好的选择。但是如果你需要创建一个具有更多功能和结构的对象,那么类可能更适合。


当在 JavaScript 中使用 class 关键字时,实际上是在使用 JavaScript 的面向对象编程(OOP)的语法糖。class 关键字使得定义类更加直观和易于理解,提供了一种更接近传统面向对象语言的方式来创建对象和定义类。

下面是关于 class 的一些重要概念和特性:

2.2.1 类声明

使用 class 关键字可以声明一个类。类声明包含类的名称以及类的主体,其中主体包含了类的属性和方法的定义。

class Person { // 类模版格式
    name = "mqy"
    age = 22

    sayHello() { // 方法
        console.log(`Hello, my name is ${this.name} and I'm ${this.age} years old.`);
    }
}

let person = new Person()
console.log(person)

2.2.2 构造器

在 JavaScript 中的类(ES6 类)中,不支持显式定义多个构造函数。每个类只能有一个构造函数。但你可以通过在构造函数中使用默认参数来模拟多个构造函数的行为,以实现有参和无参构造函数。

  • 构造器能够动态声明属性和赋值
class Person {
    // name = "mqy"
    // age = 22
    
    constructor(name, age) { // 有参构造器
        this.name = name; // 动态就声明和赋值了
        this.age = age;
    }
    
    sayHello() { // 方法
        console.log(`Hello, my name is ${this.name} and I'm ${this.age} years old.`);
    }
}

let person = new Person("mqy", 22)
console.log(person)

2.2.3 有参构造影响显式属性的初始化

class Person {
    name = "mqy"
    age = 22
    
    constructor(name, age) { // 有参构造器
        this.name = name; // 动态就声明和赋值了
        this.age = age;
    }
    
    sayHello() { // 方法
        console.log(`Hello, my name is ${this.name} and I'm ${this.age} years old.`);
    }
}

// 原因是,没有无参构造,该 类模版只支持 有参构造去构建
let person = new Person()
console.log(person)

2.2.4 一个构造器实现无参和有参的方法

class Person {
    constructor(name = '', age = 0) {
        this.name = name;
        this.age = age;
    }
}

// 使用有参构造函数创建对象
const person1 = new Person('Alice', 30);
console.log(person1.name); // 输出: Alice
console.log(person1.age);  // 输出: 30

// 使用无参构造函数创建对象
const person2 = new Person();
console.log(person2.name); // 输出: ''
console.log(person2.age);  // 输出: 0
// 牛不牛波 ~

2.2.5 继承

使用 extends 关键字可以实现类的继承。子类可以继承父类的属性和方法,并且可以在子类中添加新的属性和方法,或者覆盖父类的方法。

class Person {
    constructor(name, age) { // 构造器
        this.name = name;
        this.age = age;
    }

    sayHello() { // 方法
        console.log(`Hello, my name is ${this.name} and I'm ${this.age} years old.`);
    }
}

class Student extends Person {
    constructor(name, age, grade) {
        super(name, age); // 调用父类的构造函数
        this.grade = grade;
    }

    study() {
        // 这个是模板字符串 ES6 新增内容之一
        console.log(`${this.name} is studying in grade ${this.grade}.`);
    }
}

2.2.6 静态方法和属性

使用 static 关键字可以定义静态方法和属性,它们属于类本身而不是类的实例。

class MathUtil {
    static add(x, y) {
        return x + y;
    }

    static PI = 3.14;
}
console.log(MathUtil.add(1,2))

第三章·封装性

3.1 JS 类实现封装性(伪造)

为了实现 封装性 这一面向对象特性,开发者们也是煞费苦心啊 ~

以下是访问器属性没有出现之前,常用的一种方法:

在 JavaScript 中,可以通过在类的构造函数中声明变量来实现私有属性,并且在类的方法中访问这些私有属性和方法,从而实现封装。但是在语法上,并没有提供对私有属性和方法的直接支持,通常通过命名约定或者使用闭包来模拟私有属性和方法。

class Person {
    constructor(name, age) {
        let _name = name; // 私有属性
        let _age = age;   // 私有属性

        // 私有方法
        function privateMethod() {
            console.log('This is a private method');
        }

        // 公有方法
        this.getName = function() {
            return _name;
        };

        this.setName = function(name) {
            _name = name;
        };

        this.getAge = function() {
            return _age;
        };

        this.setAge = function(age) {
            _age = age;
        };
    }
}

const person = new Person('Alice', 30);
console.log(person.getName()); // 输出: Alice
person.setName('Bob');
console.log(person.getName()); // 输出: Bob

3.2 访问器属性

JS 发明了可以使用 getset 关键字来定义访问器属性的功能,用于自定义属性的读取和设置行为。

我们后期会学习到 Vue 的计算方法,其实跟这个就有点儿像。

但,其实访问器属性这东西,是官方建议 实现 封装性的方式,也是为此而发明的。

特别注意的是,还出现了一种 _标识符 就可以表示该方法或属性是 私有的意思!用起来十分方便。

class Person {
    constructor(name, age) {
        this._name = name; // 这不方便多了 ~
        this._age = age;   // 私有属性
    }

    // getter 方法
    get name() { // 外界调用时 => 对象.name 即可
        return this._name; // 直接返回私有属性
    }

    // setter 方法
    set name(value) { // 外界调用时 => 对象.name = xxx 即可
        this._name = value; // 直接赋值私有属性
    }

    // getter 方法
    get age() {
        return this._age;
    }

    // setter 方法
    set age(value) {
        this._age = value;
    }
}

const person = new Person('Alice', 30);
console.log(person.name); // 输出: Alice
person.name = 'Bob';
console.log(person.name); // 输出: Bob

第四章·函数

4.1 普通函数

就是之前学习过的JS 中普通的函数

function add(a = 0, b = 0) { // 默认
    return a + b;
}
console.log(add(1, 2));

4.2 匿名函数

let sub = function(a = 0, b = 0) { // 匿名函数是没有名字的, 这里是赋值给了 变量 sub
    return a - b
}
console.log(sub(2, 1))

4.3 回调函数

回调函数:当函数充当参数的时候,我们可以简单认为它是回调函数。

主函数在函数体里面,可能会根据一些特盘条件,来决定调用传过来的某个回调函数。回调因此得名!

ajax 中的 success 就是 回调函数。

function f(back, a, b){
    return back(a,b)
}

function add(a,b) {
    return a + b
}

function sub(a,b) {
    return a - b
}

console.log(f(add,1,2))
console.log(f(sub,1,2))

4.4 箭头函数

箭头函数是匿名函数的简化写法!不要觉得它多么高大上!

箭头函数将前面的 function 变为了后面的 =>

还可以隐式返回,就是如果直接就能一条语句返回,那么不需要写 { } 和 return,直接写表达式即可

let add = (a, b) =>  {
    return a + b;
}

let sub = (a, b) => a - b // 隐式返回

第五章·类型

5.1 Map(键值对类型)

当你需要在 JavaScript 中存储键值对的集合时,可以使用 Map 类型。Map 是一种有序的键值对集合,其中的键可以是任意数据类型,包括原始类型和对象引用

  • 下面是几种直接赋值 Map 的方式:

    • 使用数组的数组形式,每个内部数组表示一个键值对:
    const myMap = new Map([
      ['key1', 'value1'],
      ['key2', 'value2'],
      ['key3', 'value3']
    ]);
    
    • 使用数组的对象形式,每个对象必须包含 key 和 value 属性:
    const myMap = new Map([
      { key: 'key1', value: 'value1' },
      { key: 'key2', value: 'value2' },
      { key: 'key3', value: 'value3' }
    ]);
    
    • 使用可迭代对象,如数组或 Set:
    const keyValuePairs = [['key1', 'value1'], ['key2', 'value2'], ['key3', 'value3']];
    const myMap = new Map(keyValuePairs);
    
  • 下面是一个使用 Map 的简单示例:

// 创建一个新的 Map
let myMap = new Map();

// 向 Map 中添加键值对
myMap.set('name', 'Alice');
myMap.set(1, 'One');
myMap.set(true, 'Yes');

// 获取键值对
console.log(myMap.get('name')); // 输出:Alice
console.log(myMap.get(1));      // 输出:One
console.log(myMap.get(true));   // 输出:Yes

// 删除键值对
myMap.delete('name');

// 遍历 Map
myMap.forEach((value, key) => { // 这个就是我们讲的 回调函数,这里只是用 箭头函数来传参
  console.log(key, value);
});

5.2 Set(集合类型)

当谈到 JavaScript 中的 Set 时,它是一种集合数据结构,类似于数组,但是它的值是唯一的,不允许重复。

const mySet = new Set(); // 创建一个空的 Set
const mySet2 = new Set([1, 2, 3, 4, 5]); // 使用初始化器创建一个包含初始值的 Set

// 添加元素
mySet.add(1);
mySet.add(2);
mySet.add(3);

// 检查元素是否存在
mySet.has(1); // 返回 true
mySet.has(5); // 返回 false,因为 5 没有被添加到 Set 中

// 删除元素
mySet.delete(2); // 从 Set 中删除元素 2

// 遍历 for-each
mySet.forEach(item => {
  console.log(item);
});

// 获取大小
console.log(mySet.size); // 输出 Set 的大小

// 清空set
mySet.clear(); // 清空 Set 中的所有元素

5.3 数组

let arr = [1,2,3,4]
let brr = new Array(); // 也可以创建数组

// 添加元素到尾部
brr.push(arr);
brr.push(5);
brr.push(6,7,8);

brr.unshift(0) // 在头部添加元素

// 删除元素
let ret = brr.shift(); // 删除第一个元素并返回该元素的值
ret = brr.pop(); // 删除最后一个元素
let crr = brr.splice(0, 3); // 从下标0 开始 向后删除3个元素, 然后返回这些元素组成的数组

// 数组倒置
arr.reverse()

// 数组排序
arr.sort() 
arr.sort((a,b) => b - a) // 它是有回调函数的,用来设置比较规则

// 数组过滤
crr = arr.filter((item) => item > 2) // 大于 2 的元素全部收集起来,组成新的数组

// JS 还提供了 Array.xxx() 工具类,可以自行探索

第六章·其它内容

6.1 迭代遍历

for...offor...in 是 JavaScript 中两种不同的迭代语句,用于遍历对象的属性或数组的元素。它们之间的主要区别在于它们遍历的对象类型以及遍历的方式。

  1. for...of:

    • 用于遍历可迭代对象(Iterable),例如数组、字符串、Set、Map 等。

    • 它遍历的是对象的值(数组元素、字符串字符等)而不是索引或键。

    • 不支持普通对象的遍历,因为普通对象不是可迭代的。

    • 语法示例:

const iterable = ['a', 'b', 'c'];
for (const value of iterable) {
  console.log(value); // 输出:a、b、c
}
  1. for...in:
    • 用于遍历对象的可枚举属性,包括原型链上的属性。
    • 遍历的是对象的键(字符串类型的键)。
    • 不适用于遍历数组,因为它会遍历数组的索引,包括数组原型链上的方法。
    • 语法示例:
const obj = { a: 1, b: 2, c: 3 };
for (const key in obj) {
  console.log(key); // 输出:a、b、c
  console.log(obj[key]); // 输出:1、2、3
}

总的来说,for...of 适用于遍历可迭代对象的值,而 for...in 适用于遍历对象的键。在遍历数组时,推荐使用 for...of,而在遍历对象的属性时,可以使用 for...in

6.2 Array.from 与 ... (扩展运算符、剩余参数)

... 关键字:用来展开可迭代对象,然后进行接收。所以 Array.from() 数组转换可以用 ... 关键字来取代

let str = "abcd"
let arr = Array.from(str);
let brr = [...str]
console.log(arr, brr);

function add(...args) {
    let sum = 0;
    for(let i = 0; i < args.length; ++i){
        sum += args[i];
    }
    return sum;
}

console.log(add(1,2,3,4,5))

6.3 解构

// 数组解构
let [a,b] = [1,2];
let [a,b,x = 3] = [1,2];
let [,,c] = [1,2,3];
let [d,...arr] = [1,2,3,4,5,6];

// 两数交换
let p = 1;
let q = 2;
[p,q] = [q,p];

// 对象解构
let person = {
    name: "mqy",
    age: 22
}

let {age} = person; // 只要变量名 与 对象属性名一致,能对应上就能解构出来
// 当然也可以给对应的属性变量起别名
let {name:nick} = person; // 没有这个属性, 但是我们可以 这样玩
console.log(nick) // 比如别名我可以设为 nick

let {gender:sex = '男'} = person; // 没有这个属性, 但是我们可以 这样玩
console.log(sex);

6.4 Promise 异步

6.4.1 异步与同步

在计算机编程中,同步和异步是两种不同的执行模式,用来描述代码执行时的顺序和方式。

  1. 同步(Synchronous):同步操作是指代码按照顺序执行,每一行代码都会等待前一行代码执行完毕才会执行,执行结果会立即返回。换句话说,同步操作是顺序执行的,每一步都是阻塞的,直到前一步完成后才能执行下一步。
console.log("Step 1");
console.log("Step 2");
console.log("Step 3");
  1. 异步(Asynchronous):异步操作是指代码执行时不会等待前面的操作完成,而是通过回调函数、事件监听、Promise 等机制来处理后续的操作。异步操作不会阻塞代码的执行而是在后台执行当操作完成后会通知代码执行相应的回调函数或触发相应的事件。

  2. setTimeout() 是 JavaScript 中的一个函数,用于在指定的时间后执行一个指定的函数或者一段代码。

    它接受两个参数:

    • 第一个参数是要执行的函数或者代码块。

    • 第二个参数是延迟的时间,以毫秒为单位。

console.log("Step 1");
setTimeout(() => {
    console.log("Step 2"); // 时钟处理的任务,即回调函数的内容,是异步的!这个一定要切记!
}, 1000);
console.log("Step 3");

**所以上述的程序,Step 3 的输出,并不会等待 时钟 setTimeout 任务执行完毕。这就是异步的简单体现! **

6.4.2 Promise

Promise 是 JavaScript 中处理异步操作的一种常用的方式。它是一个对象,代表了一个尚未完成且最终会返回结果的异步操作。

让我们来看看 Promise 的基本用法:

function fetchDataFromServer() {
    return new Promise((resolve, reject) => {
        resolve("拿到数据了"); // 简单模拟下就行
    })
}
// 创建一个 Promise 实例
const myPromise = new Promise((resolve, reject) => {
  // 执行一些异步操作,比如从服务器获取数据
  const data = fetchDataFromServer();

  // 假设 fetchDataFromServer() 返回一个 Promise
  data.then((result) => {
    // 如果异步操作成功,调用 myPromise 的 resolve,并传递结果
    resolve(result);
  }).catch((error) => {
    // 如果异步操作失败,调用 myPromise 的 reject,并传递错误信息
    reject(error);
  });
});

// 使用 Promise 对象,当你设定好 成功的函数、失败的函数 之后,这个 Promise 对象就开始执行了。
myPromise.then((result) => {
  // 异步操作成功时执行的代码
  console.log('Data fetched successfully:', result);
}).catch((error) => {
  // 异步操作失败时执行的代码
  console.error('Error fetching data:', error);
});

6.5 Fetch

当涉及到从网络获取资源时,fetch 是 JavaScript 中用于发起网络请求的现代API。它提供了一种更简洁、更强大的替代方案,取代了旧的 XMLHttpRequest(XHR)对象。 我们在 JavaWEB (Servlet)时,就使用过 XHR 作为前后端数据传输的主要方式。但是在 ES6 引入了 Fetch 之后,就强烈推荐大家使用原生态的网络请求,请用 Fetch,它真的设计的不错 ~ 更 der 的是 => fetch 返回的是 一个 Promise 对象,其实它就是用 Promise 再次设计的,进行了 XHR 的二次封装

  • 格式
fetch(url)
  .then(response => {
    // 在这里处理响应
    // 可以使用 response.json()、response.text() 等方法处理响应数据
  })
  .catch(error => {
    // 处理错误
  });

特点

  1. Promise-based(基于 Promise)fetch 返回一个 Promise,使得使用者可以通过 .then().catch() 方法来处理异步操作。
  2. 简洁的API设计fetch 的 API 简洁明了,更容易理解和使用。
  3. 默认使用 GET 方法:如果不显式指定请求方法,fetch 默认使用 GET 方法。
  4. 不支持同源策略fetch 并不遵循浏览器的同源策略,因此可以进行跨域请求。
  5. 默认不携带 Cookie默认情况下,fetch 请求不会携带用户的 Cookie,可以通过设置 credentials 选项来修改这一行为。
  6. 需要检查响应状态fetch 不会在 HTTP 错误状态码(如404、500等)下抛出异常,只有在网络故障等异常情况下才会被捕获。这会显得既好又不好!好的地方在于可以让我们在编写代码时,就清晰的设定好这些错误状态码的拦截和处理。不好的地方在于,增加了工作量,而且如果经验不足很容易发生懵逼的现象。

示例

const headers = new Headers();
headers.append('Access-Control-Allow-Credentials', 'true')
fetch('http://www.baidu.com',{
    credentials: 'include', // 自动加载对应的 cookie
    method:'GET',
    headers:headers,
    mode: 'no-cors' 
})
  .then(response => {
    if (!response.ok) {
        // 这个是抛出异常 其实也不是很想讲这个东西,因为跟 Java 简直没啥区别。。
      throw new Error('Network response was not ok');
    }
    return response.text(); // 返回 HTML 文本内容
  })
  .then(data => { // 这个其实就是 链式编程的思想,上一个 then 如果 return 数据了
    // 那么要在 下一个 then 这里 再继续处理
    console.log(data);
  })
  .catch(error => { // 会拦截抛出的异常
    console.error('There was a problem with the fetch operation:', error);
  }).finally(() => {
    console.log("最后都会执行它!");
});

这个例子,可能最后会有异常,毕竟是 人家百度的嘛。

6.6 模块开发

在 JavaScript 中,模块(Modules)是一种将代码拆分成独立文件并进行组织的方法。模块可以包含变量、函数、类等,并通过导入(import)和导出(export)来共享其功能。、

导出(export)

在模块中,你可以通过 export 关键字将变量、函数、类或者其他内容导出,使其对外可见。

// math.js
export const PI = 3.14;

export function square(x) {
  return x * x;
}

export class Circle {
  constructor(radius) {
    this.radius = radius;
  }

  area() {
    return PI * this.radius * this.radius;
  }
}

导入(import)

在其他模块中,你可以通过 import 关键字来导入另一个模块导出的内容

// app.js
import { PI, square, Circle } from './math.js';

console.log(PI); // 输出: 3.14
console.log(square(2)); // 输出: 4

const circle = new Circle(5);
console.log(circle.area()); // 输出: 78.5

默认导出(default export)

除了具名导出之外,模块还可以通过 export default 导出默认值。一个模块只能有一个默认导出。

// math.js
const PI = 3.14;

function square(x) {
  return x * x;
}

export default {
  PI,
  square
}
// app.js
import Math from './math.js';

console.log(Math.PI); // 输出: 3.14
console.log(Math.square(2)); // 输出: 4

模块加载器

在浏览器环境中,可以使用模块加载器(如Webpack、Parcel、Rollup等)来管理和加载模块。在 Node.js 环境中,模块系统已经内置,你可以直接使用 requiremodule.exports 来进行模块的导入和导出。

6.7 Symbol(符号)

  • 概念:Symbol 是 ES6 中引入的一种新的基本数据类型,用于表示唯一的标识符。每个从 Symbol() 返回的 Symbol 值都是唯一的。
  • 用途:Symbol 可以用作对象属性的键,确保不会与其他属性冲突。它们在创建私有属性或定义常量时非常有用。

Symbol 的设计其实是挺失败的~ 个人认为呀 !就是那种感觉。。挺矛盾的 ~

首先,Symbol() 里面的值,我们称之为 "描述值",它是不会影响 Symbol 的唯一性的!

Symbols 的唯一性:指的是通过内部的 Symbol 注册表来实现的。在 JavaScript 引擎内部,存在一个全局的 Symbol 注册表,用于存储所有创建的 Symbol。可以看下面的这个注册表 的简易图:

Symbol Registry
+-------------------------------------------+
| Symbol("level", SymbolId: 123) |
| Symbol("level", SymbolId: 456) |
| Symbol("other", SymbolId: 789) |
+-------------------------------------------+

很显然,虽然你是相同的描述,但注册的确确实实是 两个,ID 是不一样的!

所以也就是两个不同的 Symbol,它们只不过是 描述值一样而已。

Symbol.for() 注册表中寻找 Symbol 实例(找到第一个就不会再去找)如果没找到,就会自动创建一个

const symbol1 = Symbol('description');
const symbol2 = Symbol('description'); // 描述是不影响唯一性的
console.log(symbol1 === symbol2) // 这个肯定返回 false

console.log(Symbol.for("level") === Symbol.for("level")) // 这个肯定返回 true

6.7.1 作为对象属性的键

由于 Symbol 的唯一性,它可以作为对象属性的键,确保不会与其他属性冲突,甚至是在使用 Object.keys()、for...in 循环或 JSON.stringify() 进行序列化时也不会被访问到。

const mySymbol = Symbol();
const obj = {
  [mySymbol]: 'Hello'
};
console.log(obj[mySymbol]); // 输出 'Hello'

6.7.2 定义常量

由于每个 Symbol 值都是唯一的,因此它们可以用作定义常量的键。

const COLOR_RED = Symbol('Red');
const COLOR_GREEN = Symbol('Green');

6.7.3 私有属性和方法

由于 Symbol 的唯一性和不可枚举性,它们经常用于创建对象的私有属性或方法。

const _name = Symbol('name');
class Person {
  constructor(name) {
    this[_name] = name;
  }
  getName() {
    return this[_name];
  }
}

6.7.4 注意事项

  • Symbol 值通过 Symbol() 函数创建,不能使用 new 关键字。
  • 通过 Symbol() 创建的 Symbol 值永远不会相等,即使描述相同也是如此。
  • Symbol 不能通过 JSON.stringify() 序列化。
  • Symbol 不能被隐式转换为字符串,需要显示调用 toString() 方法。

Symbol 是 JavaScript 中一个非常有用的特性,它为开发者提供了一种确保唯一性的机制,可以有效地避免属性名的冲突,并且可以用于实现一些高级的编程模式,如定义常量、创建私有属性等。

6.8 Generators(生成器)

  • 概念:Generators 是一种特殊类型的函数,可以在函数执行过程中暂停和恢复。

  • 用途:Generators 可以用于简化异步代码的编写,以及实现可迭代对象的自定义迭代行为。它们通常与迭代器一起使用,提供了一种更灵活的迭代方式。

  • 使用场景

    • 遍历数据集合:Generators 可以用于创建自定义的迭代器,用于遍历数据集合,如数组、集合、映射等。
    • 异步编程:Generators 可以与 Promises 结合使用,简化异步编程中的回调地狱,提高代码的可读性和可维护性。
    • 状态机:Generators 可以用于实现复杂的状态机,通过暂停和恢复函数的执行来管理状态转换。

6.8.1 基本语法

Generators 使用 function* 语法来定义,其中 function* 关键字表示该函数是一个 Generator 函数。在 Generator 函数中,使用 yield 关键字来定义生成器的每个值。

function* myGenerator() {
  yield 1;
  yield 2;
  yield 3;
}

6.8.2 调用 Generator 函数

调用 Generator 函数并不会执行其中的代码,而是返回一个迭代器对象,可以通过该迭代器对象逐个获取生成器函数生成的值。

const iterator = myGenerator();
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 }

6.8.3 生成器的状态控制

在 Generator 函数内部,可以使用 yield 来暂停函数的执行,并返回一个值给调用者。当调用 next() 方法时,函数将从上一次 yield 语句暂停的位置继续执行,直到遇到下一个 yield 语句或函数结束。

function* counter() {
  let count = 0;
  while (true) {
    yield count++;
  }
}
const iterator = counter();
console.log(iterator.next().value); // 0
console.log(iterator.next().value); // 1
console.log(iterator.next().value); // 2

6.8.4 Generator 函数的终止

Generator 函数可以在任意时刻通过 return 关键字终止,并返回给定的值。

function* myGenerator() {
  yield 1;
  return 2;
  yield 3; // 这一行永远不会执行
}
const iterator = myGenerator();
console.log(iterator.next()); // { value: 1, done: false }
console.log(iterator.next()); // { value: 2, done: true }

Generators 是 JavaScript 中一种强大的功能,可以用于实现各种复杂的逻辑和编程模式。掌握 Generators 可以帮助开发者编写更清晰、更简洁、更具可读性的代码,并且能够有效地处理异步任务和复杂的控制流程。

6.9 Iterators(迭代器)

  • 概念:迭代器是一种对象,它提供了一种访问数据集合(如数组)中元素的方式,而不暴露数据的内部实现。
  • 用途:迭代器使得 JavaScript 中的数据集合可以被迭代,例如使用 for...of 循环遍历数组、集合等数据结构。Generators 可以用来创建自定义的迭代器。

6.9.1 创建迭代器

在 JavaScript 中,可以通过实现 Symbol.iterator 方法来创建一个可迭代对象,该方法需要返回一个迭代器对象,该对象包含一个 next() 方法用于迭代访问集合中的每个元素。

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

6.9.2 使用迭代器

使用 for...of 循环或 next() 方法来遍历迭代器中的每个元素。

for (const value of myIterable) {
  console.log(value);
}
// Output:
// 1
// 2
// 3

const iterator = myIterable[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 }

6.9.3 内置可迭代对象

JavaScript 中的很多数据结构都是可迭代的,包括数组、集合、映射等,它们都实现了 Symbol.iterator 方法,因此可以直接使用 for...of 循环或 next() 方法进行遍历。

const arr = [1, 2, 3];
for (const value of arr) {
  console.log(value);
}
// Output:
// 1
// 2
// 3

const map = new Map([['a', 1], ['b', 2], ['c', 3]]);
for (const [key, value] of map) {
  console.log(key, value);
}
// Output:
// a 1
// b 2
// c 3

6.9.4 自定义迭代器

除了使用内置的数据结构外,还可以自定义实现迭代器,以便于遍历自定义的数据结构。

function* myIterator() {
  let count = 0;
  while (count < 3) {
    yield count++;
  }
}

const iterator = myIterator();
console.log(iterator.next()); // { value: 0, done: false }
console.log(iterator.next()); // { value: 1, done: false }
console.log(iterator.next()); // { value: 2, done: false }
console.log(iterator.next()); // { value: undefined, done: true }

迭代器是 JavaScript 中用于遍历数据集合的一种强大工具,它提供了一种统一的方式来访问集合中的元素,使得代码更具可读性、可维护性,并且可以应用于各种不同类型的数据结构中。

第七章·ES7

7.1 Array.includes()

includes() 方法是 JavaScript 中数组对象的一个方法,用于判断数组是否包含某个特定值,并返回相应的布尔值。

返回值

  • 如果数组包含指定的元素,则返回 true
  • 如果数组不包含指定的元素,则返回 false
const array = [1, 2, 3, 4, 5];

// includes(搜索的元素值, 开始搜索的索引位置)
console.log(array.includes(2)); // true
console.log(array.includes(6)); // false
console.log(array.includes(2, 3)); // false
console.log(array.includes(2, -3)); // true

7.2 指数操作符(**)

let a = 6**2;
console.log(a); // 36

第八章·ES8

8.1 Async/Await(异步/等待)

Async/Await 是 ECMAScript 2017(ES8)中引入的一种用于处理异步操作的语法糖,它建立在 Promise 的基础之上,提供了更加清晰、简洁的方式来编写异步代码。通过使用 asyncawait 关键字,可以使异步代码看起来更像同步代码,提高了代码的可读性和可维护性。

async 函数

  • async 函数是用于定义一个异步函数的关键字,它在函数声明之前加上 async 关键字,表示该函数会返回一个 Promise 对象。
  • async 函数内部可以使用 await 关键字来等待异步操作的结果。

await 表达式

  • await 关键字只能在 async 函数中使用,用于等待一个 Promise 对象的解析结果。
  • await 关键字后面可以跟随一个 Promise 对象,或任何返回 Promise 对象的表达式。await 表达式会暂停异步函数的执行,直到 Promise 对象解析完成并返回结果。
// 异步函数,模拟获取用户信息的网络请求
function getUserInfo() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve({ id: 1, name: 'John Doe', age: 30 }); // 传进去的对象会默认的成为 getUserInfo() 的返回对象,包括 reject() 也是如此
        }, 1000);
    });
}

// 使用 Async/Await 处理异步操作
async function fetchUserInfo() {
    try {
        console.log('Fetching user info...');
        // 使用 await 关键字等待异步操作的结果
        const userInfo = await getUserInfo();
        console.log('User info:', userInfo);
        console.log(`${userInfo.name} is ${userInfo.age} years old.`);
    } catch (error) {
        console.error('Error fetching user info:', error);
    }
}

// 调用异步函数
fetchUserInfo();

在上面的例子中,fetchUserInfo() 函数是一个使用了 Async/Await 的异步函数。它通过 await getUserInfo() 等待 getUserInfo() 函数返回的 Promise 结果,然后将结果赋值给 userInfo 变量。这样可以使代码看起来更像同步代码,而不需要嵌套的回调函数或者 Promise 的链式调用。

8.2 Object.entries() 和 Object.values()

  • Object.entries() 方法返回一个给定对象自身可枚举属性的键值对数组,其中每个键值对表示一个属性的键和值。
  • Object.values() 方法返回一个给定对象自身可枚举属性的值的数组,不包含对象的键名。

8.3 字符串填充方法(padStart() 和 padEnd())

  • padStart() 方法用于将当前字符串用指定的字符串填充到指定长度,以使结果字符串达到指定的长度,从字符串的开头(左侧)开始填充。
const str = 'hello';
const paddedStr = str.padStart(10, 'x');

console.log(paddedStr); // Output: "xxxxxxhello"
  • padEnd() 方法用于将当前字符串用指定的字符串填充到指定长度,以使结果字符串达到指定的长度,从字符串的末尾(右侧)开始填充。
const str = 'hello';
const paddedStr = str.padEnd(10, 'x');

console.log(paddedStr); // Output: "helloxxxxx"
posted @ 2024-01-27 11:15  小哞^同^学的技术博客  阅读(21)  评论(0编辑  收藏  举报