JavaScript知识速查
JavaScript ES6标准语法
JavaScript ES6:ES6,即ECMAScript 2015,是JavaScript语言的一个重要更新,为JavaScript带来了许多新的语法特性和编程构造。
1. 变量和常量以及块级区域
变量 (let
):
let
关键字用于声明块级作用域的变量。这意味着 let
变量的作用域限制在它们被声明的块中,以及任何包含的子块。
let x = 1;
if (x === 1) {
let x = 2;
console.log(x); // 2
}
console.log(x); // 1
在上述例子中,两个 x
变量是不同的:外部 x
的值是 1,而内部 x
的值是 2,因为 let
变量具有块级作用域。
常量 (const
):
const
关键字用于声明变量,其值在创建后不能被更改。
const PI = 3.14159;
console.log(PI); // 3.14159
// 下面的语句会报错,因为不能更改常量的值
// PI = 3.14;
2. 模板字符串
模板字符串是一种允许嵌入表达式的字符串字面量。模板字符串使用反引号 (
)(而不是单引号或双引号)来定义,并可以包含占位符,其格式为
${expression}`。
let name = 'John';
let age = 25;
let greeting = `Hello, my name is ${name} and I am ${age} years old.`;
console.log(greeting); // Hello, my name is John and I am 25 years old.
3. 解构赋值
数组解构:
数组解构赋值允许你从数组中提取值,并将它们赋给不同的变量。
let [a, b, ...rest] = [1, 2, 3, 4, 5];
console.log(a); // 1
console.log(b); // 2
console.log(rest); // [3, 4, 5]
对象解构:
对象解构赋值允许你从对象中提取属性,并将它们赋给不同的变量。
let {x, y, ...z} = {x: 10, y: 20, a: 30, b: 40};
console.log(x); // 10
console.log(y); // 20
console.log(z); // { a: 30, b: 40 }
4. 数组和对象的扩展
扩展运算符 (...
):
扩展运算符允许你将数组或对象的内容展开。
let arr1 = [1, 2, 3];
let arr2 = [...arr1, 4, 5]; // [1, 2, 3, 4, 5]
console.log(arr2);
Array.from:
Array.from
方法从一个类似数组或可迭代的对象中创建一个新的数组实例。
let args = Array.from(arguments);
console.log(args);
Object.assign:
Object.assign
方法用于将所有可枚举的自有属性的值从一个或多个源对象复制到目标对象。
let obj1 = {a: 1};
let obj2 = Object.assign({}, obj1, {b: 2}); // {a: 1, b: 2}
console.log(obj2);
5. 类和对象
定义类:
class
关键字用于定义类。
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
greet() {
console.log(`Hello, my name is ${this.name}.`);
}
}
let john = new Person('John', 25);
john.greet(); // Hello, my name is John.
继承:
extends
关键字用于创建一个类作为另一个类的子类。
class Employee extends Person {
constructor(name, age, employeeId) {
super(name, age);
this.employeeId = employeeId;
}
showId() {
console.log(`My employee ID is ${this.employeeId}.`);
}
}
let emp = new Employee('Jane', 30, 'E12345');
emp.greet(); // Hello, my name is Jane.
emp.showId(); // My employee ID is E12345.
6.箭头函数
当然可以。箭头函数是一个相对简洁的函数语法,并且有一个非常有用的特性,即它不绑定自己的this
值。它会捕获其所在上下文的this
值。这是箭头函数与传统函数之间的一个主要区别。
基本用法
let add = (a, b) => a + b;
console.log(add(5, 3)); // 输出: 8
没有参数时
如果箭头函数没有参数,你需要在箭头函数的定义中提供一对空括号:
let greet = () => console.log('Hello!');
greet(); // 输出: Hello!
单个参数时
如果箭头函数只有一个参数,你可以省略括号:
let square = x => x * x;
console.log(square(5)); // 输出: 25
多个参数时
多个参数需要括号来包围:
let multiply = (x, y) => x * y;
console.log(multiply(3, 4)); // 输出: 12
函数体
如果函数体需要多行,你需要在函数体周围添加花括号,并显式返回一个值(如果需要):
let divide = (x, y) => {
if (y !== 0) {
return x / y;
} else {
return 'Cannot divide by zero';
}
};
console.log(divide(10, 2)); // 输出: 5
console.log(divide(10, 0)); // 输出: Cannot divide by zero
this
绑定
箭头函数不绑定自己的 this
值,而是从封闭的函数作用域继承 this
值。这对于事件处理程序和回调函数非常有用。
class Counter {
constructor() {
this.count = 0;
}
increment() {
setInterval(() => {
console.log(this.count++);
}, 1000);
}
}
let c = new Counter();
c.increment(); // 输出: 0 1 2 3 4 ... 每秒递增
在上面的例子中,setInterval
的回调函数是一个箭头函数,所以它从 increment
方法的上下文中继承了 this
值,这使得它可以正确地访问 count
属性。
7.Promise
原理机制:
Promise 是 JavaScript 中处理异步操作的一种对象,它代表了某个异步操作的最终完成(或失败)及其结果值。
- 状态:
Pending(等待)
: 初始状态,既不是成功,也不是失败状态。Fulfilled(成功)
: 意味着异步操作成功完成。Rejected(失败)
: 意味着异步操作失败。
- 转换:
- Promise 对象从 Pending 状态转换到 Fulfilled 状态,或从 Pending 状态转换到 Rejected 状态,并且状态不可逆。
触发机制细节
在JavaScript中,Promise
对象用于处理异步操作,它有三种状态:Pending(进行中)、Fulfilled(已成功)和Rejected(已失败)。then
和catch
方法是Promise
链中的重要环节,它们用于处理Promise
对象的状态变化和值传递。下面是关于then
和catch
的触发机制的总结:
- 状态变化的触发:
- 当一个
Promise
对象的状态从Pending变为Fulfilled时,它的then
方法中注册的成功回调函数会被调用,并接收到Promise
对象传递的值。 - 当一个
Promise
对象的状态从Pending变为Rejected时,它的then
方法中注册的失败回调函数(如果提供了的话)或catch
方法中注册的回调函数会被调用,并接收到Promise
对象传递的错误对象。
- 当一个
- 链式调用的触发:
then
和catch
方法都会返回一个新的Promise
对象,这为链式调用提供了基础。这个新的Promise
对象的状态和值由前一个then
或catch
方法中的回调函数返回值决定。- 如果回调函数返回一个值(或者没有返回任何值),新的
Promise
对象会以该值为结果变为Fulfilled状态;如果回调函数抛出一个错误,新的Promise
对象会以该错误为结果变为Rejected状态。 - 如果回调函数返回一个
Promise
对象,新的Promise
对象的状态和值将与返回的Promise
对象的状态和值相同。
- 错误处理的触发:
- 如果
then
方法的成功回调函数中发生错误(例如抛出一个错误),它会跳过当前Promise
链中剩余的then
方法的成功回调函数,直到遇到下一个catch
方法或then
方法中的失败回调函数,并将错误传递给它。 catch
方法用于捕获Promise
链中的任何错误,一旦catch
方法捕获到错误,它会创建并返回一个新的Fulfilled状态的Promise
对象(除非你在catch
方法中又抛出了一个错误)。
- 如果
- 异步执行的触发:
then
和catch
方法中的回调函数总是在当前执行栈清空后的新的微任务队列中执行,即它们总是异步执行的。
用途:
处理异步操作,例如网络请求、定时器等,并允许将回调函数的链式调用。
语法:
基本的Promise使用:
let myPromise = new Promise((resolve, reject) => {
let condition = true; // 模拟条件
if(condition) {
resolve('Operation successful');
} else {
reject('Operation failed');
}
});
myPromise.then((message) => { // 成功时调用resolve
console.log(message); // 输出: Operation successful
}).catch((message) => { // 失败时调用reject
console.log(message);
});
多重嵌套:
// 创建一个新的Promise对象。此Promise对象立即解决,并返回值1。
new Promise((resolve, reject) => {
resolve('任务1'); // 调用resolve函数,使Promise状态变为fulfilled,并传递值'任务1'给下一个then方法。
})
// 第一个then方法的回调函数接收上一个Promise传递的值'任务1'。
.then(result => {
console.log(result); // 输出: 任务1
return result + '完成,开始任务2'; // 返回新的值,将传递给下一个then方法。
})
// 第二个then方法的回调函数接收上一个then方法传递的值。
.then(result => {
console.log(result); // 输出: 任务1完成,开始任务2
// 返回一个新的Promise对象,这个Promise对象解决,并返回新的值。
return new Promise((resolve, reject) => {
resolve(result + '完成,开始任务3'); // 调用resolve函数,使Promise状态变为fulfilled,并传递新的值给下一个then方法。
});
})
// 第三个then方法的回调函数接收上一个Promise传递的值。
.then(result => {
console.log(result); // 输出: 任务1完成,开始任务2完成,开始任务3
});
解释:
- 最初,我们创建一个Promise对象,并通过调用resolve函数立即将其解决,传递值’任务1’给下一个then方法。
- 第一个then方法的回调函数被调用,并接收到值’任务1’。然后它返回一个新的值,这个值将被传递给下一个then方法。
- 第二个then方法的回调函数被调用,并接收到值’任务1完成,开始任务2’。然后它返回一个新的Promise对象,并通过调用resolve函数立即将其解决,传递新的值给下一个then方法。
- 第三个then方法的回调函数被调用,并接收到值**‘任务1完成,开始任务2完成,开始任务3’**。
在每个步骤中,then方法的回调函数都在上一个Promise对象解决后被调用,并接收到上一个Promise传递的值。
在这个例子中,我们演示了Promise链的工作机制。**每个then
方法都返回一个新的Promise对象(如果你的处理函数返回一个值,它会被Promise.resolve
包装成一个Promise对象),而下一个then
方法的处理函数将在该Promise对象解决后被调用,**并接收到上一个Promise传递的值。通过这种方式,我们可以创建一个Promise链,以顺序执行异步任务,并在每个步骤中传递值。
promise链触发机制剖析
在下面的示例代码中,我们将展示一个更复杂的Promise链,它包含多个then
和catch
方法,以及resolve
和reject
的使用。
// 区域 1
new Promise((resolve, reject) => {
console.log('区域 1: 创建Promise');
resolve('任务1完成'); // resolve调用,将Promise状态变为fulfilled,并传递值给下一个then
})
// 区域 2
.then(result => {
console.log('区域 2:', result);
return result + ',开始任务2';
})
// 区域 3
.then(result => {
console.log('区域 3:', result);
return new Promise((resolve, reject) => { // 返回一个新的Promise
resolve(result + ',任务2完成'); // resolve调用,传递新的值给下一个then
});
})
// 区域 4
.then(result => {
console.log('区域 4:', result);
return new Promise((resolve, reject) => { // 返回一个新的Promise
reject('任务3失败'); // reject调用,将Promise状态变为rejected,并传递错误信息给下一个catch
});
})
// 区域 5
.catch(error => {
console.log('区域 5:', error);
return '处理任务3的错误,开始任务4';
})
// 区域 6
.then(result => {
console.log('区域 6:', result);
});
为了清晰地解释resolve
和reject
在Promise链中的行为,我们可以将上述代码示例的各个区域分解,并为每个区域创建一个流程图来描述resolve
和reject
的行为。通过这个流程图,你可以清楚地看到在不同区域中,resolve
和reject
的调用是如何影响Promise链的执行流程的。
下面是针对每个区域的流程图描述:
在上述流程图中:
- 区域 1:
resolve
会使Promise链进入区域 2。reject
将会跳过区域 2和区域 3,直接进入区域 5来处理错误。
- 区域 2:
resolve
或正常返回将使Promise链进入区域 3。reject
或抛出错误将跳过区域 3和区域 4,直接进入区域 5来处理错误。
- 区域 3:
resolve
会使Promise链进入区域 4。reject
将跳过区域 4,直接进入区域 5来处理错误。
- 区域 4:
resolve
将会进入下一个then
区域(如果存在)。reject
将进入区域 5来处理错误。
- 区域 5(
catch
区域):resolve
或正常返回将使Promise链进入区域 6。reject
或抛出错误将会进入下一个catch
区域(如果存在)来处理错误。
- 区域 6:
- 这是最后一个区域,不涉及
resolve
和reject
的调用。
- 这是最后一个区域,不涉及
8.Async/Await
原理机制:
Async/Await 是基于 Promises 的异步编程的新特性,它提供了一种更直观、更清晰的方式来处理异步操作和 Promises。
-
Async:
- 通过在函数声明前添加
async
关键字,可以确保函数总是返回 promise。 - Async 函数内部的错误会被捕获,然后以 rejected promise 的形式返回。
- 通过在函数声明前添加
-
Await:
await
关键字只能在async
函数内部使用,用于等待 Promise 的解决 (fulfillment) 或拒绝 (rejection)。await
会暂停函数的执行,等待 Promise 的解决,然后继续执行函数并返回结果。
用途:
- 简化异步代码,使其更易于读写和维护。
- 优雅地处理异步错误。
语法及示例:
基本的Async/Await使用:
async function getResolvedPromise(value) {
return await Promise.resolve(value);
}
getResolvedPromise('Hello, async/await!')
.then(result => console.log(result)); // 输出: Hello, async/await!
错误处理:
async function fetchUserData(url) {
try {
let response = await fetch(url);
let data = await response.json();
console.log(data);
} catch (error) {
console.error('Failed to fetch:', error.message);
}
}
fetchUserData('https://api.example.com/user/1');
多重嵌套示例:
async function performTasks() {
try {
let firstResult = await someAsyncFunction();
let secondResult = await anotherAsyncFunction(firstResult);
let finalResult = await yetAnotherAsyncFunction(secondResult);
console.log(finalResult);
} catch (error) {
console.error(error.message);
}
}
performTasks();
在这个多重嵌套示例中,我们等待 someAsyncFunction
的结果,然后将结果传递给 anotherAsyncFunction
,再等待其结果,最后将结果传递给 yetAnotherAsyncFunction
并等待最终结果。这种顺序执行的模式是通过 await
关键字实现的,它确保在继续执行下一个 await
语句之前,先完成前一个 await
语句的 Promise。
这种方式提供了一种清晰、直观的方法来处理异步操作的依赖关系和执行顺序,同时也使错误处理变得更简单。
9.Proxy
原理机制:
Proxy 对象用于创建一个对象的代理,从而实现基本操作(如查找、赋值、枚举、函数调用等)的自定义行为。
使用场景:
Proxy对象通常是用来托管对象并监控他的行为的,Proxy能够帮助我们管理好我们的对象并按照规则来处理我们需要进行的操作。
我们可以举个例子,假设我们需要修改一个对象的值的同时,也修改一个对应标签的值。我们通常会这样写
const obj = {
name : 'caixy',
gender: 'male'
}
const container = document.getElementById("container")
container.textContent = obj.name
obj.name = 'CAIXYPROMISE'
container.textContent = obj.name
从上面的例子里,如果我们需要修改名字的话,通常需要2行代码来进行修改。如果放在网站设计寸土寸金的流量里,我们如果每个地方都像上面这样的方式去写会极大的消耗我们的流量和响应时间,所以我们需要尝试用代理去托管这个行为,并帮我们在每次修改的过程中进行管理整个对象。
用途:
- 数据绑定/观察
- 数据验证
- 日志和性能度量
- 各种自定义行为和元编程操作
语法:
基本的Proxy使用:
// 初始化一个空对象 target
let target = {};
// 定义一个handler对象,它包含一个get方法
// 该方法用于拦截对目标对象属性的读取操作
let handler = {
// get方法接收目标对象target和要读取的属性key作为参数
get: function(target, key) {
// 检查属性key是否存在于目标对象target中
// 如果存在,则返回目标对象target中对应的属性值
// 如果不存在,则返回固定值37
return key in target ? target[key] : 37;
}
};
// 创建一个新的Proxy对象p,它代理目标对象target,并使用handler对象处理操作
let p = new Proxy(target, handler);
// 通过代理对象p设置目标对象target的属性a的值为1
p.a = 1;
// 输出代理对象p的属性a和属性b的值
// 由于属性a存在于目标对象target中,所以输出1
// 由于属性b不存在于目标对象target中,根据handler的get方法,输出37
console.log(p.a, p.b); // 输出: 1, 37
上面的例子中,我们可以使用以下办法
// 定义一个对象obj,它有两个属性:name和gender
const obj = {
name : 'caixy',
gender: 'male'
};
// 获取ID为'container'的DOM元素,并将其赋值给变量item
const item = document.getElementById("container");
// 在控制台打印变量item的值,以检查元素是否被正确获取
console.log(item);
// 设置元素item的文本内容为对象obj的name属性值
item.textContent = obj.name;
// 创建一个新的Proxy实例p1,它对对象obj进行代理
// 该代理定义了两个基本的操作:get和set
const p1 = new Proxy(obj, {
// 当尝试获取obj的某个属性值时,该函数会被调用
get(target, property) {
// 直接返回obj的对应属性值
return obj[property];
},
// 当尝试设置obj的某个属性值时,该函数会被调用
set (target, property, value) {
// 更新obj的对应属性值
obj[property] = value;
// 同时更新元素item的文本内容为obj的name属性值
item.textContent = obj.name;
}
});
// 通过代理对象p1设置obj的name属性值,由于代理的set操作定义,
// 这将同时更新元素item的文本内容
p1.name = 'CAIXYPROMISE'; // 实时更新
10.Module
原理机制:
JavaScript模块是一种将代码分割为可重用的片段的方式,并明确地指定了哪些片段可以被其他模块使用。
用途:
- 代码重用和组织
- 依赖管理
- 避免全局变量污染
语法:
- 导出模块:
// module.js
export function sayHello() {
console.log('Hello!');
}
export const PI = 3.14159;
- 导入模块:
// main.js
import { sayHello, PI } from './module.js';
sayHello(); // 输出: Hello!
console.log(PI); // 输出: 3.14159
多重嵌套暂时不适用于模块系统,因为模块系统的设计是为了保持清晰和简单的依赖关系。过多的嵌套可能会导致代码的组织和维护变得困难。
- 默认导出
在 JavaScript 模块化编程中,可以使用 ES6 (ECMAScript 6 或 ES2015) 提供的模块系统来组织代码。其中,export
和import
是两个主要的关键字。export
用于导出模块,而import
用于导入模块。模块中的默认导出允许你导出模块的主要功能,而不需要指定一个名字。
以下是默认导出的基本用法:
- 默认导出的语法:
// myModule.js
export default function() { // 默认导出匿名函数
console.log('Hello, World!');
}
// 或者默认导出命名函数
export default function myFunction() {
console.log('Hello, World!');
}
// 或者默认导出对象、类或值
export default {
message: 'Hello, World!'
};
- 导入默认导出模块的语法:
// app.js
import myDefaultExport from './myModule.js';
myDefaultExport(); // 如果默认导出是一个函数,则调用它
在上面的示例中,你可以看到默认导出的语法很简单。使用export default
语句来导出一个模块的默认导出,然后使用import
语句来导入它。注意,在导入默认导出时,你可以为它指定任何名字(在上述例子中是myDefaultExport
),而不需要使用大括号。
默认导出和命名导出的区别:
-
默认导出(Default Export):
- 一个模块只能有一个默认导出。
- 导入时可以用任何名字来接收默认导出的值。
- 语法是:
export default ...;
和import ... from '...';
-
命名导出(Named Export):
- 一个模块可以有多个命名导出。
- 导入时需要用大括号
{}
包裹导出的名字,并且名字必须与导出时的名字相匹配。 - 语法是:
export { ... };
和import { ... } from '...';
.name;
}
});
// 通过代理对象p1设置obj的name属性值,由于代理的set操作定义,
// 这将同时更新元素item的文本内容
p1.name = ‘CAIXYPROMISE’; // 实时更新
### 10.Module
#### 原理机制:
JavaScript模块是一种将代码分割为可重用的片段的方式,并明确地指定了哪些片段可以被其他模块使用。
#### 用途:
- 代码重用和组织
- 依赖管理
- 避免全局变量污染
#### 语法:
1. **导出模块**:
```javascript
// module.js
export function sayHello() {
console.log('Hello!');
}
export const PI = 3.14159;
- 导入模块:
// main.js
import { sayHello, PI } from './module.js';
sayHello(); // 输出: Hello!
console.log(PI); // 输出: 3.14159
多重嵌套暂时不适用于模块系统,因为模块系统的设计是为了保持清晰和简单的依赖关系。过多的嵌套可能会导致代码的组织和维护变得困难。
- 默认导出
在 JavaScript 模块化编程中,可以使用 ES6 (ECMAScript 6 或 ES2015) 提供的模块系统来组织代码。其中,export
和import
是两个主要的关键字。export
用于导出模块,而import
用于导入模块。模块中的默认导出允许你导出模块的主要功能,而不需要指定一个名字。
以下是默认导出的基本用法:
- 默认导出的语法:
// myModule.js
export default function() { // 默认导出匿名函数
console.log('Hello, World!');
}
// 或者默认导出命名函数
export default function myFunction() {
console.log('Hello, World!');
}
// 或者默认导出对象、类或值
export default {
message: 'Hello, World!'
};
- 导入默认导出模块的语法:
// app.js
import myDefaultExport from './myModule.js';
myDefaultExport(); // 如果默认导出是一个函数,则调用它
在上面的示例中,你可以看到默认导出的语法很简单。使用export default
语句来导出一个模块的默认导出,然后使用import
语句来导入它。注意,在导入默认导出时,你可以为它指定任何名字(在上述例子中是myDefaultExport
),而不需要使用大括号。
默认导出和命名导出的区别:
-
默认导出(Default Export):
- 一个模块只能有一个默认导出。
- 导入时可以用任何名字来接收默认导出的值。
- 语法是:
export default ...;
和import ... from '...';
-
命名导出(Named Export):
- 一个模块可以有多个命名导出。
- 导入时需要用大括号
{}
包裹导出的名字,并且名字必须与导出时的名字相匹配。 - 语法是:
export { ... };
和import { ... } from '...';
默认导出通常用于导出模块的主要功能,而命名导出用于导出模块的次要功能或工具函数。通过合理使用默认导出和命名导出,可以使代码组织得更清晰,更易于维护。