三刷紅寶書之變量和做用域

前言

正如標題所說,這是我第三次翻開紅寶書也就是《 JavaScript 高級程序設計第三版》,不得不說,雖然書有一些年份,不少知識點也不適合現代的前端開發,可是對於想要掌握 JavaScript 基礎的前端新手,亦或是像我同樣想找回曾經遺忘在記憶角落的那些碎片知識,這本書依舊很是的適合,不愧被成爲 "JavaScript 聖經"javascript

本文是讀書筆記,之因此又一次選擇讀這本書還有一個理由,以前都是記的紙質筆記,此次想把它做爲電子版,也算是對以前知識的整理前端

本文篇幅較長,目的是做爲個人電子版學習筆記,我會盡量去其糟粕,取其精華,同時我會添加一些書上未記載但很重要的知識點補充java

上篇在這裏數組

基本類型和引用類型

JavaScript 不容許直接訪問內存中的位置也就是說不能直接操做對象的內存空間函數

JS 只能經過指針(保存在棧中的變量)去操做堆內存中的引用類型的值即對象,若是給變量賦值另外一個保存引用類型的值的變量,實際上只是建立了一個新指針,指向同一個堆內存中的對象post

let obj = {}
let o = obj

obj.a = "123"

console.log(obj) // {a:"123"}
console.log(o)// {a:"123"}
複製代碼

在上一章末尾也提到過,這就會致使經過其中一個變量修改引用類型的值時,會反映到全部指向它的變量學習

而若是是基本類型的值,則會直接建立一個新的副本,意味着二者不會相互影響ui

當了解了這個知識點就能夠知道爲何 JavaScript 會有深拷貝和淺拷貝這 2 個概念了,二者都是做用與引用類型spa

  • 淺拷貝會建立一個新對象,而且將原對象的根屬性和值賦值給新對象,可是對於屬性值還是引用類型的屬性則指向的仍是同一個對象
  • 深拷貝會經過遞歸的方式拷貝每一層屬性,從而使得拷貝後的對象和原對象不會相互影響

擴展運算符和 JSON.stringify 是比較常見的拷貝函數,前者用於淺拷貝,後者用於深拷貝prototype

let arr = [1,2,3]
let shallowArr = [...arr]
let deepArr = JSON.parse(JSON.stringify(arr))
複製代碼

更多關於拷貝的知識點能夠看我另外一篇博客對象深拷貝和淺拷貝

檢測類型

typeof 操做符能夠很簡單的肯定變量是不是基本類型,可是對於引用類型會始終返回 'object' 或 'function'

對於進一步知道變量是哪一種引用類型,須要使用 instanceof 操做符

let arr = []

console.log(typeof arr) // 'object'
console.log(arr instanceof Array) // true 表示它是一個數組的引用類型
複製代碼

對於基本類型的變量使用 instanceof 會返回 false

let str = 'abc'
console.log(str instanceof String) // false
複製代碼

注意這裏 String 並非 string,它是一個字符串的包裝類型,一樣也是引用類型,因爲是基本類型,因此即便是字符串的包裝類型,也會返回 false

可是 instanceof 也有缺陷

let arr = []
console.log(arr instanceof Object) // true 
複製代碼

這裏返回 true,這樣就沒法判斷 arr 變量是數組類型仍是對象類型,致使這樣的緣由是數組類型是繼承自對象類型,因此 instanceof 沒法判斷變量是由子類實例化仍是由父類實例化的,因此又有了第三種解決方案 Object.prototype.toString,它能夠解決 instanceof 沒法判斷子類和父類的問題

let arr = []
console.log(Object.prototype.toString.call(arr)) // "[object Array]"
複製代碼

理想老是美好的,現實老是骨感的,雖然 Object.prototype.toString 解決了具體是那種引用類型的問題,可是又引入了另一個問題

它沒法判斷是基本類型仍是基本包裝類型

let str = 'abc'
let objStr = new String("abc")
console.log(Object.prototype.toString.call(str)) // "[object String]"
console.log(Object.prototype.toString.call(objStr)) // "[object String]"
複製代碼

能夠看到,基本類型和基本包裝類型始終都返回 "[object String]" ,這樣仍沒法區分具體的類型,可是咱們能夠將它和 typeof 結合

const isType = variable => {
    let type = typeof variable
    if(type === 'object' || type === 'function'){
       return Object.prototype.toString.call(variable).match( /\[object (\w+)]/)[1]
    }else{
        return type
    }
}

console.log(isType('123')) // 'string'
console.log(isType(new String('123'))) // 'String'
複製代碼

執行環境及做用域

執行環境有時候也被稱做上下文,每一個函數都有本身的執行環境,執行函數時,會將執行的函數推入全局惟一的環境棧,同時建立變量對象的一個做用域鏈,做用域是一套規定 JS 引擎如何查找變量的規則,它是一種嵌套的結構,層層遞進,造成了鏈

因爲在 innerFunc 中並無變量 a,JS 引擎就會沿着做用域鏈,找到保存在 func 中的變量 a (若是 func 中仍沒有,則會去全局做用域中尋找,再沒有則返回 undefined)

也有一種說法是在建立函數的同時會建立該函數的做用域鏈,在執行函數時,複製當前函數的做用域鏈,並將當前函數的活動對象放入做用域鏈的最前端,咱們來看上述代碼的打印結果

能夠看到在打印 innerFunc 的時候,innerFunc 尚未執行,此時它的內部已經有 2 個做用域造成的做用域鏈了,而在運行時纔將名爲 innerFunc 函數的做用域 (local) 放到做用域鏈的最前端

目前的 JavaScript 有 3 種環境

  • 全局環境
  • 函數環境
  • eval 環境

有 3 種做用域

  • 全局做用域
  • 函數做用域
  • 塊級做用域 (ES6+)

塊級做用域

塊級做用域通俗來講就是花括號中的代碼,在 ES6 以前 JavaScript 沒有塊級做用域,那時只能使用 var 來聲明變量,會有變量提高的效果 (將聲明變量的行爲,提高到當前環境建立時執行)

if(false) {
    var color = "blue"
}

console.log('color' in window) // true
複製代碼

即便不進入 if 語句也會在 window 上建立一個 color 屬性,由於在全局環境建立時,就已經建立 color 變量了,而後再執行代碼

而 ES6 後,使用 let/const 能夠將其包裹的花括號成爲塊級做用域,而且使用 let/const 聲明的變量不會有變量提高

if(false) {
    let color = "blue"
    var otherColor = "red"
}

console.log('color' in window) // false
console.log('otherColor' in window) // true
複製代碼

未完待續

參考資料

《JavaScript 高級程序設計第三版》

《你不知道的JavaScript》

相關文章
相關標籤/搜索