• JS高阶函数精讲

    高阶函数也称算子(运算符)或泛函。作为函数式编程最显著的特征,高阶函数是对函数运算进行进一步的抽象。高阶函数的形式应至少满足下列条件之一:

    • 函数可以作为函数被传入,也称为回调函数,如函数合成运算。
    • 可以返回函数作为输出,如函数柯里化运算。

    JS回调函数

    把函数作为值传入另一个参数,当传入参数被调用时,就称为回调函数,即异步调用已绑定的函数。例如,事件处理函数、定时器中的回调函数、异步请求中的回调函数、replace 方法中的替换函数、数组迭代中的回调函数(sort、map、forEach、filter、some、every、reduce 和 reduceRight 等),都是回调函数的不同应用形式。下面仅举两个示例,演示回调函数的应用。

    示例1

    下面代码根据日期对对象进行排序。

    //声明3个对象,每个对象都有属性id和date
    var a = {id : 1, date : new Date(2019,3,12)},
        b = {id : 2, date : new Date(2019,1,14)},
        c = {id : 3, date : new Date(2019,2,26)};
    var arr = [a,b,c];
    arr.sort(function(x,y){
        return x.date-y.date;
    });
    for (var i = 0; i < arr.length; i++) {
    console.log(arr[i].id + " " + arr[i].date.toLocaleString());
    }

    输出结果:

    2 2019 年 2 月 14 日 0:00:00
    3 2019 年 3 月 26 日 0:00:00
    1 2019 年 4 月 12 日 0:00:00

    在数组排序的时候,会迭代数组每个元素,并逐一调用回调函数 function(x,y) {return x.date - y.date}。

    示例2

    在《JS map()》一节中我们曾介绍过数组的 map 方法,实际上很多函数式编程语言均有此函数。其语法格式为:

    map(array,func)

    map 表达式将 func 函数作用于 array 的每一个元素,并返回一个新的 array。

    下面使用 JavaScript 实现 map(array,func) 表达式运算。

    function map(array,func) {
        var res = [];
        for (var i in array) {
            res.push(func(array[i]));
        }
        return res;
    }
    console.log(map([1,3,5,7,8], function (n) {  //返回元素值的平方
        return n * n;
    }));  //1,9,25,49,64
    console.log(map(["one", "two", "three", "four"], function(item) {  //返回首字母大写
        return item[0].toUpperCase() + item.slice(1).toLowerCase();
    }));  //One,Two,Three,Four

    两次调用 map,却得到了截然不同的结果,是因为 map 的参数本身已经进行了一次抽象,map 函数做的是第二次抽象。注意:高阶的“阶”可以理解为抽象的层次。

    JS 函数既可以作为参数传入函数内部,也可以作为返回值 return 到函数外部,具体应用场景包括:

    • JS 单例模式
    • JS 实现 AOP
    • JS 类型检测
    • JS 函数节流和分时函数
    • JS 惰性载入函数与分支函数
    • JS 偏函数
    • JS 泛型函数

    由于篇幅有限,本节只介绍前面三种应用场景,其它场景请猛击链接查看。

    JS单例模式

    单例就是保证一个类只有一个实例。实现方法:先判断实例是否存在,如果存在则直接返回,否则就创建实例再返回。

    单例模式可以确保一个类型只有一个实例对象。在 JavaScript 中,单例可以作为一个命名空间,提供一个唯一的访问点来访问该对象。单例模式封装代码如下:

    var getSingle = function (fn) {
        var ret;
        return function () {
            return ret || (ret = fn.apply(this, arguments));
        };
    };

    示例1

    在脚本中定义 XMLHttpRequest 对象。由于一个页面可能需要多次创建异步请求对象,使用单例模式封装之后,就不用重复创建实例对象,共用一个即可。

    function XHR () {  //定义XMLHttpRequest 对象
        return new XMLHttpRequest();
    }
    var xhr = getSingle(XHR);  //封装XHR实例
    var a = xhr();  //实例1
    var b = xhr();  //实例2
    console.log(a === b);  //true,说明这两个实例实际上相同

    示例2

    可以限定函数仅能调用一次,避免重复调用,这在事件处理函数中非常有用。

    <button>仅能点击一次</button>
    <script>
    function getSingle (fn) {
        var ret;
        return function () {
            return ret || (ret = fn.apply(this,arguments));
        };
    };
    var f = function () { console.log(this.nodeName); }  //事件处理函数
    document.getElementsByTagName("button")[0].onclick = getSingle(f);
    </script>

    JS实现 AOP

    AOP(面向切面编程)就是把一些与业务逻辑模块无关的功能抽离出来,如日志统计、安全控制、异常处理等,然后通过“动态织入”的方式掺入业务逻辑模块中。这样设计的好处是:首先可以保证业务逻辑模块的纯净和高内聚性;其次可以方便地复用日志统计等功能模块。

    示例

    在 JavaScript 中实现 AOP,一般是把一个函数“动态织入”到另外一个函数中。具体的实现方法有很多,下面通过扩展 Function.prototype 方法实现 AOP。

    Function.prototype.before = function (beforefn) {
        var __self = this;  //保存原函数的引用
        return function () {  //返回包含了原函数和新函数的“代理”函数
            beforefn.apply(this, arguments);  //执行新函数
            return __self.apply(this, arguments);  //执行原函数
        }
    };
    Function.prototype.after = function (afterfn) {
        var __self = this;  //保存原函数的引用
        return function () {  //返回包含了原函数和新函数的“代理”函数
           var ret = __self.apply(this,arguments);  //执行原函数
            afterfn.apply(this, arguments);  //执行新函数,修正this
            return ret;
        }
    };
    var func = function (){
        console.log(2);
    };
    func = func.before(function () {
        console.log(1);
    }).after(function () {
        console.log(3)
    });
    func();  //按顺序输出1,2,3

    类型检测

    本节利用 JavaScript 高阶函数特性来重新设计 typeOf() 函数,并提供单项类型判断函数。

    【实现代码】

    function typeOf(obj) {  //类型检测函数,返回字符串表示
        var str = Object.prototype.toString.call(obj);
        return str.match(/\[object(.*?)\]/)[1].toLowerCase();
    };
    ['null', 'Undefined', 'Object', 'Array', 'String', 'Number', 'Boolean', 'Function', 'RegExp'].forEach(function (t) {  //类型判断,返回布尔值
        typeOf['is' + t] = function (o) {
            return typeOf(o) === t.toLowerCase();
        };
    });

    【应用代码】

    //类型检测
    console.log(typeOf({}));  //"object"
    console.log(typeOf([]));  //"array"
    console.log(typeOf(0));  //"number"
    console.log(typeOf(null));  //"null"
    console.log(typeOf(undefined));  //"undefined"
    console.log(typeOf(//));  //"regex"
    console.log(typeOf(new Date()));  //"date"
    //类型判断
    console.log(typeOf.isObject({}));  //true
    console.log(typeOf.isNumber(NaN));  //true
    console.log(typeOf.isRegExp(true));  //false

更多...

加载中...