JS 變量松散類型的本質,決定了變量只是在特定時間保存特定值的一個名字而已,而不存在定義某個變量的數據類型的規則,因此變量的值及其數據類型可以在腳本的生命周期內發生改變。
基本類型的值是指簡單的數據段,引用類型的值指由可能有多個值構成的對象。
基本數據類型有5種:string, number,boolean,null,undefined. 它們按值訪問,因此可以操作保存在變量中實際的值。
在賦值給變量時,解析器必須確定這個值是基本類型的值還是引用類型的值。
引用類型的值是保存在內存中的對象。js 不允許直接訪問內存中的位置,也就是不能直接操作對象的內存空間。因此,在操作對象時,是在操作對象的引用,而不是實際的對象。
動態屬性
對基本類型和引用類型的變量值進行操作的方法不同。
對于引用類型的值,可以為其添加、刪除、更改屬性和方法:
var person = new Object();
person.name = "Nicholas";
alert(person.name); //"Nicholas"
alert ()是一個函數。通過函數訪問屬性
如果對象不被銷毀或者這個屬性不被刪除,則這個屬性將一直存在。
基本類型的值卻不能添加屬性。
復制變量值
從一個變量向另一個變量復制基本類型值和引用類型值時,也存在不同。
復制基本類型的值時,會在變量對象上創建一個新值,然后把該值復制到為新變量分配的位置上。
var num1 =5;
var num2 = num1;
復制引用類型的值時,也會將存儲在變量對象中的值復制一份放到為新變量分配的空間中。不同在于,這個值的副本是一個指針,這個指針指向存儲在堆中的一個對象。復制操作結束后,兩個變量引用同一個對象。改變其中一個變量,會影響另一個。
傳遞參數
ECMAScript 中所有函數的參數都是按值傳遞的。
在向參數傳遞基本類型的值時,被傳遞的值會被復制給一個局部變量(即命名參數,arguments對象中的一個元素)。
在向參數傳遞引用類型的值時,會把值在內存中的地址復制給一個局部變量,因此這個局部變量的變化會反映在函數的外部。
https://www.zhihu.com/question/27114726
檢測類型
檢測基本數據類型用 typeof s
如果變量的值是一個對象或null,則會返回object
檢測引用類型的值,則用instanceof constructor
示例: alert(person instanceof Object/Array/RegExp) //true or false
所有引用類型的值都是Obeject的實例。因此在檢測一個引用類型的值和Object構造函數時,instanceof 操作符始終返回true。如果使用instanceof 操作符檢測基本類型的值,則返回false,因為基本類型不是對象。
執行環境及作用域
執行環境,execution context,簡稱環境,定義了變量或函數有權訪問的其他數據,決定了各自的行為。每個執行環境都有一個與之關聯的變量對象(variable object),環境中定義的所有變量和函數都保存在這個對象中。編寫代碼無法訪問這個對象,但解析器在處理數據時會在后臺使用它。
全局執行環境是最外圍的一個執行環境。在web瀏覽器中,全局執行環境是Window對象,所有全局變量和函數都是作為 Window的屬性和方法創建的。某個執行環境中所有代碼執行完畢后,該執行環境被銷毀,保存在其中的所有變量和函數定義也隨之銷毀。
每個函數都有自己的執行環境。當執行流進入一個函數時,函數的環境就會被推入到一個環境棧中,函數執行之后,棧將其環境彈出,把控制權返回給之前的執行環境。ECMAScript程序中的執行流正是由這個方便的機制控制著。
當代碼在一個環境中執行時,會創建變量對象的一個作用域鏈(scope chain)。用途是保證對執行環境有權訪問的所有變量和函數的有序訪問。作用域鏈前端,是當前執行代碼所在環境的變量對象。如果這個環境是函數,則將其活動對象(activation object)作為變量對象。活動對象最開始只包含一個變量,即arguments對象。作用域鏈中下一個變量對象來自包含(外部)環境,再下一個變量對象在來自下一個包含環境,最后是全局執行環境。
標識符解析是沿著作用域鏈一級一級地搜索標識符的過程。
延長作用域鏈
可以在作用域鏈的前端臨時增加一個變量對象,該變量對象會在代碼執行后被移除。
function buildUrl(){
var qs = "?debug=true";
with (location){
var url=href +qs;
}
return url;
}
with 語句接收的是location對象,因此其變量對象中就包含了 location對象的所有屬性和方法,
href實際上是location.href
沒有塊級作用域
在其中c語言中,由花括號封閉的代碼都有自己的作用域,因而支持根據條件來定義變量。比如以下例子中,color會在if語句執行完畢后被銷毀
if(true) {
var color = "blue";
}
alert(color); //"blue"
但在js中,if語句中的變量聲明會將變量添加到當前的執行環境,這里是全局環境。
for (var i=0;i<10;i++){
doSomething(i);
}
alert(i);//10
對于塊級作用域的語言來說,for語句初始化變量的表達式所定義的變量,只會存在于循環的局部環境。而對于JS來說,變量i即使在for循環結束后,也依舊會存在于循環外部的執行環境中。
- 變量聲明
使用var聲明的變量會自動被添加到被接近的環境中,在函數內部,最接近的環境就是函數的局部環境。下面例子中,sum被var聲明,添加到add()函數的局部環境中,因此在全局環境中訪問時,是導致錯誤。這里如果省略var,則sum會被添加到全局環境中。
function add(num1,num2) {
var sum =num1 + num2 ;
return sum;
}
var result = add(10,20);//30
alert(sum); //導致錯誤
訪問局部變量要比訪問全局變量更快,因為不用向上搜索作用域鏈。
垃圾收集
js具有自動垃圾收集機制,不用手動跟蹤內存的使用情況。
函數中的局部變量只在函數執行的過程中存在,因此在函數執行完畢后,對其打上標記,將來回收其內存。
標記清除
mark-and-sweep
當變量進入環境時(例如在函數中聲明一個變量),將這個變量標記為進入環境,而當變量離開環境時,則將其標記為離開環境。
垃圾收集器在允許的時候回給存儲在內存中的所有變量都加上標記,然后去掉環境中的變量以及被環境中變量引用的變量的標記,在此之后,再被加上標記的變量將被視為準備刪除的變量。最后,垃圾收集器完成內存清楚工作,銷毀那些帶標記的值回收其占用的內存空間。
另一種是引用計數
性能問題
當環境中存在256個變量,4096個對象,64K的字符串,任意一種都會觸發垃圾回收機制,但如果環境中一直都有這么多變量存在,那么垃圾回收器會一直工作。
因此,如果垃圾回收器回收的內存分配量低于程序占用內存的15%,說明大部分內存不可被回收,那么把臨界條件翻倍,如果回收的內存高于85%,說明大部分內存該清理了,把臨界值重置為默認值。
管理內存
JS中,分配給web瀏覽器的可用內存數量通常比分配給桌面應用程序的少,處于安全考慮。內存限制問題不僅會影響給變量分配內存問題,還會影響調用棧以及在一個線程中能夠同時執行的語句數量。
因此,為了讓頁面獲得更好的性能,我們需要占用最少的內存,優化內存占用,只為執行中的代碼保存必要的數據。一旦數據不再有用,可將其值設置為null釋放其引用,這個做法叫做解除引用(dereferencing)。一般用于釋放全局變量的內存,因為局部變量在離開執行環境時自動被解決引用。