阿里云主机折上折
  • 微信号
您当前的位置:网站首页 > Rest/Spread属性

Rest/Spread属性

作者:陈川 阅读数:24957人阅读 分类: JavaScript

ECMAScript 9(ES2018)引入了Rest/Spread属性的增强功能,进一步简化了对象和数组的操作。这些特性让开发者能够更灵活地处理数据结构,尤其在函数参数传递和对象合并场景中表现突出。

Rest参数在对象解构中的应用

Rest参数(...)在对象解构中允许收集剩余的可枚举属性。这个特性在处理不确定属性数量的对象时特别有用:

const person = {
  name: 'Alice',
  age: 25,
  job: 'Engineer',
  country: 'USA'
};

const { name, age, ...rest } = person;
console.log(name); // 'Alice'
console.log(rest); // { job: 'Engineer', country: 'USA' }

需要注意几个关键点:

  1. Rest元素必须是解构模式中的最后一个元素
  2. 它只收集对象自身的可枚举属性
  3. 不会包含从原型链继承的属性

一个更复杂的例子展示了嵌套解构:

const config = {
  api: {
    endpoint: 'https://api.example.com',
    timeout: 5000,
    retries: 3
  },
  logging: {
    level: 'debug',
    format: 'json'
  }
};

const {
  api: { endpoint, ...apiRest },
  ...configRest
} = config;

console.log(endpoint); // 'https://api.example.com'
console.log(apiRest); // { timeout: 5000, retries: 3 }
console.log(configRest); // { logging: { level: 'debug', format: 'json' } }

Spread属性在对象字面量中的使用

Spread属性(...)允许将一个对象的可枚举属性展开到另一个对象中。这是实现对象浅拷贝和合并的简洁方式:

const defaults = {
  theme: 'light',
  fontSize: 14,
  notifications: true
};

const userSettings = {
  theme: 'dark',
  showHelp: false
};

const merged = {
  ...defaults,
  ...userSettings
};

console.log(merged);
// {
//   theme: 'dark',
//   fontSize: 14,
//   notifications: true,
//   showHelp: false
// }

Spread属性的行为特点:

  1. 后面对象的属性会覆盖前面对象的同名属性
  2. 只展开对象自身的可枚举属性
  3. 展开顺序影响最终结果

实际应用中,可以用于创建不可变更新:

const state = {
  user: {
    name: 'Bob',
    preferences: {
      theme: 'dark',
      notifications: true
    }
  },
  loading: false
};

const updatedState = {
  ...state,
  user: {
    ...state.user,
    preferences: {
      ...state.user.preferences,
      theme: 'light'
    }
  }
};

数组中的Rest/Spread操作

虽然数组的Rest/Spread在ES6就已引入,但在ES9中得到了更一致的处理:

// 数组解构中的Rest
const numbers = [1, 2, 3, 4, 5];
const [first, second, ...others] = numbers;
console.log(others); // [3, 4, 5]

// 数组字面量中的Spread
const newNumbers = [0, ...numbers, 6];
console.log(newNumbers); // [0, 1, 2, 3, 4, 5, 6]

一个实用的函数参数处理示例:

function processData(data, ...callbacks) {
  const result = data.map(item => item * 2);
  callbacks.forEach(cb => cb(result));
}

processData(
  [1, 2, 3],
  res => console.log('Result:', res),
  res => console.log('Length:', res.length)
);

实际应用场景

函数参数处理

Rest参数使函数能够接受任意数量的参数:

function logItems(preface, ...items) {
  console.log(preface);
  items.forEach((item, i) => console.log(` ${i + 1}. ${item}`));
}

logItems('Shopping list:', 'Apples', 'Bread', 'Milk');

对象合并与覆盖

Spread属性简化了配置合并:

function createConfig(baseConfig, overrideConfig) {
  return {
    ...baseConfig,
    ...overrideConfig,
    // 深层属性需要单独处理
    nested: {
      ...baseConfig.nested,
      ...overrideConfig.nested
    }
  };
}

React组件props传递

在React中广泛使用Spread传递props:

function Button({ variant, children, ...props }) {
  return (
    <button
      className={`btn ${variant}`}
      {...props}
    >
      {children}
    </button>
  );
}

// 使用时
<Button variant="primary" onClick={handleClick} disabled>
  Submit
</Button>

注意事项与边界情况

  1. 原型链属性:Spread不会复制原型链上的属性

    class Person {
      constructor(name) {
        this.name = name;
      }
      greet() {
        console.log(`Hello, ${this.name}`);
      }
    }
    
    const alice = new Person('Alice');
    const clone = { ...alice };
    console.log('greet' in clone); // false
    
  2. 不可枚举属性:Spread只复制可枚举属性

    const obj = Object.defineProperty({}, 'hidden', {
      value: 'secret',
      enumerable: false
    });
    
    console.log({ ...obj }); // {}
    
  3. 性能考虑:对于大型对象,频繁使用Spread可能导致性能问题

  4. 与Object.assign的区别

    const obj = { a: 1 };
    const withSpread = { ...obj, a: 2 }; // { a: 2 }
    const withAssign = Object.assign({}, obj, { a: 2 }); // { a: 2 }
    
    // 主要区别在于Spread会触发getter,而Object.assign不会
    

与其他ES9特性的结合使用

Rest/Spread属性可以与ES9的其他特性如异步迭代、Promise.finally等结合使用:

async function fetchAll(...urls) {
  const responses = await Promise.all(
    urls.map(url => fetch(url).then(res => res.json()))
  );
  return responses.flat();
}

在正则表达式命名捕获组中的应用:

function extractDate(text) {
  const { groups } = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/.exec(text);
  return { ...groups };
}

TypeScript中的Rest/Spread

TypeScript对Rest/Spread属性提供了完整的类型支持:

interface Person {
  name: string;
  age: number;
  location?: string;
}

function updatePerson(person: Person, updates: Partial<Person>) {
  return { ...person, ...updates };
}

const person: Person = { name: 'Alice', age: 30 };
const updated = updatePerson(person, { age: 31 });

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益,请来信告知我们删除。邮箱:cc@cccx.cn

前端川

前端川,陈川的代码茶馆🍵,专治各种不服的Bug退散符💻,日常贩卖秃头警告级的开发心得🛠️,附赠一行代码笑十年的摸鱼宝典🐟,偶尔掉落咖啡杯里泡开的像素级浪漫☕。‌