基本類型和引用類型javascript
在 JavaScript 中,數據類型可分爲基本類型和引用類型,前端
基本類型有六種:Null,Undefined,String,Boolean,Number,Symbol;java
而引用類型就是傳說中的 Object 了。數組
其中基本類型是按值傳遞,而引用類型的值是按引用訪問的,因此在操做對象時,其實是在操做對象的引用而不是實際的對象 ( ps:在爲對象添加屬性時,操做的是實際的對象 )。瀏覽器
關於基本類型和引用類型的不一樣,大概有如下幾點:閉包
一、引用類型是動態的屬性,而基本類型不是。函數
對於引用類型,咱們能夠爲其添加、刪除屬性和方法,但不能給基本類型的值添加屬性:ui
// 基本類型
var name = 'Fly_001';
name.age = 22;
alert(name.age); // undefined;
// 引用類型
var person = new Object();
person.name = 'Fly_001';
alert(person.name); // 'Fly_001';
複製代碼
二、複製的方式不一樣。spa
若是從一個變量向另外一個變量複製基本類型的值,會將值複製到爲新變量分配的位置上:設計
var num1 = 5;
var num2 = num1;
複製代碼
當使用 num1 的值來初始化 num2 時,num2 中也保存了值5,但該值只是 num1 中 5 的一個副本,兩個變量不會互相影響。
當從一個變量向另外一個變量複製引用類型的值時,傳遞的是一個指針,其指向存儲在堆中的一個對象,在複製結束後,兩個變量實際上將引用同一個對象,改變其中一個變量就會影響另外一個變量:
var obj1 = new Object();
var obj2 = obj1;
obj1.name = 'Fly_001';
alert(obj2.name); // 'Fly_001';
複製代碼
三、傳遞參數的特色。
這是一個容易困惑的點 😖。
ECMAScript 中全部函數的參數都是按值傳遞的。也就是說,把函數外部的值複製給函數內部的參數,就和把值從一個變量複製到另外一個變量同樣。基本類型值的傳遞如同基本類型變量的複製同樣,而引用類型的傳遞,則如同引用類型變量的複製同樣,這一點確實會引發不少小夥伴的爭議,歡迎討論~
在向參數傳遞基本類型的值時,被傳遞的值會被複制給一個局部變量( 即 arguments 對象中的一個元素 )。
在向參數傳遞引用類型的值時,會把這個值在內存中的地址複製給一個局部變量,所以該局部變量的變化會反映到函數的外部:
function addTen(num) {
num += 10;
return num;
}
var count = 20;
var result = addTen(count);
alert(count); // 20,木有變化;
alert(result); // 30
function setNmae(obj) {
obj.name = 'Fly_001';
}
var person = new Object();
setName(person);
alert(person.name); // 'Fly_001';
複製代碼
在上面代碼中咱們建立了一個對象,並將其保存在了變量 person 中。而後,這個對象被傳遞到 setName () 函數中就被複制給了 obj,在這個函數內部,obj 和 person 引用的是同一個對象。
不少小夥伴會認爲該參數是按引用傳遞的,爲了證實對象是按值傳遞的,再看下這個修改過的代碼:
function setName(obj) {
obj.name = 'Fly_001';
obj = new Object();
obj.name = 'juejin';
}
var person = new Object();
setName(person);
alert(person.name); // 'Fly_001';
複製代碼
若是 person 是按引用傳遞的,那麼 person 就會自動被修改成指向其 name 屬性爲 ‘juejin’ 的新對象。但接下來再訪問 person.name 時仍然顯示 ‘Fly_001’,這說明即便在函數內部修改了參數的值,但原始的引用仍保持不變。( 實際上,當在函數內部重寫 obj 時,這個變量引用的就是一個局部對象了,其將在函數執行完畢後當即被銷燬。)
四、檢測類型的操做符不一樣。
alert(typeof 'Fly_001'); // 'string';
alert(typeof []); // 'object';
複製代碼
由於 typeof 操做符的返回值爲 'undefined','string','boolean','number','symbol','object','function' 其中之一。
它能夠很友好地指出某一具體基本類型,而對於引用類型則籠統地返回 'object'( typeof 對 數組、正則、null 都會返回 'object' )。
result = varible instanceof constructor;
複製代碼
若是變量是給定引用類型的實例( 根據它的原型鏈來識別 ),那 instanceof 操做符將會返回 true。
執行環境及做用域
下面聊下 JavaScript 中很重要的一個概念 —— 執行環境。
JS 中每一個執行環境都有一個與之關聯的變量對象,在 Web 瀏覽器中,全局執行環境是 window 對象,所以全部全局變量和函數都是做爲 window 對象的屬性和方法建立的。
某個執行環境中的全部代碼執行完畢後,該環境將會被銷燬,保存在其中的全部變量和函數定義也隨之銷燬,全局執行環境直至網頁或瀏覽器關閉時才被銷燬( 若是存在閉包,狀況又有所不一樣,會在後面幾篇提到 😅,多謝 吳hr 指正)。
每一個函數都有本身的執行環境。當執行流進入一個函數時,函數的環境就會被推入一個環境棧中。而在函數執行以後,棧會將其環境彈出,把控制權返回給以前的執行環境。
var color = 'blue';
function changeColor() {
var anotherColor = 'red';
function swapColors() {
var tempColor = anotherColor;
anotherColor = color;
color = tempColor;
// 這裏能夠訪問 color、anotherColor 和 tempColor;
}
swapColors();
// 這裏能夠訪問 color 和 anotherColor,但不能訪問 tempColor;
}
changeColor();
// 這裏只能訪問 color;
複製代碼
以上代碼共涉及 3 個執行環境:全局環境、changeColor() 的局部環境和 swapColor() 局部環境。其中,內部環境能夠經過做用域鏈訪問全部的外部環境,但外部環境不能訪問內部環境中的任何變量和函數。 這些環境之間的聯繫是線性的、有次序的。每一個環境能夠向上搜索做用域鏈 🔍,以查詢變量和函數名;但任何環境都不能經過向下搜索做用域鏈而進入另外一個執行環境。
雖然執行環境的類型總共只有兩種 —— 全局和局部 (函數),但仍是兩種辦法來延長做用域鏈~ 就是經過 try-catch 語句的 catch 塊和 with 語句。
這兩個語句都會在做用域鏈的前端添加一個變量對象。對 with 語句來講,會將指定的對象添加到做用域鏈中;對於 catch 語句來講,會建立一個新的變量對象,其中包含的是被拋出的錯誤對象的聲明。
JavaScript 沒有塊級做用域常常會致使理解上的困惑 😲。在其它類 C 的語言中,由花括號封閉的代碼塊都有本身的做用域,即執行環境,但在 JavaScript 中卻不是這樣:
if (true) {
var color = 'blue';
}
alert(color); // 'blue';
for (var i = 0; i < 10; i ++) {
// dosomething
}
alert(i); // 10;
複製代碼
使用 var 聲明的變量會自動被添加到最接近的環境中。在函數內部,最接近的環境就是函數的局部環境,若初始化變量時沒有使用 var 聲明,該變量會自動被添加到全局環境。( 建立塊範圍局部變量使用 let 關鍵字更方便 ):
function add(num1, num2) {
var sum = num1 + num2;
return sum;
}
var result = add(10, 20); // 30;
alert(sum); // 'sum is not defined';
複製代碼
在上面代碼中,雖然 sum 從函數中返回了,但在函數外部是訪問不到的。若是省略 var 關鍵字,這時 sum 是能夠訪問到的( 不過在嚴格模式下,初始化未聲明的變量會報 'xxx is not defined' 錯 )。
雖然 js 沒有塊級做用域,但咱們能夠用匿名函數來模仿塊級做用域~,語法格式以下:
(function() {
// 這裏是塊級做用域;
}) ();
複製代碼
將函數聲明包含在一對圓括號裏,表示它其實是一個函數表達式,而緊隨其後的圓括號會當即調用這個函數。實際上就至關於:
var someFunction() {
// 這裏是塊級做用域;
};
someFunction();
複製代碼
同時由於 JavaScript 將 function 關鍵字看成一個函數聲明的開始,後面不能直接跟圓括號,而函數表達式後面能夠跟圓括號,因此將函數聲明加上圓括號轉換成函數表達式。
不管在什麼地方,只要臨時須要一些變量,就可使用私有做用域:
function outputNumbers(count) {
(function () {
for (var i = 0; i < count; i ++) {
alert(i);
}
}) ();
alert(i); // 會致使錯誤,讀取不到 i;
}
複製代碼
由於在匿名函數中定義的任何變量,都會在執行結束時當即銷燬,因此變量 i 只能在循環中使用。
當在某個環境中爲了讀取或寫入而引用一個變量或函數名 ( 標識符 ),必須經過搜索來肯定該它實際表明什麼。
搜索過程從做用域的前端開始,向上逐級查找,若是存在一個局部的變量的定義,則中止搜索,即同名局部變量將覆蓋同名全局變量:
var color = 'blue';
function getColor() {
var color = 'red'; // 局部變量;
return color;
}
alert(getColor()); // 'red';
alert(window.color); // 'blue';
複製代碼
垃圾收集。
JavaScript 具備自動垃圾收集機制,因此開發人員沒必要擔憂內存使用問題,是否是很開森 🤗,但最好仍是瞭解下 😕。
首先咱們來分析函數中局部變量的正常生命週期:局部變量只在函數執行的過程當中存在,函數執行結束後就會釋放掉它們的內存以供未來使用。因此 垃圾收集器必須跟蹤哪些變量有用、哪些變量沒用,具體到瀏覽器的實現有兩個策略:標記清除和引用計數
此乃 JavaScript 中最經常使用的垃圾收集機制。
垃圾收集器在運行的時候會把存儲在內存中的全部變量都加上標記,而後去掉環境中的變量及被環境中的變量引用的變量的標記,
在此以後還有標記的變量將被視爲準備刪除的變量,由於環境中的變量已經沒法訪問到這些變量了。最後垃圾收集器完成內存清除工做,銷燬那些帶標記的值並回收它們所佔用的內存空間。
另外一種出鏡率不高的垃圾收集策略是引用計數。
它主要跟蹤記錄每一個值被引用的次數,當某個值的引用次數爲 0 時,則說明沒有辦法再訪問這個值了,所以就能夠將其佔用的內存空間回收。
但引用計數會存在一個循環引用的問題:
function problem() {
var objA = new Object();
var objB = new Object();
objA.someOtherObject = objB;
objB.anotherObject = objA;
}
複製代碼
也就是說,在函數執行完以後,objA 和 objB 還將繼續存在,所以它們的引用次數永遠不會是 0,假如這個函數被重複屢次調用,就會致使大量內存得不到回收 😱。
爲了不這樣的循環引用問題,最好在不使用它們的時候手動斷開鏈接:
objA.someOtherObject = null;
objB.anotherObject = null;
複製代碼
當垃圾收集器下次運行時,就會刪除這些值並回收它們所佔用的內存。
Tips:一旦數據再也不有用,最好將其設爲 null。
複製代碼
( 此條適合全局變量和全局對象的屬性,由於局部變量會在它們離開執行環境時自動被解除引用 )。
ok,JavaScript 基礎的變量、做用域和垃圾回收咱就先講到這,下一篇會聊聊 JavaScript 面向對象的程序設計和函數表達式。
有贊咱再更吧,多謝多謝~ 🤗