《編寫可維護的JavaScript》- 讀書筆記

引用

這是我刷完的第一本書。萬事開頭難,總算是在2017年02月09日開了一個好頭。

這篇總結是爲了記錄在讀這本書的過程中所遇到的好的知識點和思想,以及我在實際工作中結合作者的想法所做的一些實踐和讀書的收穫。

這本書從兩個個方面(風格和實踐)來講述如何去寫維護性高、可讀性高和高效的JavaScript代碼。

編程風格

“編程風格”是指在長期編寫代碼的過程中,養成關於編寫方式、代碼結構和代碼可讀性等等方面的習慣。

統一編程風格的好處是:

  1. 由於編程風格一致,團隊合作過程中,看其他人的代碼就像在看自己寫的代碼,提高了可讀性;
  2. 由於編程風格一致,當遇到自己感到奇怪的代碼會立刻發現其可能出現的問題。

格式化

格式化是編程風格指南的核心規則,這些規則最直接的體現在於代碼的水準。

1. 縮進層級

目前主要有兩種主張縮進的方式:

    1.使用製表符進行縮進:
        每一個縮進都是用單獨的 `Tab(製表符)` 來表示。

    2.使用空格符進行縮進:
        即每一個縮進都是用單獨的 `space(空格)` 來表示

二者各自有各自的缺點:

  1. 製表符:每個系統和編輯器對製表符的解釋不一致,雖然對於編輯器來說可以單獨的對製表符進行設置,但是這些差異還是會帶來一些困惑。
  2. 空格:對於單個開發者來說,使用一個沒有配置好的文本編輯器創建格式化的代碼的方式非常原始。

個人非常傾向於使用 “Tab” 來表示行內縮進的。

2. 語句結尾

對於 JavaScript 代碼來說,在語句的結尾可以寫分號(;),亦可以不寫分號。

這是有賴於分析器的自動分號插入(Automatic Semicolon Insertion, ASI),ASI會自動的尋找代碼中應當使用分號但實際沒有分號的位置,並插入分號。

個人推薦的是在每句的結尾如果可以加上分號,那麼一定加上分號,不止防止了低級錯誤的發生,也增加了代碼的可讀性,因爲會告訴正在看代碼的人“這一句到這就結束了”。

3. 行的長度

目前關於代碼中每行的長度是由爭議的,前段時間看過阮一峯老師的一篇關於CPL爲什麼會有在72和80這兩個選項,很受啓發。

對於我來說,儘可能讓一行代碼的字符數少於72,如果72個字符滿足不了的話,就儘可能的少於80個字符,若這樣還是滿足不了,那麼就儘量的減少吧,這種情況雖然極少數(可能和我水平有關),如果出現了也實在是沒有辦法。

4. 換行

當一行的代碼長度達到了單行最大字符數的限制時,就需要手動將一行拆成兩行(這種情況居多)。通常需要在運算符後換行,下一行增加兩個層級的縮進。

請記住,一定要將運算符置於行的結尾,這樣ASI就不會再該行的結尾插入分號。

5. 空行

在編碼規範中,空行是常常被忽略的一個方面。在書中,介紹了關於一段代碼應該是一系列可讀的段落(像文章一樣),而不是一大堆揉在一起的連續的文本。

因此書中建議在以下地方插入空行(即回車):

  1. 方法之間;
  2. 在方法中的局部變量和第一條語句之間;
  3. 在多行或單行註釋之前;
  4. 在方法內的邏輯片段之間插入空行,提高可讀性;

6. 命名

計算機科學只存在兩個難題: 緩存失效和命名 —— Phil Karlton

我在寫代碼的過程中,其實是十分重視變量名的,我看過一句話大概的意思是:再好的註釋,也比不過天然自帶註釋的命名。但是由於自身的英語詞彙量少的可憐,因此變量的名稱翻來覆去也想不出多少個花樣。。。

目前存在兩種主要的命名方式:駝峯式 和 下劃線,其中駝峯式又包括小駝峯(Camel Case)和大駝峯(Pascal Case),二者主要的區別在於大駝峯要求首字母大寫,而小駝峯要求首字母小寫。下劃線的命名方式是每個單詞之間用“_”來鏈接,同時所有的字母均小寫。

個人比較習慣的命名方式是:對於變量,若是局部的變量,使用小駝峯的方式;若是全局變量和靜態變量,使用所有字母大寫加上下劃線的方式。函數的命名習慣是:構造函數使用首字母大寫的小駝峯式,普通函數使用小駝峯的方式命名

7. 直接量

什麼是直接量?JavaScript 中包含一些類型的原型值: 字符串、數字、布爾值、nullundefined。同樣的,也包含數組直接量[]和對象直接量{}

7.1 字符串

字符串可以用單括號''或者是雙括號""括起來,在這裏作者強調的是,不論是單括號還是雙括號,在你的代碼中,一定要保持相同的括號風格,即在代碼中,儘量在以一種方式的括號表示字符串。

字符串還需要關注的另一個問題就是多行字符串。作者建議,如果字符串如果太長,那麼使用多行字符串代替過長的字符串。例如:

var longString = 'Here`s the story, of a man ' + 
                 'named Brady';
7.2 數字

在 JavaScript 中只需要關心數字的一個問題,即如果一個數字是浮點型,一定要寫全小數點後面幾位。萬萬不可只有小數點,省略了小數點後面的小數部分,如10.

7.3 null

null 在 JavaScript 中是特殊值,但是它很容易和undefined搞混。作者在書中介紹說,在以下幾種場景中可以使用null

  • 用來初始化一個變量,這個變量可能被賦值爲一個對象;
  • 用來和一個已經初始化的變量比較,這個變量可以是也可以不是一個對象;
  • 當函數的參數期望是對象時,作爲參數傳入;
  • 當函數的返回值期望是對象時,用作返回值傳出。

還有一些場景中不應當使用null

  • 不要使用 null 來檢測是否傳入了某個參數;
  • 不要用 null 來檢測一個未初始化的變量。
7.4 undefined

undefined 也是一個特殊值。其中最讓人感到困惑的是 null === undefined 的結果是 true

那些沒有被初始化的變量都有一個初始值,即 undefined,表示這個變量等待被賦值。

在我的實踐中,可以通過判斷一個參數是否是 undefined 來判斷這個參數是否在函數被調用是被傳入。同時,undefined 通常來說,只與 typeof 運算符一起使用,用以判斷一個變量是否被賦值或者存在。

7.5 對象與數組直接量( {} & [] )

創建對象或者數組的最流行也是最常見的一種方式是使用對象或數組的直接量,在直接量中直接寫出所有的屬性。例如:

// 使用對象直接量
var book = {
    title: 'Maintaintable JavaScript',
    author: 'Nicholas C. Zakas'
}

// 使用數組直接量
var colors = [ 'red', 'blue', 'green' ];
var numbers = [1, 2, 3 ,4, 5];

註釋

註釋共分爲兩種方式:單行註釋和多行註釋;

對於註釋,我的習慣是:

單行註釋:雙斜槓 // 後面插入一個空格,獨佔一行並且在此註釋之前插入一個空行,縮進和要註釋的代碼的縮進一致。
多行註釋:總是會出現在將要描述的代碼段之前,縮進與要描述的代碼段一致,同時註釋之前插入一個或兩個空行。

語句和表達式

所有語句都應當使用花括號,包括:

if/for/while/do...while.../try...catch...finally

1. 花括號的對齊方式

有兩種風格:

  1. 將左花括號放置在塊語句中第一句代碼的末尾;
  2. 將左花括號放置於塊語句首行的下一個行。
// 第一種
if (condition) {
    doSomeThings();
}

// 第二種
if (condition)
{
    doSomeThings();
}

2. 塊語句間隔

共三種方式:

  1. 在語句名、圓括號和左花括號之間沒有空格間隔;
  2. 在括左圓括號之前和右圓括號之後各添加一個空格;
  3. 在左圓括號後和右圓括號前各添加一個空格;
// 第一種
if(condition){
    doSomeThings();
}

// 第二種
if (condition) {
    doSomeThings();
}

// 第三種
if ( condition ) {
    doSomeThings();
}

我個人比較傾向於第二種方式。

3. for-in 循環

for - in循環是用來遍歷對象屬性的。

但是,for - in循環有一個問題,就是它不僅遍歷對象的實例屬性,同樣的還遍歷從原型繼承來的屬性。因此,對於這個問題,最好使用 hasOwnProperty() 方法來爲 for - in 循環過濾出實例屬性。

var props;

for (var item in props) {
    if (props.hasOwnProperty(item)) {
        doSomeThings(props[item]);
    }
}

我在編碼過程中,如果使用 for-in 循環都會使用 hasOwnProperty() 方法。

需要注意的是: for-in 循環是用來遍歷實例對象,不能用它來遍歷數組,這會造成一些潛在的問題。

既然如此,就可以用這個特性來判斷一個對象是否爲空:

const isEmptyForObject = (obj) => {
    for(let item in obj) {
        return false;
    }

    return true;
}

變量、函數和運算符

1. 變量

說到變量,首先對於 JavaScript 的變量要明白一個理論:變量提升

當創建變量時,需要用到 varletconst 關鍵字,這些關鍵字意義和作用並不相同:

  • var:是 JavaScript 最經典的創建變量的關鍵字,它定義的變量可以修改並且它並不存在“塊”的作用域,也就是說在ES6之前,函數內部定義的變量,在其外部依舊可以調用:
function change () {
  var demo = 4;
  console.log(demo);
}

change();           // 4
console.log(demo)   // demo = 4
  • let:是ES6新添的創建變量的關鍵字,它定義的變量也可以修改,但是它強調“塊作用域”的概念,即在函數內部使用let並修改,對函數外部沒有影響:
function change () {
  let demo = 4;
  console.log(demo);
}

change();           // 4
console.log(demo)   // demo is not defined
  • const:同樣是ES6特有的創建變量的關鍵字,不過它定義的變量除了初始化以外,是不可以修改賦值的。
const demo = 4;
demo = 5;       // Assignment to constant variable.

請注意:
當我在創建變量的時候,習慣於將所有的變量放到局部代碼塊的首行,並且很喜歡將所有的 var/let/const 語句合併成一句:

var a = 5,
    b = 6;

let c = 7,
    d = 8;

const e = '123',
      f = 'afdasdf'; 
變量提升

在 JavaScript 中,函數變量聲明都將被提升到函數的最頂部。

在 JavaScript 中,變量可以在使用後聲明,也就是變量可以先使用再聲明,即:在函數內部任意地方定義變量和在函數定義變量是完全啊一樣的。例如:

// 提升前(源代碼)
function doSomething () {
    var result = 10 + value;
    var value = 10;

    return result;
}

// 變量提升
function doSomething () {
    var result, value;
    result = 10 + value;
    valut = 10;

    return result;
}

doSomething()   // NaN (not a number)

需要注意的是,只有變量的聲明纔會被提升,至於變量的初始化,並不會被提升。

我的習慣是,總是將局部變量的定義作爲函數內第一條語句。

2. 函數

和變量聲明一樣的,函數的聲明也會被變量提升機制提升到當前作用域下的頂部。

// 函數聲明提升之後:
function doSomethingWithItems (items) {
    var i, len,
        value = 10,
        result = value + 10;

    function doSomething (item) {
        // todo...
    }

    for (i = 0; len = item.length; i < len; i++) {
        doSomething(items[i])
    }
}

同樣需要注意的是,請看下面的栗子:

if (condition) {
    function doSomething (item) {
        alert("HI!");
    }
} else {
    function doSomething (item) {
        alert("YO!");
    }
}

這段代碼的實際效果是 alert 彈窗裏面的內容是 “YO!”。這也是由於函數聲明提升造成的,上面這段代碼轉換爲下面這段代碼:

// 被第二個 doSomething 覆蓋
function doSomething () {
   alert("HI!");
};

function doSomething () {
   alert("YO!");
}

if (condition) {
    doSomething();
} else {
    doSomething();
}

這也是大多數瀏覽器都會自動運行第二個聲明的原因。

函數調用的風格是:在函數名和左括號之間沒有空格。

在 JavaScript 中允許聲明匿名函數,並將匿名函數賦值給變量或者是對象的屬性。

var doSomething = function () {
    // todo...
}

這種匿名函數在函數的最後加上一對括號可以立即執行並返回一個值,然後將這個值賦值給變量或者對象的屬性。

// 這種寫法並不推薦,只是爲了展示作用
var value = funciton () {
    return {
        message: 'hi'
    };
}();

這種模式的問題在於,會讓人誤認爲將一個匿名函數賦值給了這個變量。除非讀完了完整的代碼。更好的做法是,爲了能讓立即執行的函數能夠被一眼看出來,用一對括號將立即執行的函數包裹起來:

var value = (funciton () {
    return {
        message: 'hi'
    };
}());

3. 相等

由於 JavaScript 具有強制類型轉換機制,因此在 JavaScript 中判斷相等的操作是很微妙的。

發生強制類型轉換最常見的場景,就是使用了判斷相等運算符 ==!=。如果發生了強制類型轉換,那麼對於判斷變量的類型或者是否相等就變得尤爲的困難。

在使用 ==!= 的情況下:

  1. 在判斷數字和字符串時,字符串會首先轉換爲數字,然後進行比較;
  2. 在判斷數字與布爾值時,布爾值會首先轉換爲數字,然後進行比較;
  3. 若其中一個值是對象,則會先調用對象的 valueOf() 方法,得到原始類型。若沒有定義 valueOf(), 則調用 toString()
  4. 需要注意的是,如果 nullundefined 相比較,這個兩個特殊值是相等的。
// 比較數字 5 和字符串 5
console.log(5 == '5');          // true

// 比較數字 25 和十六進制的字符串 25
console.log(25 == '0x19');      // true

// 數字 1true
console.log(1 == true);         // true

// 對象
var object = {
    toString: function () {
        return '0x19';
    }
}

console.log(object == 25);      // true

// null & undefined
console.log(null == undefined);

前面說了,如果使用 ==!= 會造成變量之間的類型互相轉換,但是有些時候我們想要嚴格檢查,即兩個變量的類型不同,我們就認爲二者不同,這就需要使用 ===!==,這兩個運算符並不會觸發強制類型轉換。

我的習慣是總是使用 ===!== 進行嚴格檢查。

4. 原始包裝類型

JavaScript 中一個不易被理解且常常被誤解的方面是,這門語言對原始包裝類型的依賴。

JavaScript 有3種原始包裝類型: StringBooleanNumber。每種類型代表全局作用域中的一個構造函數,並分別表示各自對應的原始值的對象。 原始包裝類型的主要作用是讓原始值具有對象般的行爲

var name = 'Nicholas';
console.log(name.toUpperCase());        // NICAHOLAS

儘管 name 是一個字符串,是原始類型不是對象,但是仍然可以使用如 toUpperCase()toLowerCase()等方法,把字符串當成對象來用。

原因是:
在創建一個字符串變量時,JavaScript 引擎創建了 String 類型的新實例,緊跟着就被銷燬了,當在此需要的時候再重新創建另一個實例。

var name = 'Nicholas';
name.author =  true;
console.log(name.author);               // undefined

編程實踐

現如今的前端工程師幾乎人人都在談模塊化,如:AMDCMDUMD等等,就是在解決某一個複雜問題或者一系列的雜糅問題時,依照一種分類的思維把問題進行系統性的分解以之處理。

那麼 AMD / CMD / UMD 的區別是什麼呢?參考資料 Javascript模塊化

首先來解釋下什麼是模塊化,模塊化是指將複雜的系統分解爲多個代碼結構合理可維護性更高可管理的模塊的方式。解耦軟件系統的複雜性,使得不管多麼大的系統,也可以將管理,開發,維護變得“有理可循”。

作爲模塊化的系統必須具有如下三個特點:

  1. 定義封裝的模塊。
  2. 定義新模塊對其他模塊的依賴。
  3. 可對其他模塊的引入支持。

因此基於模塊化,目前在 JavaScript 中出現了一些非傳統開發方式的模式: CommonJS模式、AMD模式、CMD模式和UMD模式。

CommonJS

CommonJS 是用於服務器端的模塊規範,NodeJs 主要採用這種模式。

根據 CommonJs 模式的規範,一個單獨的文件即爲一個模塊。加載模塊使用 require 方法,該方法讀取一個文件並執行,最後返回文件內部的 exports 對象。

// foobar.js

//私有變量
var test = 123;

//公有方法
function foobar () {

    this.foo = function () {
        // do someing ...
    }
    this.bar = function () {
        //do someing ...
    }
}

//exports對象上的方法和變量是公有的
var foobar = new foobar();
exports.foobar = foobar;
//require方法默認讀取js文件,所以可以省略js後綴
var test = require('./boobar').foobar;

test.bar();

從上面可以看出,CommonJS 是同步加載的,只有將其他的依賴都加載完纔可以執行文件本身的內容。如 NodeJs 編寫的服務器代碼,文件一般都是存放在本地硬盤上,加載起來比較快,因此適用於 CommonJS 模式。但是對於瀏覽器來說,下載資源必須要異步的方式,所以就有了 AMD / CMD 解決方案。

AMD(Asynchronous Module Definition)

AMD 模式是異步模塊定義模式,它設計出一個簡潔的寫模塊 API:

define(id?, dependencies?, factory);

第一個參數 id 爲字符串類型,表示了模塊標識,爲可選參數。若不存在則模塊標識應該默認定義爲在加載器中被請求腳本的標識。如果存在,那麼模塊標識必須爲頂層的或者一個絕對的標識。
第二個參數 dependencies 是一個當前模塊依賴的,已被模塊定義的模塊標識的數組字面量(直接量)。
第三個參數 factory 是一個需要進行實例化的函數或者一個對象。

define("alpha", [ "require", "exports", "beta" ], function( require, exports, beta ){
    export.verb = function(){
        return beta.verb();
        // or:
        return require("beta").verb();
    }
});

在不考慮多了一層函數外,格式和 NodeJs 是一樣的:使用 require 獲取依賴模塊,使用 exports 導出 API。

除了define之外,AMD 還保留了一個關鍵字 requirerequire 作爲規範保留的全局標識符,可以實現爲 module loader,也可以不實現。

require([module], callback)

第一個參數 [module] 是一個數組,裏面的成員就是要加載的模塊;
第二個參數 callback 是加載完成這些依賴的模塊之後執行的回調函數。

require(['math'], function(math) {
    math.add(2, 3);
});

CMD(Common Module Definition)

CMD 是 SeaJS 在推廣過程中對模塊定義的規範化產出:

  • 對於依賴的模塊 AMD 是提前執行,CMD 是延遲執行。不過 RequireJS 從2.0開始,也改成可以延遲執行(根據寫法不同,處理方式不通過)。
  • CMD 推崇依賴就近,AMD 推崇依賴前置。
// AMD
define(['./a','./b'], function (a, b) {

    //依賴一開始就寫好
    a.test();
    b.test();
});

// CMD
define(function (requie, exports, module) {

    //依賴可以就近書寫
    var a = require('./a');
    a.test();

    ...
    //軟依賴
    if (status) {

        var b = requie('./b');
        b.test();
    }
});

雖然 AMD也支持CMD寫法,但依賴前置是官方文檔的默認模塊定義寫法。

UMD(Universal Module Definition)

UMD 是 AMD 和 CommonJS 的結合。

AMD 模塊以瀏覽器第一的原則發展,異步加載模塊。
CommonJS 模塊以服務器第一原則發展,選擇同步加載,它的模塊無需包裝。
這迫使人們又想出另一個更通用的模式 UMD(Universal Module Definition)
希望解決跨平臺的解決方案。

UMD 先判斷是否支持 NodeJs 的模塊(exports)是否存在,存在則使用 NodeJs 模塊模式。在判斷是否支持 AMD(define是否存在),存在則使用 AMD 方式加載模塊。

(function (window, factory) {
    if (typeof exports === 'object') {
        module.exports = factory();
    } else if (typeof define === 'function' && define.amd) {
        define(factory);
    } else {

        window.eventUtil = factory();
    }
})(this, function () {
    // this is factory
    // module ...
});

關於這本書的收穫

  • 寫出一個具有兼容性的事件綁定/掛載的方法(原生JS):

    function addListener (target, type, handler) {
    
        if (target.addEventListener) {
            // 判斷 addEventListener 是否存在
            target.addEventListener(type, handler, false);
        } else if (target.attachEvent) {
            // 判斷 attachEvent 是否存在
            target.attachEvent('on' + type, handler);
        } else {
            target['on' + type] = handler;
        }
    
    }
  • 全局 name 實際上是 window 的一個默認屬性,window.name 屬性經常用於框架(frame)和 iframe 的場景中。

  • 瀏覽器處理事件的過程: 事件捕獲 –> 事件目標 –> 事件冒泡;

    事件捕獲:事件(click/mouseenter/mouseleave…)首先發生在 document -> body -> … -> 節點(事件目標);

    事件冒泡:事件到達事件目標之後不會結束,會逐層向上冒泡,直至document對象,跟事件捕獲相反;

    事件委託即利用這2個特性,將事件綁定在父節點中,通過事件捕獲獲得節點,通過冒泡執行事件。避免了對每個節點都進行事件的綁定,節省了勞動力。

  • 事件觸發代碼要與應用邏輯代碼分開,這麼做的好處是應用邏輯代碼可以複用,同時在測試時直接功能測試,而不需要模擬對元素觸發事件來測試,同時不要無限地分發事件對象

    var myApplication = {
        handleClick: function (event) {
            // 阻止 事件默認行爲 和 事件冒泡
            event.preventDefault();
            event.stopPropagation();
    
            // 傳入應用邏輯
            this.showPopup(event.clientX, event.clientY);
        },
    
        showPopup: function (x, y) {
            var popup = document.getElementById('popup');
            popup.style.left = x + 'px';
            popup.style.top = y + 'px';
            popup.className = 'reveal';
        }
    };
    
    addListener(element, "click", function(event) {
        myApplication.handleClick(event);
    })
  • typeof 是運算符;

  • instanceof 也是運算符;
  • 檢測一個對象是否是 Array

    function isArray (arg) {
        if ('function' === typeof Array.isArray) {
            return Array.isArray(arg);
        } else {
            return Object.prototype.toString.call(arg) === '[object Array]';
        }
    }
  • inhasOwnProperty 的區別

    1. 使用 in 的方式來檢測某實例是否具有某屬性時,in 會在其實例屬性中、原型鏈中遍歷查找該屬性;
    2. 使用 hasOwnProperty 的方式檢測時,只會檢測實例屬性,不會檢測其原型鏈;

    若不確定是否存在 hasOwnProperty 可以這麼寫:

    if ('hasOwnProperty' in object && object.hasOwnProperty('xxxx')) {
        // todo...
    }
  • 配置數據需要從代碼中分離開,這麼做的目的防止在修改源代碼的時候引入bug,尤其是對修改一些數據的值從而帶來一些不必要的bug風險,那麼什麼是配置數據呢?配置數據是可發生變更的,而且你不希望因爲有人突然想修改頁面中的展示信息,導致去修改 JavaScript 源碼。

  • JavaScript 的常見可用文件格式有三種:JSON/JSONP/JS格式。

    1. JSON:這是一種很常見的數據格式,使用一組 JavaScript 的數組轉換爲字符串作爲表示格式。
    2. JSONP:是將 JSON 結構用一個函數(調用)包裝起來。
    3. JavaScript:將 JSON 對象賦值給一個變量,這個變量會被程序用到。

    這裏,我習慣的方式是第三種,使用 JavaScript 的格式,同樣的會將所有的配置數據全部存入全局對象中,防止過多的全局變量導致的變量混亂問題。

  • JavaScript 錯誤:

    • 拋出錯誤:
      使用 throw 的操作符,將提供的一個對象作爲錯誤拋出,任何類型的對象都可以作爲錯誤拋出,一般的,Error 對象是最常用的。

      throw new Error('something is happened.');
    • 捕獲錯誤:
      JavaScript 提供了 try-catch 的語句,使得能在瀏覽器處理拋出的錯誤之前捕獲它,可能引發錯誤的代碼塊放到 try 快中,錯誤的代碼放在 catch 塊中。
      但是,請注意 try-catch 還有一種寫法 try-catch-finally 的格式,這種格式中 finally 塊中的代碼是在try-catch 中不論是否發生錯誤,均會執行。但是,如果 try 塊中包含了一個 return 語句,那麼它必須等到 finally 中的代碼執行完畢之後才能返回。

    • 錯誤類型:
      所有的錯誤類型繼承了 Error,因此用 instanceof Error 運算符檢查其類型得不到任何的有用的信息。但是可以通過檢查“特定”的錯誤類型來處理:

      try {
          if (ex instanceof TypeError) {
              // 處理 TypeError 錯誤    
          } else if (ex instanceof ReferenceError) {
              // 處理 ReferenceError 錯誤            
          } else {
              // 處理其他類型的錯誤
          }
      }

      因爲錯誤類型比較多,在判斷錯誤類型時並不好區分,因此一個比較好的解決方案是創建自己的錯誤類型,讓它繼承 Error這種做法的好處是自定義錯誤類型可以檢測自己的錯誤。

      function MyError (msg) {
          this.message = msg;
      }
      
      MyError.prototype = new Error();
  • Object 的鎖:
    Object 的鎖主要有三種:prevent extensionsealfreeze;

    1. preventExtensions & isExtensible

      preventExtensions: Object.prevenExtensions() 方法是阻止對象擴展;

      isExtensible: Object.isExtensible() 方式是檢查對象是否已經阻止了擴展。

      var person = {
          name: 'testName'
      };
      
      Object.preventExtensions(person);
      Object.isExtensible(person);            // true
      
      person.age = 25                         // 正常情況下會悄悄地失敗,嚴格模式下會報錯
    2. seal & isSealed

      seal: Object.seal() 方法是將對象密封,即不允許添加/刪除屬性或者方法;

      isSealed: Object.isSealed() 方式是檢查對象是否已經設置了密封狀態。

      var person = {
          name: 'testName'
      };
      
      Object.seal(person);
      Object.isSealed(person);                // true
      
      // 添加或刪除對象的屬性或者方法
      person.age = 25                         // 正常情況下會悄悄地失敗,嚴格模式下會報錯
      delete person.name                      // 正常情況下會悄悄地失敗,嚴格模式下會報錯
      
      // 修改對象的屬性
      person.name = 'Nicholas';               // 正常發生
    3. freeze & isFrozen

      freeze: Object.freeze() 方法是凍結對象,即不能添加/刪除/更改對象的屬性和方法;

      isFrozen: Object.isFrozen() 方式是檢查對象是否已經被凍結。

      var person = {
          name: 'testName'
      };
      
      Object.freeze(person);
      Object.isFrozen(person);            // true
      
      // 添加/刪除/修改對象的屬性或者方法
      person.age = 25                         // 正常情況下會悄悄地失敗,嚴格模式下會報錯
      delete person.name                      // 正常情況下會悄悄地失敗,嚴格模式下會報錯
      person.name = 'Nicholas';               // 正常情況下會悄悄地失敗,嚴格模式下會報錯

    由以上可知,對象的權限的大小級關係是:preventExtensions < seal < freeze

  • 瀏覽器的兼容問題:
    書中所說的大概的意思是,禁止使用特性推斷,建議使用特性檢測,不建議使用瀏覽器嗅探(user-agent)。

    // 特性檢測
    function setAnimation (cb) {
    
        // 標準
        if (window.requestAnimationFram) {
            return window.requestAnimationFram(cb);
    
        // Firefox
        } else if (window.mozRequestAnimationFram) {
            return window.requestAnimationFram(cb);
    
        // webkit
        } else if (window.webkitRequestAnimationFram) {
            return window.requestAnimationFram(cb);
    
        // Opera
        } else if (window.oRequestAnimationFram) {
            return window.requestAnimationFram(cb);
    
        // IE
        } else if (window.msRequestAnimationFram) {
            return window.requestAnimationFram(cb);
    
        // 瀏覽器都不支持的時候,使用 setTimeout 方法
        } else {
            setTimeout(cb, 0);
        }
    }

個人總結

關於這本書,其實我並不知道這本書會給我帶來多大的價值,但是卻堅定我的信心。通過通讀和細讀,加之寫了這篇回顧和總結,我收穫了在寫代碼過程需要注意的和需要改進的地方,同時也得到了一些我意想不到的處理問題的方法。然而每個人都有自己的風格和理解,因此未來的 coding 之路,我會基於這本書的思想來做,但是多少會有些出入。總之,就是希望自己越來越好,努力成爲牛逼閃閃的攻城獅之類的就不說了,至少要做到讓自己身後的家人幸福快樂。

2017-02-13 完!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章