JavaScript(四):拷贝、解构、扩展

54 阅读4分钟

浅拷贝和深拷贝

首先,大家需要区分,赋值不属于拷贝。

基础类型赋值是值复制,复制后会有独立的内存空间;

引用类型赋值是引用复制,复制后共享同一个内存空间。

let a = 1
let b = a
b = 2
console.log(a) // 1

let arr = [1, [2, 3]]
let newArr = arr
console.log(arr[0] === newArr[0]) // true
console.log(arr[1] === newArr[1]) // true

newArr[0] = 11
console.log(arr[0]) // 11

newArr[1] = 2
console.log(arr[1]) // 2

浅拷贝和深拷贝都是针对对象的,不是针对基础类型的。

浅拷贝

  • 如果原始对象的属性是基本类型,拷贝的就是基本类型的值,会有独立的内存空间

  • 如果原始对象的属性是引用类型(对象、数组等),拷贝的就是内存地址,也就是说原始对象和新对象的这个属性指向同一个内存地址,那么该属性被修改就会影响另一个对象

  • 浅拷贝的方法有

    • 扩展运算符

    • Object.assign()

// 例一:

let arr = [1, [2, 3]]
let newArr = [...arr] // [1, [2, 3]] 浅拷贝

console.log(arr === newArr) // false
console.log(arr[0] === newArr[0]) // true 因为基础类型是值比较
console.log(arr[1] === newArr[1]) // true 因为引用类型公用一个内存地址

// 修改基础类型
newArr[0] = 11
console.log(newArr) // [11, [2, 3]]
console.log(arr) // [1, [2, 3]] 原对象没有跟着变

// 修改引用类型
newArr[1][0] = 22
console.log(newArr) // [11, [22, 3]]
console.log(arr) // [1, [22, 3]] 原对象跟着变了

// -----分割线-----

// 例二:

let arr = [1, [2, 3]]
let newArr = [...arr]

newArr[1] = [22, 33]

console.log(newArr) // [1, [22, 33]]
console.log(arr) // [1, [2, 3]]

/*
这里的 arr 为什么没有跟着变化呢?
因为在执行 newArr[1] = [22, 33] 的时候,
实际上是修改的 newArr[1] 对象的引用,即它指向了一个新的引用对象,
这与例一不同,
例一修改的不是对象的引用,修改的是引用对象的内部的数据
*/

深拷贝

深拷贝不仅复制对象本身,还递归复制对象的引用类型的属性所指向的对象,直到那些对象都是基本类型为止。这意味着深拷贝后的对象和原始对象是完全独立的,修改其中一个对象不会影响到另一个对象。

对于一些简单的对象可以使用 JSON.parse(JSON.stringify(obj)) 方法来实现深拷贝,但是如果对象中包含 undefined、Symbol、Date、Map、Set 等类型的数据时就会出现问题。这时候就需要自己封装方法来实现深拷贝。

这里给出一个深拷贝封装的函数:

export function deepClone(source) {
    if (!source && typeof source !== 'object') {
        throw new Error('error arguments', 'deepClone')
    }
    const targetObj = source.constructor === Array ? [] : {}
    Object.keys(source).forEach(keys => {
        if (source[keys] && typeof source[keys] === 'object') {
            targetObj[keys] = deepClone(source[keys])
        } else {
            targetObj[keys] = source[keys]
        }
    })
    return targetObj
}

扩展运算符

ES6 新特性,主要用于数组扩展和对象扩展

数组扩展

computed: {
    test() {
        { label: 'a', value: '1'},
        this.add
        { label: 'b', value: '2'},
    }
},
data() {
    return {
        add: []
    }
},
created() {
    this.add = [
        { label: 'c', value: '3'},
        { label: 'd', value: '4'},
    ]

    console.log(this.test) // 打印出的数组并不是连续的对象数组,而是中间有一段是Array
}

正确的写法是运用扩展运算符

computed: {
    test() {
            { label: 'a', value: '1'},
            ...this.add
            { label: 'b', value: '2'},
    }
},

// ...add 扩展运算符

对象扩展

let t1 = {
  a: '1',
  b: '2'
}
let t2 = {
  c: '3',
  d: '4',
  e: [1,2],
  f: {f1: '1', f2: '2'}
}
let t3 = {...t1, ...t2}
console.log("t3:", t3);

注意:扩展运算符是浅拷贝。

其他

获取数组的最大最小值

var arr = [22, 13, 6, 55, 30];
console.log(Math.max(...arr)); // 55
console.log(Math.min(...arr)); // 6
console.log(Math.min(arr)); // NaN

解构赋值

解构赋值 ES6 新增,允许你从数组或对象中提取数据,并将其赋值给声明的变量。这种语法提供了一种非常方便的方式来处理数组或对象的属性/元素,使得代码更加简洁和易读。

// 数组解构
const arr = [1, 2, 3, 4]
const [first, second] = arr // 取的数组的第一个和第二个,数组解构要按顺序
console.log(first) // 1

// 对象解构
const usr = {
    name: '小李',
    age: 20,
    num: 1
}
const {age, name} = usr // 对象解构不按顺序按属性
console.log(name) // 小李

// 剩余参数(...)
let [a, ...rest] = [1, 2, 3, 4, 5]; 
console.log(a); // 1
console.log(rest); // [2, 3, 4, 5]

// 取别名
let { foo: baz } = { foo: 'hello' };
console.log(baz); // 输出: hello

// 函数参数解构
function person({ name, age }) {
  console.log(name); // 张三
  console.log(age); // 10
}  
person({ name: '张三', age: 10 });

注意:解构赋值得到的对象变量实际上是原对象的一个引用,而不是对象的一个拷贝,这与浅拷贝类似。

ES6闪电教程

一个综合示例:

// bad
const oirginal = { a: 1, b: 2 }
const copy = Object.assign(original, { c: 3 })
delete copy.a

// good
const oirginal = { a: 1, b: 2 }
const copy = { ...original, c: 3 } // copy => { a: 1, b: 2, c: 3}
const  { a, ...noA } = copy // noA => { b: 2, c: 3}