Javascript之變量,做用域及內存管理

JavaScript變量及類型檢測

javascript數據類型分爲基本數據類型,引用數據類型和Symbol。javascript

基本數據類型

String,Number,Boolean,undefined,nulljava

引用數據類型

Object
這裏的Object包括帶編號的有序集合Array,包含key/value的無序集合和另外一種特殊對象Function。算法

要點:基本數據類型的值是不可變的,引用數據類型值是可變的
基本數據類型的複製

基本數據類型變量複製是分配新的地址,新值是被複制變量的一個副本,變量之間是獨立的 ,互不影響。數組

var num1 = 5; 
var num2 = num1;

image.png

引用數據類型的複製

引用數據類型複製時候也會分配新的地址,不一樣的是新地址中存儲的是引用數據在堆內存中的指針。複製操做結束後,二者都指向同一個引用地址。所以,修改其中一個會影響另一個。瀏覽器

var obj1 = new Object(); 
var obj2 = obj1; 
obj1.name = "Nicholas"; 
alert(obj2.name); //"Nicholas"

image.png

類型檢測

一、typeof閉包

typeof是操做符不是函數。使用typeof會返回以下值:ide

  • "undefined"———該值沒有定義
  • "boolean" ———布爾值
  • "string"----字符串
  • "function"----函數
  • "number"----數字
  • "object"----對象或者null

image.png

注意:在使用typeof檢測數組和null都會返回object,所以typeof並不能精確判斷某一數據類型。

二、instanceof
若是某個變量是指定引用類型的實例,則instanceof會返回true。固然數組的判斷可使用Array.isArray()。函數

注意:instanceof也並不是徹底可靠,1.因爲變量的原型並非一層不變的,一旦原型被修改,就可能返回false。2.沒法判斷多全局對象,好比在多窗口之間進行原型判斷,多窗口意味着多全局對象,擁有不一樣的內置構造函數,好比[] instanceof window.frames[0].Array會返回false

三、constructor
「javascript中一切都是對象」,全部對象都會在其原型上繼承一個constructor屬性指向其構造函數。
image.pngspa

注意:null和undefined沒有constructor。一樣,對象的constructor也是能夠改變的,好比:
var a=[];
a.constructor=new Number();
console.log(a.constructor);//Number

四、Object.prototype.toString操作系統

Object.prototype.toString會返回一個表示該對象的字符串
Object.prototype.toString.call()

image.png

JavaScript執行環境與做用域

JavaScript執行環境決定了變量或函數是否有權訪問,每一個執行環境都有一個變量對象(variable object),執行環境中全部變量和函數都保存在該對象中,雖然咱們沒法直接訪問該對象,JavaScript解析器執行時會使用到它。某個執行環境全部代碼執行完畢後隨之銷燬。

全局執行環境

全局執行環境是最外圍的一個執行環境,在瀏覽器中,全局執行環境被認爲是window對象,全部變量和函數都是window下的屬性或者方法。

局部執行環境

每一個函數都有本身的局部執行環境,當執行流進入函數時候,函數的執行環境就會被推入到執行棧中,函數執行完畢後就會被彈出執行棧。函數的參數也是局部變量,其優先級高於外部變量。

函數做用域和變量聲明提高

每一個函數內部變量只能在函數體內訪問,函數外是無權訪問的,包括函數的參數。
函數內定義的變量在整個函數體內均可訪問,即便變量聲明在變量使用以後,這就是變量聲明提高。最典型的例子以下:

var a=1;
function test(){
    console.log(a);//1
    a=2;
    console.log(a);//2
}
test();

換一下

var a=1;
function test(){
    console.log(a);//undefined
    var a=2;
    console.log(a);//2
}
test();
for(var i=0;i<10;i++){
  console.log(i);//輸出1~9
}
console.log(i);//輸出10
ES6以前JavaScript沒有塊級做用域

理解這句話能夠看以下例子:

function test(o){
  if(typeof o==='object'){
    var i="test";
    for(var k=0;k<10;k++){
      console.log(k);
    }
    console.log(k)
  }
  console.log(i);
}

test();//undefined
test({});//輸出0~9,10,test

可見,即便加了if條件判斷或者循環,變量i和k均可以在大括號代碼塊外訪問。

做用域鏈

每段JavaScript代碼或者函數都有一個與之關聯的做用域鏈(scope chain),這個做用域鏈是一個對象列表或者鏈表,這組對象定義了這段代碼「做用域」中的變量。
當JavaScript須要查找變量a的時候,它會從鏈中的第一個對象開始查找,若是這個對象包含變量a屬性,則會直接使用該對象中的a屬性,若是不存在,則繼續向上查找第二個對象,若是第二個對象也沒有,則繼續查找下一個,以此類推。若是整個做用域鏈上都沒有a屬性,則會拋出異常。
注意:在JavaScript頂層代碼中,做用域鏈由一個全局對象組成,在函數體內,做用域鏈有2個,一個是定義函數參數和函數局部變量的對象,一個是全局對象,若是函數內找不到某個變量,會繼續在全局對象中查找。

JavaScript內存管理

聲明變量就得在內存中給它分配存儲地址,基本數據類型存儲在棧中,引用數據類型存儲在堆中

內存的生命週期
  1. 分配你所須要的內存
  2. 使用分配到的內存(讀、寫)
  3. 不須要時將其釋放\歸還

image

內存的分配

let myNumber = 23

JavaScript在執行上面代碼時候流程以下:

  1. 爲myNumber定義惟一標識符(identifier);
  2. 在內存中分配一個地址(運行時候分配);
  3. 將值23存儲到分配的地址中。

image

基本數據類型的複製是分配新的地址,是被複制對象的副本,所以會互不干擾;
引用數據類型的複製是堆地址指針的複製,複製後它們指向同一地址,所以修改其中一個會影響另一個。

image.png

image.png

內存的使用
在JavaScript中使用分配的內存意味着在其中讀寫,這能夠經過讀取或寫入變量或對象屬性的值,或者將參數傳遞給函數來實現。

內存釋放
這裏最困難的地方是肯定什麼時候再也不須要分配的內存,它一般要求開發人員肯定程序中哪些地方再也不須要內存的並釋放它。

垃圾收集

引用計數

這是最簡單的垃圾收集器算法。若是沒有引用指向這個對象的時候,這個對象就被認爲是「能夠做爲垃圾收集」。

var o = { 
 a: {
   b:2
 }
}; 
// 兩個對象被建立,一個做爲另外一個的屬性被引用,另外一個被分配給變量o
// 很顯然,沒有一個能夠被垃圾收集


var o2 = o; // o2變量是第二個對「這個對象」的引用
o = 1;      // 如今,「這個對象」的原始引用o被o2替換了

var oa = o2.a; // 引用「這個對象」的a屬性
// 如今,「這個對象」有兩個引用了,一個是o2,一個是oa

o2 = "yo"; // 最初的對象如今已是零引用了
       // 他能夠被垃圾回收了
       // 然而它的屬性a的對象還在被oa引用,因此還不能回收

oa = null; // a屬性的那個對象如今也是零引用了
       // 它能夠被垃圾回收了

循環引用的問題
當遇到循環的時候就會有一個限制。在下面的實例之中,建立兩個對象,而且互相引用,所以就會產生一個循環。當函數調用結束以後它們會走出做用域以外,所以它們就沒什麼用而且能夠被釋放。可是,基於引用計數的算法認爲這兩個對象都會被至少引用一次,因此它倆都不會被垃圾收集器收集。

function f(){
  var o = {};
  var o2 = {};
  o.a = o2; // o 引用 o2
  o2.a = o; // o2 引用 o

  return "azerty";
}

f();
標記清除

JavaScript 中最經常使用的垃圾收集方式是標記清除(mark-and-sweep)。當變量進入環境(例如,在函數中聲明一個變量)時,就將這個變量標記爲「進入環境」。從邏輯上講,永遠不能釋放進入環境的變量所佔用的內存,由於只要執行流進入相應的環境,就可能會用到它們。而當變量離開環境時,則將其標記爲「離開環境」。這個算法由如下步驟組成:

  1. 這個垃圾收集器構建一個「roots」列表。Root是全局變量,被代碼中的引用所保存。在 JavaScript中,「window」就是這樣的做爲root的全局變量的例子。
  2. 全部的root都會被監測而且被標誌成活躍的(好比不是垃圾)。全部的子代也會遞歸地被監測。全部可以由root訪問的一切都不會被認爲是垃圾。
  3. 全部再也不被標誌成活躍的內存塊都被認爲是垃圾。這個收集器如今就能夠釋放這些內存並將它們返還給操做系統。

image

四種常見的JavaScript泄露

1. 全局變量
一個未聲明變量的引用會在全局對象內部產生一個新的變量。在瀏覽器的狀況,這個全局變量就會是window。
function foo(arg) {
   bar = "some text";
}
等同於:
function foo(arg) { window.bar = "some text"; }
2.被遺忘的計時器和回調
setInterval 在 JavaScript 中是常常被使用的。大多數提供觀察者和其餘模式的回調函數庫都會在調用本身的實例變得沒法訪問以後對其任何引用也設置爲不可訪問。 可是在setInterval的狀況下,這樣的代碼很常見
var serverData = loadData();
setInterval(function() {
   var renderer = document.getElementById('renderer');
   if(renderer) {
       renderer.innerHTML = JSON.stringify(serverData);
   }
}, 5000); //每5000ms執行一次

renderer所表明的對象在將來可能被移除,讓部分interval 處理器中代碼變得再也不被須要。然而,這個處理器不可以被收集由於interval依然活躍的(這個interval須要被中止從而表面這種狀況)。若是這個interval處理器不可以被收集,那麼它的依賴也不可以被收集。這意味這存儲大量數據的severData也不可以被收集。

3. 閉包
閉包的特性是內部函數可以訪問外部函數的做用域。
var sayName = function(){
  var name = 'jozo';
  return function(){
    alert(name);
  }
};
var say = sayName(); 
say();

sayName返回了一個匿名函數,該函數又引用了sayName的局部變量name,sayName 調用後變量name應該被回收,可是因爲say繼續引用,致使沒法回收。

小結: 一、JavaScript基本數據類型:string,number,boolean,null,undefined,引用類型,包括Object,Array,function,ES6新增的symbol。 二、判斷數據類型的方法有typeof,instanceof,constructor,Object.prototype.toString。 三、JavaScript分爲全局做用域和局部做用域,做用域鏈向上層層查找。 四、基本數據類型佔據固定大小空間,所以存儲在棧內存中,引用類型佔據空間不肯定,存儲在堆內存中。複製基本數據類型會分配新地址,新舊互不影響,引用類型複製是複製指針,新舊變量會互相影響。 五、JavaScript垃圾回收方法包括引用計數和標記清除。 六、JavaScript常見的內存泄漏包括全局變量,循環引用,計時器,閉包。
相關文章
相關標籤/搜索