目錄
拷貝
數組的拷貝
物件的拷貝
#相關的概念
传递
拷贝
数组的拷贝
对象的拷贝
相关的概念
首頁 web前端 js教程 js中傳遞和拷貝詳解

js中傳遞和拷貝詳解

Mar 13, 2018 pm 06:15 PM
javascript 拷貝 詳解

我們知道js有幾種基本的資料類型和其他的複雜資料類型包括(對象,數組,函數),基本資料類型的賦值其實就是值的拷貝,我們稱之為值傳遞,賦值後的變量而原來的變數除了值相等之外並無其他關聯。

let x = 666
let y = x
let m = 'abc'
let n = m
y = 888
n = 'def'
console.log(x, y)//666,888
console.log(m, n)//'abc','def'
登入後複製
登入後複製

複雜資料類型的傳遞並不是這樣子的,因為將一個變數綁定到一個複雜資料類型的時候記錄的並不是這個複雜資料的值,而是存放了這個資料的一個位址訊息,當將這個變數賦值給另一個變數的時候只是將地址傳遞了過去,這兩個變數指向的其實是一個資料訊息,當改變任意一個變數的時候,另外的變數都會受到影響,這種傳遞方式我們稱之為引用傳遞

let obj1 = {
    a : '1',
    b : 2
} 
let obj2 = obj1
obj2.b = 3
console.log(obj1,obj2)//{a: "1", b: 3},{a: "1", b: 3}
登入後複製
登入後複製

拷貝

我們知道複雜資料型別的賦值是引用傳遞,賦值前後的變數會互相影響,在實際專案中我們常常不希望這樣子,譬如:

我們在一個view裡面有兩處用到了data(是一個Array),其一是一個list只要把data按順序展示即可,其二是一個chart需要把data逆序之後再做資料處理,這時候我們就犯難了,如果直接data.reverse()之後第一個的列表也是逆序的了,這不是我們想見的,我們需要一個方法只複製數組的值,並且這個新數組和原數組的資料位址並不一樣,這種複製方式我們稱為數組的拷貝。

let obj1 = {a:1, b:{c:2}}
let shallowCopy = (src)=> {
    let dst = {}
    for (let prop in src) {
        if (src.hasOwnProperty(prop)) {
          dst[prop] = src[prop]
        }
    }
    return dst
}
let obj2 = shallowCopy(obj1)
console.log(obj1,obj2) //@1
obj1.a = 6
console.log(obj2.a) //@2
obj2.b.c = 666
console.log(obj1.b.c) //@3
obj2.b = {
    c: 888
}
console.log(obj1.b.c) //@4
登入後複製
登入後複製

上面的例子可以看出來obj1的第一層屬性是複製屬性值,沒有繼承位址的拷貝,但是第二層就是b屬性確實共享一塊記憶體位址的,這就是淺拷貝,但是在@4處obj1卻沒有收到obj2的影響,是因為屬性b是一個對象,這種引用傳遞的重新賦值,計算機會重新分配一塊新的內存來存放數據和記錄地址信息,所以這時obj1. b.c和obj2.b.c已經不是記錄的一個屬性值了

也可以理解為:拷貝是之於傳遞的,直接對複雜資料類型進行賦值是引用傳遞,不能稱之為拷貝,拷貝是原始資料的單純的資料備份,資料的記憶體位址資訊並不完全一樣,這是因為拷貝也分為淺拷貝和深拷貝。

對複雜資料類型的非嵌套的拷貝,就是只拷貝第一層的資料資訊的拷貝是淺拷貝,如果第一層的資料有複雜資料類型,則依然採用引用傳遞的方式,複製的仍然是位址訊息,透過其他方式實現的數組物件等的多層嵌套拷貝就是深拷貝。

下面我們再來看下數組和物件如何實現深淺拷貝:

數組的拷貝

  • slice方法

    let arr1 = [1,2,[3,4]]
    let arr2 = arr1.slice(0)
    arr2[2].push(5)
    arr2.push(6)
    console.log(arr1,arr2)
    登入後複製
    登入後複製
  • concat方法

    let arr1 = [1,2,[3,4]]
    let arr2 = arr1.concat()
    arr2[2].push(5)
    arr2.push(6)
    console.log(arr1,arr2)
    登入後複製
    登入後複製
  • for迴圈

    let arr1 = [1,2,[3,4]]
    let arr2 = []
    for(let i = 0; i<arr1.length; i++){
        arr2.push(arr1[i])
    }
    arr2[2].push(5)
    arr2.push(6)
    console.log(arr1,arr2)
    登入後複製
  • …運算子

    let arr1 = [1,2,[3,4]]
    let [...arr2] = arr1
    arr2[2].push(5)
    arr2.push(6)
    console.log(arr1,arr2)
    登入後複製
    登入後複製

#以上4種數組的拷貝都是淺拷貝,要實現數組的深拷貝就要遞歸實現

let deepClone = (src)=> {
    let result
    (src instanceof Array) ? (result = []) :(result = {})
    for (let key in src) {
        result[key] = (typeof src[key] === &#39;object&#39;) ? deepClone(src[key]) : src[key]//数组和对象的type都是object
    }
    return result
}   
let arr1 = [1,2,[3,4]]
let arr2 = deepClone(arr1)
arr2[2].push(5)
arr2.push(6)
console.log(arr1,arr2)
登入後複製
登入後複製

可以發現用方面的方法arr1[2]和arr2[2]不一樣,同樣上面的深拷貝的方法也適用於物件

物件的拷貝

  • #萬能的for迴圈

    let obj1 = {a:1,b:{c:2}}
    let obj2 = {}
    for(let key in obj1){
        obj2[key] = obj1[key]
    }
    obj1.b.c = 6
    console.log(obj1,obj2)
    登入後複製
    登入後複製
  • …運算符

    let obj1 = {a:1,b:{c:2}}
    let {...obj2} = obj1
    obj1.b.c = 6
    console.log(obj1,obj2)
    登入後複製
    登入後複製
  • Object.assign()

    let obj1 = {a:1,b:{c:2}}
    let obj2 = Object.assign({},obj1)
    obj1.b.c = 6
    console.log(obj1,obj2)
    登入後複製

#上面3種方法是物件的淺拷貝,再介紹2種物件的深拷貝的方法:

  • 轉為字串再轉回物件

    let obj1 = {a:1,b:{c:2}}
    let obj2 = JSON.parse(JSON.stringify(obj1))
    obj1.b.c = 6
    console.log(obj1,obj2)
    登入後複製
    登入後複製
  • #deepClone方法,就是上面的陣列的deepClone方法

#相關的概念

純函數

給定函數一個輸入返回一個唯一的輸出,並且不對外部環境附帶任何影響的函數我們稱為純函數,其內定義的變數在函數傳回後都會被垃圾回收機制回收。

但是如果函數的參數是數組、物件或函數時,傳入的是一個引用,對其操作會影響到原來的數據,這樣子寫的函數會產生附帶的影響,使得可讀性變低。

降低影響的方式就是對傳入參數進行深度拷貝,並賦給一個新的變量,方式原來的參數被竄改。

我們來看一個純函數的例子:

let pureFunc = (animal)=> {
    let newAnimal = JSON.parse(JSON.stringify(animal))
    newAnimal.type = &#39;cat&#39;
    newAnimal.name = &#39;Miao&#39;
    return newAnimal
}

let wang = {
    type: &#39;dog&#39;,
    name: &#39;Wang&#39;
}

let miao = pureFunc(wang)
console.log(wang,miao)
登入後複製
登入後複製

透過上面的範例可以看到wang並沒有被純函數所改變值。

大家再來思考一下下面的例子,如果答對了說明你已經對本文所講的東西有了很深刻的理解了(提醒大家一下—>引用的重新賦值)

let afterChange = (obj)=>{
    obj.a = 6
    obj = {
        a: 8,
        b: 9
    }
    return obj
}
let objIns = {
    a: 1,
    b: 2
}

let objIns2 = afterChange(objIns)
console.log(objIns, objIns2)
登入後複製
登入後複製

以上就是我對js的引用和傳遞的理解,如有不當之處敬請諒解,Thanks!

大家還可以看看一些其他的文章加深理解,我推薦這篇Explaining Value vs. Reference in Javascript。

我們在專案中常常會碰到將一個變數賦值給另外一個變數的情形,其實就是js的傳遞,但是不同的資料型別賦值後表現不一樣,下面讓我們一起來研究一下

传递

我们知道js有几种基本的数据类型和其他的复杂数据类型包括(对象,数组,函数),基本数据类型的赋值其实就是值的拷贝,我们称之为值传递,赋值后的变量和原来的变量除了值相等之外并无其他关联

let x = 666
let y = x
let m = &#39;abc&#39;
let n = m
y = 888
n = &#39;def&#39;
console.log(x, y)//666,888
console.log(m, n)//&#39;abc&#39;,&#39;def&#39;
登入後複製
登入後複製

复杂数据类型的传递并不是这样子的,因为将一个变量绑定到一个复杂数据类型的时候记录的并不是这个复杂数据的值,而是存放了这个数据的一个地址信息,当将这个变量赋值给另一个变量的时候只是将地址传递了过去,这两个变量指向的其实是一个数据信息,当改变任意一个变量的时候,另外的变量都会受到影响,这种传递方式我们称之为引用传递

let obj1 = {
    a : &#39;1&#39;,
    b : 2
} 
let obj2 = obj1
obj2.b = 3
console.log(obj1,obj2)//{a: "1", b: 3},{a: "1", b: 3}
登入後複製
登入後複製

拷贝

我们知道复杂数据类型的赋值是引用传递,赋值前后的变量会相互影响,在实际项目中我们经常不希望这样子,譬如:

我们在一个view里面有两处用到了data(是一个Array),其一是一个list只要把data按顺序展示即可,其二是一个chart需要把data逆序之后再做数据处理,这时候我们就犯难了,如果直接data.reverse()之后第一处的列表也是逆序的了,这不是我们想见的,我们需要一个方法只复制数组的值,并且这个新数组和原数组的数据地址并不一样,这种复制方式我们称为数组的拷贝。

let obj1 = {a:1, b:{c:2}}
let shallowCopy = (src)=> {
    let dst = {}
    for (let prop in src) {
        if (src.hasOwnProperty(prop)) {
          dst[prop] = src[prop]
        }
    }
    return dst
}
let obj2 = shallowCopy(obj1)
console.log(obj1,obj2) //@1
obj1.a = 6
console.log(obj2.a) //@2
obj2.b.c = 666
console.log(obj1.b.c) //@3
obj2.b = {
    c: 888
}
console.log(obj1.b.c) //@4
登入後複製
登入後複製

上面的例子可以看出来obj1的第一层属性是复制属性值,没有继承地址的拷贝,但是第二层就是b属性确实共享一块内存地址的,这就是浅拷贝,但是在@4处obj1却没有收到obj2的影响,是因为属性b是一个对象,这种引用传递的重新赋值,计算机会重新分配一块新的内存来存放数据和记录地址信息,所以这时obj1.b.c和obj2.b.c已经不是记录的一个属性值了

也可以理解为:拷贝是之于传递的,直接对复杂数据类型进行赋值是引用传递,不能称之为拷贝,拷贝是对原数据的单纯的数据备份,数据的内存地址信息并不完全一样,这是因为拷贝还分为浅拷贝和深拷贝。

对复杂数据类型的非嵌套的拷贝,就是只拷贝第一层的数据信息的拷贝是浅拷贝,如果第一层的数据有复杂数据类型,则依然采用引用传递的方式,复制的仍然是地址信息,通过其他方式实现的数组对象等的多层嵌套拷贝就是深拷贝。

下面我们再来看下数组和对象如何来实现深浅拷贝:

数组的拷贝

  • slice方法

    let arr1 = [1,2,[3,4]]
    let arr2 = arr1.slice(0)
    arr2[2].push(5)
    arr2.push(6)
    console.log(arr1,arr2)
    登入後複製
    登入後複製
  • concat方法

    let arr1 = [1,2,[3,4]]
    let arr2 = arr1.concat()
    arr2[2].push(5)
    arr2.push(6)
    console.log(arr1,arr2)
    登入後複製
    登入後複製
  • for循环

    let arr1 = [1,2,[3,4]]
    let arr2 = []
    for(let i = 0; i<arr1.length; i++){
        arr2.push(arr1[i])
    }
    arr2[2].push(5)
    arr2.push(6)
    console.log(arr1,arr2)
    登入後複製
  • …运算符

    let arr1 = [1,2,[3,4]]
    let [...arr2] = arr1
    arr2[2].push(5)
    arr2.push(6)
    console.log(arr1,arr2)
    登入後複製
    登入後複製

以上4种数组的拷贝都是浅拷贝,要实现数组的深拷贝就要递归实现

let deepClone = (src)=> {
    let result
    (src instanceof Array) ? (result = []) :(result = {})
    for (let key in src) {
        result[key] = (typeof src[key] === &#39;object&#39;) ? deepClone(src[key]) : src[key]//数组和对象的type都是object
    }
    return result
}   
let arr1 = [1,2,[3,4]]
let arr2 = deepClone(arr1)
arr2[2].push(5)
arr2.push(6)
console.log(arr1,arr2)
登入後複製
登入後複製

可以发现用方面的方法arr1[2]和arr2[2]不一样,同样上面的深拷贝的方法也适用于对象

对象的拷贝

  • 万能的for循环

    let obj1 = {a:1,b:{c:2}}
    let obj2 = {}
    for(let key in obj1){
        obj2[key] = obj1[key]
    }
    obj1.b.c = 6
    console.log(obj1,obj2)
    登入後複製
    登入後複製
  • …运算符

    let obj1 = {a:1,b:{c:2}}
    let {...obj2} = obj1
    obj1.b.c = 6
    console.log(obj1,obj2)
    登入後複製
    登入後複製
  • Object.assign()

    let obj1 = {a:1,b:{c:2}}
    let obj2 = Object.assign({},obj1)
    obj1.b.c = 6
    console.log(obj1,obj2)
    登入後複製

上面3种方法是对象的浅拷贝,再介绍2种对象的深拷贝的方法:

  • 转为字符串再转回对象

    let obj1 = {a:1,b:{c:2}}
    let obj2 = JSON.parse(JSON.stringify(obj1))
    obj1.b.c = 6
    console.log(obj1,obj2)
    登入後複製
    登入後複製
  • deepClone方法,就是上面的数组的deepClone方法

相关的概念

纯函数

给定函数一个输入返回一个唯一的输出,并且不对外部环境附带任何影响的函数我们称为纯函数,其内定义的变量在函数返回后都会被垃圾回收机制回收掉。

但是如果函数的参数是数组、对象或函数时,传入的是一个引用,对其操作会影响到原来的数据,这样子写的函数会产生附带的影响,使得可读性变低。

降低影响的方式就是对传入参数进行深度拷贝,并赋给一个新的变量,方式原来的参数被篡改。

我们来看一个纯函数的例子:

let pureFunc = (animal)=> {
    let newAnimal = JSON.parse(JSON.stringify(animal))
    newAnimal.type = &#39;cat&#39;
    newAnimal.name = &#39;Miao&#39;
    return newAnimal
}

let wang = {
    type: &#39;dog&#39;,
    name: &#39;Wang&#39;
}

let miao = pureFunc(wang)
console.log(wang,miao)
登入後複製
登入後複製

通过上面的例子可以看到wang并没有被纯函数所改变值。

大家再来思考一下下面的例子,如果答对了说明你已经对本文所讲的东西有了很深刻的理解了(提醒大家一下—>引用的重新赋值)

let afterChange = (obj)=>{
    obj.a = 6
    obj = {
        a: 8,
        b: 9
    }
    return obj
}
let objIns = {
    a: 1,
    b: 2
}

let objIns2 = afterChange(objIns)
console.log(objIns, objIns2)
登入後複製
登入後複製

以上就是我对js的引用和传递的理解,如有不当之处敬请谅解,Thanks!

相关推荐:

js函数的按值传递参数

JavaScript参数传递图解教程

js函数参数的按值传递解释

以上是js中傳遞和拷貝詳解的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

Win11管理員權限取得詳解 Win11管理員權限取得詳解 Mar 08, 2024 pm 03:06 PM

Windows作業系統是全球最受歡迎的作業系統之一,其新版本Win11備受矚目。在Win11系統中,管理員權限的取得是一個重要的操作,管理員權限可以讓使用者對系統進行更多的操作和設定。本文將詳細介紹在Win11系統中如何取得管理員權限,以及如何有效地管理權限。在Win11系統中,管理員權限分為本機管理員和網域管理員兩種。本機管理員是指具有對本機電腦的完全管理權限

Oracle SQL中的除法運算詳解 Oracle SQL中的除法運算詳解 Mar 10, 2024 am 09:51 AM

OracleSQL中的除法運算詳解在OracleSQL中,除法運算是一種常見且重要的數學運算運算,用來計算兩個數相除的結果。除法在資料庫查詢中經常用到,因此了解OracleSQL中的除法運算及其用法是資料庫開發人員必備的技能之一。本文將詳細討論OracleSQL中除法運算的相關知識,並提供具體的程式碼範例供讀者參考。一、OracleSQL中的除法運算

linux系統呼叫system()函數詳解 linux系統呼叫system()函數詳解 Feb 22, 2024 pm 08:21 PM

Linux系統呼叫system()函數詳解系統呼叫是Linux作業系統中非常重要的一部分,它提供了一種與系統核心互動的方式。其中,system()函數是常用的系統呼叫函數之一。本文將詳細介紹system()函數的使用方法,並提供對應的程式碼範例。系統呼叫的基本概念系統呼叫是使用者程式與作業系統核心互動的一種方式。使用者程式透過呼叫系統呼叫函數來請求作業系統

PHP模運算子的作用及用法詳解 PHP模運算子的作用及用法詳解 Mar 19, 2024 pm 04:33 PM

PHP中的模運算子(%)是用來取得兩個數值相除的餘數的。在本文中,我們將詳細討論模運算子的作用及用法,並提供具體的程式碼範例來幫助讀者更好地理解。 1.模運算子的作用在數學中,當我們將一個整數除以另一個整數時,就會得到一個商和一個餘數。例如,當我們將10除以3時,商數為3,餘數為1。模運算子就是用來取得這個餘數的。 2.模運算子的用法在PHP中,使用%符號來表示模

簡易JavaScript教學:取得HTTP狀態碼的方法 簡易JavaScript教學:取得HTTP狀態碼的方法 Jan 05, 2024 pm 06:08 PM

JavaScript教學:如何取得HTTP狀態碼,需要具體程式碼範例前言:在Web開發中,經常會涉及到與伺服器進行資料互動的場景。在與伺服器進行通訊時,我們經常需要取得傳回的HTTP狀態碼來判斷操作是否成功,並根據不同的狀態碼來進行對應的處理。本篇文章將教你如何使用JavaScript來取得HTTP狀態碼,並提供一些實用的程式碼範例。使用XMLHttpRequest

Linux的curl指令詳解 Linux的curl指令詳解 Feb 21, 2024 pm 10:33 PM

Linux的curl命令詳解摘要:curl是一種強大的命令列工具,用於與伺服器進行資料通訊。本文將介紹curl指令的基本用法,並提供實際的程式碼範例,幫助讀者更好地理解和應用該指令。一、curl是什麼? curl是命令列工具,用於發送和接收各種網路請求。它支援多種協議,如HTTP、FTP、TELNET等,並提供了豐富的功能,如檔案上傳、檔案下載、資料傳輸、代

六張圖講清楚Linux零拷貝技術 六張圖講清楚Linux零拷貝技術 Feb 22, 2024 pm 06:40 PM

大家好,今天讓我們來聊聊Linux零拷貝技術。我們將以sendfile系統呼叫作為切入點,深入探討零拷貝技術的基本原理。零拷貝技術的核心思想是盡量減少資料在記憶體之間的複製,透過優化資料傳輸路徑,提高資料傳輸的效率和效能。 1.零拷貝技術簡介Linux零拷貝技術是一種用於最佳化資料傳輸的技術,透過減少資料在內核態和使用者態之間的複製次數,進而提高資料傳輸的效率。在資料傳輸的過程中,通常需要將資料從核心緩衝區複製到應用程式的緩衝區,再從應用程式緩衝區複製到網路裝置的緩衝區,最終才能完成傳送。零拷貝技術的優

深入了解Promise.resolve() 深入了解Promise.resolve() Feb 18, 2024 pm 07:13 PM

Promise.resolve()詳解,需要具體程式碼範例Promise是JavaScript中一種用來處理非同步操作的機制。在實際開發中,常常需要處理一些需要依序執行的非同步任務,而Promise.resolve()方法就是用來傳回一個已經Fulfilled狀態的Promise物件。 Promise.resolve()是Promise類別的靜態方法,它接受一個

See all articles