1. 程式人生 > 實用技巧 >一篇文章帶你瞭解JavaScript中的變數,作用域和記憶體問題

一篇文章帶你瞭解JavaScript中的變數,作用域和記憶體問題

1

在JavaScript中的變數分別區分為兩種:

一種為基本型別值,一種為應用型別值。

基本型別值指的是簡單的資料段

引用型別值為可能由多個值組成的物件

引用型別的值是儲存在記憶體中的物件,JavaScript不允許直接操作物件的記憶體空間,實際上操作物件的引用而不是實際物件。

var dada = new Object();undefineddada.name = "dada";"dada"console.log(dada.name);VM158:1 dadaundefined

var da1 = "da1";undefinedda1.age = 12;12console.log(da1.age);VM272:1 undefinedundefined

基本型別的值新增屬性,是不管用的,只能給引用型別的值動態地新增屬性,才是有用的。

2

複製變數值

就是從一個變數向另一個變數複製 基本型別值 和 引用型別值

基本型別的值新增屬性,是不管用的,只能給引用型別的值動態地新增屬性,才是有用的。2複製變數值就是從一個變數向另一個變數複製 基本型別值 和 引用型別值

da1中儲存的值是12,使用da1的值來初始化da2時,da2中也儲存了值12,但是d2中的值12和da1中的值12是完全獨立的。這兩個變數可以參與任何操作互不影響。

我自己是一名從事了多年開發的web前端老程式設計師,目前辭職在做自己的web前端私人定製課程,今年年初我花了一個月整理了一份最適合2019年學習的web前端學習乾貨,各種框架都有整理,送給每一位前端小夥伴,想要獲取的可以關注我並新增我的web前端交流群:600610151,即可免費獲取。

從一個變數向另一個變數複製引用型別的值:

引用型別的值實際上是一個指標,是指向儲存在堆中的一個物件,引用型別的複製,是將指向引用同一個物件,所以改變其中一個變數,另一個變數也會受到影響。

var da3 = new Object();var da4 = da3;da3.name = "dada";console.log(da4.name);

da3和da4指向同一個物件,da3新增name屬性後,da4來訪問這個屬性,因為兩個變數指向同一個物件,所以輸出結果為dada。

3

引數傳遞:

在JavaScript中所有函式的引數都是按值傳遞的,引數按值傳遞的意思,和複製一樣的,把函式外的值傳遞到函式內部。

function addNum(num){ num = num + 1; return num;}var da5 = 12;var result = addNum(da5);console.log(da5);console.log(result);

函式addNum有一個引數num,這個引數實際為函式的區域性變數。呼叫這個函式,變數da5作為引數被傳遞給了這個函式,這個變數的值為12,所以引數num為12在這個addNum()函式中使用。

function setName(obj) { obj.name = "dada";}var da6 = new Object();setName(da6);console.log(da6.name);

檢測型別:

typeof操作符是用來檢測一個變數是否是基本資料型別,如果變數的值為一個物件或null,那麼這個typeof操作符下返回的就是object。

var da7 = "dada";var da8 = 12;var da9;var da10 = null;var da11 = new Object();console.log(typeof da7);console.log(typeof da8);console.log(typeof da9);console.log(typeof da10);console.log(typeof da11);

instanceof操作符,是用來幹什麼的呢?判斷是什麼型別的物件。

// 前提先定義personconsole.log(personinstanceof Object);console.log(person instanceof Array);console.log(personinstanceofRegExp);

注意,所有的引用型別的值都是Object的例項,所以檢測引用型別的值和Object建構函式時,instanceof操作符都是返回true。instanceof操作符檢測基本型別的值,返回的則都是false。因為instanceof檢測的都是什麼型別的物件。

4

作用域:

當代碼在一個環境中執行時,會建立變數物件的一個作用域鏈,這個作用域鏈的用途是 保證對執行環境有權訪問的多有變數和函式的有序訪問。全域性執行環境的變數物件都是作用域鏈中的最後一個物件。

識別符號解析是沿著作用域鏈一級一級地搜尋識別符號的過程。

var da12 = "dada"function changeDa(){ if(da12 === "dada"){  da12 = "da"; }else{  da12 = "da1"; }}changeDa();console.log(da12);

函式changeDa()的作用域鏈包含兩個物件:

它自己的變數物件,和,全域性環境的 變數物件。

它自己的 定義著的arguments物件

var da12 = "dada"function changeDa(){ var anotherDa = "dadada";  function daDa(){  var tempDa = anotherDa;  anotherDa = da12;  da12 = tempDa;  // 可以訪問 tempDa, anotherDa,da12 } // 這裡只能訪問 da12,anotherDa daDa();}// 這裡只能訪問da12;changeDa();

分析執行環境,有3個,一個為全域性環境,一個為changeDa()的區域性環境,一個為daDa()的區域性環境。

全域性環境中有一個變數da12,和一個函式changeDa()。

changeDa()的區域性環境中有什麼?

一個變數anotherDa,一個名為daDa()的函式。這個函式可以訪問全域性變數中的da12。

daDa()的區域性環境中有什麼?

一個變數tempDa,該變數只能在這個環境中訪問。

無論是全域性環境還是changeDa()的區域性環境都無法訪問tempDa。

為什麼內部的daDa()可以訪問其他兩個環境中的所有變數呢?

因為那是它們兩個的環境是它的父執行環境。

內部環境可以通過作用域鏈訪問所有的外部環境,但是外部環境不能訪問內部環境中的任何變數和函式,內部環境都可以向上搜尋作用域鏈,查變數和函式名,不能向下搜尋作用域鏈進入另一個環境。

對於daDa()函式,其中作用域鏈包含3個物件:

daDa()的變數物件,changeDa()的變數物件,全域性變數物件。

過程:

daDa()函式的區域性環境,會先開始搜尋自己的變數物件中的變數和函式名,如果找不到,會向上搜尋上一級的作用域鏈。

對於changDa()中的環境:

它包含兩個物件::一為它自己的變數物件,二為全域性變數物件。

即它不能訪問daDa()函式的區域性環境。

5

執行環境分兩種:

一種為全域性作用域,一種為區域性作用域。

如何理解 try catch 延長了作用域鏈?

with語句和 try catch 都可以延長作用域鏈

with比較好理解,而且一般有效能問題,也不推薦用

try catch 是捕獲Error物件的時候 會新開一個作用域嗎?

還是說 catch的大括號內就是一個能訪問到error物件的塊級作用域?

try中的程式碼捕獲到錯誤以後,會把異常物件推入一個可變物件並置於用域的頭部,在catch程式碼塊內部,函式的所有區域性變數將會被放在第二個作用域物件中,catch中的程式碼執行完,會立即銷燬當前作用域。

什麼叫延長作用域鏈

執行環境(變數物件可謂是它的衍生物)、作用域、作用域鏈

作用域:函式當前執行環境。

作用域鏈:執行環境產生的變數物件構成。 作用域鏈是保證函式在執行時能夠正確訪問需要的變數和函式。

作用域鏈最外層就是全域性作用域

var i = 0;function dada(){    console.log(i);}undefineddada();VM656:3 0undefine

在函式中是沒有存在i的,但是在呼叫這個函式時會返回為0,這是為什麼呢?這就是函式作用域鏈的作用。

延長一: try catch

(function(window){   try{       throw Error("出錯誤了");   }catch(e){      alert(e);  //alert("Error: 出錯誤了")   }   console.log(e);  //undefind})(window);

在執行catch語句塊時,JavaScript自動把其執行環境新增作用域鏈中,但是該語句塊執行完後又自動把該執行環境(變數物件)移除。

alert(e) ==  alert("Error:出現錯誤");console.log(e)  ==  undefined;

IE結果:

alert(e)  =>  alert("Error: 出錯誤了");   console.log(e) =>   object Error: 出錯誤了{description: "出錯誤了",message: "出錯誤了",name: "Error"}

延長二:with

 function da(){    console.log(location.href); } function da(){     with(location){         console.log(href);     } }

兩種方式是等價的:

前提是非嚴格模式下, 因為嚴格模式下不支援 with這種方式。

延長作用域的表現

什麼是作用域鏈?

我的理解就是,根據在內部函式可以訪問外部函式變數的這種機制,用鏈式查詢決定哪些資料能被內部函式訪問。

想要知道js怎麼鏈式查詢,就得先了解js的執行環境。

每個函式執行時都會產生一個執行環境,而這個執行環境怎麼表示呢?

js為每一個執行環境關聯了一個變數物件。環境中定義的所有變數和函式都儲存在這個物件中。

沒有塊級作用域:

if(true) { var da = "dada";}console.log(da);

function add(num1,num2){    var num = num1 + num2;    return num;}undefinedvar result = add(1,2);undefinedconsole.log(result);VM962:1 3undefined

向上查詢:

var da = "dada";function getDa(){ var da = "dadada"; return da;}console.log(getDa());

JavaScript中最常用的垃圾收集方式是標記清除,另一種不太常見的垃圾策略叫做引用計數。

基本型別值和引用型別值:

基本型別值在記憶體中佔據固定的空間,儲存在棧記憶體中,從一個變數向另一個變數複製基本型別的值,會建立這個值的一個副本,引用型別的值為物件,儲存在堆記憶體中。

包含引用型別值的變數實際上包含的並不是物件本身,而是一個指向物件的指標。

typeof操作符判斷的是一個值是哪種基本型別,instanceof操作符判斷的是一個值是哪種引用型別。

執行環境分:

全域性執行環境,函式執行環境。

每次進入一個新的執行環境時,都會建立一個用於搜尋變數和函式的作用域鏈。