classicemi / blog

🖋 my personal blog
https://wushuang.name/
32 stars 2 forks source link

让函数进化 #13

Open classicemi opened 8 years ago

classicemi commented 8 years ago

假设现在有这样一个场景,一个下单页面,需要根据特定的条件去计算购物车商品的总价,特定的条件包括但不限于选中的商品、是否选择折扣、是否叠加套餐和运费险等。这时我们通常需要写一个用来计算总价的函数getTotalPrice(),然而,函数的参数是不确定的,同时函数内的逻辑也会根据参数的不同而有所变化,最简单的方法是在函数内部对参数进行判断:

如果参数分开传入,那么函数的伪代码可能写成这样:

function getTotalPrice(selectedGoods, isDiscounted, pacakge) {
    var totalPrice = 0;
    if (selectedGoods) {
        totalPrice = ...;
        if (isDiscounted) {
            // 计算折扣
            if (package) {...}
        } else if (pacakge) {...}
        return totalPrice;
    } else {
        return 0;
    }
}

有的人可能觉得将参数分开传入函数看起来比较乱,会习惯用一个参数对象包裹一下,在函数内部再对这个对象进行解析。看起来确实有一定的改进,但我认为并没有什么本质上的区别。

现在希望能够做到针对不同参数的情况,在函数内部只书写针对特定个数参数的逻辑,不去写乱七八糟的if-else语句或是参数对象的解析逻辑。同时,这些函数共用一个函数名,并且该逻辑可扩展,即可以随时根据需要,添加函数。

通过对需求的分析,应该会有这样的一个工具函数:

var methods = {};

addFunction(methods, 'getTotalPrice', function(selectedGoods) {...});
addFunction(methods, 'getTotalPrice', function(selectedGoods, isDiscounted) {...});
addFunction(methods, 'getTotalPrice', function(selectedGoods, isDiscounted, pacakge) {...});

这样,在添加每个函数的时候,只需要写针对特定参数的逻辑,调用getTotalPrice的时候,自动根据参数长度实际调用不同的函数。

很容易想到要对不同的函数进行缓存,同时为了公用同一个函数名,缓存的函数需要匿名,进而联想到闭包可以保存局部变量的引用的特性,以下是addFunction的实现:

function addFunction(object, funcName, fn) {
    var oldFunc = object[funcName];
    object[funcName] = function() {
        return (fn.length === arguments.length ? fn : oldFunc).apply(this, arguments);
    }
}

addFunction方法的作用域中,保存了原方法的引用,通过对参数长度的比较确定最终执行哪个匿名函数,要注意的是一个函数的length属性表示改函数接受的形参数量,而arguments.length表示实际传入的参数数量。匿名函数访问oldFuncfn则是典型的闭包的应用,并没有把函数存储在任何典型的数据结构中。

addFunction(methods, 'getTotalPrice', function(total) {
    console.log(total);
});
addFunction(methods, 'getTotalPrice', function(total, discount) {
    console.log(total * discount);
});

methods.getTotalPrice(20); // 输出20
methods.getTotalPrice(20, 0.8); // 输出16

要注意的是,这种方式只能针对不同数量的参数,不能判断参数的名称或类型,如果要判断的话,势必会牺牲addFunction方法的通用性。另外,每添加一个函数,就会增加一层嵌套,调用深度过深的话性能开销也会比较大,使用的时候要进行权衡。

gideonstele commented 8 years ago

这算是利用闭包实现类似方法重载的方案了,很精髓啊