真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

如何實現(xiàn)JavaScript的共享傳遞和按值傳遞

這期內(nèi)容當(dāng)中小編將會給大家?guī)碛嘘P(guān)如何實現(xiàn)JavaScript的共享傳遞和按值傳遞,文章內(nèi)容豐富且以專業(yè)的角度為大家分析和敘述,閱讀完這篇文章希望大家可以有所收獲。

讓客戶滿意是我們工作的目標(biāo),不斷超越客戶的期望值來自于我們對這個行業(yè)的熱愛。我們立志把好的技術(shù)通過有效、簡單的方式提供給客戶,將通過不懈努力成為客戶在信息化領(lǐng)域值得信任、有價值的長期合作伙伴,公司提供的服務(wù)項目有:主機(jī)域名、雅安服務(wù)器托管、營銷軟件、網(wǎng)站建設(shè)、古雷港網(wǎng)站維護(hù)、網(wǎng)站推廣。

關(guān)于JavaScript如何將值傳遞給函數(shù),在互聯(lián)網(wǎng)上有很多誤解和爭論。大致認(rèn)為,參數(shù)為原始數(shù)據(jù)類時使用按值傳遞,參數(shù)為數(shù)組、對象和函數(shù)等數(shù)據(jù)類型使用引用傳遞。

按值傳遞 和 引用傳遞參數(shù) 主要區(qū)別簡單可以說:

  • 按值傳遞:在函數(shù)里面改變傳遞的值不會影響到外面

  • 引用傳遞:在函數(shù)里面改變傳遞的值會影響到外面

但答案是 JavaScript  對所有數(shù)據(jù)類型都使用按值傳遞。它對數(shù)組和對象使用按值傳遞,但這是在的共享傳參或拷貝的引用中使用的按值傳參。這些說有些抽象,先來幾個例子,接著,我們將研究JavaScript在  函數(shù)執(zhí)行期間的內(nèi)存模型,以了解實際發(fā)生了什么。

按值傳參

在 JavaScript 中,原始類型的數(shù)據(jù)是按值傳參;對象類型是跟Java一樣,拷貝了原來對象的一份引用,對這個引用進(jìn)行操作。但在 JS  中,string 就是一種原始類型數(shù)據(jù)而不是對象類。

let setNewInt = function (i) {  ii = i + 33;  };  let setNewString = function (str) {  str += "cool!";  };  let setNewArray = function (arr1) {  var b = [1, 2];  arr1 = b;  };  let setNewArrayElement = function (arr2) {  arr2[0] = 105;  };  let i = -33;  let str = "I am ";  let arr1 = [-4, -3];  let arr2 = [-19, 84];  console.log('i is: ' + i + ', str is: ' + str + ', arr1 is: ' + arr1 + ', arr2 is: ' + arr2);  setNewInt(i);  setNewString(str);  setNewArray(arr1);  setNewArrayElement(arr2);  console.log('現(xiàn)在, i is: ' + i + ', str is: ' + str + ', arr1 is: ' + arr1 + ', arr2 is: ' + arr2);

運(yùn)行結(jié)果

i is: -33, str is: I am , arr1 is: -4,-3, arr2 is: -19,84  現(xiàn)在, i is: -33, str is: I am , arr1 is: -4,-3, arr2 is: 105,84

這邊需要注意的兩個地方:

1)***個是通過 setNewString 方法把字符串 str 傳遞進(jìn)去,如果學(xué)過面向?qū)ο蟮恼Z言如C#,Java 等,會認(rèn)為調(diào)用這個方法后 str  的值為改變,引用這在面向?qū)ο笳Z言中是 string 類型的是個對象,按引用傳參,所以在這個方法里面更改 str 外面也會跟著改變。

但是 JavaScript 中就像前面所說,在JS 中,string 就是一種原始類型數(shù)據(jù)而不是對象類,所以是按值傳遞,所以在 setNewString  中更改 str 的值不會影響到外面。

2)第二個是通過 setNewArray 方法把數(shù)組 arr1 傳遞進(jìn)去,因為數(shù)組是對象類型,所以是引用傳遞,在這個方法里面我們更改 arr1  的指向,所以如果是這面向?qū)ο笳Z言中,我們認(rèn)為***的結(jié)果arr1 的值是重新指向的那個,即 [1, 2],但***打印結(jié)果可以看出 arr1  的值還是原先的值,這是為什么呢?

共享傳遞

Stack Overflow上Community Wiki  對上述的回答是:對于傳遞到函數(shù)參數(shù)的對象類型,如果直接改變了拷貝的引用的指向地址,那是不會影響到原來的那個對象;如果是通過拷貝的引用,去進(jìn)行內(nèi)部的值的操作,那么就會改變到原來的對象的。

可以參考博文 JavaScript Fundamentals (2) – Is JS call-by-value or  call-by-reference?

function changeStuff(state1, state2)  {  state1.item = 'changed';  state2 = {item: "changed"};  }  var obj1 = {item: "unchanged"};  var obj2 = {item: "unchanged"};  changeStuff(obj1, obj2);  console.log(obj1.item); // obj1.item 會被改變  console.log(obj2.item); // obj2.item 不會被改變

緣由: 上述的 state1 相當(dāng)于 obj1, 然后 obj1.item = 'changed',對象 obj1 內(nèi)部的 item  屬性進(jìn)行了改變,自然就影響到原對象 obj1 。類似的,state2 也是就 obj2,在方法里 state2  指向了一個新的對象,也就是改變原有引用地址,這是不會影響到外面的對象(obj2),這種現(xiàn)象更專業(yè)的叫法:call-by-sharing,這邊為了方便,暫且叫做  共享傳遞。

內(nèi)存模型

JavaScript 在執(zhí)行期間為程序分配了三部分內(nèi)存:代碼區(qū),調(diào)用堆棧和堆。 這些組合在一起稱為程序的地址空間。

如何實現(xiàn)JavaScript的共享傳遞和按值傳遞

代碼區(qū):這是存儲要執(zhí)行的JS代碼的區(qū)域。

調(diào)用堆::這個區(qū)域跟蹤當(dāng)前正在執(zhí)行的函數(shù),執(zhí)行計算并存儲局部變量。變量以后進(jìn)先出法存儲在堆棧中。***一個進(jìn)來的是***個出去的,數(shù)值數(shù)據(jù)類型存儲在這里。

例如:

  1. var corn = 95  

  2. let lion = 100 


如何實現(xiàn)JavaScript的共享傳遞和按值傳遞

在這里,變量 corn 和 lion 值在執(zhí)行期間存儲在堆棧中。

堆:是分配 JavaScript 引用數(shù)據(jù)類型(如對象)的地方。 與堆棧不同,內(nèi)存分配是隨機(jī)放置的,沒有 LIFO策略。  為了防止堆中的內(nèi)存漏洞,JS引擎有防止它們發(fā)生的內(nèi)存管理器。

class Animal {}  // 在內(nèi)存地址 0x001232 上存儲 new Animal() 實例  // tiger 的堆棧值為 0x001232  const tiger = new Animal()  // 在內(nèi)存地址 0x000001 上存儲 new Objec實例  // `lion` 的堆棧值為 0x000001  let lion = {  strength: "Very Strong"  }
如何實現(xiàn)JavaScript的共享傳遞和按值傳遞   

Here,lion 和 tiger 是引用類型,它們的值存儲在堆中,并被推入堆棧。它們在堆棧中的值是堆中位置的內(nèi)存地址。

激活記錄(Activation Record),參數(shù)傳遞

我們已經(jīng)看到了 JS 程序的內(nèi)存模型,現(xiàn)在,讓我們看看在 JavaScript 中調(diào)用函數(shù)時會發(fā)生什么。

// 例子一  function sum(num1,num2) {  var result = num1 + num2  return result  }  var a = 90  var b = 100  sum(a, b)

每當(dāng)在 JS 中調(diào)用一個函數(shù)時,執(zhí)行該函數(shù)所需的所有信息都放在堆棧上。這個信息就是所謂的激活記錄(Activation Record)。

這個 Activation Record,我直譯為激活記錄,找了好多資料,沒有看到中文一個比較好的翻譯,如果朋友們知道,歡迎留言。

激活記錄上的信息包括以下內(nèi)容:

  • SP 堆棧指針:調(diào)用方法之前堆棧指針的當(dāng)前位置。

  • RA 返回地址:這是函數(shù)執(zhí)行完成后繼續(xù)執(zhí)行的地址。

  • RV 返回值:這是可選的,函數(shù)可以返回值,也可以不返回值。

  • 參數(shù):將函數(shù)所需的參數(shù)推入堆棧。

  • 局部變量:函數(shù)使用的變量被推送到堆棧。

我們必須知道這一點,我們在js文件中編寫的代碼在執(zhí)行之前由 JS 引擎(例如 V8,Rhino,SpiderMonke y等)編譯為機(jī)器語言。

所以以下的代碼:

let shark = "Sea Animal"

會被編譯成如下機(jī)器碼:

01000100101010  01010101010101

上面的代碼是我們的js代碼等價。 機(jī)器碼和 JS 之間有一種語言,它是匯編語言。 JS 引擎中的代碼生成器在最終生成機(jī)器碼之前,首先是將 js  代碼編譯為匯編代碼。

為了了解實際發(fā)生了什么,以及在函數(shù)調(diào)用期間如何將激活記錄推入堆棧,我們必須了解程序是如何用匯編表示的。

為了跟蹤函數(shù)調(diào)用期間參數(shù)是如何在 JS 中傳遞的,我們將例子一的代碼使用匯編語言表示并跟蹤其執(zhí)行流程。

先介紹幾個概念:

ESP:(Extended Stack Pointer)為擴(kuò)展棧指針寄存器,是指針寄存器的一種,用于存放函數(shù)棧頂指針。與之對應(yīng)的是  EBP(Extended Base Pointer),擴(kuò)展基址指針寄存器,也被稱為幀指針寄存器,用于存放函數(shù)棧底指針。

EBP:擴(kuò)展基址指針寄存器(extended base pointer) 其內(nèi)存放一個指針,該指針指向系統(tǒng)棧最上面一個棧幀的底部。

EBP 只是存取某時刻的 ESP,這個時刻就是進(jìn)入一個函數(shù)內(nèi)后,cpu 會將ESP的值賦給 EBP,此時就可以通過 EBP  對棧進(jìn)行操作,比如獲取函數(shù)參數(shù),局部變量等,實際上使用 ESP 也可以。

// 例子一  function sum(num1,num2) {  var result = num1 + num2  return result  }  var a = 90  var b = 100  var s = sum(a, b)

我們看到 sum 函數(shù)有兩個參數(shù) num1 和 num2。函數(shù)被調(diào)用,傳入值分別為 90 和 100 的 a 和 b。

記住:值數(shù)據(jù)類型包含值,而引用數(shù)據(jù)類型包含內(nèi)存地址。

在調(diào)用 sum 函數(shù)之前,將其參數(shù)推入堆棧

ESP->[......]  ESP->[ 100 ]  [ 90 ]  [.......]

然后,它將返回地址推送到堆棧。返回地址存儲在EIP 寄存器中:

ESP->[Old EIP]  [ 100 ]  [ 90 ]  [.......]

接下來,它保存基指針

ESP->[Old EBP]  [Old EIP]  [ 100 ]  [ 90 ]  [.......]

然后更改 EBP 并將調(diào)用保存寄存器推入堆棧。

ESP->[Old ESI]  [Old EBX]  [Old EDI]  EBP->[Old EBP]  [Old EIP]  [ 100 ]  [ 90 ]  [.......]

為局部變量分配空間:

ESP->[ ]  [Old ESI]  [Old EBX]  [Old EDI]  EBP->[Old EBP]  [Old EIP]  [ 100 ]  [ 90 ]  [.......]

這里執(zhí)行加法:

mov ebp+4, eax ; 100  add ebp+8, eax ; eaxeax = eax + (ebp+8)  mov eax, ebp+16  ESP->[ 190 ]  [Old ESI]  [Old EBX]  [Old EDI]  EBP->[Old EBP]  [Old EIP]  [ 100 ]  [ 90 ]  [.......]

我們的返回值是190,把它賦給了 EAX。

mov ebp+16, eax

EAX 是"累加器"(accumulator), 它是很多加法乘法指令的缺省寄存器。

然后,恢復(fù)所有寄存器值。

[ 190 ] DELETED  [Old ESI] DELETED  [Old EBX] DELETED  [Old EDI] DELETED  [Old EBP] DELETED  [Old EIP] DELETED  ESP->[ 100 ]  [ 90 ]  EBP->[.......]

并將控制權(quán)返回給調(diào)用函數(shù),推送到堆棧的參數(shù)被清除。

[ 190 ] DELETED  [Old ESI] DELETED  [Old EBX] DELETED  [Old EDI] DELETED  [Old EBP] DELETED  [Old EIP] DELETED  [ 100 ] DELETED  [ 90 ] DELETED  [ESP, EBP]->[.......]

調(diào)用函數(shù)現(xiàn)在從 EAX 寄存器檢索返回值到 s 的內(nèi)存位置。

mov eax, 0x000002 ; // s 變量在內(nèi)存中的位置

我們已經(jīng)看到了內(nèi)存中發(fā)生了什么以及如何將參數(shù)傳遞匯編代碼的函數(shù)。

調(diào)用函數(shù)之前,調(diào)用者將參數(shù)推入堆棧。因此,可以正確地說在 js  中傳遞參數(shù)是傳入值的一份拷貝。如果被調(diào)用函數(shù)更改了參數(shù)的值,它不會影響原始值,因為它存儲在其他地方,它只處理一個副本。

function sum(num1) {  num1 = 30  }  let n = 90  sum(n)  // `n` 仍然為 90

讓我們看看傳遞引用數(shù)據(jù)類型時會發(fā)生什么。

function sum(num1) {  num1 = { number:30 }  }  let n = { number:90 }  sum(n)  // `n` 仍然是 { number:90 }

用匯編代碼表示:

n -> 0x002233  Heap: Stack:  002254 012222  ... 012223 0x002233  002240 012224  002239 012225  002238  002237  002236  002235  002234  002233 { number: 90 }  002232  002231 { number: 30 }  Code:  ...  000233 main: // entry point  000234 push n // n 值為 002233 ,它指向堆中存放 {number: 90} 地址。 n 被推到堆棧的 0x12223 處.  000235 ; // 保存所有寄存器  ...  000239 call sum ; // 跳轉(zhuǎn)到內(nèi)存中的`sum`函數(shù)  000240  ...  000270 sum:  000271 ; // 創(chuàng)建對象 {number: 30} 內(nèi)在地址主 0x002231  000271 mov 0x002231, (ebp+4) ; // 將內(nèi)存地址為 0x002231 中 {number: 30} 移動到堆棧 (ebp+4)。(ebp+4)是地址 0x12223 ,即 n 所在地址也是對象 {number: 90} 在堆中的位置。這里,堆棧位置被值 0x002231 覆蓋。現(xiàn)在,num1 指向另一個內(nèi)存地址。 000272 ; // 清理堆棧  ...  000275 ret ; // 回到調(diào)用者所在的位置(000240)

我們在這里看到變量n保存了指向堆中其值的內(nèi)存地址。 在sum 函數(shù)執(zhí)行時,參數(shù)被推送到堆棧,由 sum 函數(shù)接收。

sum 函數(shù)創(chuàng)建另一個對象 {number:30},它存儲在另一個內(nèi)存地址 002231 中,并將其放在堆棧的參數(shù)位置。 將前面堆棧上的參數(shù)位置的對象  {number:90} 的內(nèi)存地址替換為新創(chuàng)建的對象 {number:30} 的內(nèi)存地址。

這使得 n 保持不變。因此,復(fù)制引用策略是正確的。變量 n 被推入堆棧,從而在 sum 執(zhí)行時成為 n 的副本。

此語句 num1 = {number:30} 在堆中創(chuàng)建了一個新對象,并將新對象的內(nèi)存地址分配給參數(shù) num1。 注意,在 num1 指向 n  之前,讓我們進(jìn)行測試以驗證:

// example1.js  let n = { number: 90 }  function sum(num1) {  log(num1 === n)  num1 = { number: 30 }  log(num1 === n)  }  sum(n)  $ node example1  true  false

是的,我們是對的。就像我們在匯編代碼中看到的那樣。最初,num1 引用與 n 相同的內(nèi)存地址,因為n被推入堆棧。

然后在創(chuàng)建對象之后,將 num1 重新分配到對象實例的內(nèi)存地址。

讓我們進(jìn)一步修改我們的例子1:

function sum(num1) {  num1.number = 30  }  let n = { number: 90 }  sum(n)  // n 成為了 { number: 30 }

這將具有與前一個幾乎相同的內(nèi)存模型和匯編語言。這里只有幾件事不太一樣。在 sum 函數(shù)實現(xiàn)中,沒有新的對象創(chuàng)建,該參數(shù)受到直接影響。

...  000270 sum:  000271 mov (ebp+4), eax ; // 將參數(shù)值復(fù)制到 eax 寄存器。eax 現(xiàn)在為 0x002233  000271 mov 30, [eax]; // 將 30 移動到 eax 指向的地址

num1 是(ebp+4),包含 n 的地址。值被復(fù)制到 eax 中,30 被復(fù)制到 eax 指向的內(nèi)存中。任何寄存器上的花括號 [] 都告訴 CPU  不要使用寄存器中找到的值,而是獲取與其值對應(yīng)的內(nèi)存地址號的值。因此,檢索 0x002233 的 {number: 90} 值。

看看這樣的答案:

原始數(shù)據(jù)類型按值傳遞,對象通過引用的副本傳遞。

具體來說,當(dāng)你傳遞一個對象(或數(shù)組)時,你無形地傳遞對該對象的引用,并且可以修改該對象的內(nèi)容,但是如果你嘗試覆蓋該引用,它將不會影響該對象的副本-  即引用本身按值傳遞:

function replace(ref) {  ref = {}; // 這段代碼不影響傳遞的對象  }  function update(ref) {  ref.key = 'newvalue'; // 這段代碼確實會影響對象的內(nèi)容  }  var a = { key: 'value' };  replace(a); // a 仍然有其原始值,它沒有被修改的  update(a); // a 的內(nèi)容被更改

從我們在匯編代碼和內(nèi)存模型中看到的。這個答案***正確。在 replace 函數(shù)內(nèi)部,它在堆中創(chuàng)建一個新對象,并將其分配給 ref 參數(shù),a  對象內(nèi)存地址被重寫。

update 函數(shù)引用 ref 參數(shù)中的內(nèi)存地址,并更改存儲在存儲器地址中的對象的key屬性。

總結(jié)

根據(jù)我們上面看到的,我們可以說原始數(shù)據(jù)類型和引用數(shù)據(jù)類型的副本作為參數(shù)傳遞給函數(shù)。不同之處在于,在原始數(shù)據(jù)類型,它們只被它們的實際值引用。JS 不允許我們獲取他們的內(nèi)存地址,不像在C與C++程序設(shè)計學(xué)習(xí)與實驗系統(tǒng),引用數(shù)據(jù)類型指的是它們的內(nèi)存地址。

上述就是小編為大家分享的如何實現(xiàn)JavaScript的共享傳遞和按值傳遞了,如果剛好有類似的疑惑,不妨參照上述分析進(jìn)行理解。如果想知道更多相關(guān)知識,歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道。


網(wǎng)站欄目:如何實現(xiàn)JavaScript的共享傳遞和按值傳遞
網(wǎng)站鏈接:http://weahome.cn/article/iidcsh.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部