返回首页

02-解构赋值与扩展运算符

分类:02-ES6+现代特性
发布于:
阅读时间:85 分钟

解构赋值与扩展运算符

📋 学习目标

  • 掌握数组和对象的解构赋值语法
  • 学会使用扩展运算符进行数组和对象操作
  • 理解函数参数解构和返回值解构
  • 掌握高级解构技巧和实际应用场景
  • 了解解构赋值的性能影响和最佳实践

🎯 数组解构赋值

1. 基础数组解构

// 基础解构
const fruits = ['apple', 'banana', 'orange', 'grape'];

const [first, second, third] = fruits;
console.log(first, second, third); // 'apple', 'banana', 'orange'

// 跳过某些元素
const [, , thirdFruit] = fruits;
console.log(thirdFruit); // 'orange'

// 使用剩余元素
const [head, ...tail] = fruits;
console.log(head); // 'apple'
console.log(tail); // ['banana', 'orange', 'grape']

// 默认值
const colors = ['red'];
const [primary = 'black', secondary = 'white'] = colors;
console.log(primary, secondary); // 'red', 'white'

// 解构不存在的位置
const [a, b, c, d, e = 'default'] = ['x', 'y', 'z'];
console.log(a, b, c, d, e); // 'x', 'y', 'z', undefined, 'default'

// 交换变量
let x = 1, y = 2;
[x, y] = [y, x];
console.log(x, y); // 2, 1

2. 嵌套数组解构

// 嵌套数组解构
const matrix = [
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9]
];

const [[a, b, c], [d, e, f], [g, h, i]] = matrix;
console.log(a, b, c, d, e, f, g, h, i); // 1, 2, 3, 4, 5, 6, 7, 8, 9

// 部分解构
const [, [, secondRow]] = matrix;
const [firstInSecondRow] = secondRow;
console.log(firstInSecondRow); // 4

// 使用默认值处理不规则数组
const irregularArray = [1, [2, 3], 4];
const [num1, [num2, num3], num4, num5 = 5] = irregularArray;
console.log(num1, num2, num3, num4, num5); // 1, 2, 3, 4, 5

3. 函数返回值解构

// 函数返回数组
function getUser() {
    return ['Alice', 25, 'Developer'];
}

const [name, age, profession] = getUser();
console.log(name, age, profession); // 'Alice', 25, 'Developer'

// 函数返回多个值
function getMinMax(numbers) {
    return [Math.min(...numbers), Math.max(...numbers)];
}

const [min, max] = getMinMax([5, 2, 8, 1, 9]);
console.log(min, max); // 1, 9

// 解构函数参数
function processUser([name, age, city]) {
    console.log(`${name} is ${age} years old and lives in ${city}`);
}

processUser(['Bob', 30, 'New York']); // Bob is 30 years old and lives in New York

🏗️ 对象解构赋值

1. 基础对象解构

// 基础对象解构
const person = {
    name: 'Alice',
    age: 25,
    city: 'New York'
};

const { name, age, city } = person;
console.log(name, age, city); // 'Alice', 25, 'New York'

// 重命名变量
const { name: userName, age: userAge } = person;
console.log(userName, userAge); // 'Alice', 25

// 默认值
const user = {
    name: 'Bob',
    age: 30
};

const { name: name2, age: age2, city: city2 = 'Unknown' } = user;
console.log(name2, age2, city2); // 'Bob', 30, 'Unknown'

// 解构嵌套对象
const user2 = {
    personal: {
        name: 'Charlie',
        age: 35
    },
    contact: {
        email: 'charlie@example.com',
        phone: '123-456-7890'
    }
};

const {
    personal: { name: name3 },
    contact: { email }
} = user2;
console.log(name3, email); // 'Charlie', 'charlie@example.com'

2. 对象属性解构

// 解构对象属性
const settings = {
    theme: 'dark',
    language: 'en',
    notifications: true,
    autoSave: false
};

// 只解构需要的属性
const { theme, language } = settings;
console.log(theme, language); // 'dark', 'en'

// 解构到新变量名
const { theme: currentTheme, language: currentLang } = settings;
console.log(currentTheme, currentLang); // 'dark', 'en'

// 解构并设置默认值
const { autoSave = true, fontSize = 14 } = settings;
console.log(autoSave, fontSize); // false, 14

// 解构剩余属性
const { theme, ...otherSettings } = settings;
console.log(theme); // 'dark'
console.log(otherSettings); // { language: 'en', notifications: true, autoSave: false }

3. 动态属性名解构

// 动态属性名解构
const person3 = {
    firstName: 'David',
    lastName: 'Johnson',
    age: 28
};

const propName = 'firstName';
const { [propName]: firstName } = person3;
console.log(firstName); // 'David'

// 计算属性名
const user3 = {
    'user-name': 'Eve Wilson',
    'user-age': 32
};

const { ['user-name']: userName, ['user-age']: userAge } = user3;
console.log(userName, userAge); // 'Eve Wilson', 32

// 从Map解构
const userMap = new Map([
    ['name', 'Frank'],
    ['age', 40]
]);

const { 0: nameKey, 1: ageKey } = [...userMap.entries()];
console.log(nameKey, ageKey); // 'name', 'age'

🔧 函数参数解构

1. 对象参数解构

// 函数参数解构
function displayUser({ name, age, city = 'Unknown' }) {
    console.log(`${name} is ${age} years old and lives in ${city}`);
}

displayUser({ name: 'Alice', age: 25, city: 'Boston' });
// Alice is 25 years old and lives in Boston

displayUser({ name: 'Bob', age: 30 });
// Bob is 30 years old and lives in Unknown

// 可选参数解构
function processConfig({
    timeout = 1000,
    retries = 3,
    endpoint = '/api'
} = {}) {
    console.log(`Config: timeout=${timeout}, retries=${retries}, endpoint=${endpoint}`);
}

processConfig(); // Config: timeout=1000, retries=3, endpoint=/api
processConfig({ timeout: 2000 }); // Config: timeout=2000, retries=3, endpoint=/api

// 嵌套对象参数解构
function printAddress({
    person: { name },
    location: { street, city, country }
}) {
    console.log(`${name} lives at ${street}, ${city}, ${country}`);
}

printAddress({
    person: { name: 'Grace' },
    location: {
        street: '123 Main St',
        city: 'Seattle',
        country: 'USA'
    }
});
// Grace lives at 123 Main St, Seattle, USA

2. 数组参数解构

// 数组参数解构
function calculateSum([a, b, c]) {
    return a + b + c;
}

console.log(calculateSum([1, 2, 3])); // 6

// 使用默认值
function getCoordinates([x = 0, y = 0] = []) {
    return { x, y };
}

console.log(getCoordinates([5, 10])); // { x: 5, y: 10 }
console.log(getCoordinates([])); // { x: 0, y: 0 }
console.log(getCoordinates()); // { x: 0, y: 0 }

// 混合解构
function processData([id, { name, age }, options = {}]) {
    console.log(`Processing ${name} (ID: ${id}, Age: ${age})`);
    console.log('Options:', options);
}

processData([
    1,
    { name: 'Henry', age: 45 },
    { priority: 'high' }
]);
// Processing Henry (ID: 1, Age: 45)
// Options: { priority: 'high' }

3. REST参数与解构结合

// 解构第一个参数,其余作为数组
function logFirstAndRest([first, ...rest]) {
    console.log('First:', first);
    console.log('Rest:', rest);
}

logFirstAndRest([1, 2, 3, 4, 5]);
// First: 1
// Rest: [2, 3, 4, 5]

// 解构前几个参数,其余作为对象
function extractRequired({ required1, required2, ...rest }) {
    console.log('Required:', required1, required2);
    console.log('Rest:', rest);
}

extractRequired({
    required1: 'A',
    required2: 'B',
    optional1: 'C',
    optional2: 'D'
});
// Required: A B
// Rest: { optional1: 'C', optional2: 'D' }

🚀 扩展运算符

1. 数组扩展运算符

// 数组合并
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const combined = [...arr1, ...arr2];
console.log(combined); // [1, 2, 3, 4, 5, 6]

// 在数组中插入元素
const original = [2, 3, 4];
const withExtension = [1, ...original, 5];
console.log(withExtension); // [1, 2, 3, 4, 5]

// 数组复制
const numbers = [1, 2, 3];
const copy = [...numbers];
console.log(copy); // [1, 2, 3]
console.log(copy === numbers); // false (不同的数组)

// 类数组对象转数组
const arrayLike = { 0: 'a', 1: 'b', 2: 'c', length: 3 };
const realArray = [...arrayLike];
console.log(realArray); // ['a', 'b', 'c']

// 字符串转数组
const str = 'hello';
const charArray = [...str];
console.log(charArray); // ['h', 'e', 'l', 'l', 'o']

// Set转数组
const set = new Set([1, 2, 3, 3, 4]);
const setArray = [...set];
console.log(setArray); // [1, 2, 3, 4]

2. 对象扩展运算符

// 对象合并
const obj1 = { a: 1, b: 2 };
const obj2 = { b: 3, c: 4 };
const merged = { ...obj1, ...obj2 };
console.log(merged); // { a: 1, b: 3, c: 4 }

// 添加属性
const base = { x: 1, y: 2 };
const extended = { ...base, z: 3 };
console.log(extended); // { x: 1, y: 2, z: 3 }

// 覆盖属性
const original = { name: 'Alice', age: 25 };
const updated = { ...original, age: 26 };
console.log(updated); // { name: 'Alice', age: 26 }

// 对象复制
const user = { name: 'Bob', age: 30 };
const userCopy = { ...user };
console.log(userCopy); // { name: 'Bob', age: 30 }
console.log(userCopy === user); // false (不同的对象)

// 合并多个对象
const defaults = { theme: 'light', language: 'en' };
const userSettings = { theme: 'dark' };
const finalSettings = { ...defaults, ...userSettings };
console.log(finalSettings); // { theme: 'dark', language: 'en' }

3. 函数调用中的扩展运算符

// 函数参数展开
function add(a, b, c) {
    return a + b + c;
}

const numbers = [1, 2, 3];
console.log(add(...numbers)); // 6

// Math.max应用
const maxNumber = Math.max(...[10, 20, 30, 40, 50]);
console.log(maxNumber); // 50

// 数组去重
const uniqueArray = [...new Set([1, 2, 2, 3, 4, 4, 5])];
console.log(uniqueArray); // [1, 2, 3, 4, 5]

// 数组扁平化
const nested = [1, [2, 3], [4, [5, 6]]];
const flat = [].concat(...nested);
console.log(flat); // [1, 2, 3, 4, [5, 6]]

🎨 高级解构技巧

1. 复杂嵌套解构

// 复杂嵌套对象解构
const data = {
    users: [
        {
            id: 1,
            name: 'Alice',
            contact: {
                email: 'alice@example.com',
                phones: {
                    mobile: '123-456-7890',
                    home: '098-765-4321'
                }
            }
        },
        {
            id: 2,
            name: 'Bob',
            contact: {
                email: 'bob@example.com',
                phones: {
                    mobile: '555-123-4567'
                }
            }
        }
    ]
};

// 解构第一个用户的手机号
const {
    users: [
        {
            contact: {
                phones: { mobile: firstUserMobile }
            }
        }
    ]
} = data;

console.log(firstUserMobile); // '123-456-7890'

// 解构所有用户的邮箱
const {
    users: [
        { contact: { email: email1 } },
        { contact: { email: email2 } }
    ]
} = data;

console.log(email1, email2); // 'alice@example.com', 'bob@example.com'

2. 解构与默认值结合

// 深度嵌套解构与默认值
const response = {
    data: {
        user: {
            name: 'Charlie'
        }
    }
};

const {
    data: {
        user: { name },
        profile: { age = 25, city = 'Unknown' } = {}
    }
} = response;

console.log(name); // 'Charlie'
console.log(age); // 25
console.log(city); // 'Unknown'

// 函数参数的复杂解构
function processApiResponse({
    data: {
        items = [],
        total = 0
    } = {},
    status = 'unknown'
} = {}) {
    console.log(`Status: ${status}`);
    console.log(`Items: ${items.length}, Total: ${total}`);
}

processApiResponse({
    data: {
        items: [{ id: 1 }, { id: 2 }],
        total: 2
    },
    status: 'success'
});
// Status: success
// Items: 2, Total: 2

3. 解构在算法中的应用

// 快速排序中的解构
function quickSort([pivot, ...rest]) {
    if (rest.length === 0) return [pivot];

    const less = rest.filter(x => x <= pivot);
    const greater = rest.filter(x => x > pivot);

    return [...quickSort(less), pivot, ...quickSort(greater)];
}

const unsorted = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5];
const sorted = quickSort(unsorted);
console.log(sorted); // [1, 1, 2, 3, 3, 3, 4, 5, 5, 5, 6, 9]

// 数组交换元素
function swap([a, b, ...rest]) {
    return [b, a, ...rest];
}

console.log(swap([1, 2, 3, 4, 5])); // [2, 1, 3, 4, 5]

🔄 解构与扩展运算符结合使用

1. 数组操作

// 数组首尾操作
const arr = [2, 3, 4, 5];

// 在开头添加元素
const newStart = [1, ...arr];
console.log(newStart); // [1, 2, 3, 4, 5]

// 在结尾添加元素
const newEnd = [...arr, 6];
console.log(newEnd); // [2, 3, 4, 5, 6]

// 替换中间元素
const [first, , ...middle] = arr;
const replaced = [first, 99, ...middle];
console.log(replaced); // [2, 99, 4, 5]

// 数组旋转
const rotateLeft = ([first, ...rest]) => [...rest, first];
const rotateRight = (arr) => {
    const [last, ...rest] = [...arr].reverse();
    return [last, ...rest.reverse()];
};

console.log(rotateLeft([1, 2, 3, 4, 5])); // [2, 3, 4, 5, 1]
console.log(rotateRight([1, 2, 3, 4, 5])); // [5, 1, 2, 3, 4]

2. 对象操作

// 选择性更新对象
const user = { name: 'Alice', age: 25, city: 'New York' };

const updateUser = { ...user, age: 26 };
console.log(updateUser); // { name: 'Alice', age: 26, city: 'New York' }

// 选择性删除属性(通过解构和重组)
const { city, ...userWithoutCity } = user;
console.log(userWithoutCity); // { name: 'Alice', age: 25 }

// 对象属性重命名
const { name: fullName, ...rest } = user;
const renamedUser = { fullName, ...rest };
console.log(renamedUser); // { fullName: 'Alice', age: 25, city: 'New York' }

// 合并配置
const defaultConfig = {
    theme: 'light',
    language: 'en',
    autoSave: true,
    timeout: 5000
};

const userConfig = {
    theme: 'dark',
    timeout: 10000
};

const finalConfig = { ...defaultConfig, ...userConfig };
console.log(finalConfig);
// { theme: 'dark', language: 'en', autoSave: true, timeout: 10000 }

3. 函数式编程应用

// 函数组合
const compose = (...fns) => (x) => fns.reduceRight((acc, fn) => fn(acc), x);

const addOne = x => x + 1;
const double = x => x * 2;
const toString = x => x.toString();

const processNumber = compose(toString, double, addOne);
console.log(processNumber(5)); // '12'

// 管道函数
const pipe = (...fns) => (x) => fns.reduce((acc, fn) => fn(acc), x);

const pipeline = pipe(
    x => x + 1,
    x => x * 2,
    x => x - 3
);

console.log(pipeline(5)); // 9

// 使用解构和扩展运算符的数据转换
const transformUsers = users =>
    users.map(({ name, age, ...rest }) => ({
        fullName: name,
        isAdult: age >= 18,
        ...rest
    }));

const sampleUsers = [
    { name: 'Alice', age: 25, city: 'NYC' },
    { name: 'Bob', age: 17, city: 'LA' }
];

console.log(transformUsers(sampleUsers));
// [
//   { fullName: 'Alice', isAdult: true, city: 'NYC' },
//   { fullName: 'Bob', isAdult: false, city: 'LA' }
// ]

⚠️ 常见陷阱

1. 解构不存在的属性

// ❌ 错误:解构不存在的嵌套属性
const user = { name: 'Alice' };
// const { profile: { age } } = user; // TypeError: Cannot destructure property 'profile' of undefined

// ✅ 解决方案:使用默认值
const { profile: { age = 25 } = {} } = user;
console.log(age); // 25

// 或者先检查属性是否存在
const userProfile = user.profile || {};
const { age: userAge = 25 } = userProfile;
console.log(userAge); // 25

2. 数组越界解构

// ❌ 可能的问题:数组长度不足
const [a, b, c, d] = [1, 2];
console.log(d); // undefined

// ✅ 解决方案:使用默认值
const [a2, b2, c2, d2 = 0] = [1, 2];
console.log(d2); // 0

// 或者检查数组长度
const arr = [1, 2];
const [first, second, third] = arr.length >= 3 ? arr : [1, 2, 0];
console.log(third); // 0

3. 扩展运算符的性能问题

// ❌ 性能问题:在大型数组上使用扩展运算符
const largeArray = new Array(100000).fill(0);
const copied = [...largeArray]; // 可能很慢

// ✅ 更好的方法:使用slice
const copiedBetter = largeArray.slice();

// ❌ 对象深度克隆的性能问题
const deepObj = { /* 很深的对象 */ };
const cloned = { ...deepObj }; // 浅拷贝,不会复制嵌套对象

// ✅ 深度克隆函数
function deepClone(obj) {
    if (obj =<mark> null || typeof obj !</mark> 'object') return obj;
    if (obj instanceof Date) return new Date(obj);
    if (obj instanceof Array) return obj.map(item => deepClone(item));

    const cloned = {};
    for (const key in obj) {
        if (obj.hasOwnProperty(key)) {
            cloned[key] = deepClone(obj[key]);
        }
    }
    return cloned;
}

4. this指向问题

// ❌ 问题:解构中的this指向
const obj = {
    data: { name: 'Alice', age: 25 },

    // 使用普通函数,this指向有问题
    getData() {
        const { name, age } = this.data;
        return function() {
            console.log(this.name, this.age); // undefined (this指向全局或undefined)
        };
    },

    // 使用箭头函数,this指向正确
    getArrowData() {
        const { name, age } = this.data;
        return () => {
            console.log(name, age); // 正确访问外层变量
        };
    }
};

const regularFunc = obj.getData();
regularFunc(); // undefined, undefined

const arrowFunc = obj.getArrowData();
arrowFunc(); // 'Alice', 25

📝 最佳实践

1. 使用默认值

// ✅ 总是提供默认值
function processUser({ name = 'Anonymous', age = 0, city = 'Unknown' } = {}) {
    return { name, age, city };
}

// ✅ 嵌套解构时也提供默认值
function processNested({
    profile = {},
    contact: { email = '' } = {}
} = {}) {
    return { profile, contact };
}

2. 解构重命名

// ✅ 使用有意义的变量名
const {
    'user-name': userName,
    'user-age': userAge
} = userObject;

// ✅ 避免命名冲突
const {
    name: userName,
    age: userAge
} = user;

3. 性能考虑

// ✅ 避免不必要的扩展运算符
// 不要这样:
const arr = [...array1, ...array2, ...array3];

// 更好的方式:
const arr = array1.concat(array2, array3);

// ✅ 浅拷贝时优先使用Object.assign
const shallowCopy = Object.assign({}, sourceObj);

// 深拷贝时使用专门的函数
const deepCopy = JSON.parse(JSON.stringify(sourceObj));

4. 错误处理

// ✅ 解构时进行错误处理
function safeDestructure(obj) {
    try {
        const { required, optional = 'default' } = obj;
        return { required, optional };
    } catch (error) {
        console.error('Destructuring error:', error);
        return { required: null, optional: null };
    }
}

🎯 小结

  • 掌握了数组和对象的解构赋值语法
  • 学会了使用扩展运算符进行数组和对象操作
  • 理解了函数参数解构和返回值解构
  • 掌握了高级解构技巧和实际应用场景
  • 了解了常见的解构陷阱和性能影响
  • 学会了解构赋值的最佳实践

解构赋值和扩展运算符是ES6+的重要特性,它们让代码更简洁、更易读,是现代JavaScript开发中不可或缺的工具。


下一步学习Symbol类型与SetMap