JavaScriptES6新特性

发布于:2024-08-01 ⋅ 阅读:(102) ⋅ 点赞:(0)

一. 块级作用域(let和const)

ES6引入了letconst两个新的关键字用于声明变量,解决了var关键字带来的作用域问题。let声明的变量只在当前块级作用域内有效,而const声明的变量是常量,一旦被赋值后就不能再改变。

1.let

let 是 ES6 中引入的用于声明块级作用域变量的关键字。与 var 不同,let 声明的变量只在其声明的块(或子块)作用域内有效,且不会发生变量提升(hoisting)现象(即变量在声明之前不可用)。

特点

  1. 块级作用域let 变量只在其声明的块或子块中可用。
  2. 不提升(Temporal Dead Zone):在声明之前引用 let 变量会抛出 ReferenceError
  3. 可重新赋值let 声明的变量可以重新赋值。

示例代码

if (true) {
    let a = 10;
    console.log(a); // 10
}
// console.log(a); // ReferenceError: a is not defined

let b;
// console.log(b); // ReferenceError: b is not defined,因为b没有初始化,处于Temporal Dead Zone
b = 20;
console.log(b); // 20

let c = 30;
c = 40; // 重新赋值
console.log(c); // 40

2.const

const 也是 ES6 引入的,用于声明一个只读的常量。一旦一个常量被赋值后,它的值就不能再被改变(但如果是对象或数组,则内部状态可以被修改,只是指向的引用不能变)。

特点

  1. 块级作用域:与 let 相同,const 声明的常量也只在其声明的块或子块中可用。
  2. 不提升(Temporal Dead Zone):在声明之前引用 const 变量同样会抛出 ReferenceError
  3. 不可重新赋值:一旦 const 变量被赋值后,尝试修改其值会抛出 TypeError

示例代码

const PI = 3.14;
// PI = 3.14159; // TypeError: Assignment to constant variable.

if (true) {
    const MAX_VALUE = 100;
    console.log(MAX_VALUE); // 100
}
// console.log(MAX_VALUE); // ReferenceError: MAX_VALUE is not defined

const obj = { key: 'value' };
obj.key = 'new value'; // 对象内部状态可以修改
console.log(obj.key); // 'new value'
// obj = {}; // TypeError: Assignment to constant variable.

3.let 和 var 的区别

  1. 作用域var 声明的变量是函数作用域或全局作用域,而 let 声明的变量是块级作用域。
  2. 变量提升(Hoisting)var 声明的变量会提升到其作用域的顶部,而 let 声明的变量不会提升,在声明前引用会抛出 ReferenceError
  3. 重复声明:在同一个作用域内,var 可以重复声明同一个变量,而 let 不能(会抛出 SyntaxError)。
  4. 全局对象属性:在浏览器环境下,var 声明的全局变量会成为 window 对象的属性,而 letconst 声明的全局变量不会。

示例代码对比

// var
var x = 1;
{
    var x = 2; // 同一作用域内可以重复声明
    console.log(x); // 2
}
console.log(x); // 2

// let
let y = 1;
{
    let y = 2; // 不同的块级作用域,允许重复声明
    console.log(y); // 2
}
console.log(y); // 1

// 尝试在let前使用
// console.log(z); // ReferenceError: z is not defined
let z = 3;

// var 和全局对象
var globalVar = 42;
console.log(window.globalVar); // 42

let globalLet = 42;
console.log(window.globalLet); // undefined

二. 箭头函数

箭头函数提供了一种更简洁的函数书写方式,并自动绑定了上下文(即this的值)。这解决了传统函数在回调中this指向不一致的问题。箭头函数(Arrow Functions)是ES6中引入的一种更简洁的函数写法,它使用 => 语法。箭头函数不绑定自己的 thisargumentssuper,或 new.target。这些函数表达式更适用于非方法函数,并且它们不能用作构造函数。

1.特点

  1. 更简洁的语法:特别是对于只有一个参数的函数,或者函数体只有一行代码的情况。
  2. 不绑定自己的 this:箭头函数不创建自己的 this 上下文,而是继承自父执行上下文中的 this 值(即所谓的词法作用域或静态作用域)。这是箭头函数最显著的特点,也是它最常被使用的原因之一。
  3. 不支持 arguments 对象:因为箭头函数没有自己的 this,所以它也没有自己的 arguments 对象。在箭头函数内访问 arguments 实际上会访问到外层函数的 arguments 对象。
  4. 不支持 new 操作符:由于箭头函数没有自己的 this,且没有 prototype 属性,因此它们不能被用作构造函数,即不能使用 new 关键字来调用。
  5. 没有 super:箭头函数不能通过 super 关键字调用父类的方法。
  6. 不支持 yield:箭头函数不能用作 Generator 函数,即不能包含 yield 表达式。

2.语法

箭头函数的基本语法如下:

(参数1, 参数2,, 参数N) => { 函数体 }

如果函数体只有一行代码(且不需要返回对象字面量),可以省略大括号和 return 关键字:

(参数1, 参数2,, 参数N) => 表达式

当函数只有一个参数时,可以省略圆括号:

参数 => { 函数体 }
参数 => 表达式

3.示例代码

// 基本用法
const add = (a, b) => a + b;
console.log(add(2, 3)); // 5

// 单个参数时省略圆括号
const square = x => x * x;
console.log(square(4)); // 16

// 函数体只有一行代码时省略大括号和return
const greet = name => `Hello, ${name}!`;
console.log(greet('Alice')); // Hello, Alice!

// 箭头函数中的this继承自父作用域
function Person() {
  this.age = 0;

  setInterval(() => {
    this.age++; // `this` 正确地指向了person对象
    console.log(this.age);
  }, 1000);
}

const p = new Person();

// 传统定义函数
var f1 = function (n) {
    return n * 2
}
console.log("传统= " + f1(2))
// ES6 , 箭头函数使用

let f2 = (n) => {
    return n * 2;
}
console.log("f2() 结果= ", f2(100));//200
//上面的es6 函数写法,还可以简化
let f3 = n => n * 3;
console.log("f3() 结果=", f3(100));//300

//函数也可以传给一个变量=> 看看java基础匿名内部类
function hi(f4) {
    console.log(f4(900));
}
hi((n) => {
    return n + 100
});
hi((n) => {
    return n - 100
});

在上面的 Person 例子中,由于箭头函数不绑定自己的 this,所以它内部的 this 指向了 Person 实例,这与传统的函数声明或函数表达式在回调函数中的行为形成了对比,后者在回调函数内部 this 通常不会指向预期的对象,除非显式绑定或使用其他技巧。

三. 解构赋值

解解构赋值(Destructuring assignment)是ES6中引入的一种表达式,它允许你将数组或对象中的数据解构出来,然后赋值给声明的变量。这种方式可以使得从数组或对象中提取数据变得更加简洁和直观。

1.数组解构

对于数组,解构赋值允许你直接从数组中提取值,然后赋值给声明的变量,而不需要通过索引来获取。

基本用法

let [a, b, c] = [1, 2, 3];
console.log(a); // 1
console.log(b); // 2
console.log(c); // 3

不完全解构

如果解构的变量少于数组中的元素,则额外的元素会被忽略。

let [x, y] = [1, 2, 3];
console.log(x); // 1
console.log(y); // 2

剩余元素

使用 ... 语法可以捕获数组中的剩余元素。

let [head, ...tail] = [1, 2, 3, 4];
console.log(head); // 1
console.log(tail); // [2, 3, 4]

2.对象解构

对象解构允许你从对象中提取数据,并赋值给声明的与对象属性同名的变量。

基本用法

let { foo, bar } = { foo: 'hello', bar: 'world' };
console.log(foo); // hello
console.log(bar); // world

默认值

如果解构的变量在对象中没有对应的属性,则可以指定默认值。

let { baz = 'default' } = { foo: 'hello' };
console.log(baz); // default

重命名

你可以在解构时重命名变量,以匹配对象的属性名。

let { foo: newName } = { foo: 'hello' };
console.log(newName); // hello

嵌套解构

对象解构也支持嵌套对象。

let { user: { name, age } } = { user: { name: 'Alice', age: 30 } };
console.log(name); // Alice
console.log(age);  // 30

3.函数参数解构

解构赋值也可以用于函数参数,这使得从函数参数中提取数据变得更加方便。

function greet({ name, age }) {
  console.log(`Hello, my name is ${name} and I am ${age} years old.`);
}

greet({ name: 'Bob', age: 25 });
// Hello, my name is Bob and I am 25 years old.

解构赋值是ES6中一项非常有用的特性,它极大地简化了从数组和对象中提取数据的语法,使得代码更加简洁和易于理解。

四. 模板字符串

模板字符串允许在字符串中嵌入表达式,并支持多行文本,无需使用拼接或转义字符。

示例代码

// ES5  
var name = "John";  
var greeting = "Hello, " + name + "!";  
  
// ES6  
const name = "John";  
const greeting = `Hello, ${name}!`;  
  
// 多行字符串  
const multiLineText = `This is line 1.  
This is line 2.`;

五. 类(Class)

ES6引入了类的语法糖,使得面向对象编程更加简洁和易用。类可以通过extends关键字实现继承,使用super关键字调用父类的方法。

示例代码

class Person {  
    constructor(name, age) {  
        this.name = name;  
        this.age = age;  
    }  
  
    introduce() {  
        return `My name is ${this.name}, and I am ${this.age} years old.`;  
    }  
}  
  
const alice = new Person("Alice", 30);  
console.log(alice.introduce()); // My name is Alice, and I am 30 years old.

六. Promise

Promise是异步编程的一种解决方案,提供了更优雅的链式调用和错误处理方式,替代了传统的回调函数。

Promise 是 JavaScript 中用于异步编程的一个非常重要的概念,它代表了一个最终可能完成(fulfilled)、也可能失败(rejected)的异步操作及其结果值。Promise 对象使得异步代码看起来和同步代码更加相似,易于理解和维护。

1.基本概念

  • Promise 对象有三种状态

    • Pending(进行中):初始状态,既不是成功,也不是失败状态。
    • Fulfilled(已成功):意味着操作成功完成。
    • Rejected(已失败):意味着操作失败。
  • 一旦状态改变,就不会再变。即 Promise 对象的状态只能从 Pending 变为 Fulfilled 或 Rejected,且这个过程是单向的。

2.创建 Promise

你可以使用 new Promise() 构造函数来创建一个新的 Promise 对象。Promise 构造函数接受一个执行器(executor)函数作为参数,这个执行器函数本身又接受两个函数作为参数:resolvereject

const promise = new Promise((resolve, reject) => {
  // 异步操作
  if (/* 异步操作成功 */) {
    resolve(value); // 将 Promise 的状态从 pending 变为 fulfilled,并将 value 作为结果值
  } else {
    reject(error); // 将 Promise 的状态从 pending 变为 rejected,并将 error 作为原因
  }
});

3.使用 Promise

Promise 提供了 .then().catch() 方法来分别处理成功和失败的情况,以及 .finally() 方法来无论成功或失败都会执行的操作。

promise.then(
  value => {
    // 当 Promise 状态变为 fulfilled 时执行
    console.log(value);
  },
  error => {
    // 可选:当 Promise 状态变为 rejected 时执行
    console.error(error);
  }
)
.catch(error => {
  // 捕获在 promise.then() 中抛出的错误
  console.error(error);
})
.finally(() => {
  // 无论 promise 最后的状态如何,都会执行
  console.log('Promise 执行完毕');
});

4.链式调用

Promise 支持链式调用,因为 .then().catch() 方法都会返回一个新的 Promise 对象,这允许我们将多个异步操作串联起来。

doSomething()
  .then(result => doSomethingElse(result))
  .then(newResult => doThirdThing(newResult))
  .catch(failureCallback);

5.Promise 静态方法

  • Promise.all(iterable):当所有给定的 Promise 对象都成功完成时,它才会成功完成,并返回一个包含所有结果值的数组。
  • Promise.race(iterable):当给定的 Promise 对象中的任意一个成功或失败完成时,它就会以那个 Promise 的结果作为自己的结果,并立即结束。
  • Promise.resolve(value):返回一个以给定值解析后的 Promise 对象。
  • Promise.reject(reason):返回一个以给定原因拒绝的 Promise 对象。

6.总结

Promise 是处理异步操作的重要工具,它提供了一种优雅的方式来处理异步操作的结果,避免了传统回调函数的“回调地狱”问题。通过使用 Promise,你可以编写更清晰、更易于维护的异步代码。

7.示例代码

//1. 创建Promise对象
//2. 构造函数传入一个箭头函数
//3. (resolve, reject) 参数列表resolve: 如果请求成功, 调用resolve函数
//4. 如果请求失败, 调用reject函数
//5. 箭头函数体, 仍然是通过jquery发出ajax
let p = new Promise((resolve, reject) => {
    //发出ajax
    $.ajax({
        url: "data/monster.json",
        success(resultData) {//成功的回调函数
            console.log("promise发出的第1次ajax monster基本信息=", resultData);
            resolve(resultData);
        },
        error(err) {
            //console.log("promise 1发出的异步请求异常=", err);
            reject(err);
        }
    })
}).then((resultData) => {
    //这里我们可以继续发出请求
    console.log("p.then 得到 resultData", resultData);
    return new Promise((resolve, reject) => {
        $.ajax({
            url: `data/monster_detail_${resultData.id}.json`,
            success(resultData) { //第2次ajax请求成功,回调函数
                console.log("第2次ajax请求 monster的详细信息=", resultData);
                //继续进行下一次的请求
                resolve(resultData);
            },
            error(err) { //第2次ajax请求失败,回调函数
                //console.log("promise2 发出的异步请求异常=", err);
                reject(err);
            }
        })
    })
}).then((resultData) => {

    console.log("p.then().then(), resultData", resultData)
    //即可以在这里发出第3次ajax请求=》 获取该妖怪的女友
    return new Promise((resolve, reject) => {
        $.ajax({
            url: `data/monster_gf_${resultData.gfid}.json`,

            success(resultData) { //第3次ajax请求成功,回调函数
                console.log("第3次ajax请求 monster女友的详细信息=", resultData);
                //继续进行下一次的请求
                //resolve(resultData);
            },
            error(err) { //第2次ajax请求失败,回调函数
                //console.log("promise2 发出的异步请求异常=", err);
                //reject(err);
            }
        })
    })

}).catch((err) => { //这里可以对多次ajax请求的异常进行处理
    console.log("promise异步请求异常=", err);
})

//========================================重排============================================
/**
 * 这里我们将重复的代码,抽出来,编写一个方法get
 *
 * @param url ajax请求的资源
 * @param data ajax请求携带的数据
 * @returns {Promise<unknown>}
 */
function get(url, data) {
    return new Promise((resolve, reject) => {
        $.ajax({
                url: url,
                data: data,
                success(resultData) {
                    resolve(resultData);
                },
                error(err) {
                    reject(err);
                }
            }
        )
    })
}

//1. 先获取monster.json
//2. 获取monster_detail_1.json
//2. 获取monster_gf_2.json
get("data/monster.json").then((resultData) => {
    //第1次ajax请求成功后的处理代码
    console.log("第1次ajax请求返回数据=", resultData);
    return get(`data/monster_detail_${resultData.id}.json`);
}).then((resultData) => {
    //第2次ajax请求成功后的处理代码
    console.log("第2次ajax请求返回数据=", resultData);
    //return get(`data/monster_detail_${resultData.id}.json`);
    return get(`data/monster_gf_${resultData.gfid}.json`);
}).then((resultData) => {
    //第3次ajax请求成功后的处理代码
    console.log("第3次ajax请求返回数据=", resultData);
    //继续..
}).catch((err) => {
    console.log("promise请求异常=", err);
})

七. 模块化导入和导出

ES6(ECMAScript 2015)引入了模块系统,使得JavaScript代码的组织和复用变得更加容易和高效。模块系统允许你将代码分割成可复用的单元,每个单元包含一个或多个模块,这些模块可以导入其他模块提供的功能,也可以导出自己提供的功能供其他模块使用。

1.导出(Export)

在ES6中,你可以使用export关键字来导出函数、对象或原始值,以便其他模块可以通过import语句来使用它们。

导出函数
// math.js
export function add(x, y) {
  return x + y;
}

export function subtract(x, y) {
  return x - y;
}
导出变量
// config.js
export const PI = 3.14159;
export let flag = true;
导出默认成员

每个模块可以有一个默认导出,它可以是函数、类、对象或原始值。默认导出在导入时不需要使用花括号{}

// myDefaultModule.js
export default function() {
  console.log('Hello, world!');
}

2.导入(Import)

使用import语句可以导入其他模块导出的功能。

导入命名导出

如果你想要导入特定的命名导出,你需要使用花括号{}

// app.js
import { add, subtract } from './math.js';

console.log(add(2, 3)); // 输出: 5
console.log(subtract(5, 3)); // 输出: 2
导入默认导出

导入默认导出时,你可以给它起任何名字(当然,最好保持有意义的命名)。

// app.js
import myFunction from './myDefaultModule.js';

myFunction(); // 输出: Hello, world!
导入并重命名

你也可以在导入时给模块或导出项重命名。

// app.js
import { add as sum, subtract as diff } from './math.js';

console.log(sum(2, 3)); // 输出: 5
console.log(diff(5, 3)); // 输出: 2
导入整个模块

虽然这不是推荐的做法(因为它失去了模块化的优势),但你可以将整个模块作为对象导入。

// app.js
import * as math from './math.js';

console.log(math.add(2, 3)); // 输出: 5
console.log(math.subtract(5, 3)); // 输出: 2

3.注意事项

  • 当你使用importexport时,JavaScript文件应该被当作ES6模块来处理。这通常意味着你需要使用支持ES6模块的系统(如现代浏览器、Node.js的ES模块模式或使用Babel等转译器)。
  • 在Node.js中,你需要将文件扩展名从.js改为.mjs,或者在package.json中设置"type": "module",以启用ES模块支持。
  • 导出和导入是静态的,这意味着它们必须在文件的最顶层(在任何执行语句之前)进行。然而,你可以使用动态import()语法来按需加载模块,这在某些情况下非常有用。