Object.assign和展开运算

88 阅读4分钟

1. Object.assign()

1-1. Object.assign() 基本使用

Object.assign() 方法将所有可枚举(Object.propertyIsEnumerable() 返回 true)的自有(Object.hasOwnProperty() 返回 true)属性从一个或多个源对象复制到目标对象,返回修改后的对象。

例:

let target = { a: 1, b: 2 }
let source1 = { c: 3, d: 4 }
let res = Object.assign(target, source1)
console.log(res) // { a: 1, b: 2, c: 3, d: 4 }
console.log(target) // { a: 1, b: 2, c: 3, d: 4 }
console.log(res === target) // true

1-2. 来源对象具有相同(===)属性

如果目标对象与源对象具有相同的 key,则目标对象中的属性将被源对象中的属性覆盖,后面的源对象的属性将类似地覆盖前面的源对象的属性。

let target = { a: 1, b: 2 }
let source1 = { b: 3, d: 4 }
let source2 = { d: 5, e: 6 }
let res = Object.assign(target, source1, source2)
console.log(res) // { a: 1, b: 3, d: 5, e: 6 }
console.log(target) // { a: 1, b: 3, d: 5, e: 6 }
console.log(res === target) // true

1-3. 浅拷贝

假如源对象的属性是一个对象的引用,它仅仅会复制其引用值。

let target = { a: 1, b: 2 }
let source1 = { c: {name: 'zhangsan', age: 20} }
let res = Object.assign(target, source1)
source1.c.age = 21
console.log(res) // { a: 1, b: 2, c: { name: 'zhangsan', age: 21 } }

1-4. 可拷贝 Symbol 类型属性

let target = { a: 1, b: 2 }
let source1 = { [Symbol('age')]: 20 }
let res = Object.assign(target, source1)
console.log(res)
//{ a: 1, b: 2, Symbol(age): 20 }

1-5. 基本类型会被包装为对象

在基本类型 Object.assign() 的参数为基本数据类型时,会将基本数据类型转换其对应的成包装类对象,在进行后续的合并操作。

注意:

  • nullundefined 会被忽略
  • 只有 string 类型的包装对象拥有可枚举的自有属性
  • 所以,只有字符串转换成的包装对象会被复制到目标对象中!!!
let target = { a: 1, b: 2 }
let source1 = '123'
let source2 = true
let source3 = 456
let source4 = null
let source5 = void 0
// 基本数据类型将被包装
let res = Object.assign(target, source1, source2, source3, source4, source5) 
console.log(res)
// { '0': '1', '1': '2', '2': '3', a: 1, b: 2 }

2. 展开运算符(...)

2-1. 数组展开运算符

可以将一个数组转为用逗号分隔的参数序列,且每次只能展开一层数组。

2-1-1. 展开运算符在数组中的基本使用

let a = [1,2,4]
console.log(...a) // 1 2 4

function fn(...args) {
    console.log(args)
}
function foo(args) {
    console.log(args)
    console.log(...args)
}
fn(1,2,3) // [ 1, 2, 3 ]
fn([1,2,3]) // [ [ 1, 2, 3 ] ]
fn('123') // [ '123' ]

foo(1,2,3)
// 123
// 报错:Found non-callable @@iterator
foo([1,2,3])
// [ 1, 2, 3 ]
// 1 2 3
foo('123')
// 123
// 1 2 3
foo({
    1:'1-1',
    2:'2-2',
    3:'3-3'
})
// { '1': '1-1', '2': '2-2', '3': '3-3' }
// 报错:Found non-callable @@iterator

2-1-2 展开运算符在数组中的使用场景:

  1. 将数组转换为参数序列
function add(x, y) {
  return x + y;
}
const numbers = [1, 2];
console.log(add(...numbers)); // 3
  1. 复制数组
const arr1 = [1, 2, 3]
const arr2 = [...arr1]
console.log(arr2) // [ 1, 2, 3 ]
  1. 合并数组
const arr1 = [1, 2, 3]
const arr2 = [4, 5, 6]
const arr3 = ['a', 'b', ...arr1, 'c', ...arr2, 'd']
console.log(arr3) // ['a', 'b', 1, 2, 3, 'c', 4, 5, 6, 'd']
  1. 扩展运算符与解构赋值结合起来,用于生成数组
const arr1 = ['a', 1, 2, 3]
const [num1, ...numArr] = arr1
console.log(numArr) // [ 1, 2, 3 ]
  1. 字符串转换为数组
const str = 'hello'
const strToArr = [...str]
console.log(strToArr) // [ 'h', 'e', 'l', 'l', 'o' ]
  1. 使用Math函数获取数组中特定的值(实际上也是利用了将数组转换成参数序列)
const arr1 = [1, 3, 2, 7, 12, 8]
const min = Math.min(...arr1)
const max = Math.max(...arr1)
console.log(min) // 1
console.log(max) // 12

2-2. 对象展开运算符

取出参数对象中的所有可遍历属性,拷贝(浅拷贝)到当前对象之中。

2-2-1. 拷贝对象

let a = {a:1}
let b = {...a}
console.log(b) // { a: 1 }

let a = {a1:1 , a2:2}
let b = {...a}
console.log(b) // { a1: 1, a2: 2 }

let a = {
    a1:'1-1',
    a2:'2-2',
    a3:'3-3'
}
let b = {...a}
console.log('b', b) // b { a1: '1-1', a2: '2-2', a3: '3-3' }

2-2-2. 合并对象

var obj1 = { foo: 'bar', x: 42 };
var obj2 = { foo: 'baz', y: 13 };
var mergedObj = { ...obj1, ...obj2 };
console.log(mergedObj) // { foo: 'baz', x: 42, y: 13 }

2-2-3. 展开运算是浅拷贝

var obj1 = { foo: 'bar', x: {a: 1, b: 2} };
var obj2 = { foo: 'baz', y: 13 };
var mergedObj = { ...obj1, ...obj2 };
obj1.x.a = 3
console.log(mergedObj)  // { foo: 'baz', x: { a: 3, b: 2 }, y: 13 }

注意:

  • 在数组或函数参数中使用展开语法时,该语法只能用于 可迭代对象
  • 扩展操作符 … 使用它时, 数组或对象中的每一个值都会被拷贝到一个新的数组或对象中。它不复制继承的属性或类的属性, 但是它会复制 ES6symbols 属性。
  • Object.assign() 函数会触发 setters,而展开语法则不会。
  • 不能替换或者模拟 Object.assign() 函数:
var obj1 = { foo: 'bar', x: 42 };
var obj2 = { foo: 'baz', y: 13 };
const merge = ( ...objects ) => ( { ...objects } );
var mergedObj1 = merge ( obj1, obj2);
console.log(mergedObj1) // { '0': { foo: 'bar', x: 42 }, '1': { foo: 'baz', y: 13 } }
var mergedObj2 = merge ( {}, obj1, obj2);
console.log(mergedObj2) // { '0': {}, '1': { foo: 'bar', x: 42 }, '2': { foo: 'baz', y: 13 } }

3.Object.assign展开运算的区别

  • Object.assign() 方法接收的第一个参数作为目标对象, 后面的所有参数作为源对象。然后把所有的源对象合并到目标对象中。它会修改了一个对象, 因此会触发 ES6setter