almost 5 years ago

Namespace

使用 namespace 可避免發生命名衝突,並降低 global variable 的需求量。

// 只用一個 global object
var MYAPP = {};

// constructors
MYAPP.Parent = function () {};
MYAPP.Child = function () {};

// a variable
MYAPP.some_var = 1;

// an object container
MYAPP.modules = {};

// nested objects
MYAPP.modules.module1 = {};
MYAPP.modules.module1.data = {a: 1, b: 2};
MYAPP.modules.module2 = {};

為避免覆蓋掉已存在的 namespace 或 property,我們可以設計一個檢查用的 function,用來處理建立 namespace 與新增其 property 的細節。

var MYAPP = MYAPP || {};
MYAPP.namespace = function (ns_string) {
    var parts = ns_string.split('.'),
        parent = MYAPP,
    i;
    // strip redundant leading global
    if (parts[0] === "MYAPP") {
        parts = parts.slice(1);
    }
    for (i = 0; i < parts.length; i += 1) {
        // create a property if it doesn't exist
        if (typeof parent[parts[i]] === "undefined") {
            parent[parts[i]] = {};
        }
        parent = parent[parts[i]];
    }
    return parent;
};

// 使用
var mod = MYAPP.namespace('MYAPP.modules.module2');
mod === MYAPP.modules.module2; // true

這類使用 namespace 且模組化的程式(尤其在完善的 library 中常見),使用時可在 function 頂端建立 local variable 並指向所需模組,如:var event = MYAPP.util.Event。此 pattern 稱為 declaring dependency,這麼做有許多優點:

  • 明確提醒其他會用到這份 code 的人有哪些特殊的 script 檔案必須 include
  • 使用 local variable(如 event)永遠比使用 global variable(如 MYAPP)來得快,尤其比巢狀 property(如 MYAPP.util.Event)效能更好
  • minify 工具會 rename local variable,可得到較少的 code

Private Properties & Methods

JavaScript 所有 object member 皆是 public,雖然語言並沒有為 private member 提供特殊語法,但我們仍可用 closure 來實作出 private member。

Constructor Function

主要觀念即 function 的 scope 特性,只要在 function 中宣告即可成為 local variable,雖然並非真正的 property,但已可達成 private 的目的。

function Rectangle() {
    // private member
    var height = 10,
        width = 5,
        area = height * width;

    // public function
    this.getArea = function () {
        return area;
    };
}
var shape1 = new Rectangle();
console.log(shape1.area); // undefined (private)
console.log(shape1.getArea()); // 50 (public)

要特別注意的一點是,當我們從 privileged method(如上例的 getArea())回傳 private 變數時,該變數千萬不可以是 object 或 array,因為此時回傳的是其 reference,如此將會使得外部的 code 仍然可修改 private member。

如果要解決這個問題,可以讓 privileged method 直接回傳一個新的 object,或直接使用 extend()extendDeep() 做 object 的複製。

Object Literal

使用 object literal 建立 object 一樣也能實作出 private member,可以用 immediate function 來建立 closure。

var obj = (function () {
    // private member
    var name = "test";

    // public function
    return {
        getName: function () {
            return name;
        }
    };
}());
obj.getName(); // "test"
Prototype

如果每次使用 constructor function 建立的 object 都會重新建立相同的 private member,則可以將共同的 property 或 method 加到 prototype 中以節省 memory,這麼做能使共有的 member 在所有 instance 間共享。

Rectangle.prototype = (function () {
    // private member
    var type = "Rectangle";

    // public prototype member
    return {
        getType: function () {
            return type;
        }
    };
}());
Revelation

此 pattern 可將 private method 揭露為 public,但原始的實作內容並不會如同 public 的狀態能夠真的被修改。

var test;

(function () {
    function foo() {
        return "still alive";
    }

    test = {
        fn1: foo,
        fn2: foo
    };
}());

test.fn1 = null;
test.fn2(); // "still alive"

Module

雖然 JavaScript 沒有 package 的語法,我們可以結合前面介紹過的 namespace、immediate function、private member、declaring dependency 等 patterns 來實現 module pattern。

MYAPP.namespace('MYAPP.utilities.array');
MYAPP.utilities.array = (function () {

        // dependencies
    var uobj = MYAPP.utilities.object,
        ulang = MYAPP.utilities.lang,
        // private properties
        array_string = "[object Array]",
        ops = Object.prototype.toString;
        // private methods
        // ...
        // end var

    // optionally one-time init procedures
    // ...

    // public API
    return {
        inArray: function (needle, haystack) {
            for (var i = 0, max = haystack.length; i < max; i += 1) {
                if (haystack[i] === needle) {
                    return true;
                }
            }
        },
        isArray: function (a) {
            return ops.call(a) === array_string;
        }
        // ... more methods and properties
    };
}());

Sandbox

此 pattern 解決 namespace 的缺點:隨時可被變更的 global variable、很長的巢狀 property 導致打字不便及解析效能下降。 (詳細使用方式筆記略)

Static Members

static member 特性:instance 間可共享、不需建立 instance 即可直接使用

Public Static Members

即直接新增 property 給 constructor function。

// constructor
var Circle = function () {};

// a static method
Circle.isShape = function () {
    return true;
};

// a normal method added to the prototype
Shape.prototype.setColor = function (color) {
    this.color = color;
};

// calling a static method
Circle.isShape(); // true

// creating an instance and calling a method
var moon = new Circle();
moon.setColor("yellow");

typeof Circle.setColor; // "undefined"
typeof moon.isShape; // "undefined"

比較兩種 method,static 可直接透過 constructor 呼叫,一般的則需先有 instance 才可呼叫。當然也可以讓 static method 透過 instance 也能呼叫:Circle.prototype.isShape = Circle.isShape;,但若其實作中有使用到 this,須特別注意兩者 reference 有所不同。

Private Static Members

如同上方 private properties & methods 的做法,只需使用一 immediate function 來建立 closure 以容納 private member,並在 prototype 實作一 privileged method。(closure 內的 private static member 為所有 instance 間共享)

// constructor
var Circle = (function () {
    // static variable/property
    var counter = 0,
        NewCircle;

    // new constructor implementation
    NewCircle = function () {
        counter += 1;
    };

    // a privileged method
    NewCircle.prototype.getLastId = function () {
        return counter;
    };

    // overwrite the constructor
    return NewCircle;
}()); // execute immediately

var shape1 = new Circle();
shape1.getLastId(); // 1
var shape2 = new Circle();
shape2.getLastId(); // 2

Object Constants

一般會使用 naming convention 將所有字母改為全大寫來建立 constant,如 Math.PI

附註:如果真的想實作一個不可變動的值,可參考書上提供的 general-purpose constant object。

Chaining

jQuery 的一大特色即使用此 pattern,允許讓 object 不用 assign 給變數或拆成多行,可以直接連續地呼叫多個 method。實作時讓 method 回傳 this(即 instance 本身),即可達成此效果。

var calculate = {
    value: 0,
    plus: function (v) {
        this.value += v;
        return this;
    },
    minus: function (v) {
        this.value -= v;
        return this;
    },
    output: function () {
        console.log(this.value);
    }
};

calculate.plus(7).minus(2).plus(3).output(); // 8
  • 優點:code 較精簡、各 method 有各別專屬功能,提升可維護性
  • 缺點:debug 難度增加
← JavaScript Patterns 閱讀筆記 (4) Functions JavaScript Patterns 閱讀筆記 (6) Code Reuse Patterns →
 
comments powered by Disqus