高级函数

一、安全的类型检测
    ①为什么引入安全的类型检测,难道javascript内置的类型检测不靠谱吗?
        1) typeof 只能检测基本的数据类型,对于引用类型全部返回object,所以不靠谱吧
        2) instanceof 是可以检测出引用类型,但是它在存在多个全局作用域的情况下,问题多多。
            例如:var isArray = value instanceof Array;    只有value是一个数组,且value还必须和Array构造函数再同一个作用域下返回true。如果value是在另个frame中,那么返回的必定是false
    ②解决问题:大家知道,在任何值上调用Object 原生的toString()方法,都会返回一个[object NativeConstructorName]格式的字符串。每个类在内部都有一个[[Class]]属性,这个属性中就指定了上述字符串中的构造函数名。且由于原生数组的构造函数名与全局作用域无关,因此使用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]";
            }
        }
        // 测试
        var checkType = new SafeTypeCheck();
        alert(checkType.isFunction(function () {}));    // true
        alert(checkType.isArray([]));   // true
        alert(checkType.isString(123)); // false
二、作用域安全的构造函数
    ①引子:先看一个例子:
        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);      // 为空
    ②正常情况下这是完全没有问题的,但当Person在实例化时被忘记了new了,那么可想而知,this指向了window
        var li = Person("li", 20, "student");
        alert(li.name);     // 报错
        alert(window.name); // li
    ③因此,我们在寻找一个良好的代码来屏蔽这样的问题
        function Person(name, age, job){
            // 当使用了new实例化时,this指向Person
            if(this instanceof Person) {
                this.name = name;
                this.age = age;
                this.job = job;
            } else {
            // 当忘记了new对象时,自动会加上一个new关键字,这样就避免了指向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); // 为空
    ④同样,安全作用域的构造函数也是有bug的,如果你想继承但只是用窃取模式,不是原型链继承模式时,会因为父类作用域被锁住而失败的现象
        function Student(grade) {
            Person.call(this, "wang", 29, "teacher");
            this.grade = grade;
        }
        var stu = new Student(100);
        // 由于父类的作用域被锁住,所以无法调用
        alert(stu.name);    // undefined
        alert(stu.grade);   // 100
    ⑤所以,我们没办法。只能老老实实的用原型链搞定它。
        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
三、惰性载入函数
    ①引子:为什么要有惰性载入函数?先看下面一个创建XHR对象的例子:
        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.");
            }
        }
    ②问题所在:上述例子的功能是:针对不同的浏览器创建一个XHR对象,函数中有许多if语句,在代码执行期间我们需要判断。因为我们使用的浏览器不会发送变化,在这个函数第二次被调用的时候,我个人认为该函数的if语句就应该不会再次去判断是什么样的浏览器而去实例化不同的XHR对象。因此我们提出了惰性载入函数。
    ③应用:现在将上述的函数优化一下:
        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
    ④优点:这样,我们在调用时,就不需要执行了if语句,这又提高了效率。
四、函数绑定
    ①引子:先看下面一个例子:
        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
    ②原因:为什么会弹出undefined?在于没有保存handler.handleClick()的环境,所以this 对象最后是指向了DOM 按钮而非handler(在IE8 中)。因此我们可以用闭包来保留handler.handleClick()的环境,起码在这个类中中。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
        });
    ③解决方案:虽然可以用闭包解决此类问题,但是使用闭包会使代码变得很难理解和调试.我们可以用apply或者call方法改变作用域来实现绑定。
        EventUtil.addEvent(btn, "click", function () {
            return handler.handleClick.call(handler);
        });
    ④封装成函数:
        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 为所有函数定义了一个原生的bind()方法,进一步简单了操作.
        EventUtil.addEvent(btn, "click", handler.handleClick.bind(handler));
五、函数柯里化.
    1.含义:调用另一个函数,并为它传入要柯里化的函数和参数。以下函数能很好的展示库里化的概念。
        function add(num1, num2){
            return num1 + num2;
        }
        function curriedAdd(num2){
            // 调用了add()函数,并为它传入了参数
            return add(5, num2);
        }
        alert(add(2, 3)); //5
        alert(curriedAdd(3)); //8
    2.创建函数库里化的通用方式
        function curry(fn){
            // 取得外部函数的从第二个开始的所有参数
            var args = Array.prototype.slice.call(arguments, 1);
            // alert(args); // 10, 10, 10
            return function(){
                // 取得内部函数(匿名函数)的全部参数
                var innerArgs = Array.prototype.slice.call(arguments);
                // alert(innerArgs);    // 15, 15
                // 将外部的参数和内部参数全部聚集在一起
                var finalArgs = args.concat(innerArgs);
                // alert(finalArgs);        // 10, 10, 10, 15, 15
                // 最后将所有的传入到要调用的函数中去
                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;
        }
        // 对于下面的函数调用
        // fn为add函数
        // args为10, 10, 10
        // innerArgs为15, 15
        var curried = curry(add, 10, 10, 10);
        alert(curried(15, 15)); // 60
    3.上述的curry函数没用改变相应的作用域,我们可以继续修改
        function curry(fn, context){
            // 取得外部函数的从第三个开始的所有参数
            var args = Array.prototype.slice.call(arguments, 2);
            // alert(args); // btn
            return function(){
                // 取得内部函数(匿名函数)的全部参数
                var innerArgs = Array.prototype.slice.call(arguments);
                // alert(innerArgs);    // [object MouseEvent]
                // 将外部的参数和内部参数全部聚集在一起
                var finalArgs = args.concat(innerArgs);
                alert(finalArgs);       // btn, [object MouseEvent]
                // 最后将所有的传入到要调用的函数中去
                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()方法也实现函数柯里化,只要在this 的值之后再传入另一个参数即可。
        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"));

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,997评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,603评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,359评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,309评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,346评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,258评论 1 300
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,122评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,970评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,403评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,596评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,769评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,464评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,075评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,705评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,848评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,831评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,678评论 2 354

推荐阅读更多精彩内容