高級(jí)函數(shù)

一、安全的類型檢測(cè)
    ①為什么引入安全的類型檢測(cè),難道javascript內(nèi)置的類型檢測(cè)不靠譜嗎?
        1) typeof 只能檢測(cè)基本的數(shù)據(jù)類型,對(duì)于引用類型全部返回object,所以不靠譜吧
        2) instanceof 是可以檢測(cè)出引用類型,但是它在存在多個(gè)全局作用域的情況下,問題多多。
            例如:var isArray = value instanceof Array;    只有value是一個(gè)數(shù)組,且value還必須和Array構(gòu)造函數(shù)再同一個(gè)作用域下返回true。如果value是在另個(gè)frame中,那么返回的必定是false
    ②解決問題:大家知道,在任何值上調(diào)用Object 原生的toString()方法,都會(huì)返回一個(gè)[object NativeConstructorName]格式的字符串。每個(gè)類在內(nèi)部都有一個(gè)[[Class]]屬性,這個(gè)屬性中就指定了上述字符串中的構(gòu)造函數(shù)名。且由于原生數(shù)組的構(gòu)造函數(shù)名與全局作用域無關(guān),因此使用toString()就能保證返回一致的值。
        function SafeTypeCheck() {
            if (typeof this.isNumber != "function") {
                // Number類型
                SafeTypeCheck.prototype.isNumber = function (value) {
                    return Object.prototype.toString.call(value) == "[object Number]";
                };

                // String類型
                SafeTypeCheck.prototype.isString = function (value) {
                    return Object.prototype.toString.call(value) == "[object String]";
                };

                // Boolean類型
                SafeTypeCheck.prototype.isBoolean = function (value) {
                    return Object.prototype.toString.call(value) == "[object Boolean]";
                };

                // Array類型
                SafeTypeCheck.prototype.isArray = function (value) {
                    return Object.prototype.toString.call(value) == "[object Array]";
                };

                // Function類型
                SafeTypeCheck.prototype.isFunction = function (value) {
                    return Object.prototype.toString.call(value) == "[object Function]";
                };

                // RegExp類型
                SafeTypeCheck.prototype.isRegExp = function (value) {
                    return Object.prototype.toString.call(value) == "[object RegExp]";
                };

                // Null類型
                SafeTypeCheck.prototype.isNull = function (value) {
                    return Object.prototype.toString.call(value) == "[object Null]";
                };

                // Undefined類型
                SafeTypeCheck.prototype.isUndefined = function (value) {
                    return Object.prototype.toString.call(value) == "[object Undefined]";
                };

                // Object類型, 包括JOSN類型
                SafeTypeCheck.prototype.isObject = function (value) {
                    return window.JSON && Object.prototype.toString.call(value) == "[object Object]";
                };

                // Native JSON類型
                // var isNativeJSON = window.JSON && Object.prototype.toString.call(JSON) == "[object JSON]";
            }
        }
        // 測(cè)試
        var checkType = new SafeTypeCheck();
        alert(checkType.isFunction(function () {}));    // true
        alert(checkType.isArray([]));   // true
        alert(checkType.isString(123)); // false
二、作用域安全的構(gòu)造函數(shù)
    ①引子:先看一個(gè)例子:
        function Person(name, age, job){
            this.name = name;
            this.age = age;
            this.job = job;
        }
        var zhang = new Person("zhang", 34, "worker");
        // alert(zhang.name);       // zhang
        // alert(window.name);      // 為空
    ②正常情況下這是完全沒有問題的,但當(dāng)Person在實(shí)例化時(shí)被忘記了new了,那么可想而知,this指向了window
        var li = Person("li", 20, "student");
        alert(li.name);     // 報(bào)錯(cuò)
        alert(window.name); // li
    ③因此,我們?cè)趯ふ乙粋€(gè)良好的代碼來屏蔽這樣的問題
        function Person(name, age, job){
            // 當(dāng)使用了new實(shí)例化時(shí),this指向Person
            if(this instanceof Person) {
                this.name = name;
                this.age = age;
                this.job = job;
            } else {
            // 當(dāng)忘記了new對(duì)象時(shí),自動(dòng)會(huì)加上一個(gè)new關(guān)鍵字,這樣就避免了指向window的問題
                return new Person(name, age, job);
            }
        }
        var zhang = new Person("zhang", 34, "worker");
        // alert(zhang.name);       // zhang
        // alert(window.name);      // 為空
        var li = Person("li", 20, "student");
        alert(li.name);     // li
        alert(window.name); // 為空
    ④同樣,安全作用域的構(gòu)造函數(shù)也是有bug的,如果你想繼承但只是用竊取模式,不是原型鏈繼承模式時(shí),會(huì)因?yàn)楦割愖饔糜虮绘i住而失敗的現(xiàn)象
        function Student(grade) {
            Person.call(this, "wang", 29, "teacher");
            this.grade = grade;
        }
        var stu = new Student(100);
        // 由于父類的作用域被鎖住,所以無法調(diào)用
        alert(stu.name);    // undefined
        alert(stu.grade);   // 100
    ⑤所以,我們沒辦法。只能老老實(shí)實(shí)的用原型鏈搞定它。
        function Student(grade) {
            Person.call(this, "wang", 29, "teacher");
            this.grade = grade;
        }
        Student.prototype = new Person();
        var stu = new Student(100);
        // 這樣就ok了
        alert(stu.name);    // wang
        alert(stu.grade);   // 100
三、惰性載入函數(shù)
    ①引子:為什么要有惰性載入函數(shù)?先看下面一個(gè)創(chuàng)建XHR對(duì)象的例子:
        function createXHR(){
            if (typeof XMLHttpRequest != "undefined"){      // 非ie
                return new XMLHttpRequest();
            } else if (typeof ActiveXObject != "undefined"){    // ie
                if (typeof arguments.callee.activeXString != "string"){
                    var versions = ["MSXML2.XMLHttp.6.0", "MSXML2.XMLHttp.3.0",
                    "MSXML2.XMLHttp"],
                    i,len;
                    for (i=0,len=versions.length; i < len; i++){
                        try {
                            new ActiveXObject(versions[i]);
                            arguments.callee.activeXString = versions[i];
                            break;
                        } catch (ex){
                            //跳過
                        }
                    }
                }
                return new ActiveXObject(arguments.callee.activeXString);
            } else {
                throw new Error("No XHR object available.");
            }
        }
    ②問題所在:上述例子的功能是:針對(duì)不同的瀏覽器創(chuàng)建一個(gè)XHR對(duì)象,函數(shù)中有許多if語句,在代碼執(zhí)行期間我們需要判斷。因?yàn)槲覀兪褂玫臑g覽器不會(huì)發(fā)送變化,在這個(gè)函數(shù)第二次被調(diào)用的時(shí)候,我個(gè)人認(rèn)為該函數(shù)的if語句就應(yīng)該不會(huì)再次去判斷是什么樣的瀏覽器而去實(shí)例化不同的XHR對(duì)象。因此我們提出了惰性載入函數(shù)。
    ③應(yīng)用:現(xiàn)在將上述的函數(shù)優(yōu)化一下:
        function createXHR(){
            if (typeof XMLHttpRequest != "undefined"){      // 非ie
                arguments.callee = function () {
                    return new XMLHttpRequest();
                }
            } else if (typeof ActiveXObject != "undefined"){    // ie
                arguments.callee = function () {
                    if (typeof arguments.callee.activeXString != "string"){
                        var versions = ["MSXML2.XMLHttp.6.0", "MSXML2.XMLHttp.3.0",
                        "MSXML2.XMLHttp"],
                        i,len;
                        for (i=0,len=versions.length; i < len; i++){
                            try {
                                new ActiveXObject(versions[i]);
                                arguments.callee.activeXString = versions[i];
                                break;
                            } catch (ex){
                                //跳過
                            }
                        }
                    }
                    return new ActiveXObject(arguments.callee.activeXString);
                }
            } else {
                arguments.callee = function () {
                    throw new Error("No XHR object available.");
                }
            }
            return arguments.callee();
        }
        alert(new createXHR() instanceof XMLHttpRequest);       // true
    ④優(yōu)點(diǎn):這樣,我們?cè)谡{(diào)用時(shí),就不需要執(zhí)行了if語句,這又提高了效率。
四、函數(shù)綁定
    ①引子:先看下面一個(gè)例子:
        var handler = {
            message: "Event handled",
            handleClick: function(event){
                alert(this.message);
            }
        };
        var message = "window";
        var btn = document.getElementById("btn");
        EventUtil.addEvent(btn, "click", handler.handleClick);  // undefined,less ie8彈出window
    ②原因:為什么會(huì)彈出undefined?在于沒有保存handler.handleClick()的環(huán)境,所以this 對(duì)象最后是指向了DOM 按鈕而非handler(在IE8 中)。因此我們可以用閉包來保留handler.handleClick()的環(huán)境,起碼在這個(gè)類中中。this 指向window。)
        var handler = {
            message: "Event handled",
            handleClick: function(event){
                alert(this.message);
            }
        };
        var message = "window";
        var btn = document.getElementById("btn");
        EventUtil.addEvent(btn, "click", function(event){
            handler.handleClick(event);     // Event handled
        });
    ③解決方案:雖然可以用閉包解決此類問題,但是使用閉包會(huì)使代碼變得很難理解和調(diào)試.我們可以用apply或者call方法改變作用域來實(shí)現(xiàn)綁定。
        EventUtil.addEvent(btn, "click", function () {
            return handler.handleClick.call(handler);
        });
    ④封裝成函數(shù):
        var handler = {
            message: "Event handled",
            handleClick: function(event){
                alert(this.message);
            }
        };
        var message = "window";
        function bind(fn, context) {
            return function () {
                fn.call(context, arguments);
            }
        }
        var btn = document.getElementById("btn");
        EventUtil.addEvent(btn, "click", bind(handler.handleClick, handler));
    ⑤ECMAScript 5 為所有函數(shù)定義了一個(gè)原生的bind()方法,進(jìn)一步簡(jiǎn)單了操作.
        EventUtil.addEvent(btn, "click", handler.handleClick.bind(handler));
五、函數(shù)柯里化.
    1.含義:調(diào)用另一個(gè)函數(shù),并為它傳入要柯里化的函數(shù)和參數(shù)。以下函數(shù)能很好的展示庫里化的概念。
        function add(num1, num2){
            return num1 + num2;
        }
        function curriedAdd(num2){
            // 調(diào)用了add()函數(shù),并為它傳入了參數(shù)
            return add(5, num2);
        }
        alert(add(2, 3)); //5
        alert(curriedAdd(3)); //8
    2.創(chuàng)建函數(shù)庫里化的通用方式
        function curry(fn){
            // 取得外部函數(shù)的從第二個(gè)開始的所有參數(shù)
            var args = Array.prototype.slice.call(arguments, 1);
            // alert(args); // 10, 10, 10
            return function(){
                // 取得內(nèi)部函數(shù)(匿名函數(shù))的全部參數(shù)
                var innerArgs = Array.prototype.slice.call(arguments);
                // alert(innerArgs);    // 15, 15
                // 將外部的參數(shù)和內(nèi)部參數(shù)全部聚集在一起
                var finalArgs = args.concat(innerArgs);
                // alert(finalArgs);        // 10, 10, 10, 15, 15
                // 最后將所有的傳入到要調(diào)用的函數(shù)中去
                return fn.apply(null, finalArgs);
            };
        }
        function add(){
            var res = 0;
            for(var i = 0, len = arguments.length; i < len; i++) {
                res += arguments[i];
            }
            return res;
        }
        // 對(duì)于下面的函數(shù)調(diào)用
        // fn為add函數(shù)
        // args為10, 10, 10
        // innerArgs為15, 15
        var curried = curry(add, 10, 10, 10);
        alert(curried(15, 15)); // 60
    3.上述的curry函數(shù)沒用改變相應(yīng)的作用域,我們可以繼續(xù)修改
        function curry(fn, context){
            // 取得外部函數(shù)的從第三個(gè)開始的所有參數(shù)
            var args = Array.prototype.slice.call(arguments, 2);
            // alert(args); // btn
            return function(){
                // 取得內(nèi)部函數(shù)(匿名函數(shù))的全部參數(shù)
                var innerArgs = Array.prototype.slice.call(arguments);
                // alert(innerArgs);    // [object MouseEvent]
                // 將外部的參數(shù)和內(nèi)部參數(shù)全部聚集在一起
                var finalArgs = args.concat(innerArgs);
                alert(finalArgs);       // btn, [object MouseEvent]
                // 最后將所有的傳入到要調(diào)用的函數(shù)中去
                return fn.apply(context, finalArgs);
            };
        }
        var handler = {
            message: "Event handled",
            handleClick: function(name, event){
                alert(this.message + ":" + name + ":" + event.type);
            }
        };
        var btn = document.getElementById("btn");
        EventUtil.addEvent(btn, "click", curry(handler.handleClick, handler, "btn"));
    4.ECMAScript 5 的bind()方法也實(shí)現(xiàn)函數(shù)柯里化,只要在this 的值之后再傳入另一個(gè)參數(shù)即可。
        var handler = {
            message: "Event handled",
            handleClick: function(name, event){
                alert(this.message + ":" + name + ":" + event.type);
            }
        };
        var btn = document.getElementById("btn");
        EventUtil.addEvent(btn, "click", handler.handleClick.bind(handler, "btn"));

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容