Function
前言
參考書
  • function

    JavaScript 的 function 也是物件.

  • arguments

    (引數似乎是比較好的翻譯名詞).

  • invocation context

    (調用情境?)就是this關鍵字的值.

  • method

    就是函式被指定成為物件的屬性時, 就稱為方法.

  • constructors

    函式物件的建構式.

Function Objects

Function 是 JavaScript 的基礎模形單元 , 用來 reuse , information hiding , and composition. also 具體指定物件的行為 .
Generally, * 程式設計的工藝便是將許多需求的集合拆解製造成 function 和資料結構的總合 ) the craft of programming is the factoring of a set of requirements into a set of functions and data structures.

Objects 是成對的 Name/Value , 它與 prototype object 之間具有一個隱形的連結

Objects 從 object literals 產生 , 與 object.prototype 相連

Function 物件與 Function.prototype . 相關連

每一個 function object 也都會產生 prototype 屬性 . function物件的值凡是帶有 constructor property 的, 這個property的值也是 function . 這樣子的function 有別於與 Function.prototype相關聯的function , 就是 function invocation pattarn

由於 Function 在 JavaScript 中也是物件, 所以它可以存在於 objects , variables , arrays 之中, 它也可以像參數一樣傳遞, 也可以回傳 function (當事件)

function statement
(函式宣告述句)
 
        function funStatement(x) {
            console.log(x); // Displays the initial value of the argument
            alert(x);
        }              
function literal
(函式字面值)

也稱為

function definition expression
(函式定義運算式)
 
                var add = function (a, b) {
                    return a + b;
                };

有四部份組成


closure (閉鎖) , 注意一下 .

在巢狀的 function , 內部的function (是一個外部function產生的function object , 故維持著一個連結的關係) 仍能自由地存取外層的參數及變數

 
        var i = 0;
    var fs = [];
    function run_fs() {
        for (var j = 0, f = fs[j]; f; f = fs[++j]) {
            f();
        }
    }
    document.write('Pure anonymous function');
    for (i = 0; i < 3; ++i) {
        fs[i] = function () { document.write(i + ""); }
    }
    run_fs();
    document.write('Closure');
    for (i = 0; i < 3; ++i) {
        fs[i] = (function (i) {
            return function () { document.write(i + ""); }
        })(i);
    }            
Invocation

Invoking a function , 意謂著暫停目前的程式 , 將控制權轉移至新的function中.
宣告一個function時 , 每一個 function 接收兩個額外的參數 , this 及 arguments
this這個參數對 OO 設計很重要 , this 的值會因為 invocation pattarn 的不同而不同 .

參數可以是任何的 expression (變數,運算式,function皆可) .
參數的數量與呼叫時傳入的數量不符時, 並不會有runtime error發生, 太多時會被呼略 , 太少時參數會使用預設的 undefined值 , 傳入的東西並不限制型別 , 所以你可以傳物件,變數, 甚至是funcation都可以

四種函式調用的 pattern
Function Invocation
  • 在此種模式下 , this 指的是 global objet , 在瀏覽器中即為 window
    //ECMAScript 3 及非 strict的 ECMAScript 5 中, 函式調用裡使用 this 值 指的是全域物件,
    // strict 模式中 this 值為 undefined
    // 可用來判定是否處於 strict 模式
    var strict = function () { return !this; }();
    console.log(strict());
    
Method Invocation
  1. function 為物件的屬性時, 此 function 即為 物件的方法 (method)
  2. 在物件中的method 可以使用 this 來存取物件的內容 , 像下方就是用 this 存取同物件下的 value

 
                // myObject. 有一個值及一個 method
                var myObject = {
                    value: 0,
                    increment: function (inc) {
                        this.value += typeof inc === 'number' ? inc : 1;
                    }
                };
                myObject.increment();             // 預設加一
                console.log('myObject.value = ' + myObject.value);
                myObject.increment(2);            // 再加二
                console.log('myObject.value = ' + myObject.value);
Method Chaining : 當方法回傳物件時, 就可以使用方法串鍵. (也就是當 method沒有回傳值時 , 就可以考慮回傳 this 即可)
** this 與變數不同, 它不具有 scope , 巢狀函式並不會繼承本身物件的 this 值 , 所以如果要在巢狀函式中使用物件的 this , 要先將 var that = this; 然後再給巢狀函式使用.
也就是 nested function 的 this 值是 global object (在 strict 模式下是 undefined ).並不會繼承自呼叫它的function..
    var o = {
        m: function () {
            var self = this;            // Save the this value in a variable.
            console.log(this === o);    // Prints "true": this is the object o.
            f();
            function f() {
                console.log(this === o);    // false : this is global or undefined
                console.log(self === o);    // true  : self 就是 o 這個物件了.
            }
        }
    };
    o.m();

. 故在下例中 , help 的設計會無法存取myObject 這個物件本身的 this , 因為這樣的設計關鍵字 this 在 function 裡是綁定到 global object (window) 這個物件 . 但我們仍可使用另外的方法來修正它 ,如下例 使用 ooThis 變數來儲存...然後...

 
    // 加入一個 double method
    myObject.double = function () {
        var ooThis = this;
    
        // 這裏用到一個 anonymous function 一般的 function 所以它並沒有 this....
        // 故用到一個 ooThis 來儲存 myObject 這個 this (說明可以在 inner function 裡使用另一個變數來處理這樣子的事)
        var helper = function () {
            ooThis.value = add(ooThis.value, ooThis.value);
        };
    
        helper(); // 然後直接呼叫它來執行
    };
    
    myObject.double();
    alert(myObject.value);
Constructor Invocation

JavaScript 是 prototypal inheritance , 雖然如此 , javaScript 仍然提供類似建立物件的語法 . 也就是可以用 new 來產生就物件 , 這樣的 function 稱為 constructor invocation pattarn

constructor 建立一個空的物件, 這個物件繼承自該建構式的 prototype property . constructor function 的 this 表示物件 new 出來的 instance 本身 .
建構函式一般不 return 值. 如果 return 基本型別或 空值 那會忽略值. 如果回傳的是物件, new 結果則是物件 .
 
                            // 建立一個名為 Quo 的 constructor function , 有個屬性叫 status
                            var Quo = function (string) {
                                this.status = string;
                            };
            
                            // 讓所有 Quo 的 instance 都有一個 public method 叫 get_status.
                            Quo.prototype.get_status = function () {
                                return this.status;
                            };
            
                            // Make an instance of Quo.
                            // ***** 注意 . 只有這樣子的 constructor pattarn 才可以使用 new 前置詞來建立物件
                            var myQuo = new Quo("confused");
                            document.writeln(myQuo.get_status()); // confused
                            var a = new Quo('a');
                            var b = new Quo('b');  //所以 a.status 與 b.status 都不同
            
Apply Invocation
apply 讓我們建構一個陣列參數 用於invoke (呼喚, 呼叫) function

apply method 帶二個參數 ,第一個是與 this 綁在一起的參數 , 第二個是陣列參數
 
                            var add = function (a, b) {
                                return a + b;
                            };
                            var array = [3, 4];
                            var sum = add.apply(null, array);   // sum is 7
                    
                            // 建立一個有 status 屬性的物件
                            var sObj = {
                                status: 'A-OK'
                            };
                    
                            // sObj並沒有繼承自Quo.prototype, 但是我們可以透過apply讓sObj invoke get_status方法
                            var status = Quo.prototype.get_status.apply(sObj);
                            // status is 'A-OK'
                                    function cFun1(a, b, d) {
                                        this.A = a;
                                        this.B = b;
                                        this.D = d;
                                    }

                                    function cFun2(a, b, c) {
                                        this.C = c;
                                        cFun1.apply(this, arguments);
                                    }

                                    function cFun3(a, b, c) {
                                        this.C = c;
                                        cFun1.call(this, "這red這個值呼叫物件本身");
                                    }

                                    function cFun4(a, b, c) {
                                        this.C = c;
                                        cFun1(this, b, c);
                                    }

                                    function cllOtherObjectInObject() {
                                        var msg = "";
                                        var c1 = new cFun1('A', 'B', 'C');      // ==> {A: 'A', B: 'B', D: 'C'}
                                        var c2 = new cFun2('A', 'B', 'C');      // ==> {A: 'A', B: 'B', C: 'C', D: 'C'}
                                        var c3 = new cFun3('A', 'B', 'C');      // ==> {A: '這red這個值呼叫物件本身', B: undefined , C: 'C', D: undefined}
                                        var c4 = new cFun4('A', 'B', 'C');      // ==> {C: 'C'}

                                        c1.toString();
                                        c2.toString();
                                        c3.toString();
                                        c4.toString();
                                        alert(c1.toString());

                                    }
call 與apply 一樣的應用, 只不過 call(this, arg1 , arg2 , arg3....) 參數是要個別給.
Arguments

arguments是一個特別的參數物件, 所有傳進 function的參數都會存到裡面.
arguments 並不是一個 array 它只是一個像 array 的物件 , 且有一個length 屬性 , 但它缺少所有 array 的方法

** 傳遞引數時, 漏掉的話在 function 裡會是 undefined . 多的話會被忽略 (除非自己在 arguments 裡去找出來) , 呼叫時並不會因為引數的不對而出現 exception

 
    //選擇性參數的設計方法
    function getPropertyNames(o, a) {
        if (a === undefined) a = [];
        a = a || [];                                //可以用這一行取代 if 判斷式.
        for (var property in o) {
            a.push(property);
        }
        return a;
    }
    //var a = getPropertyNames(o);
    //var b = getPropertyNames(p, a);
                                        
    //說明 arguments 物件
    var sum = function (v1, v2) {
        var i, sum = "";
        for (i = 0; i < arguments.length; i++) {
            sum += arguments[i] + ", ";
        }
        console.log(v1 + v2);                       //為 12
        
        arguments[0] = 50;                          // 直接將值 assign 時, 相對應的參數也會變動, v1 也會變成 50;
        console.log(arguments.arity);               // undefined 沒有這個方法或屬性
		console.log(arguments.length);              // 6
		console.log(arguments[0]);                  // 4
		return sum;
    };
    var su = sum(4, 8, 15, 16, 23, 42);
    console.log(su);                                //4, 8, 15, 16, 23, 42,
在 ECMAScript 5 的 strict 模式中, arguments 行為有不一樣, 所以還是照參數去呼叫即可.
callee
caller
Object Properties As Arguments
    //原來多參數的function
    function arraycopy(/* array */ from,
                       /* index */ from_start,
                       /* array */ to,
                       /* index */ to_start,
                       /* integer */ length) {
    }
    
    //用物件取代, 不用記順序, 但呼叫時少了IDE的提示幫助. 有好有壞
    function easycopy(args) {
        arraycopy(args.from,
        args.from_start || 0, // Note default value provided
        args.to,
        args.to_start || 0,
        args.length);
    }
    
    var a = [1, 2, 3, 4], b = [];
    easycopy({ from: a, to: b, length: 4 });
Argument Types JavaScript 傳遞參數時沒有型別檢查, 故可以很有彈性, 也需要注意.
    function flexisum(a) {
        var total = 0;
        for (var i = 0; i < arguments.length; i++) {
            var element = arguments[i], n;
            if (element == null) continue;
            if (isArray(element))
            {
                n = flexisum.apply(this, element);      // 這一行是遞迴 , 遞回計算它的和
            }
            else if (typeof element === "function")     // 如果是 function 呼叫它並取得回傳值
            {
                n = Number(element());
            }
            else {
                n = Number(element); // Else try to convert it
            }
            if (isNaN(n))           //如果無法轉成數字丟出例外
            {
                throw Error("flexisum(): can't convert " + element + " to number");
            }
            total += n;
        }
        return total;
    }
Functions As Values

指函式可以當做值一樣地進行傳遞

    function add(x, y) { return x + y; }
    function subtract(x, y) { return x - y; }
    function multiply(x, y) { return x * y; }
    function divide(x, y) { return x / y; }
    var add2 = function (x, y) { return x + y; }     //add 可以改寫為這樣, 比較容易理解它可以當成變數或參數值
    function operate(op, arg1, arg2) {
        return op(arg1, arg2);
    }
    //直接將函式當做參數值傳遞去運算
    var i = operate(add, operate(add, 2, 3), operate(multiply, 4, 5));  // i 值為 25
    var operators = {
        add: function (x, y) { return x + y; },
        subtract: function (x, y) { return x - y; },
        multiply: function (x, y) { return x * y; },
        divide: function (x, y) { return x / y; },
        pow: Math.pow
    };
    function operate2(prop, arg1, arg2) {
        if (typeof operators[prop] === "function") {
            return operators[prop](arg1, arg2);
        }
        else {
            throw "unknown operator";
        }
    }
    var j = operate2("add", "hello", operate2("add", " ", "world"));    // => "hello world"
    var k = operate2("pow", 10, 2);                 // => 100
定義函式屬性
因為函式不是基本型別, 而是javascript一種特化的物件, 所以它也可以加屬性.
因為某些狀況下, 呼叫函式時需要回傳超過一個值時 , 可以使用此種方式. 又或者要記錄呼叫次數時. 可以加一個 counter 屬性. 然後再處理之.即可
    uniqueInteger.counter = 0;          // 函式加入一個屬性
    function uniqueInteger() {
        uniqueInteger.counter += 2.1;   //
        return 100;
    };
    $(document).ready(function () {
        for (var i = 0 ; i < 100; i++) {
            uniqueInteger();
            console.log(uniqueInteger.counter);
        }
    });
   
    // Compute factorials and cache results as properties of the function itself.
    function factorial(n) {
        if (isFinite(n) && n > 0 && n == Math.round(n)) {
            if (!(n in factorial)) // If no cached result
                factorial[n] = n * factorial(n - 1); // Compute and cache it
            return factorial[n];
        }
        else return NaN;
    }
    factorial[1] = 1; // Initialize the cache to hold this base case.
函式作為命名空間

JavaScript 的變數是 function scope , 所以在一個函式內定義的變數在該函式內的每個地方都可見 (包含巢狀函式在內) . 所以我們可以定義一個函式作為暫存的 namespace , 在其中可以定義變數,不用怕會污染到全域命名空間。

下方為定義一個匿名 (anonymous) 函式並立即調用之的方法, function 前的括號是必須的, 如果沒有的話直譯器會試著把 function 關鍵字當成函式宣告述句來解析,有了這個括號 直譯器才會正確地將它視為函式定義運算式
    (function () { 
        
    }());
    $(document).ready(function () {
        
    });
    // 將第二個及之後的引數複製到第一個引數上
    // 
    // IE 一個 bug (o的原型有個與 o 的可列舉特性相同名稱的不可列舉特性, for/in 就不會列舉 o 的這個同名特性)
    // 
    var extend = (function () { // Assign the return value of this function
        // First check for the presence of the bug before patching it.
        for (var p in { toString: null }) {
            // If we get here, then the for/in loop works correctly and we return
            // a simple version of the extend() function
            return function extend(o) {
                for (var i = 1; i < arguments.length; i++) {
                    var source = arguments[i];
                    for (var prop in source) o[prop] = source[prop];
                }
                return o;
            };
        }
        // If we get here, it means that the for/in loop did not enumerate
        // the toString property of the test object. So return a version
        // of the extend() function that explicitly tests for the nonenumerable
        // properties of Object.prototype.
        return function patched_extend(o) {
            for (var i = 1; i < arguments.length; i++) {
                var source = arguments[i];
                // Copy all the enumerable properties
                for (var prop in source) o[prop] = source[prop];
                // And now check the special-case properties
                for (var j = 0; j < protoprops.length; j++) {
                    prop = protoprops[j];
                    if (source.hasOwnProperty(prop)) o[prop] = source[prop];
                }
            }
            return o;
        };
        // This is the list of special-case properties we check for
        var protoprops = ["toString", "valueOf", "constructor", "hasOwnProperty",
        "isPrototypeOf", "propertyIsEnumerable", "toLocaleString"];
    }());
Functions As Namespaces
Return return 可以迫使 function 提早結束 .
return 一定會傳回值 , 如果不指定 , 它傳回的是 undefined .
如果 function 是以 new 來建置的話, 而且回傳的不是object , 那麼 this 就是 return值了
 
              
Closures
(閉鎖)

參考資料

Closures 是一種將變數設為特定內容來運作的表達式,通常是函式。說的簡單一點,當巢狀函式的內層函式存取了上層函式的變數,而且內層函式回傳至外部可被 invoke 就會產生 closure .

    //  這是W3C closure 的範例 計數器, 直接明白 , 它回傳的是一個已執行的 function 物件 , 所以是一個 instance (存在於記憶體的)  , 
    //  由於 Function scope 所以一直記得 counter 這個變數, 在 add() ...add()... add() 三次後依然使用這個 
    //  counter變數回傳數值 所以是3
    var add = (function () {
        var counter = 0;
        return function () { return counter += 1; }
    })();
    add();   // => 1
    add();   // => 2
    add();   // => 3
*** js 為語彙範疇 (lexical scoping) , 這表示 "函式執行時使用的變數範疇是 "函式定義時生效的範疇" ",而非 invoke 時的變數範疇.
***這裏是觀念的重點 , 要好好瞭解
  • JavaScript函式執行時使用的範疇鏈 (scope chain) 是在它們定義時生效的範疇鏈。

    //這個計數器比較難用 因為它直接產生一個 instance 算是 static 概念
    var uniqueInteger = (function () {
        var counter = 0;
        return function () { return counter++; };
    }());
    uniqueInteger();    // => 0
    uniqueInteger();    // => 1
    uniqueInteger();    // => 2


    //這個計數器比較符合物件需求
    function counter() {
        var n = 0;
        return {
            count: function () { return n++; },
            reset: function () { n = 0; }
        };
    };
    var c1 = new counter();
    var d1 = new counter();     //獨立的 instance

    console.log(c1.count());
    console.log(c1.count());
    console.log(d1.count());
    c1.reset();
    console.log(c1.count());
    
    //定義一個能夠在物件中附加屬性的方法 , 第三個參數是檢查必須是字串的函式
    function addPrivateProperty(o, name, predicate) {
        var value;
        
        o["get" + name] = function () {
            return value;
        };
        o["set" + name] = function (v) {
            if (predicate && !predicate(v))
                throw Error("set" + name + ": invalid value " + v);
            else
                value = v;
        };
    }
    var ks = {};
    var kk = {};
    
    //在 ks 中附加 Name 方法 (有點像是委派)
    addPrivateProperty(ks,
                       "Name",
                       function (x) { return typeof x == "string"; });
    
    ks.setName("Frank");
    console.log(ks.getName());
    ks.setName(0);  //因為有檢查變數, 不是字串的會丟出例外
 
    // 一般物件的用法
    // myObject. 有一個值及一個 method
    //藉由呼叫一個function 初始化 myObject 並傳回一個 object literal .也就是實體化的物件
    //       這個function 定義一個value 變數 , 而這個變數一直都可以被 increment 及 getValue 這二個方法所用
    //       value 可以直接被 get 或 set by 外部呼叫
    var myA = {
        value: 0,
        increment: function (inc) {
            //因為它用屬性, 所以得用 this.value
            this.value += (typeof(inc) === 'number') ? inc : 1;
        }
    };


    //Closure 的用法
    // 注意最後的 () , 執行後回傳物件 所以不能 var objB = new myB(); 這也是 jQuery的用法..
    var myB = function () {
        var value = 0;
        return {
            increment: function (inc) {
                value += (typeof (inc) === 'number') ? inc : 1;
            },
            getValue: function () { return value; }
        };
    }();        //注意這個括號

    //最後沒有加 () 它就是一般的建構函式了.
    var myC = function () {
        var value = 0;
        return {
            increment: function (inc) {
                value += (typeof (inc) === 'number') ? inc : 1;
            },
            getValue: function () { return value; }
        };
    };
        
    $(document).ready(function () {
        //一般物件的用法
        myA.increment(5);
        myA.value;        //==> 5
        myA.increment(30)
        myA.value;        //==> 35
        //myB 是一個物件
        var newobB = myB;
        newobB.increment(10);
        newobB.getValue();     // => 10
        myB.increment(37);
        myB.getValue();         // => 47 (因為都已經變成物件所以 newobB 與 myB都是參考同一個)
        newobB.getValue();      // => 47
        //  回傳一個方法      ...這裏要注意一下... 因為
        var obC = myC();
        obC.increment(5);
        obC.getValue();             // => 5
        myC().increment(10);        // 一個新的 myObjectC() 加十...可以這樣用, 但getValue沒什麼意義
        myC().getValue();           // => 0 , 仍是零, 因為myC() 回傳的是一個全新的 instance
        obC.increment(17);
        obC.getValue();             //==> 22
    });
                    

    //一個變背景色的 fadeIn....
    var fade = function (node) {
        var level = 1;
        var step = function () {
            var hex = level.toString(16);       //***這裏是個特別的用法...之前都不知道
            node.style.backgroundColor = '#FFFF' + hex + hex;
            if (level < 15) {
                level += 1;
                setTimeout(step, 100);
            }
        };
        setTimeout(step, 100);
    };
    fade(document.body);
    //我要傳入一個節點的集合 , 例如table 的 td 的集合 , 然後給序它 onclick的事件 , 每點一個列出它的序號
    $(document).ready(function () {
        add_the_handlers2($('.LearnDiv table tr td'));
    });


    //這樣的設計方法結果每次 alert 都是最後一個數量 (假設nodes 數量42)
    // 會什麼呢? 因為跑完定義後 i 值 = 42 , 故 每次都會 alert 42的值
    var add_the_handlers_no = function (nodes) {
        var i;
        for (i = 0; i < nodes.length; i += 1) {
            nodes[i].onclick = function (e) {
                alert(i);
            };
        }
        //如果在這裏指定 555 那 每次 alert 出來的就是 555
        i = 555;
    };

    //與上方的差異在與 helper(n) 回傳的是一個 function 物件 , 在onclick 事件時才會觸發這個物件
    //  所以 nodes.length 如果有 42 個 , 那就是產生了 42 個匿名function物件給 onclick 事件等它觸發
    var add_the_handlers2 = function (nodes) {

        //避免宣告在廻圈裡, 這樣只是浪費效能
        var helper = function (num) {
            return function () {
                alert(num);
            };
        };

        for (i = 0; i < nodes.length; i += 1) {
            v = i;
            nodes[i].onclick = helper(i);
        }
    };
              
另一件 closures 要記住的事為 this 是一個關鍵字而不是變數 , 每個函式都有一個 this 值, 而 closure 無法存取它外層函式的 this 值,除非 that = this 然後再使用於巢狀函式中.
(arguments關鍵字也是 outArguments = arguments 然後再給 nasted function使用).
函式的propertys、Methods及建構式
length ??
property
call()
apply()
基本上 call 以及 apply 都是去執行這個 function 並將這個 function 的 context (this值) 替換成第一個參數帶入的物件;
沒有實作過還是沒辨法理解它的威力在哪!
    // 追蹤的 function 透過 apply 來處理
    function trace(o, m) {
        var original = o[m];    //記錄舊的
        o[m] = function () {
            console.log(new Date(), "Entering:", m);
            var result = original.apply(this, arguments);
            console.log(new Date(), "Exiting:", m);
            return result;
        };
    }
    function Album(id, title, owner_id) {
        this.id = id;
        this.name = title;
        this.owner_id = owner_id;
    };
    Album.prototype.get_owner = function (callback) {
        var own_name = '';
        switch (this.owner_id) {
            case 30:
                own_name = '林瑞生';
                break;
            case 28:
                own_name = '陳文達';
                break;
            case 29:
                own_name = '陳慶章';
                break;
            default:
                own_name = '李世輝';
                break;
        }
        callback.call(this, own_name);
    };
    var album = new Album(1, '相薄', 30);
    trace(album, 'get_owner');
    album.get_owner(function (owner) {
        console.log(new Date(), 'The album ' + this.name + ' belongs to ' + owner);
    });
bind()
把一個函式繫結到一個物件 , 函式的this值就變成被繫結的物件. bind後回傳一個新的函式. 任何傳給新函式的 參數都會傳到原本的函式
    function f(y) {
        //this值變成 f.bind(o) 裡的 o 物件
        if (this.x) {
            return this.x + y + ', arguments 數量為 :' + arguments.length;
        }
        else {
            return y + ', arguments 數量為 :' + arguments.length;
        }
    }
    var o = { x: 1 };
    var g = f.bind(o);                      // 
    g(2);          // => 3 , arguments.length = 1
    g(3, 4, 5);    // => 4 , arguments.length = 3
    
    
    function bind(f, o) {
        if (f.bind) {
            return f.bind(o);
        }
        else {
            return function () {
                return f.apply(o, arguments);
            }
        };
    }
    var sum = function (x, y) { return x + y };
    var succ = sum.bind(null, 1);
    succ(2) // => 3: x is bound to 1, and we pass 2 for the y argument
    function f(y, z) { return this.x + y + z }; // Another function that adds
    var g = f.bind({ x: 1 }, 2); // Bind this and y
    g(3) // => 6: this.x is bound to 1, y is bound to 2 and z is 3
    //ECMAScript 3 模擬 bind 方法
    if (!Function.prototype.bind) {
        Function.prototype.bind = function (o /*, args */) {
            var self = this, boundArgs = arguments;
            
            return function () {
                // to bind after the first one, and follow those with all args
                // passed to this function.
                var args = [], i;
                for (i = 1; i < boundArgs.length; i++) args.push(boundArgs[i]);
                for (i = 0; i < arguments.length; i++) args.push(arguments[i]);
                // Now invoke self as a method of o, with those arguments
                return self.apply(o, args);
            };
        };
    }
toString()
Function() 建構式

用大寫 Function 的建構式如下!

  • 因為是字串, 所以是動態編譯
  • 每次呼叫時都會parse一次, 故效能低落
  • 不使用語彙範疇, 它們一定會被當成頂層函式來編譯.
有彈性, 但是要少用.
    var f = new Function("x", "y","z", "return Math.pow(x*y,z);");
    
    $(document).ready(function () {
        
        alert(f(13, 12, 3));
    });
Callable Objects 像是RegExp
Functional Programming
函式型程式設計
使用函式型程式設計處理陣列
高階函式
指的是操作函式的函式.
函式的部份應用
Memoization
Exceptions

throw 物件
 
                    //加入 throw 功能 , 但是外圈如果沒有 try catch 的處理的話 , 就javascript 來說是沒有意義的
                      var msg;
                      try {
                          msg = " typeof(a) :" + typeof (a) + "\n typeof(b) : " + typeof (b);
                          if (typeof (a) !== 'number' || typeof (b) !== 'number') {
                              // throw 要配合try catch(e) 來使用, 否則會是 uncatch error 的javascript錯誤
                              throw {
                                  name: 'TypeError',
                                  message: 'add needs numbers'
                              };
                          }
                          msg += ", a+b = " + a + b;
                      }
                      catch (e) {
                          msg += "\n發生exception\n" + e.name + "\n" + e.message;
                      }
                      finally {
                          alert(msg);
                      }
        
              
jump 的用法  
Augmenting Types
(應該是擴充型別)

JavaScript 允許基本型別被 augmented (擴充)

在上面 . 我們可以使用 Object.prototype 使得方法可以應用到所有instance物件
 
                    //Function 加入擴充方法 . 使所有的 物件 (constructor invocation funcation)的不必使用 .prototype去擴充方法
                    //  不過這東東要放在程式的最高處..不然會沒作用
                    Function.prototype.method = function (name, func) {
                        this.prototype[name] = func;
                        return this;
                    };
                    var constructorObject = function (v1, v2) {
                        this.value1 = v1;
                        this.value2 = v2;
                    }
                    constructorObject.prototype.Add = function () {
                        return this.value1 + this.value2;
                    }
                    constructorObject.method('GoHappy', function () {
                        alert('我真的很悲觀!! 我覺得未來沒希望' + this.value1 );
                    });
                    //然後我就可以用
                    var vi = new constructorObject(2, 3);
                    vi.GoHappy();
              

Number 擴充方法來處理 integer 的問題


 
                    Number.method('Tointeger', function () {
                        return Math[this < 0 ? 'ceil' : 'floor'](this);
                    });
              

String 擴充方法來處理 trim 的問題


 
                    String.method('trim', function () {
                        return this.replace(/^\s+|\s+$/g, '');
                    });
              

由於 JavaScript 的動態特性 , 我們可以很容易地賦與物件新的methods , 為了避免與原 methods 發生 衝突 , 我們可以使用下方的特性來處理
 
                    Function.prototype.method = function (name, func) {
                        
                        //判斷原來方法存不存在 . 不存在時再新增方法 (public structor)
                        if (!this.prototype[name]) {
                            this.prototype[name] = func;
                            return this;
                        }
                    };
              
Recursion
( 遞廻 )
河內塔 : 有三根木頭 , 有 n 個圓環排在一起呈金字塔狀
規則一 : 每次只能移動一個圓環金屬
規則二 : 上到下都必須圓環直徑的小到大 , 也就是須呈金字塔狀...
結果是將一根木頭上的圓環統統移動到另一根木頭上...

 
                
                //河內塔遊戲
                hanoi(4, 'A', 'B', 'C');
                var hanoi = function hanoi(disc, src, aux, dst) {
                    if (disc > 0) {
                        hanoi(disc - 1, src, dst, aux);
                        document.writeln('Move disc ' + disc + ' from ' + src + ' to ' + dst + '< br />');
                        hanoi(disc - 1, aux, src, dst);
                    }
                };
              

樹狀結構的瀏灠方式

 
                    //使用此方法來瀏灠HTML下的每一個節點
                    var walk_the_DOM = function walk(node, func) {
                        func(node);
                        node = node.firstChild;
                        while (node) {
                            walk(node, func);
                            node = node.nextSibling;
                        }
                    };
                    //取得 document.body 下某個 tag 有某個值的....傳回陣列
                    //     配合上方遞廻的方式來尋找
                    var getElementsByAttribute = function (att, value) {
                        var results = [];
                        walk_the_DOM(document.body, function (node) {
                            var actual = node.nodeType === 1 && node.getAttribute(att);
                            if (typeof actual === 'string' && (actual === value || typeof value !== 'string')) {
                                results.push(node);
                            }
                        });
                        return results;
                    };
              

JavaScript 並沒有提供 tail recursion optimization , 所以在 Function resursion 到很深的節點時 , 可能會發生 fail by exhausting the return stack

 
                //
                var factorial = function factorial(i, a) {
                    a = a || 1;
                    if (i < 2) {
                        return a;
                    }
                    return factorial(i - 1, a * i);
                };
                document.writeln(factorial(4));
              
Scope

最新的語言都建議在變數使用之前才宣告它 , 但這對JavaScript來說卻不是好的 . 因為 JavaScript 沒有 block scope 所以要全域使用的參數最好在 程式一開始最前方就宣告它 , 以免在途中因為某程式的變數而被影響.

JavaScript 是 function scope , 這表示 變數及參數在 function 裡宣告的話 , 外部是看不到的 , 但是在內部宣告時 inner function 的任何地方都是看的到的 ????

Callbacks
 
                //這是一個 call back 的範本
                
                request = prepare_the_request( );
                
                send_request_asynchronously(request, function (response) {
                    display(response);
                });
              
Module

使用 functions 及 closure 來建立 module

module 是一個呈現像介面但是隱藏它的狀態或實做的function 或 object.

 
                    
                    Function.prototype.method = function (name, func) {
                        this.prototype[name] = func;
                        return this;
                    };
                    
                    String.method('deentityify', function () {
                        var entity = {
                            quot: '"',
                            lt: '<',
                            gt: '>'
                        };
                        // 傳回 deentityify method
                        return function () {
                            // 正規表示式 start with '&' and end with ';'. If the characters in
                            return this.replace(/&([^&;]+);/g, 
                            function (a, b) {
                                var r = entity[b];
                                return typeof r === 'string' ? r : a;
                            });
                
                        };
                    } ());
              

The general pattern of a module is a function that defines private variables and functions; creates privileged functions which, through closure, will have access to the private variables and functions; and that returns the privileged functions or stores them in an accessible place.

使用 module pattarn 可以有效地減低 global variables 的使用 , 並且 promotes 資料的隱藏及 promotes 其他好的設計的實踐 

 
                var serial_maker = function ( ) {
                    var prefix = '';
                    var seq = 0;
                    return {
                        set_prefix: function (p) {
                            prefix = String(p);
                        },
                        set_seq: function (s) {
                            seq = s;
                        },
                        gensym: function () {
                            var result = prefix + seq;
                            seq += 1;
                            return result;
                        }
                    };
                };

                    //以上方法都沒有用到 this 或 that , 這樣的設計隱藏了 prefix 及 seq 變數, 使用外程式無論如何都無法直接
                    //改變 prefix 及 seq 的值 (只能透過, set_prefix 及 set_seq 這兩個方法來改變它)
                    
                    //呼叫方法及範例
                    var seqer = serial_maker();
                    seqer.set_prefix('Q');
                    seqer.set_seq(1000);
                    var unique = seqer.gensym();
                    for (var i = 0; i < 10; i++) {
                        if (i === 5) {
                            //unique;
                        }
                        alert(seqer.gensym());
                    }
              
Cascade

應該就是連綴呼叫. 只要呼叫的方法回傳 this 就可以達到此種效果

Curry
咖喱

Currying allows us to produce a new function by combining a function and an argument

它主要是產生一個 closure , 這個 closure hold住了原本的function跟arguments.
當 invoked 這個方法時 , 會處理傳入的 parameter 及原來的 parameters (使用陣列的concat來結合) , 然後傳回原方法

 
                
                //同樣需要 Function 的擴充方法 , curry 
                    Function.method('curry', function () {
                        
                        //slice 是因為 arguments 並不是陣列 , 所以沒有 concat 方法 , 所以這樣.apply來處理它們的結合
                        var slice = Array.prototype.slice,
                            args = slice.apply(arguments), 
                            that = this;
                        return function () {
                            return that.apply(null, args.concat(slice.apply(arguments)));
                        };
                    });
              
Memoization

function 可以使用變數來記住上個操作所產生的結果, 以避免不必要的重工 . 這就是 memoization 的說明 , 物件及陣列很適合來處理這事 .
使用費伯納數列來說明

範例
               
                    var fibonacci = function (n) {
                        return n < 2 ? n : fibonacci(n - 1) + fibonacci(n - 2);
                    };
                    //以下的處理我們呼叫了 11次 fibonacci , 但是它自己卻遞迴了453 次
                    for (var i = 0; i <= 10; i += 1) {
                        document.writeln('// ' + i + ': ' + fibonacci(i));
                    }
              

進階版

為了避免數量極為龐大的遞迴所導致的效能低落, 我們可以使用

                var fibonacci = (function () {
                    var memo = [0, 1];
                    var fib = function (n) {
                        var result = memo[n];
                        if (typeof result !== 'number') {
                            result = fib(n - 1) + fib(n - 2);
                            memo[n] = result;
                        }
                        return result;
                    };
                    return fib;
                } ());

                var memoizer = function (memo, formula) {
                    var recur = function (n) {
                        var result = memo[n];
                        if (typeof result !== 'number') {
                            result = formula(recur, n);
                            memo[n] = result;
                        }
                        return result;
                    };
                    return recur;
                };
                var fibonacci = memoizer([0, 1], function (recur, n) {
                    return recur(n - 1) + recur(n - 2);
                });
                var factorial = memoizer([1, 1], function (recur, n) {
                    return n * recur(n - 1);
                });
            
其他的用法
 
                 //這樣的用法好像沒有用過
                  var setBGColor;
                  function selfBuiltNewFunction() {
                      setBGColor = new Function("document.body.bgColor='arguments[0]'");
                  }

                  setBGColor('red');
              
caller
constructor
                 .caller : 傳回呼叫來源的程式
   .constructor : 
constructor
function queryObjConstructor(){
   varobjD = new Date( );
   var objArr = new Array( );
   alert( objD.constructor );
   alert( objArr.constructor );

}
顯示函式陣列物件
 
之類的程式原始程式碼
  IE好像不支援