一. 块级作用域(let和const)
ES6引入了let
和const
两个新的关键字用于声明变量,解决了var
关键字带来的作用域问题。let
声明的变量只在当前块级作用域内有效,而const
声明的变量是常量,一旦被赋值后就不能再改变。
1.let
let
是 ES6 中引入的用于声明块级作用域变量的关键字。与 var
不同,let
声明的变量只在其声明的块(或子块)作用域内有效,且不会发生变量提升(hoisting)现象(即变量在声明之前不可用)。
特点:
- 块级作用域:
let
变量只在其声明的块或子块中可用。 - 不提升(Temporal Dead Zone):在声明之前引用
let
变量会抛出ReferenceError
。 - 可重新赋值:
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 引入的,用于声明一个只读的常量。一旦一个常量被赋值后,它的值就不能再被改变(但如果是对象或数组,则内部状态可以被修改,只是指向的引用不能变)。
特点:
- 块级作用域:与
let
相同,const
声明的常量也只在其声明的块或子块中可用。 - 不提升(Temporal Dead Zone):在声明之前引用
const
变量同样会抛出ReferenceError
。 - 不可重新赋值:一旦
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 的区别
- 作用域:
var
声明的变量是函数作用域或全局作用域,而let
声明的变量是块级作用域。 - 变量提升(Hoisting):
var
声明的变量会提升到其作用域的顶部,而let
声明的变量不会提升,在声明前引用会抛出ReferenceError
。 - 重复声明:在同一个作用域内,
var
可以重复声明同一个变量,而let
不能(会抛出SyntaxError
)。 - 全局对象属性:在浏览器环境下,
var
声明的全局变量会成为window
对象的属性,而let
和const
声明的全局变量不会。
示例代码对比:
// 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中引入的一种更简洁的函数写法,它使用 =>
语法。箭头函数不绑定自己的 this
,arguments
,super
,或 new.target
。这些函数表达式更适用于非方法函数,并且它们不能用作构造函数。
1.特点
- 更简洁的语法:特别是对于只有一个参数的函数,或者函数体只有一行代码的情况。
- 不绑定自己的
this
:箭头函数不创建自己的this
上下文,而是继承自父执行上下文中的this
值(即所谓的词法作用域或静态作用域)。这是箭头函数最显著的特点,也是它最常被使用的原因之一。 - 不支持
arguments
对象:因为箭头函数没有自己的this
,所以它也没有自己的arguments
对象。在箭头函数内访问arguments
实际上会访问到外层函数的arguments
对象。 - 不支持
new
操作符:由于箭头函数没有自己的this
,且没有prototype
属性,因此它们不能被用作构造函数,即不能使用new
关键字来调用。 - 没有
super
:箭头函数不能通过super
关键字调用父类的方法。 - 不支持
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)函数作为参数,这个执行器函数本身又接受两个函数作为参数:resolve
和 reject
。
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.注意事项
- 当你使用
import
或export
时,JavaScript文件应该被当作ES6模块来处理。这通常意味着你需要使用支持ES6模块的系统(如现代浏览器、Node.js的ES模块模式或使用Babel等转译器)。 - 在Node.js中,你需要将文件扩展名从
.js
改为.mjs
,或者在package.json
中设置"type": "module"
,以启用ES模块支持。 - 导出和导入是静态的,这意味着它们必须在文件的最顶层(在任何执行语句之前)进行。然而,你可以使用动态
import()
语法来按需加载模块,这在某些情况下非常有用。