本節目標在于增加對計算機底層的一些認識,掌握后可以更清楚地明了一些"坑"產生的原因。本節內容也是一些筆面試中考察的基礎知識點,將有助于加快打通"任督二脈"的步伐。
# 值傳送與引用傳遞
在上個小節中不小心又回到了選擇組件,在生產項目中這種選擇組件非常常用。還記得在班級管理中初始接觸[選擇組件](http://www.hmoore.net/yunzhiclub/springboot_angular_guide/1364543)時遇到[教師未自動選中的問題](http://www.hmoore.net/yunzhiclub/springboot_angular_guide/1372247)時只是草草的介紹了可以使用`compareFn`方法來比較兩個對象是否相等,但卻未說明原由:
根本上來講,由于js中對變量的`判等`會根據變量類型而采取不同的方法,所以在進行兩個`教師`是否相等的判斷上,js會認為兩個值一樣的`教師`僅僅是一對雙胞胎而已。如果問他兩個教師是否相等,它會毫不猶豫的告訴我們:不!
js為什么在對象的判等上會這么處理呢,這還得從JS的變量傳遞方式說起:
基本上所有的語言都歸避不了值傳遞與引用傳遞的問題,也都存在值傳遞與引用傳遞。這包括了筆者當前接觸的所有的語言:C、C++、JAVA、JAVASCRIPT、PHP等。同時引用傳遞又被稱為:地址傳遞或指針傳遞。
> 注意:引用傳遞、地址傳遞及指針傳遞只是說法不同,教程中不會做刻意區分,用哪個詞語來描繪也將比較隨性。
**傳遞**發生了方法的調用上。比如有以下方法:
```javascript
var a = function(name) {
alert('the name is ' + name);
};
```
然后調用上述方法:
```
a('mengyunzhi');
```

> TIP: 可以直接在chrome中的控制臺來測試JS代碼.
上述代碼在進行調用`a`方法的過程中,將`mengyunzhi`這個字符串傳遞給了`a`方法。而這個將`mengyunzhi`字符串傳遞給`a`方法,就叫做傳遞。
## 值傳遞簡介
剛剛上面的例子就是最典型的值傳遞,也是在計算機語言的學習中接觸的最早的一種傳遞方法。也比較容易理解。就是簡單的在調用的過程中,把方法所需要的值傳給方法。把值傳給方法后,該方法對該值進行變更,不會對原來的值有任何的影響:
比如:
```javascript
var a = function(name) {
name = 'hello'; // ?
alert('the name is ' + name);
};
var name = 'mengyunzhi';
a(name);
alert(name); // ?
```
* ? a方法接收了name值后,將其變更為`hello`。
* ? 原來的name還是`mengyunzhi`,并沒有授`a`方法改變其接收的`name`的值的影響。

## 引用傳送簡介
同樣是傳值。只要上面的基礎上稍加改動,那就不一樣了:
```javascript
var a = function(value) {
value.name = 'hello'; // ?
alert('a: ' + value.name);
}
var value = {name: 'mengyunzhi'};
a(value); // ★
alert(value.name); // ?
```

如上所示:
* ? a方法接收了`value`值后,將其`name`變更為`hello`。
* ? 原來的`value`上的`name`已經由`mengyunzhi`被成功的變更為了`hello`。也就是說:方法`a`中對自己變量`value`的改變直接影響到了原變量的值。
之所以會這樣的原因是:在★進行`a`的調用時,將`value`的**地址(引用、指針)傳遞給了`a`,而非`value`的值**。這種在方法調用時值的傳遞方式就是引用傳遞。
## 二者區別
值傳遞與地址傳遞像極了上機實驗中遇到難題時的兩種解決方式。在上機實驗中遇到自己解決不了的問題是常有的事,而向大牛求助應該是最簡單最有效的一種方式。在求助的過程中按性別的不同,大概可以分為兩種求救模式。
### 男生向大牛求助
```
某男生:大牛哥,這是我的代碼,能幫忙看看錯哪里了嗎?
大牛: 各種修改,各種DEBUG。
5分鐘后
大牛:請修改第35行,第75行....
```
這就是最典型的值傳遞,我們把自己的代碼傳給了大牛。大牛接收代碼后進代碼進行的修改并不會影響我們計算機上的源碼。這本就是擁有相同內容的**兩份**代碼。也就是:值傳遞就是最普通的`復制`+`粘貼`,經過此操作后1個變量變成了2個,對任何一個變量的修改都不會影響另外一個。
上述過程中。
### 女生向大牛求助
情景一:
```
某女生:大牛哥能幫幫我嗎?
大牛:請求控制對方的電腦
某女生:接受控制
大牛:修改對方的代碼并最終完成上機實驗。
```
情景二:
```
某女生:大牛哥能幫幫我嗎?
大牛:你是第幾排第幾號
某女生:3排5號 ?
大牛:稍等馬上就來 ?
...
```
* ? 女生向大牛傳遞了一個地址
* ? 大牛將按地址找到該女生所在的電腦
而這便是現實生活的引用傳遞(地址傳遞、指針傳遞)。請求大牛幫忙時,直接把自己的計算機的控制權交給了大牛。此時大牛對代碼的修改就實實在在的發生在求助女生的計算機上。也就是說:無論誰在修改,修改的都是一份代碼。
### 總結
值傳遞:在調用時,我的變量還是我的變量,傳遞給你的僅是個`贗品`
地址傳遞:在調用時,我的變量就是你的變量,傳遞給你的就是我擁有的那個`真品`
## 判斷
終于來到最關鍵的點上:什么時候進行是值傳遞、什么時候進行的是引用傳遞呢?
筆者認為這里面的規律可以簡單的歸納為:該語言認為容易復制的變量按值傳遞、不容易復制的變量按引用傳遞。
javascript:該語言認為基本類型是容易被復制的,所以所有的基本類型都是按值進行傳遞的,javascript的基本類型為:`字符串(String)、數字(Number)、布爾(Boolean)、對空(Null)、未定義(Undefined)、Symbol`,除此以外`對象(Object)、數組(Array)、函數(Function)`不屬于基本類型,則選擇按引用傳遞。
java: 該語言的想法和js差不多,也是認為基本類型是容易被復制的,所以按值進行傳遞,而其它的按引用進行傳遞。java的基本類型為:`byte(字節型)、short(短整型)、int(整型)、long(長整型)、float(單精度浮點型)、double(雙精度浮點型)、boolean(布爾型)、char(字符型)`。除此以外其它按地址傳遞。
php:該語言大體和java及js一致,認為基本的類型都是容易被復制的,所以按值傳遞。但有一點稍微不同的是:該語言認為數組也是比較容易復制的,所以數組在傳值時也是按值傳遞的方法,而其它的非基本類型則是按地址傳遞。
c: 該語言相對于上述幾門語言屬于低級語言(無貶低,只是由于它更貼近于直接操作硬件,所以就是這種叫法)。C語言中凡是要進行地址傳遞的,都會明確的在編寫代碼時指出,比較好區分。不同于其它語言的是:即使是主類型C語言也可以選擇按地址傳遞;而對于非主類型,則必然按地址進行傳遞(你無法在C語言中直接傳遞數組)。
# 延伸閱讀
既然已經講到這了,那么借機帶領大家再深入的理解一下。否則當有一天你在較權威的書籍上看到"js沒有引用傳遞,全部是按值傳遞"的論調時,一定會反過來懷疑教程的權威性。
繼續閱讀以前推薦先復習一下[4.6.2](http://www.hmoore.net/yunzhiclub/springboot_angular_guide/1396784)中**圖的認讀**部分。
> 本教程中的理論不見得是正確的,但一定是較于當前的現實情況適用的。因為我們一直認為:"適用的就是最好的!",在學習的過程中一定要把握好學習的邊界。
## JAVASCRIPT與C
在值傳遞的過程中。幾種語言的理論大體是相同的,但細節稍有不同。javascript傳值過程大概如下:
```javascript
var a = function(value1) {
value1.name = 'hello';
alert('a: ' + value1.name);
}
var value = {name: 'meng'};
a(value);
alert(value.name);
```
則相應的執行過程如下:

* ? 變量定義:開辟一塊沒有占用的內存給對象value(非主類型,存引用地址);
* ? 再開辟一塊沒有占用的內存對象value中的屬性name(非主類型,存引用地址);
* ? 最后給name中的值分配連續的內存空間用于存字符串meng。
* ? 調用方法a,傳入value。
* ? 由于value為對象,所以實際上獲取的value對象的地址引用值1232H。
* ? 將value的地址引用值1232H傳給方法a。
* ? 使用接收到的引用值1232H作為對象value1的引用。
* ? 開辟一塊沒有占用的內存給對象value1,該內存存入value1的引用值1232H。
所以執行`value1.name = 'hello'`內存中發生的變化筆者猜測試如下(未經考證):

* ? 要存新的字符串,并要重新劃分一塊內存空間。(C語言是在原2340H位置做覆蓋處理還是如上圖一樣重新開辟一塊內存給`hello`,需要看具體的編碼設計。JS是否重新開辟一塊新的內容未驗證。JAVA是重新開辟一塊新內存)。
* ? 將原name的指向地址變更為新字符串`hello`所在的地址。
大概看清內存中的實際變化后,再重點看下?????。此5項共同決定了在進行引用傳值時,接收方則會把引用的值做為數字變量來處理,復制一個而非直接利用。
## JAVA
java則不然,同樣的代碼,JAVA則是如下分配內存空間的:

在????步的傳值過程中,java向方法中傳入的并不是該變量所在內存單元的**值**,而是該變量所在內存單元的**地址**。這也是為什么有人會說JAVA是**地址**傳遞而js及c是均是**值**傳遞的原因。
## 總結
筆者堅持認為:所有的語言都是有值傳遞及地址傳地遞的。傳過去容易復制的,就會用值傳遞;傳過去不容易復制的,就會用地址傳遞。而是否容易復制取決于偉大的語言創建者們的理解,比如javascript認為字符串是容易復制的,所以采用值傳遞;而java卻認為字符串是不容易復制的,所以就采用了地址傳遞。至于有部分權威的書籍堅持說js沒有址址傳遞,對于初學者而言只會增加我們理解該語言變量傳遞的難度,并無益處。
如果已經充分理解了上面幾張主要的內存圖,相信一定可以很好的理解以下代碼的運行結果:
```
var a = function(value1) { ?
value1 = {name: 'hello'}; ?
console.log('a: ' + value1.name); ?
}
var a1 = function(value1) { ?
value1.name = 'hello'; ?
console.log('a: ' + value1.name);
}
var value = {name: 'meng'}; ?
a(value); ?
console.log(value.name); ?
a1(value); ?
console.log(value.name); ?
```
* ? value的實際值為1232H
* ? 將1232H傳入a,此時value1的值為1232H
* ? 創建了新的對象`{name: hello}`,假設該對象的存儲地址為:1240H,則此時value1的值為1240H
* ? 獲取1240H對應的對象中的name的值
* ? 獲取1232H對應的對象的name的值(value的值仍然為1232H,并未發生變化)。
* ? 將1232H傳入a1,此時value1的值為1232H
* ? 設置1232H對應的對象中的name的值為hello(實際上為hello字符串對應的內存起始地址)
* ? 獲取1232H對應的對象的name的值(value的值還然為1232H,并未發生變化)。
最終結果如下:

# compareFn
再回到選擇組件。正是由于js在對象的判等上采用的是`比較變量所在內存地址的值`的方法,該方法并不能很好的滿足當前現實需求。所以angular為`select`提供了`compareFn`屬性,在決定是否默認選中某個選項時,`select`將使用`compareFn`提供的方法來決定。如果該方法的值返回`true`,則選中;相反返回false則表示不選中。如果未給`select`傳入`compareFn`方法,則`select`將采用默認的js的判斷方法來決定該選中哪個選項。
再來回顧一下該方法:
src/app/core/select/select.component.ts
```
/**
* 比較函數,標識用哪個字段來比較兩個對象是否為同一個對象
* @param t1 源
* @param t2 目標
*/
compareFn(t1: { id: number }, t2: { id: number }) {
return t1 && t2 ? t1.id === t2.id : t1 === t2; // ?
}
```
* ? 如果由外界傳入的klass、以及當前選項(指select里列表中的值)均存在,且兩者的id值相等時,則選中該選項。
舉個例子:
傳入`klass = {id: 1}`,klass列表:`[{id: 1}}, {id: 2}]`。則在依次執行:`compareFn({id: 1}, {id: 1})`,返回true,該項選中;`compareFn({id: 1}, {id: 2})`,返回false,該項不選中。
就到這吧。
- 序言
- 第一章:Hello World
- 第一節:Angular準備工作
- 1 Node.js
- 2 npm
- 3 WebStorm
- 第二節:Hello Angular
- 第三節:Spring Boot準備工作
- 1 JDK
- 2 MAVEN
- 3 IDEA
- 第四節:Hello Spring Boot
- 1 Spring Initializr
- 2 Hello Spring Boot!
- 3 maven國內源配置
- 4 package與import
- 第五節:Hello Spring Boot + Angular
- 1 依賴注入【前】
- 2 HttpClient獲取數據【前】
- 3 數據綁定【前】
- 4 回調函數【選學】
- 第二章 教師管理
- 第一節 數據庫初始化
- 第二節 CRUD之R查數據
- 1 原型初始化【前】
- 2 連接數據庫【后】
- 3 使用JDBC讀取數據【后】
- 4 前后臺對接
- 5 ng-if【前】
- 6 日期管道【前】
- 第三節 CRUD之C增數據
- 1 新建組件并映射路由【前】
- 2 模板驅動表單【前】
- 3 httpClient post請求【前】
- 4 保存數據【后】
- 5 組件間調用【前】
- 第四節 CRUD之U改數據
- 1 路由參數【前】
- 2 請求映射【后】
- 3 前后臺對接【前】
- 4 更新數據【前】
- 5 更新某個教師【后】
- 6 路由器鏈接【前】
- 7 觀察者模式【前】
- 第五節 CRUD之D刪數據
- 1 綁定到用戶輸入事件【前】
- 2 刪除某個教師【后】
- 第六節 代碼重構
- 1 文件夾化【前】
- 2 優化交互體驗【前】
- 3 相對與絕對地址【前】
- 第三章 班級管理
- 第一節 JPA初始化數據表
- 第二節 班級列表
- 1 新建模塊【前】
- 2 初識單元測試【前】
- 3 初始化原型【前】
- 4 面向對象【前】
- 5 測試HTTP請求【前】
- 6 測試INPUT【前】
- 7 測試BUTTON【前】
- 8 @RequestParam【后】
- 9 Repository【后】
- 10 前后臺對接【前】
- 第三節 新增班級
- 1 初始化【前】
- 2 響應式表單【前】
- 3 測試POST請求【前】
- 4 JPA插入數據【后】
- 5 單元測試【后】
- 6 惰性加載【前】
- 7 對接【前】
- 第四節 編輯班級
- 1 FormGroup【前】
- 2 x、[x]、{{x}}與(x)【前】
- 3 模擬路由服務【前】
- 4 測試間諜spy【前】
- 5 使用JPA更新數據【后】
- 6 分層開發【后】
- 7 前后臺對接
- 8 深入imports【前】
- 9 深入exports【前】
- 第五節 選擇教師組件
- 1 初始化【前】
- 2 動態數據綁定【前】
- 3 初識泛型
- 4 @Output()【前】
- 5 @Input()【前】
- 6 再識單元測試【前】
- 7 其它問題
- 第六節 刪除班級
- 1 TDD【前】
- 2 TDD【后】
- 3 前后臺對接
- 第四章 學生管理
- 第一節 引入Bootstrap【前】
- 第二節 NAV導航組件【前】
- 1 初始化
- 2 Bootstrap格式化
- 3 RouterLinkActive
- 第三節 footer組件【前】
- 第四節 歡迎界面【前】
- 第五節 新增學生
- 1 初始化【前】
- 2 選擇班級組件【前】
- 3 復用選擇組件【前】
- 4 完善功能【前】
- 5 MVC【前】
- 6 非NULL校驗【后】
- 7 唯一性校驗【后】
- 8 @PrePersist【后】
- 9 CM層開發【后】
- 10 集成測試
- 第六節 學生列表
- 1 分頁【后】
- 2 HashMap與LinkedHashMap
- 3 初識綜合查詢【后】
- 4 綜合查詢進階【后】
- 5 小試綜合查詢【后】
- 6 初始化【前】
- 7 M層【前】
- 8 單元測試與分頁【前】
- 9 單選與多選【前】
- 10 集成測試
- 第七節 編輯學生
- 1 初始化【前】
- 2 嵌套組件測試【前】
- 3 功能開發【前】
- 4 JsonPath【后】
- 5 spyOn【后】
- 6 集成測試
- 7 @Input 異步傳值【前】
- 8 值傳遞與引入傳遞
- 9 @PreUpdate【后】
- 10 表單驗證【前】
- 第八節 刪除學生
- 1 CSS選擇器【前】
- 2 confirm【前】
- 3 功能開發與測試【后】
- 4 集成測試
- 5 定制提示框【前】
- 6 引入圖標庫【前】
- 第九節 集成測試
- 第五章 登錄與注銷
- 第一節:普通登錄
- 1 原型【前】
- 2 功能設計【前】
- 3 功能設計【后】
- 4 應用登錄組件【前】
- 5 注銷【前】
- 6 保留登錄狀態【前】
- 第二節:你是誰
- 1 過濾器【后】
- 2 令牌機制【后】
- 3 裝飾器模式【后】
- 4 攔截器【前】
- 5 RxJS操作符【前】
- 6 用戶登錄與注銷【后】
- 7 個人中心【前】
- 8 攔截器【后】
- 9 集成測試
- 10 單例模式
- 第六章 課程管理
- 第一節 新增課程
- 1 初始化【前】
- 2 嵌套組件測試【前】
- 3 async管道【前】
- 4 優雅的測試【前】
- 5 功能開發【前】
- 6 實體監聽器【后】
- 7 @ManyToMany【后】
- 8 集成測試【前】
- 9 異步驗證器【前】
- 10 詳解CORS【前】
- 第二節 課程列表
- 第三節 果斷
- 1 初始化【前】
- 2 分頁組件【前】
- 2 分頁組件【前】
- 3 綜合查詢【前】
- 4 綜合查詢【后】
- 4 綜合查詢【后】
- 第節 班級列表
- 第節 教師列表
- 第節 編輯課程
- TODO返回機制【前】
- 4 彈出框組件【前】
- 5 多路由出口【前】
- 第節 刪除課程
- 第七章 權限管理
- 第一節 AOP
- 總結
- 開發規范
- 備用