從JS底層理解var,const,let

目錄

  • 基本數據類型和引用數據類型
  • 聲明提高
  • var,let,const

基本數據類型和引用數據類型

基本數據類型是按值訪問的,由於能夠操做保存在變量中的實際的值。
引用數據類型的值是保存在內存中的對象,JS不容許直接訪問內存中的位置,因此在操做的時候操做的是對象的引用;所以是引用數據類型是按照引用訪問的。es6

複製變量值

複製基本類型的值數組

var num1 = 5;
var num2 = num1;

num1和num2中的5是徹底獨立的,互不影響
基本類型複製數據結構

複製引用類型函數

var obj1 = new Object();
var obj2 = obj1;
obj1.name = 'lucyStar';
console.log(obj2.name);
// lucyStar

咱們能夠看到,obj1保存了一個對象的實例,這個值被複制到 Obj2中。複製操做完成後,兩個變量實際引用的是同一個對象,改變了其中一個,會影響另一個值
引用類型複製spa

傳遞參數

參數傳遞就跟把函數外部的值複製給函數內部的參數;設計

基本類型傳參3d

function addTen(num) {
    num+=10;
    return num;
}
const count = 20;
const result = addTen(count);
console.log(count);
// 20,沒有變化

console.log(result);
// 30

引用類型傳參指針

function setName(obj) {
    obj.name = 'luckyStar';
    obj = new Object();
    obj.name = 'litterStar'
}
const person = new Object();
setName(person);
console.log(person.name);
// luckyStar

在函數內部修改了參數的值,可是原始的引用仍然保持未變。
實際上,在函數內部重寫 obj時,這個變量引用的就是一個局部對象了。而這個局部對象會在函數執行完畢以後當即銷燬。code

變量提高(hoisting)

爲了更好地解釋聲明提高,下面例子中使用 var 而不是使用 ES6新增的let和const(它們不存在聲明提高)
  1. 下面的代碼輸出什麼
a = 2;
var a;
console.log(a);
// 2

可能有人會認爲是 undefined, 由於 var a 聲明在 a = 2以後,會被從新賦值爲 undefined。但他實際上的輸出結果是 2對象

  1. 下面的代碼輸出什麼
console.log(a);
var a = 2;

可能有人會認爲,因爲變量 a 在使用前沒有先進行聲明,所以會拋出 ReferenceError異常。但實際它的輸出是 undefined

引擎會在解釋JavaScript代碼以前首先會對其進行編譯。編譯階段中一部分工做就是找到全部的聲明,並用合適的做用域將他們關聯起來。

因此正確的思考思路是:包含變量和函數在內的全部聲明都會在任何代碼被執行前首先被處理。

當你看到 var a = 2時,可能會被認爲這是一個聲明。可是 JavaScript實際上會將其當作兩個聲明:var aa = 2; 第一個聲明是在編譯階段進行的。第二個聲明會被留在原地等待執行階段。

因此第一個例子中的代碼會以以下的形式進行處理

var a;

a = 2;
console.log(a);

其中第一部分是編譯,第二部分是執行。

第二個例子會按照如下流程進行處理

var a;

console.log(a);
a = 2;
注意:只有聲明自己會被提高,而賦值或其餘運行邏輯會留在原地。

函數聲明和變量聲明都會被提高,可是函數會首先被提高,而後纔是變量

foo(); // 1

var foo;
function foo(){
    console.log(1);
}
foo = function() {
    console.log(2);
}

// 上面代碼會按照如下流程進行處理

// 函數聲明會提高到變量前面
function foo(){
    console.log(1);
}
var foo;

foo(); // 1
foo = function() {
    console.log(2);
}

雖然重複的 var聲明會被忽略掉,可是出如今後面的函數聲明仍是會覆蓋以前的

foo(); // 3

function foo(){
    console.log(1);
}
var foo = function() {
    console.log(2);
}
function foo() {
    console.log(3);
}

思考一下下面的代碼輸出什麼

var name = 'Tom';
(function() {
    if (typeof name == 'undefined') {
        var name = 'Jack';
        console.log('Goodbye ' + name);
    } else {
        console.log('Hello ' + name);
    }
})();

答案是 Goodbye Jack

改爲下面這樣應該會更容易理解一些

// 去掉下面這行也是同樣的,由於會優先訪問函數做用域內部的變量
// var name = 'Tom';
(function() {
    var name; // 注意這行
    if (typeof name == 'undefined') {
        var name = 'Jack';
        console.log('Goodbye ' + name);
    } else {
        console.log('Hello ' + name);
    }
})();

當即執行函數的中的變量 name 的定義被提高到了頂部,並在初始化賦值以前是 undefined,因此 typeof name == 'undefined'

var,let,const

咱們先來看看,var,let,const 聲明變量的位置
變量位置
能夠看到 let和const聲明的變量在塊級做用域中,不存在變量提高。

// var 的狀況
console.log(a); // 輸出undefined
var a = 2;

// let 的狀況
console.log(b); // 報錯ReferenceError
let b = 2;

let

  1. 聲明的變量能夠被修改。
  2. 要注意暫時性死區(TDZ)

總之,在代碼塊內,使用let命令聲明變量以前,該變量都是不可用的。這在語法上,稱爲「暫時性死區」(temporal dead zone,簡稱 TDZ)

function foo(x = y, y = 2) {
  return [x, y];
}

foo(); // 報錯

由於參數x默認值等於另外一個參數y,而此時y尚未聲明,屬於「死區」。

const

聲明的變量是常量;

const 實際保證的,並非變量的值不變,而是變量指向的那個內存地址所保存的數據不得改動。

對於基本數據類型(數值。字符串。布爾值)。值就保存在變量指向的那個內存地址,所以等同於常量。
但對於引用數據類型主要是對象和數組)。變量指向的內存地址,保存的只是一個指向實際數據的指針。

const 只能保證這個指針是固定的(即便老是指向另外一個固定的地址),至於它指向的數據結構是否是可變的,那就徹底不能控制了。所以,將一個對象聲明爲常量必須很是當心。

const foo = {};
// 爲 foo 添加一個屬性,能夠成功
foo.prop = 123;
foo.prop // 123

// 將 foo 指向另外一個對象,就會報錯
foo = {}; // TypeError: "foo" is read-only

參考

相關文章
相關標籤/搜索