溫故而知新--JavaScript書摘(一)

前言:

畢業到入職騰訊已經差很少一年的時光了,接觸了不少項目,也積累了不少實踐經驗,在處理問題的方式方法上有很大的提高。隨着時間的增長,越發發現基礎知識的重要性,不少開發過程當中遇到的問題都是由最基礎的知識點遺忘形成,基礎不牢,地動山搖。因此,就再次迴歸基礎知識,從新學習JavaScript相關內容,加深對JavaScript語言本質的理解。日知其所亡,身爲有追求的程序員,理應不斷學習,不斷拓展本身的知識邊界。本系列文章是在此階段產生的積累,以記錄下以往沒有關注的核心知識點,供後續查閱之用。程序員

2017

02/26

apply(參數爲數組) 、 call (參數需一一列舉)、bind 三者都是用來改變函數的this對象的指向的;apply 、 call 、bind 三者第一個參數都是this要指向的對象,也就是想指定的上下文;apply 、 call 、bind 三者均可以利用後續參數傳參;bind 是返回對應函數,便於稍後調用;apply 、call 則是當即調用 。

02/27

匿名函數:在棧追蹤中不顯示有意義的函數名,難以調試,沒有函數名很差引用自身,缺乏了可讀性和可理解性。
塊級做用域:with、try{} catch(){}、let、const。let:不會有變量提高。

02/28

JS代碼執行分爲兩個階段:編譯階段、執行階段。
包含函數和變量的全部聲明都會在任何代碼被執行前首先被處理,聲明自己會被提高,而賦值或其餘運行邏輯會留在原地。函數聲明和函數表達式不一樣,函數表達式不會總體提高,只會把表達式賦給的變量聲明提高,函數表達式賦值還留在原來位置。
函數聲明和變量聲明都會被提高,可是一個值得注意的細節(這個細節能夠出如今有多個 「重複」聲明的代碼中)是函數會首先被提高,而後纔是變量。  

03/01

閉包使得函數能夠繼續訪問定義時的詞法做用域。不管經過何種手段將內部函數傳遞到所在的詞法做用域之外,它都會持有對原始定義做用域的引用,不管在何處執行這個函數都會使用閉包。
for (var i = 1; i <= 5; i++) {
    (function(j) {
        setTimeout(function timer() {
            console.log(j);
        }, j * 1000);
    })(i);
}
在迭代內使用 IIFE 會爲每一個迭代都生成一個新的做用域,使得延遲函數的回調能夠將新的做用域封閉在每一個迭代內部,每一個迭代中都會含有一個具備正確值的變量供咱們訪問。  
for (let i = 1; i <= 5; i++) {
    setTimeout(function timer() {
        console.log(i);
    }, i * 1000);
}

03/02

模塊有兩個主要特徵:(1)爲建立內部做用域而調用了一個包裝函數;(2)包裝函數的返回 值必須至少包括一個對內部函數的引用,這樣就會建立涵蓋整個包裝函數內部做用域的閉 包。
主要區別:詞法做用域是在寫代碼或者說定義時肯定的,而動態做用域是在運行時肯定的。(this也是!)詞法做用域關注函數在何處聲明,而動態做用域關注函數從何處調用。 動態做用域並不關心函數和做用域是如何聲明以及在何處聲明的,只關心它們從何處調用。換句話說,做用域鏈是基於調用棧的,而不是代碼中的做用域嵌套。

03/03

ES6 中的箭頭函數並不會使用四條標準的綁定規則,而是根據當前的詞法做用域來決定 this,具體來講,箭頭函數會繼承外層函數調用的 this 綁定(不管 this 綁定到什麼)。這 其實和 ES6 以前代碼中的 self = this 機制同樣。 簡單來講,箭頭函數在涉及 this 綁定時的行爲和普通函數的行爲徹底不一致。它放棄了所 有普通 this 綁定的規則,取而代之的是用當前的詞法做用域覆蓋了 this 原本的值。

03/04

須要明確的是,this 在任何狀況下都不指向函數的詞法做用域。在 JavaScript 內部,做用 域確實和對象相似,可見的標識符都是它的屬性。可是做用域「對象」沒法經過 JavaScript 代碼訪問,它存在於 JavaScript 引擎內部。  以前咱們說過 this 是在運行時進行綁定的,並非在編寫時綁定,它的上下文取決於函數調 用時的各類條件。this 的綁定和函數聲明的位置沒有任何關係,只取決於函數的調用方式。  this 既不指向函數自身也不指向函數的詞法做用域,this 其實是在函數被調用時發生的綁定,它指向什麼徹底取決於函數在哪裏被調用。

03/06

Object.create(null) 和 {} 很 像, 但  並 不 會 創 建 Object. prototype 這個委託,因此它比 {}「更空」。
typeof null == 「object」 :原理是這樣的,不一樣的對象在底層都表示爲二進制,在 JavaScript 中二進制前三位都爲 0 的話會被判 斷爲 object 類型,null 的二進制表示是全 0,天然前三位也是 0,因此執行 typeof 時會返回「object」。  

03/07

若是你試圖向數組添加一個屬性,可是屬性名「看起來」像一個數字,那它會變成一個數值下標(所以會修改數組的內容而不是添加一個屬性)。 
對於 JSON 安全(也就是說能夠被序列化爲一個 JSON 字符串而且能夠根據這個字符串解析出一個結構和值徹底同樣的對象)的對象來講,有一種巧妙的複製方法:  var newObj = JSON.parse( JSON.stringify( someObj ) );  
要注意有一個小小的例外:即使屬性是 configurable:false,咱們仍是能夠把 writable 的狀態由 true 改成 false,可是沒法由 false 改成 true。
不變性:
  • 1. 對象常量 : 結合 writable:false 和 configurable:false 就能夠建立一個真正的常量屬性(不可修改、 重定義或者刪除)。
  • 2. 禁止擴展若是你想禁止一個對象添加新屬性而且保留已有屬性, 可 以使用 Object.prevent Extensions(..)。
  • 3. 密封 Object.seal(..) 會建立一個「密封」的對象,這個方法實際上會在一個現有對象上調用 Object.preventExtensions(..) 並把全部現有屬性標記爲 configurable:false。
  • 4. 凍結 Object.freeze(..) 會建立一個凍結對象,這個方法實際上會在一個現有對象上調用 Object.seal(..) 並把全部「數據訪問」屬性標記爲 writable:false,這樣就沒法修改它們 的值。
在 ES5 中可使用 getter 和 setter 部分改寫默認操做,可是隻能應用在單個屬性上,沒法應用在整個對象上。getter 是一個隱藏函數,會在獲取屬性值時調用。setter 也是一個隱藏函數,會在設置屬性值時調用。  

03/08

「可枚舉」就至關於「能夠出如今對象屬性 的遍歷中」。  
更增強硬的方法來進行判 斷:Object.prototype.hasOwnProperty. call(myObject,"a"),它借用基礎的 hasOwnProperty(..) 方法並把它顯式綁定到 myObject 上。  
propertyIsEnumerable(..) 會檢查給定的屬性名是否直接存在於對象中(而不是在原型鏈 上)而且知足 enumerable:true。
Object.keys(..) 會返回一個數組,包含全部可枚舉屬性,Object.getOwnPropertyNames(..) 會返回一個數組,包含全部屬性,不管它們是否可枚舉。
遍歷數組下標時採用的是數字順序(for 循環或者其餘迭代器),可是遍歷對象屬性時的順序是不肯定的,在不一樣的 JavaScript 引擎中可能不同。所以, 在不一樣的環境中須要保證一致性時,必定不要相信任何觀察到的順序,它們是不可靠的。

03/09

使用 for..in 遍歷對象時原理和查找 [[Prototype]] 鏈相似,任何能夠經過原型鏈訪問到 (而且是 enumerable)的屬性都會被枚舉。使用 in 操做符來檢查屬性在對象 中是否存在時,一樣會查找對象的整條原型鏈(不管屬性是否可枚舉)。 所以,當你經過各類語法進行屬性查找時都會查找 [[Prototype]] 鏈,直到找到屬性或者查找完整條原型鏈。
全部普通的 [[Prototype]] 鏈最終都會指向內置的 Object.prototype。因爲全部的「普通」 (內置,不是特定主機的擴展)對象都「源於」(或者說把 [[Prototype]] 鏈的頂端設置爲) 這個 Object.prototype 對象,因此它包含 JavaScript 中許多通用的功能。
若是 foo 不直接存在於 myObject 中而是存在於原型鏈上層時 myObject.foo = "bar" 會出現的三種狀況:
  • 1. 若是在 [[Prototype]] 鏈上層存在名爲 foo 的普通數據訪問屬性而且沒有被標記爲只讀(writable:false),那就會直接在 myObject 中添加一個名爲 foo 的新 屬性,它是屏蔽屬性。
  • 2. 若是在 [[Prototype]] 鏈上層存在 foo,可是它被標記爲只讀(writable:false),那麼 沒法修改已有屬性或者在 myObject 上建立屏蔽屬性。若是運行在嚴格模式下,代碼會 拋出一個錯誤。不然,這條賦值語句會被忽略。總之,不會發生屏蔽。
  • 3. 若是在 [[Prototype]] 鏈上層存在 foo 而且它是一個 setter,那就必定會 調用這個 setter。foo 不會被添加到(或者說屏蔽於)myObject,也不會從新定義 foo 這 個 setter。 若是你但願在第二種和第三種狀況下也屏蔽 foo,那就不能使用 = 操做符來賦值,而是使 用 Object.defineProperty(..)來向 myObject 添加 foo。

03/10

Foo.prototype 默認有一個公有而且不可枚舉的屬性 .constructor,這個屬性引用的是對象關聯的函數。 能夠看到經過「構造函數」調用 new Foo() 建立的對象也有一個 .constructor 屬性,指向 「建立這個對象的函數」。 在普通的函數調用前面加上 new 關鍵字以後,就會把這個函數調用變成一個「構造函數 調用」。實際上,new 會劫持全部普通函數並用構造對象的形式來調用它。 換句話說,在 JavaScript 中對於「構造函數」最準確的解釋是,全部帶 new 的函數調用。 這是一個很不幸的誤解。實際上,.constructor 引用一樣被委託給了 Foo.prototype,而 Foo.prototype.constructor 默認指向 Foo。 Foo.prototype 的 .constructor 屬性只是 Foo 函數在聲明時的默認屬性。若是 你建立了一個新對象並替換了函數默認的 .prototype 對象引用,那麼新對象並不會自動獲 得 .constructor 屬性。
function Foo() { /* .. */ }
Foo.prototype = { /* .. */ }; // 建立一個新原型對象
var a1 = new Foo();
a1.constructor === Foo; // false!
a1.constructor === Object; // true!
委託給委託鏈頂端的 Object.prototype。這個對象 有 .constructor 屬性,指向內置的 Object(..) 函數。
Bar.prototype = Object.create( Foo.prototype )
若是使用內置的 .bind(..) 函數來生成一個硬綁定函數的話, 該函數是沒有 .prototype 屬性的。在這樣的函數上使用 instanceof 的話, 目標函數的 .prototype 會代替硬綁定函數的 .prototype。  
Foo.prototype.isPrototypeOf(a);
b.isPrototypeOf(c);
.__proto__ 實際上並不存在於你正在使用的對象中 ,存在於內置的 Object.prototype 中(它是不可枚舉的)。  

03/11

Object.create(null) 會建立一個擁有空( 或者說 null)[[Prototype]] 連接的對象,這個對象沒法進行委託。這些特殊的空 [[Prototype]] 對象一般被稱做「字典」,它們徹底不會受到原型鏈的干擾,所以很是適合用來存儲數據。
對象的連接被稱爲「原型鏈」,  JavaScript 中這個機制的本質就是對象之間的關聯關係。
委託行爲意味着某些對象(XYZ)在找不到屬性或者方法引用時會把這個請求委託給另外一 個對象(Task)。  
對象關聯能夠更好地支持關注分離(separation of concerns)原則,建立和初始化並不須要 合併爲一個步驟。
// 讓 Foo 和 Bar 互相關聯
Foo.isPrototypeOf(Bar); // true
Object.getPrototypeOf(Bar) === Foo; // true
行爲委託認爲對象之間是兄弟關係,互相委託,而不是父類和子類的關係。JavaScript 的 [[Prototype]] 機制本質上就是行爲委託機制。也就是說,咱們能夠選擇在 JavaScript 中努 力實現類機制,也能夠擁抱更天然的 [[Prototype]] 委託機制。
對象關聯(對象以前互相關聯)是一種編碼風格,它倡導的是直接建立和關聯對象,不把它們抽象成類。對象關聯能夠用基於 [[Prototype]] 的行爲委託很是天然地實現。
首先,你可能會認爲 ES6 的 class 語法是向 JavaScript 中引入了一種新的「類」機制,其 實不是這樣。class 基本上只是現有 [[Prototype]](委託!)機制的一種語法糖。 也就是說,class 並不會像傳統面向類的語言同樣在聲明時靜態複製全部行爲。
class 語法沒法定義類成員屬性(只能定義方法)。class 語法仍然面臨意外屏蔽的問題。class 很好地假裝成 JavaScript 中類和繼承設計模式的解決方案,可是它實際上起到了反做 用:它隱藏了許多問題而且帶來了更多更細小可是危險的問題。  
相關文章
相關標籤/搜索