解锁ES6+:前端开发的魔法升级

引言

在前端开发的快速发展进程中,JavaScript 作为核心语言,不断推陈出新,其中 ES6 + 特性更是为开发者带来了前所未有的便利和强大功能。从优化变量声明与作用域管理,到革新函数定义与异步编程,再到增强面向对象编程能力和代码模块化组织,ES6 + 特性全方位地提升了前端开发的效率、可读性和可维护性。无论是构建复杂的单页应用,还是打造高性能的 Web 应用程序,ES6 + 特性都已成为现代前端开发不可或缺的基石。

ES6 + 基础特性速览

let 与 const:变量声明的新姿势

在 ES6 之前,JavaScript 主要使用var关键字来声明变量,然而var存在函数作用域和变量提升等问题,容易导致一些难以排查的错误。例如:

js 复制代码
// 使用var声明变量

function varTest() {
	var a = 1;
	if (true) {
		var a = 2;
		console.log(a); // 输出2,因为var声明的变量在函数内作用域是相同的
	}
	console.log(a); // 输出2,同样是因为var的函数作用域特性
}

从 ES6 开始,let和const的出现很好地解决了这些问题。let声明的变量具有块级作用域,只在其所在的代码块内有效,不存在变量提升,并且不能在同一作用域内重复声明。例如:

js 复制代码
// 使用let声明变量
function letTest() {
	let a = 1;
	if (true) {
		let a = 2;
		console.log(a); // 输出2,因为let声明的变量具有块级作用域
	}
	console.log(a); // 输出1,这里访问的是外层作用域的a
}

const用于声明常量,一旦声明,其值就不能被改变,同样具有块级作用域,且必须在声明时初始化。比如:

js 复制代码
// 使用const声明常量
const PI = 3.14159;
// PI = 3.14; // 这会报错,因为常量不能被重新赋值

let和const的块级作用域特性还能避免内层变量覆盖外层变量的问题,以及循环变量泄露为全局变量的情况,让代码的逻辑更加清晰,可维护性更强。

箭头函数:简洁至上的函数表达

箭头函数是 ES6 引入的一种更简洁的函数表达方式,它摒弃了传统函数的复杂语法,让代码更加简洁明了。例如,传统函数定义一个简单的加法函数如下:

js 复制代码
// 传统函数定义加法函数
function add(x, y) {
	return x + y;
}

使用箭头函数可以写成:

js 复制代码
// 箭头函数定义加法函数
const add = (x, y) => x + y;

箭头函数不仅语法简洁,在this绑定上也有独特的规则。它没有自己的this,而是捕获其所在上下文的this值,这使得在处理回调函数时,this的指向更加可预测,避免了传统函数中常见的this指向混乱问题。例如:

js 复制代码
const obj = {
    data: [1, 2, 3],
    sum: function() {
        return this.data.reduce((acc, num) => acc + num, 0);
    }
};
console.log(obj.sum()); // 输出6,这里箭头函数中的this指向obj

然而,箭头函数也有其局限性,它不能作为构造函数使用,没有arguments对象,不适合需要动态改变this指向的场景。所以在实际使用中,要根据具体需求合理选择。

模板字符串:告别繁琐的字符串拼接

在 ES6 之前,拼接字符串是一件繁琐的事情,需要使用大量的+运算符,代码可读性较差。例如:

js 复制代码
// ES6之前的字符串拼接
const name = "Alice";
const age = 25;
const message = "My name is " + name + ". I'm " + age + " years old.";
console.log(message);

ES6 引入的模板字符串使用反引号(`)来定义,并且可以在其中嵌入变量和表达式,极大地简化了字符串拼接操作。例如:

js 复制代码
// 使用模板字符串
const name = "Alice";
const age = 25;
const message = `My name is ${name}. I'm ${age} years old.`;
console.log(message);

模板字符串还能轻松处理多行字符串,无需使用转义字符。例如:

js 复制代码
// 多行字符串
const html = `
    <div>
        <p>这是一段多行的HTML代码</p>
        <p>使用模板字符串处理非常方便</p>
    </div>
`;
console.log(html);

此外,模板字符串还支持标签函数,通过标签函数可以对模板字符串进行更灵活的处理,比如实现字符串的格式化、安全过滤等功能。

数据结构与操作的革新

解构赋值:轻松提取数据

解构赋值是 ES6 中一种强大的语法,它允许我们从数组和对象中提取数据,并将其赋值给变量,大大简化了数据提取的过程。

在数组解构中,我们可以按照位置匹配的方式提取数组元素。例如:

js 复制代码
// 数组解构
const numbers = [1, 2, 3];
const [a, b, c] = numbers;
console.log(a); // 输出1
console.log(b); // 输出2
console.log(c); // 输出3

还可以使用默认值,当解构的位置没有对应元素时,会使用默认值。比如:

js 复制代码
const [x, y = 10] = [1];
console.log(x); // 输出1
console.log(y); // 输出10,因为数组中没有第二个元素,所以使用默认值

对于对象解构,是通过属性名进行匹配赋值的。例如:

js 复制代码
// 对象解构
const user = { name: "Bob", age: 30, email: "bob@example.com" };
const { name, age, email } = user;
console.log(name); // 输出Bob
console.log(age); // 输出30
console.log(email); // 输出bob@example.com

也能进行嵌套对象的解构,以及更改属性名。例如:

js 复制代码
const user = {
    name: "Alice",
    address: {
        city: "New York",
        zip: "10001"
    }
};
// 嵌套对象解构
const { name, address: { city } } = user;
console.log(name); // 输出Alice
console.log(city); // 输出New York
// 更改属性名
const { name: userName, age: userAge } = user;
console.log(userName); // 输出Alice
console.log(userAge); // 这里user中没有age属性,所以会报错

解构赋值在函数参数中也非常实用,可以使函数参数的传递和处理更加清晰。例如:

js 复制代码
function printUser({ name, age }) {
    console.log(`Name: ${name}, Age: ${age}`);
}
const user = { name: "Charlie", age: 25 };
printUser(user); // 输出Name: Charlie, Age: 25

扩展运算符与剩余运算符:灵活的数据处理

扩展运算符(...)和剩余运算符(...)是 ES6 中用于灵活处理数据的重要工具,它们在数组和对象操作中发挥着关键作用。

扩展运算符可以将数组或对象展开成一系列值,在数组操作中,常用于合并数组、复制数组等。例如:

js 复制代码
// 合并数组
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const combinedArr = [...arr1,...arr2];
console.log(combinedArr); // 输出[1, 2, 3, 4, 5, 6]
// 复制数组
const originalArr = [1, 2, 3];
const copiedArr = [...originalArr];
console.log(copiedArr); // 输出[1, 2, 3]

在对象操作中,扩展运算符可以用于合并对象、克隆对象等。例如:

js 复制代码
// 合并对象
const obj1 = { name: "Alice", age: 25 };
const obj2 = { email: "alice@example.com" };
const mergedObj = {...obj1,...obj2 };
console.log(mergedObj); // 输出{name: "Alice", age: 25, email: "alice@example.com"}
// 克隆对象
const originalObj = { name: "Bob", age: 30 };
const clonedObj = {...originalObj };
console.log(clonedObj); // 输出{name: "Bob", age: 30}

剩余运算符则用于收集多余的参数,将其转换为一个数组,它主要用于函数参数中。例如:

js 复制代码
function sum(...numbers) {
    return numbers.reduce((acc, num) => acc + num, 0);
}
console.log(sum(1, 2, 3, 4, 5)); // 输出15

在这个例子中,...numbers 收集了所有传入的参数,并将它们存储在一个数组中,方便后续处理。

Set 和 Map:新的数据结构

Set 和 Map 是 ES6 引入的两种新的数据结构,它们为 JavaScript 带来了更强大的数据处理能力。

Set 是一种集合数据结构,它的成员是唯一的,没有重复的值,常用于数组去重等场景。例如:

js 复制代码
// 创建Set
const mySet = new Set();
mySet.add(1);
mySet.add(2);
mySet.add(2); // 重复添加,不会生效
console.log(mySet.size); // 输出2
// 数组去重
const numbers = [1, 2, 2, 3, 3, 3];
const uniqueNumbers = [...new Set(numbers)];
console.log(uniqueNumbers); // 输出[1, 2, 3]

Set 还提供了一些方法,如has用于检查是否包含某个元素,delete用于删除元素,clear用于清空集合等。

Map 是一种键值对的集合,与普通对象不同,它的键可以是任意类型的数据,并且保留了键值对的插入顺序。例如:

js 复制代码
// 创建Map
const myMap = new Map();
myMap.set("name", "Alice");
myMap.set(1, "One");
const objKey = {};
myMap.set(objKey, "Value for object key");
console.log(myMap.get("name")); // 输出Alice
console.log(myMap.get(1)); // 输出One
console.log(myMap.get(objKey)); // 输出Value for object key

Map 也有一系列方法,如has检查是否包含某个键,delete删除键值对,clear清空 Map,size获取键值对数量等,还可以通过keys、values、entries方法分别获取键、值、键值对的迭代器,方便进行遍历操作。

异步编程的进化

Promise:告别回调地狱

在 JavaScript 的异步编程发展历程中,Promise 的出现堪称一次重大变革。在 Promise 之前,异步操作主要通过回调函数来实现,这在简单场景下尚可应付,但当异步操作变得复杂,尤其是存在多个嵌套的异步调用时,就会陷入令人头疼的 "回调地狱"。例如,在进行一个需要依次获取用户信息、用户订单,再根据订单获取商品详情的操作时,使用回调函数的代码可能如下:

js 复制代码
// 模拟异步获取用户信息
function getUserInfo(callback) {
    setTimeout(() => {
        const userInfo = { id: 1, name: "Alice" };
        callback(null, userInfo);
    }, 1000);
}
// 模拟异步获取用户订单
function getUserOrders(userInfo, callback) {
    setTimeout(() => {
        const orders = [{ id: 101, product: "Book" }, { id: 102, product: "Pen" }];
        callback(null, orders);
    }, 1000);
}
// 模拟异步获取商品详情
function getProductDetails(order, callback) {
    setTimeout(() => {
        const productDetails = { id: order.id, details: "This is a " + order.product };
        callback(null, productDetails);
    }, 1000);
}
getUserInfo((err, userInfo) => {
    if (err) return console.error(err);
    getUserOrders(userInfo, (err, orders) => {
        if (err) return console.error(err);
        orders.forEach(order => {
            getProductDetails(order, (err, productDetails) => {
                if (err) return console.error(err);
                console.log(productDetails);
            });
        });
    });
});

这段代码不仅嵌套层次深,而且阅读和维护难度极大,一旦中间某个异步操作出现问题,调试起来非常困难。

Promise 的出现有效地解决了这个问题。Promise 是一个代表异步操作最终完成或失败的对象,它有三种状态:pending(进行中)、fulfilled(已完成)和rejected(已拒绝)。使用 Promise,上述代码可以改写为:

js 复制代码
// 模拟异步获取用户信息
function getUserInfo() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            const userInfo = { id: 1, name: "Alice" };
            resolve(userInfo);
        }, 1000);
    });
}
// 模拟异步获取用户订单
function getUserOrders(userInfo) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            const orders = [{ id: 101, product: "Book" }, { id: 102, product: "Pen" }];
            resolve(orders);
        }, 1000);
    });
}
// 模拟异步获取商品详情
function getProductDetails(order) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            const productDetails = { id: order.id, details: "This is a " + order.product };
            resolve(productDetails);
        }, 1000);
    });
}
getUserInfo()
  .then(userInfo => getUserOrders(userInfo))
  .then(orders => {
        const promises = orders.map(order => getProductDetails(order));
        return Promise.all(promises);
    })
  .then(productDetailsList => {
        productDetailsList.forEach(productDetails => {
            console.log(productDetails);
        });
    })
  .catch(err => console.error(err));

在这段代码中,通过then方法来处理 Promise 成功后的结果,通过catch方法来捕获 Promise 链中任何一个环节出现的错误,使得异步操作的流程更加清晰,代码的可读性和可维护性大大提高。同时,Promise 还支持链式调用,使得多个异步操作可以按照顺序依次执行,避免了回调函数的层层嵌套。

async/await:异步操作的语法糖

async/await是 ES7 引入的异步编程语法,它基于 Promise,是对 Promise 的进一步优化,被称为异步操作的 "语法糖"。async用于声明一个异步函数,该函数返回一个 Promise 对象;await只能在async函数内部使用,用于等待一个 Promise 对象的解决,它会暂停async函数的执行,直到 Promise 对象被解决(resolved)或拒绝(rejected),然后恢复async函数的执行,并返回 Promise 对象的值。

使用async/await,上述获取用户信息、订单和商品详情的代码可以进一步简化为:

js 复制代码
// 模拟异步获取用户信息
function getUserInfo() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            const userInfo = { id: 1, name: "Alice" };
            resolve(userInfo);
        }, 1000);
    });
}
// 模拟异步获取用户订单
function getUserOrders(userInfo) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            const orders = [{ id: 101, product: "Book" }, { id: 102, product: "Pen" }];
            resolve(orders);
        }, 1000);
    });
}
// 模拟异步获取商品详情
function getProductDetails(order) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            const productDetails = { id: order.id, details: "This is a " + order.product };
            resolve(productDetails);
        }, 1000);
    });
}
async function main() {
    try {
        const userInfo = await getUserInfo();
        const orders = await getUserOrders(userInfo);
        const productDetailsList = await Promise.all(orders.map(order => getProductDetails(order)));
        productDetailsList.forEach(productDetails => {
            console.log(productDetails);
        });
    } catch (err) {
        console.error(err);
    }
}
main();

在这段代码中,async/await让异步代码看起来更像同步代码,极大地提高了代码的可读性和可维护性。错误处理也变得更加直观,通过try/catch块可以捕获await操作中可能出现的错误,而不需要像 Promise 链式调用那样在每个then方法后面添加catch方法。同时,async/await还可以与Promise.all、Promise.race等方法结合使用,更加灵活地处理复杂的异步操作场景。

模块化与面向对象编程的加强

模块化:代码组织的新方式

在 JavaScript 的发展历程中,模块化一直是一个重要的议题。在 ES6 之前,JavaScript 缺乏原生的模块化支持,开发者们只能依赖于各种第三方库和工具来实现模块化,如 CommonJS(主要用于 Node.js 环境)和 AMD(主要用于浏览器环境)。这些方案虽然在一定程度上解决了模块化的问题,但存在一些局限性,例如在浏览器中使用 CommonJS 需要额外的工具进行转换,AMD 的语法相对复杂等。

ES6 引入的模块系统为 JavaScript 带来了原生的模块化支持,它使用export和import关键字来实现模块的导出和导入,使得代码的组织和管理更加方便和直观。

在一个模块中,可以使用export关键字将需要暴露给其他模块使用的变量、函数、类等导出。例如,创建一个mathUtils.js模块,其中定义了一些数学运算函数并导出:

js 复制代码
// mathUtils.js
// 导出一个加法函数
export function add(a, b) {
    return a + b;
}
// 导出一个乘法函数
export function multiply(a, b) {
    return a * b;
}

在另一个模块中,可以使用import关键字导入mathUtils.js模块中导出的函数:

js 复制代码
// main.js
import { add, multiply } from './mathUtils.js';
console.log(add(3, 5)); // 输出8
console.log(multiply(4, 6)); // 输出24

这里使用了具名导入,通过大括号指定要导入的具体内容。如果模块中只有一个主要的导出内容,还可以使用默认导出和默认导入。例如,创建一个message.js模块,使用默认导出一个函数:

js 复制代码
// message.js
// 默认导出一个函数
export default function greet(name) {
    return `Hello, ${name}!`;
}

在导入时,不需要使用大括号,直接指定导入的名称即可:

js 复制代码
// main.js
import greet from './message.js';
console.log(greet('Alice')); // 输出Hello, Alice!

此外,还可以使用import * as的方式将模块中的所有导出内容导入到一个对象中,方便统一管理和使用:

js 复制代码
// main.js
import * as math from './mathUtils.js';
console.log(math.add(2, 3)); // 输出5
console.log(math.multiply(5, 7)); // 输出35

ES6 模块系统还支持动态导入,通过import()函数实现按需加载模块,这在一些场景下可以提高应用的性能,例如在路由懒加载中,只有当用户访问到特定页面时才加载对应的模块代码。

类:面向对象编程的升级

在 ES6 之前,JavaScript 主要通过构造函数和原型链来实现面向对象编程,这种方式虽然灵活,但语法相对复杂,容易让开发者感到困惑。例如,使用构造函数创建一个简单的Person对象:

js 复制代码
// 使用构造函数创建Person对象
function Person(name, age) {
    this.name = name;
    this.age = age;
}
// 为Person对象的原型添加方法
Person.prototype.sayHello = function() {
    console.log(`Hello, my name is ${this.name} and I'm ${this.age} years old.`);
};
const person = new Person('Bob', 30);
person.sayHello(); // 输出Hello, my name is Bob and I'm 30 years old.

ES6 引入的class关键字为 JavaScript 的面向对象编程带来了更简洁、直观的语法,它是基于原型链的面向对象编程的语法糖,使得代码更易于理解和维护。使用class定义一个Person类:

js 复制代码
// 使用class定义Person类
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.`);
    }
}
const person = new Person('Alice', 25);
person.sayHello(); // 输出Hello, my name is Alice and I'm 25 years old.

在这个例子中,class定义了一个Person类,constructor是类的构造函数,用于初始化对象的属性。sayHello是类的实例方法,直接定义在类的内部,不需要像传统方式那样通过原型来添加。

类的继承在 ES6 中也变得更加简单和直观,使用extends关键字实现类的继承。例如,创建一个Student类继承自Person类:

js 复制代码
// Student类继承自Person类
class Student extends Person {
    constructor(name, age, grade) {
        // 调用父类的构造函数
        super(name, age);
        this.grade = grade;
    }
    // 子类特有的方法
    study() {
        console.log(`${this.name} is studying in grade ${this.grade}.`);
    }
}
const student = new Student('Charlie', 18, 12);
student.sayHello(); // 输出Hello, my name is Charlie and I'm 18 years old.
student.study(); // 输出Charlie is studying in grade 12.

在这个例子中,Student类通过extends关键字继承了Person类的属性和方法,super关键字用于调用父类的构造函数,确保父类的属性被正确初始化。study方法是Student类特有的方法,展示了子类对父类的扩展。

此外,ES6 类还支持静态方法,通过在方法前加上static关键字来定义,静态方法可以直接通过类名调用,而不需要创建类的实例。例如:

js 复制代码
class MathUtils {
    static add(a, b) {
        return a + b;
    }
}
console.log(MathUtils.add(3, 5)); // 输出8

通过 ES6 的class语法,JavaScript 的面向对象编程变得更加简洁、清晰,提高了代码的可读性和可维护性,也更符合现代面向对象编程的习惯。

实践与应用案例

在实际的前端项目开发中,ES6 + 特性发挥着至关重要的作用,尤其是在 React 和 Vue 这两个主流的前端框架开发中。

React 开发中的 ES6 + 应用

在 React 开发中,ES6 + 的类语法让组件的定义更加简洁和直观。例如,使用 ES6 类来定义一个 React 组件:

js 复制代码
import React, { Component } from'react';
class MyComponent extends Component {
    constructor(props) {
        super(props);
        this.state = {
            count: 0
        };
    }
    increment = () => {
        this.setState(prevState => ({
            count: prevState.count + 1
        }));
    }
    render() {
        return (
            <div>
                <p>Count: {this.state.count}</p>
                <button onClick={this.increment}>Increment</button>
            </div>
        );
    }
}
export default MyComponent;

在这个例子中,使用class定义组件,constructor方法用于初始化组件的状态,increment方法使用箭头函数定义,避免了手动绑定this的问题。同时,this.setState使用了函数式更新的方式,确保在状态依赖于前一个状态时能正确更新。

解构赋值在 React 中也经常用于提取props和state中的数据。例如:

js 复制代码
import React, { Component } from'react';
class UserInfo extends Component {
    constructor(props) {
        super(props);
        this.state = {
            user: {
                name: "Alice",
                age: 25,
                email: "alice@example.com"
            }
        };
    }
    render() {
        const { name, age } = this.state.user;
        return (
            <div>
                <p>Name: {name}</p>
                <p>Age: {age}</p>
            </div>
        );
    }
}
export default UserInfo;

这里通过解构赋值从this.state.user中提取出name和age,使代码更加简洁,可读性更强。

Vue 开发中的 ES6 + 应用

在 Vue 开发中,ES6 的模块化特性使得代码的组织和管理更加方便。例如,在一个 Vue 项目中,我们可以将不同的功能模块分别封装在不同的文件中,然后通过import和export进行导入和导出。

假设我们有一个utils.js文件,定义了一些工具函数:

js 复制代码
// utils.js
export function formatDate(date) {
    return date.toISOString().split('T')[0];
}
export function capitalize(str) {
    return str.charAt(0).toUpperCase() + str.slice(1);
}

在 Vue 组件中使用这些函数:

js 复制代码
<template>
    <div>
        <p>Formatted Date: {{ formattedDate }}</p>
        <p>Capitalized Name: {{ capitalizedName }}</p>
    </div>
</template>
<script>
import { formatDate, capitalize } from './utils.js';
export default {
    data() {
        return {
            originalDate: new Date(),
            originalName: "alice"
        };
    },
    computed: {
        formattedDate() {
            return formatDate(this.originalDate);
        },
        capitalizedName() {
            return capitalize(this.originalName);
        }
    }
};
</script>

在这个例子中,通过import导入utils.js中的函数,在组件的computed属性中使用这些函数对数据进行处理,使代码结构更加清晰,逻辑更加明确。

在 Vue 的methods中,也经常使用箭头函数来简化代码。例如:

js 复制代码
<template>
    <div>
        <button @click="increment">Increment</button>
        <p>Count: {{ count }}</p>
    </div>
</template>
<script>
export default {
    data() {
        return {
            count: 0
        };
    },
    methods: {
        increment: () => {
            // 这里需要注意,箭头函数中的this不是指向Vue实例,
            // 通常用于一些不依赖于Vue实例的回调函数场景
            console.log('Increment button clicked');
        }
    }
};
</script>

虽然在这个例子中,箭头函数在methods中的使用有一定局限性(因为this指向问题),但在一些特定场景,如处理外部库的回调函数时,箭头函数可以提供简洁的语法和明确的this指向控制。

总结与展望

ES6 + 特性为 JavaScript 带来了全方位的升级,从基础语法到异步编程,从数据结构到模块化和面向对象编程,每一个方面都得到了显著的改进和增强。这些特性不仅提升了代码的可读性、可维护性和开发效率,还使得 JavaScript 能够更好地应对复杂的前端开发需求。在 React 和 Vue 等主流前端框架开发中,ES6 + 特性更是发挥了关键作用,成为现代前端开发不可或缺的组成部分。

随着技术的不断发展,JavaScript 还将持续演进,未来有望在性能优化、与其他技术的融合(如 WebAssembly、人工智能等)以及语言特性的进一步完善等方面取得更大的突破。作为前端开发者,我们应紧跟技术发展的步伐,不断学习和掌握新的特性和技能,充分利用 ES6 + 以及未来 JavaScript 的新特性,打造出更加高效、优质的前端应用。

相关推荐
二川bro13 分钟前
前端项目Axios封装Vue3详细教程(附源码)
前端
古柳_Deserts_X14 分钟前
看看 ManusAI 相关网站长啥样。通过「新词新站」思路挖到720K月访问、140K月访问的两个新站
前端·程序员·创业
Moment23 分钟前
前端白屏检测SDK:从方案设计到原理实现的全方位讲解 ☺️☺️☺️
前端·javascript·面试
阿波次嘚27 分钟前
关于在electron(Nodejs)中使用 Napi 的简单记录
前端·javascript·electron
接着奏乐接着舞。29 分钟前
Electron + Vue 项目如何实现软件在线更新
javascript·vue.js·electron
Ting丶丶33 分钟前
Electron入门笔记
javascript·笔记·electron
咖啡虫34 分钟前
解决 React 中的 Hydration Failed 错误
前端·javascript·react.js
贩卖纯净水.35 分钟前
《React 属性与状态江湖:从验证到表单受控的实战探险》
开发语言·前端·javascript·react.js
阿丽塔~35 分钟前
面试题之react useMemo和uesCallback
前端·react.js·前端框架
束尘36 分钟前
React面试(二)
javascript·react.js·面试