javascript --- Function模式

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了javascript --- Function模式相关的知识,希望对你有一定的参考价值。

回调函数

javascript中,当一个函数A作为另外一个函数B的其中一个参数时,则称A函数为回调函数,即A可以在函数B的运行周期内执行(开始,中间,结束)。

举例来说,有一个函数用于生成node.

var complexComutation = function(){
    // 内部处理,并返回一个node
}

又有一个findNodes函数声明用于查找所有节点,然后通过callback回调进行执行代码。

var findNodes = function(callback){
    var nodes = [];
    var node = complexComputation();
    // 如果回调函数可用,则执行她
    if(typeof callback === ‘function‘){
        callback(node);
    }  
    nodes.push(node);
    return nodes;
}

关于callback的定义,我们可以事先定义好来用:

// 定义callback
var hide = function(node){
    node.style.display = ‘node‘;
}
var hiddenNodes = findNodes(hide);

也可以在调用的时候使用匿名定义,如下:

// 使用匿名定义callback
var blockNodes = findNodes(function(node){
     node.style.display = ‘block‘;
})

我们平时用的最多的,估计就数jQuery的ajax方法的调用了,通过在done/faild上定义callback,以便在ajax调用成功或者失败的时候做进一步处理,代码如下(本代码基于jquery1.8版):

var menId = $(‘ul.nav‘).first().attr(‘id‘);
var request = $.ajax({
    url: ‘script.php,
    type: ‘post‘,
    data: {id: menuId},
    dataType: html
})
// 调用成功时的回调处理
request.done(function(msg){
    $(‘#log‘).html(msg);
})
// 调用失败时的回调处理
request.fail(function(jqXHR, textStatus){
    alert(‘Request failed:‘ + textStatus);
});

 

配置对象

如果一个函数(或方法)的参数只有一个,并且参数为对象字面量,我们则称这种模式为配置对象模式。如下所述:

var her = {
    username: ‘gaohan‘,
    first: ‘gao‘,
    last: ‘han‘
};
addPerson(her);

则在addPerson函数内部,就可以随意使用her对象内的值了,一般用于初始化工作,例如jquery里的ajaxSetup也就是这种方式来实现的:

// 事先设置好初始值
$.ajaxSetup({
    url: ‘/xmlhttp/‘,
    global: false,
    type: ‘POST‘
})
// 然后再调用
$.ajax({ data:myData });

另外,很多jquery的插件也有这种形式的传参,只不过也可以不传,不传的时候则就使用默认值了。

返回函数

返回函数,则是指一个函数的返回值为另一个函数,或者根据特定的条件灵活创建的新函数,示例如下:

var setup = function(){
    console.log(1);
    return function(){
        console.log(2);
    };
};
// 调用setup函数
var her = setup(); // 1
her(); // 2
// 或者直接调用也可以;
setup()(); // 2

或者我们可以利用闭包的特性,在函数中加入一个计数器,用来计算函数被调用的次数:

var setup = function(){
    var count = 0;
    return function(){
        retunr ++count;
    };
};
var next = serup();
next(); // 1
next(); // 2
next(); // 3

 

Currying

Currying是函数式编程的一个特性,将多个参数的处理转化成单个参数的处理,类似链式调用。

下面来举一个简单的例子:

function add(x, y){
    var oldx = x, oldy = y;
    if(typeof oldy === ‘undefined‘){
        return function (newy){
             return oldx + newy;
        }
    } 
    return x + y ;
}

这样的话,调用方式就有很多种了,比如:

// 测试
typeof add(5); // ‘function‘
add(3)(4); // 7
// 也可以这样调用
var add2000 = add(2000);
add2000(10); // 2010

接下来我们定义一个,比较常用的currying函数:

// 第一个参数为要应用的function, 第二个参数是需要传入的最少参数个数
function curry(func, minArgs) {
    if(minArgs == undefined) {
         minArgs = 1;
    }
    function funcWithArgsFrozen(frozenargs){
         return function () {
              // 优化处理,如果调用时没有参数,返回该函数本身
              var ages = Array.prototype.slice.call(arguments);
              var newArgs = frozenargs.concat(args);
              if(newArgs.length >= minArgs) {
                   return func.apply(this, newArgs);
              }else{
                   return funcWithArgsFrozen(newArgs);
              }
         }
    }
    return funcWithArgsFrozen([]);
}

这样,我们就可以随意定义我们的业务行为了,比如定义加法:

var plus = curry(function(){
    var result = 0;
    for(var i=0; i<arguments.length; ++i){
        result += arguments[i];
    }
    return result;
}, 2)

使用方法多种多样。

plus(3, 2) // 正常调用
plus(3) // 偏应用
plus(3) (2) // 完整应用(返回5)
plus() (3) () () (2) // 返回5
plus(3, 2, 4, 5) // 可以接受多个参数
plus(3) (2, 3, 5) // 同理

JavaScript里的Function有很多特殊的功效,可以利用闭包以及arguments参数特性实现很多不同的技巧,下一篇我们将继续介绍利用Function进行初始化的技巧。

立即执行的函数

// 声明完函数以后,立即执行该函数
(function (){
     console.log(‘watch out‘);
}());
// 这种方式声明的函数,也可以立即执行
!function(){
    console.log(‘watch out!‘);
}();
// 如下的方式也都可以哦
~function(){ /* do someing*/ }();
-function(){ /* do someing*/ }();
+function(){ /* do someing*/ }();

 

 立即执行的对象初始化

该模式的意思是指在声明一个对象(并非函数)的时候、立即执行对象里的某一个方法进行初始化工作,通常该模式可以用在执行一次性的代码上:

({
    // 这里你可以定义常量,并设置其它值
    maxwidth:  600,
    maxheight: 400,
    // 当然也可以定义方法
    gimmMax: function(){
        return this.maxwidth + ‘x‘ + this.maxheight;
    },
    init: function(){
        console.log(this.gimmeMax());
        // 更多代码......
    }
}).init(); // 这样就能初始化喽

 

分支初始化

分支初始化是指在初始化的时候,根据不同的条件(场景)初始化不同的代码,也就是所谓的条件语句赋值。之前我们在处理的时候,常常使用类似下面的代码:

var utils = {
    addListener: function(el, type, fn){
        if(typeof window.addEventListener === ‘function‘){
             el.addEventListener(type, fn, false);
        }else if(typeof document.attachEvent !== ‘undefined‘){
             el.attachEvent(‘on‘ + type, fn); 
        }else{
             el[‘on‘ + type] = fn;
        }
    },
    removeListener: function(el, type, fn){
        // 神马之类的......
    }
}

我们来改进一下,首先我们要定义两个接口,一个用来add事件句柄,一个用来remove事件句柄,代码如下:

var utils = {
    addListener: null,
    removeListener: null
};

实现代码如下:

if (typeof window.addEventListener === ‘function‘) {
    utils.addListener = function (el, type, fn) {
        el.addEventListener(type, fn, false);
    };
} else if (typeof document.attachEvent !== ‘undefined‘) { // IE
    utils.addListener = function (el, type, fn) {
        el.attachEvent(‘on‘ + type, fn);
    };
    utils.removeListener = function (el, type, fn) {
        el.detachEvent(‘on‘ + type, fn);
    };
} else { // 其它旧浏览器
    utils.addListener = function (el, type, fn) {
        el[‘on‘ + type] = fn;
    };
    utils.removeListener = function (el, type, fn) {
        el[‘on‘ + type] = null;
    };
}

用起来,是不是就很方便了?代码也优雅多了。

自声明函数

一般是在函数内部,重写重名函数代码,比如:

var her = function(){
    alert(‘Boo!‘);
    her = function(){
        alert(‘Double Boo!!‘);
    }
}

这种代码,非常容易使人迷惑,我们先来看看例子的执行结果:

// 1.添加新属性
scareMe.prototype = ‘Anna‘;
// 2. scareMe赋予一个新值
var prank = scareMe;
// 3. 作为一个方法调用
var spooky = {
    boo: scareMe
}
// 使用新变量名称进行调用
prank(); // ‘Boo!‘
prank(); // ‘Boo!‘
console.log(spooky.boo.property); // ‘properly‘

通过执行结果,可以发现,将定于的函数赋值与新变量(或内部方法),代码并不执行重载的scareMe代码,而如下例子则正好相反:

// 使用自声明函数
scareMe(); // Double boo!
scareMe(); // Double boo!
console.log(scareMe.property); // undefined

大家使用这种模式时,一定要非常小心才行,否则实际结果很可能和你期望的结果不一样,当然你也可以利用这个特殊做一些特殊的操作。

内存优化

该模式主要是利用函数的属性特性来避免大量的重复计算。通常代码形式如下:

var myFunc = function (param) {
    if (!myFunc.cache[param]) {
        var result = {};
        // ... 复杂操作 ...
        myFunc.cache[param] = result;
    }
    return myFunc.cache[param];
};

// cache 存储
myFunc.cache = {};

 

但是上述代码有个问题,如果传入的参数是toString或者其它类似Object拥有的一些公用方法的话,就会出现问题,这时候就需要使用传说中的hasOwnProperty方法了,代码如下:

var myFunc = function (param) {
    if (!myFunc.cache.hasOwnProperty(param)) {
        var result = {};
        // ... 复杂操作 ...
        myFunc.cache[param] = result;
    }
    return myFunc.cache[param];
};

// cache 存储
myFunc.cache = {};

或者如果你传入的参数是多个的话,可以将这些参数通过JSON的stringify方法生产一个cachekey值进行存储,代码如下:

var myFunc = function () {
    var cachekey = JSON.stringify(Array.prototype.slice.call(arguments)),
        result;
    if (!myFunc.cache[cachekey]) {
        result = {};
        // ... 复杂操作 ...
        myFunc.cache[cachekey] = result;
    }
    return myFunc.cache[cachekey];
};

// cache 存储
myFunc.cache = {};

或者多个参数的话,也可以利用arguments.callee特性:

var myFunc = function (param) {
    var f = arguments.callee,
        result;
    if (!f.cache[param]) {
        result = {};
        // ... 复杂操作 ...
        f.cache[param] = result;
    }
    return f.cache[param];
};

// cache 存储
myFunc.cache = {};
总结

 

以上是关于javascript --- Function模式的主要内容,如果未能解决你的问题,请参考以下文章

javascript --- Function模式

JavaScript设计模式

深入理解JavaScript系列(49):Function模式(上篇)

深入理解JavaScript系列(50):Function模式(下篇)

JavaScript常用设计模式

JavaScript--Module模式