如何用 es5 寫 const 和 let

genetalks_大資料發表於2019-04-04
前言

無意逛某論壇,看到某位博主當面試官的經歷,說是準備一道簡單的面試題,可是面試者幾乎沒有一個答出來的。我看了下。便是如何用 es5 的方式實現一個 const。我也懵逼了,我去面試肯定也是答不出來的。仔細一想不禁覺得這個題目很妙,於是整理一下如何用 es5 實現一個 const 和 let

實現 let

let 大家應該用的非常熟悉了,定義一個僅作用於該程式碼塊的變數。首先想到的是去 babel 上面線上轉換一下。可是得到的結果竟然是 var。這很不科學,於是我們自己倒騰一下。在 es6 出現以前我們一般使用無限接近閉包的形式或者立即執行函式的形式來定義不會被汙染的變數。我們這也可以做類似的操作。

(function(){var a = 1;console.log(a)})();console.log(a)
複製程式碼

效果不錯,這大概也是使用 es6 的方便之處吧。

實現 const

進入這次的主題,const 該怎麼實現呢?const 宣告一個只讀的常量。一旦宣告,常量的值就不能改變。有什麼方法是可以限制一個值不能發生改變的呢?是的,需要用到 Object.defineProperty。其中有一個屬性是這樣的。writable:當前物件元素的值是否可修改。於是一切都好說的.於 ES5 環境沒有 block 的概念,所以是無法百分百實現 const,只能是掛載到某個物件下,要麼是全域性的 window,要麼就是自定義一個 object 來當容器

var __const = function __const(data, value) {
  window.data = value // 把要定義的data掛載到window下,並賦值value
  Object.defineProperty(window, data, { // 利用Object.defineProperty的能力劫持當前物件,並修改其屬性描述符
    enumerable: false,
    configurable: false,
    get: function () {
      return value
    },
    set: function (data) {
      if (data !== value) { // 當要對當前屬性進行賦值時,則丟擲錯誤!
        throw new TypeError('Assignment to constant variable.')
      } else {
        return value
      }
    }
  })
}
__const('a', 10)
console.log(a)
delete a
console.log(a)
for (let item in window) { // 因為const定義的屬性在global下也是不存在的,所以用到了enumerable: false來模擬這一功能
  if (item === 'a') { // 因為不可列舉,所以不執行
    console.log(window[item])
  }
}
a = 20 // 報錯
複製程式碼

程式碼有點略長,但是還算通透。那。。還有沒有更簡單一點的實現方法呢?至少不要這麼長的程式碼demo了。答案是肯定的,只不過沒有上面例子那麼透徹。我們這次將用到es5的Object.freeze();

var f = Object.freeze({'name':'admin'});
f.name = 'hello'; // 嚴格模式下是會報錯的
f.name; // 列印出admin ,值沒有被改變
複製程式碼

想要的效果還是出來了,可以大概的定義一個常量了。那麼一個新的問題來了,const 真的定義的一定是一個常量嘛?一定是不可變的嘛?我們看看程式碼的結果

const a ={};
a.name='admin';
a.name // admin;
複製程式碼

const實際上保證的,並不是變數的值不得改動,而是變數指向的那個記憶體地址所儲存的資料不得改動。對於簡單型別的資料(數值、字串、布林值),值就儲存在變數指向的那個記憶體地址,因此等同於常量。但對於複合型別的資料(主要是物件和陣列),變數指向的記憶體地址,儲存的只是一個指向實際資料的指標,const只能保證這個指標是固定的(即總是指向另一個固定的地址),至於它指向的資料結構是不是可變的,就完全不能控制了。因此,將一個物件宣告為常量必須非常小心。

帶來的新特性

let,const問世之前,js是沒有塊級作用域這種特性的,只有函式作用域,導致了很多稀奇古怪的bug。這裡就要理解兩個的區別,函式作用域好理解,就是函式{}包裹的區域,其實這裡使用var,let,const都一樣,外部是不能呼叫函式內部的變數的。但是塊級就有區別了,最明顯的就是if(){}。在函式內使用條件判斷,花括號內使用let,const,那麼條件外是訪問不到條件內的變數的,程式碼就會更清晰一點。 ,

總結

生活中真的很少看到這種清新的題目,發生在身邊卻從來沒有注意到。或許這就是別人為什麼是大牛,而我還在這裡寫著業務程式碼的原因吧。善於思考與發揮,然後總結於身,這就是我們現在都缺失的吧。。


*作者簡介: 張栓,人和未來大資料前端工程師,專注於 html/css/js 的學習與開發。

相關文章