阿里云主机折上折
  • 微信号
Current Site:Index > Array.prototype.with()

Array.prototype.with()

Author:Chuan Chen 阅读数:47214人阅读 分类: JavaScript

Array.prototype.with() in ECMAScript 14

ECMAScript 14 introduces the Array.prototype.with() method, which brings new possibilities to array operations. This method allows developers to modify specific elements of an array in an immutable way, returning a new array without altering the original array.

Basic Usage of Array.prototype.with()

The with() method takes two parameters: the index to modify and the new value. It returns a new array where the element at the specified index is replaced with the new value, while the original array remains unchanged.

const originalArray = [1, 2, 3, 4, 5];
const newArray = originalArray.with(2, 10);

console.log(originalArray); // [1, 2, 3, 4, 5]
console.log(newArray);      // [1, 2, 10, 4, 5]

Difference from Direct Assignment

The traditional way to modify an array is through direct assignment by index, which changes the original array:

const array = [1, 2, 3];
array[1] = 4;
console.log(array); // [1, 4, 3]

In contrast, the with() method preserves the immutability of the original array:

const array = [1, 2, 3];
const newArray = array.with(1, 4);
console.log(array);    // [1, 2, 3]
console.log(newArray); // [1, 4, 3]

Handling Negative Indices

The with() method supports negative indices, where -1 represents the last element, -2 the second-to-last element, and so on:

const colors = ['red', 'green', 'blue'];
const newColors = colors.with(-1, 'yellow');

console.log(newColors); // ['red', 'green', 'yellow']

Edge Case Handling

When the index is out of the array's bounds, with() throws a RangeError:

try {
  const arr = [1, 2, 3];
  arr.with(5, 10); // Throws RangeError
} catch (error) {
  console.error(error); // RangeError: Invalid index
}

Comparison with the Spread Operator

The spread operator can achieve a similar effect, but the syntax is more verbose:

const original = [1, 2, 3, 4];
const index = 2;
const value = 10;

// Using the spread operator
const newArray = [...original.slice(0, index), value, ...original.slice(index + 1)];

// Using with()
const withArray = original.with(index, value);

Performance Considerations

Although the with() method creates a new array, modern JavaScript engines optimize this operation. For large arrays, engines like V8 use techniques such as structural sharing to improve performance.

Practical Use Cases

  1. State Management: Maintaining immutability in Redux or React state updates
function updateItem(state, index, newValue) {
  return {
    ...state,
    items: state.items.with(index, newValue)
  };
}
  1. Undo/Redo Functionality: Maintaining history without modifying the original array
const history = [];
let currentState = [1, 2, 3];

function updateState(index, value) {
  history.push(currentState);
  currentState = currentState.with(index, value);
}
  1. Functional Programming: Using with other array methods
const users = [
  { id: 1, name: 'Alice' },
  { id: 2, name: 'Bob' },
  { id: 3, name: 'Charlie' }
];

const updatedUsers = users
  .map(user => user.id === 2 ? {...user, name: 'Robert'} : user)
  // Equivalent to
  .with(1, {...users[1], name: 'Robert'});

Combining with Other Array Methods

with() can be chained with other array methods:

const numbers = [1, 2, 3, 4, 5];
const result = numbers
  .filter(n => n % 2 === 0)
  .with(0, 10)
  .map(n => n * 2);

console.log(result); // [20, 8]

Use in Type Systems

In TypeScript, with() maintains type safety for arrays:

const strings: string[] = ['a', 'b', 'c'];
const updated = strings.with(1, 'd'); // Type remains string[]

// Type error example
// strings.with(1, 123); // Error: Cannot assign number to string

Browser Compatibility and Polyfill

Most modern browsers now support Array.prototype.with(). For older environments, a polyfill can be used:

if (!Array.prototype.with) {
  Array.prototype.with = function(index, value) {
    if (index < -this.length || index >= this.length) {
      throw new RangeError('Invalid index');
    }
    const actualIndex = index < 0 ? this.length + index : index;
    const copy = [...this];
    copy[actualIndex] = value;
    return copy;
  };
}

Comparison with Similar Methods

Method Modifies Original Return Value Use Case
with() No New array Immutable updates
splice() Yes Removed items When modifying original
fill() Yes/No (with copy) Modified array Batch value filling
Spread operator No New array For complex operations

Usage in React

In React components, with() can simplify state updates:

function TodoList() {
  const [todos, setTodos] = useState([
    { text: 'Learn React', completed: false },
    { text: 'Build app', completed: false }
  ]);

  const toggleTodo = (index) => {
    setTodos(todos.with(index, {
      ...todos[index],
      completed: !todos[index].completed
    }));
  };

  // ...
}

Handling Multidimensional Arrays

For multidimensional arrays, with() can be nested:

const matrix = [
  [1, 2, 3],
  [4, 5, 6],
  [7, 8, 9]
];

const updatedMatrix = matrix.with(1, matrix[1].with(2, 10));
// [
//   [1, 2, 3],
//   [4, 5, 10],
//   [7, 8, 9]
// ]

Combining with Object.freeze()

with() works particularly well with Object.freeze() to create truly immutable data structures:

const frozenArray = Object.freeze([1, 2, 3]);
// frozenArray[1] = 4; // Throws an error in strict mode

const updated = frozenArray.with(1, 4); // This is allowed

Common Pitfalls

  1. Assuming with() modifies the original array:

    const arr = [1, 2, 3];
    arr.with(1, 4); // No effect, must capture the return value
    
  2. Ignoring the return value:

    // Incorrect usage
    array.with(index, value);
    // Correct usage
    const newArray = array.with(index, value);
    
  3. Confusing with at() method:

    // at() retrieves an element
    array.at(-1); 
    // with() replaces an element
    array.with(-1, value);
    

Performance Testing Example

Comparing the performance of with() and the spread operator:

const largeArray = Array(10000).fill(0);

console.time('with');
for (let i = 0; i < 1000; i++) {
  largeArray.with(5000, 1);
}
console.timeEnd('with');

console.time('spread');
for (let i = 0; i < 1000; i++) {
  [...largeArray.slice(0, 5000), 1, ...largeArray.slice(5001)];
}
console.timeEnd('spread');

Application on Array-like Objects

Although with() is an Array method, it can be used on array-like objects via Array.from() or the spread operator:

const arrayLike = { 0: 'a', 1: 'b', 2: 'c', length: 3 };
const realArray = Array.from(arrayLike);
const updated = realArray.with(1, 'x');

Integration with Other ECMAScript 14 Features

with() can be combined with the Record and Tuple proposal (if implemented) to create fully immutable data structures:

// Assuming Record and Tuple are implemented
const record = #{ items: #[1, 2, 3] };
const newRecord = #{ ...record, items: record.items.with(1, 4) };

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

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

Front End Chuan

Front End Chuan, Chen Chuan's Code Teahouse 🍵, specializing in exorcising all kinds of stubborn bugs 💻. Daily serving baldness-warning-level development insights 🛠️, with a bonus of one-liners that'll make you laugh for ten years 🐟. Occasionally drops pixel-perfect romance brewed in a coffee cup ☕.