about 5 years ago

Object Literal

使用 literal notaion 優於使用內建 constructor function(Object())。

// 1. object literal
var car = {goes: "far"};

// 2. built-in constructor function (antipattern)
var car = new Object();
car.goes = "far";

因為 Object() 接受一個參數,並會根據該參數的值將建立 object 的動作委派給另一個內建的 constructor function 執行,如此再回傳的便是我們預料之外的 object,違反了「可預期的」這條規則。

// empty object
var obj = new Object();
console.log(obj.constructor === Object); // true

// number object
var obj = new Object(1);
console.log(obj.constructor === Number); // true
console.log(obj.toFixed(3)); // "1.000"

// string object
var obj = new Object("string");
console.log(obj.constructor === String); // true
console.log(obj.substring(1,3)); // "tr"
// 一般物件沒有 substring() 這個 method,但 string object 有
console.log(typeof obj.substring); // "function"

// boolean object
var o = new Object(true);
console.log(o.constructor === Boolean); // true

自訂 Constructor Function

var Dog = function (name) {
    this.name = name;
    this.bark = function () {
        return "Woof!";
    }
    this.getName = function () {
        return this.name;
    }
}

使用 new 呼叫 constructor function 時,幕後發生的事類似:

var Dog = function (name) {
    // 使用 object literal 建立一個 empty object
    // var this = {};
  // 正確來說應該是:var this = Object.create(Dog.prototype);

    // 加入 property 及 method
    this.name = name;
    this.bark = function () {
        return "Woof!";
    }
    this.getName = function () {
        return this.name;
    }
  
  // 最後將此 object 回傳,成為一個 instance
  // return this;
}

上例中,每次呼叫 new Dog() 時,新 function 就會在 memery 中佔用空間,但因在每個 instance 中的 bark()getName() 皆相同,因此我們可以把這類會重複使用的相同 method 放入 prototype 中,以避免浪費效能。

Dog.prototype.bark = function () {
    return "Woof!";
}
Dog.prototype.getName = function () {
    return this.name;
}

強制造成 new 效果

本質上仍是 function 的 constructor function,如果忘了 new,其中的 this 將會指向 global object(即 browser 中的 window),造成邏輯錯誤。
註:ECMAScript 5 的 strict mode 中 this 不再指向 global object。

// constructor function
function Pen() {
    this.color = "red";
}

// 有加 new
var stationery = new Pen();
console.log(typeof stationery); // "object"
console.log(stationery.color); // "red"

// 沒加 new (antipattern)
var stationery = Pen();
console.log(typeof stationery); // "undefined"
console.log(stationery.color); // TypeError: Cannot read property 'color' of undefined
console.log(window.color); // "red" (成為 global object 的一個 property)

解決方法:self-invoking constructor

function Pen() {

    // 兩種用法皆可
/*
    // 第一種在不想寫死 function 名稱時可用(但 ECMAScript 5 的 strict mode 不可用 arguments.callee)
    if (!(this instanceof arguments.callee)) {
        return new arguments.callee();
    }
*/
    if (!(this instanceof Pen)) {
        return new Pen();
    }

    this.color = "red";
}

Array Literal

使用 literal notaion 優於使用內建 constructor function(Array())。

// 1. array literal
var animals = ["bird", "cat", "dog"];

// 2. built-in constructor function (antipattern)
var animals = new Array("bird", "cat", "dog");

使用 new Array() 須注意此 constructor function 可能的陷阱,當傳遞單一參數給 Array() 且其為數值時,該數值表示此 array 的長度而非第一個元素。

// 一個元素的 array
var a = [3];
console.log(a.length); // 1
console.log(a[0]); // 3

// 三個元素的 array
var a = new Array(3); // 3 是指長度而非元素值
console.log(a.length); // 3
console.log(typeof a[0]); // "undefined"

// 當參數非整數時便會發生錯誤
var a = new Array(3.14); // RangeError: Invalid array length
console.log(typeof a); // "undefined"

若要檢查某 object 是否為 array,ECMAScript 5 提供了 Array.isArray()可用來檢查。如果環境不支援此 method,可用以下方式處理:

if (typeof Array.isArray === "undefined") {
    Array.isArray = function (arg) {
        return Object.prototype.toString.call(arg) === "[object Array]";
    };
}

JSON (JavaScript Object Notation)

JSON 其實就是 object literal 與 array literal 組合成的一種資料格式。

{
    "name": "value",
    "list": [1, 2, 3]
}

JSON 與 object literal notaion 在語法上的差別:前者必須以引號包住其 property 才算合法;後者只在不合法的名稱(如中間有穿插空格)時才需要引號。除此之外,JSON 中不可使用 function 及 regex literal。

基於安全性考量,不可使用 eval() 執行 JSON 字串,應用 ECMAScript 5 提供的 JSON.parse(),若不支援也可用其他 library 的 parse 工具。

Regular Expression Literal

使用 literal notaion 優於使用內建 constructor function(RegExp())。

// 1. regex literal
var re = /\\/gm;

// 2. built-in constructor function (antipattern)
var re = new RegExp("\\\\", "gm");

因為使用 constructor function 需使用 escape character(如上例的四個反斜線),如此會使 regex 變長且難以閱讀。除非無法事先得知,在執行時才能決定 pattern,此時才適合使用 new RegExp()

regex literal 用 / 包住 regex,最後可加入修飾詞:

  • g: global matching
  • m: multiline
  • i: case-insensitive matching

Primitive Wrapper

原始型別有對應的 wrapper object,可用內建的 constructor function 來建立,number, string, boolean 分別對應 Number(), String(), Boolean()

// primitive number
var n = 100;
console.log(typeof n); // "number"

// number object
var n = new Number(100);
console.log(typeof n); // "object"

wrapper object 內建許多有用的 property 或 method,如 string object 可用 substring()toLowerCase() 等 method,以及 length 此 property。而原始型別也可以直接使用這些 property 及 method,當我們使用時會在背景暫時轉型為一 object。

// privitive string 也可以當成 string object 來使用
var str = "test";
console.log(str.toUpperCase()); // "TEST"

使用 wrapper object 可以擴充功能,原始型別因並非 object 則無法擴充。除非必要,否則應使用較簡潔的原始型別。

// 盡量少用
var s = new String("my string");
var n = new Number(101);
var b = new Boolean(true);

// 這些比較簡潔
var s = "my string";
var n = 101;
var b = true;

總結

除了 Data() 之外,極少有需求會使用內建的 constructor function。下表整理此篇建議用來取代內建 constructor 的 literal 與原始型別:

內建 constructors (少用) literals & primitives (推薦)
var o = new Object(); var o = {};
var a = new Array(); var a = [];
var re = new RegExp("[a-z]", "g"); var re = /[a-z]/g;
var s = new String(); var s = "";
var n = new Number(); var n = 0;
var b = new Boolean(); var b = false;

附註:書中此章節還有提到 error object,但因我個人比較少用到,暫時先略過,之後有疑惑再回去翻書。

← JavaScript Patterns 閱讀筆記 (2) Essentials JavaScript Patterns 閱讀筆記 (4) Functions →
 
comments powered by Disqus