本文共 5603 字,大约阅读时间需要 18 分钟。
JavaScript是函数式编程与面向对象编程的混合编程语言,加上本身一些可扩展性(比如:函数参数个数及类型的不确定),使得JavaScript非常灵活,当然也可以说非常不可控。正是这个特点,使得一个团队维护一个共同的前端项目时,JavaScript代码可能非常难以读懂。试想,你新加入一个团队,让你去读别人的代码本身就不太容易,如果团队的注释习惯及编程风格不太好,真的很难读懂每个js文件到底在干什么。因此,编写可维护的高质量代码真的非常重要。
阅读代码比阅读文章困难是因为,一段代码可能会依赖很多函数,你为了了解每个函数的作用不得不跳到其他代码块,这不符合我们的阅读习惯。 偶尔在一本书中看到这样一句话:“面向对象语言的问题是,它们永远都要随身携带那些隐式的环境。你只需要一个香蕉,但却得到一个拿着香蕉的大猩猩…以及整个丛林”。所以,尝试在我们的代码中更巧妙利用JavaScript的特点,使用函数式编程,可以使得我们的JS代码可维护性更高,可读性更强。
首先我们来了解一下什么是函数式编程。
在函数式编程语言中,函数是第一类的对象,也就是说,函数 不依赖于任何其他的对象而可以独立存在,而在面向对象的语言中,函数 ( 方法 ) 是依附于对象的,属于对象的一部分。这一点就 决定了函数在函数式语言中的一些特别的性质,比如作为传出 / 传入参数,作为一个普通的变量等。
我们常听到这样一种说法,在JavaScript中函数是“一等公民”。当我们说函数是“一等公民”的时候,我们实际上说的是它们和其他对象都一样,你可以像对待任何其他数据类型一样对待它们——把它们存在数组里,当作参数传递,赋值给变量…等等。而函数式编程的一个基础就是纯函数。
纯函数是这样一种函数,即相同的输入,永远会得到相同的输出,而且没有任何可观察的副作用。
实际上我们可以这样理解,纯函数就是这样一种函数,它不依赖于外部环境(例如:全局变量、DOM)、不改变外部环境(例如:发送请求、改变DOM结构),函数的输出完全由函数的输入决定。
比如 slice 和 splice,这两个函数的作用并无二致——但是注意,它们各自的方式却大不同,但不管怎么说作用还是一样的。我们说 slice 符合纯函数的定义是因为对相同的输入它保证能返回相同的输出。而 splice 却会嚼烂调用它的那个数组,然后再吐出来;这就会产生可观察到的副作用,即这个数组永久地改变了。相关代码请查看
var array1 = [0,1,2,3,4,5,6]; var array2 = [0,1,2,3,4,5,6]; var spliceArray = array1.splice(0,2); var sliceArray = array2.slice(0,2); console.log('array1: ' + array1); console.log('spliceArray: ' + spliceArray); console.log('array2: ' + array2); console.log('sliceArray: ' + sliceArray);
运行结果
array1: 2,3,4,5,6spliceArray: 0,1array2: 0,1,2,3,4,5,6sliceArray: 0,1
可以看到,splice改变了原始数组,而slice没有。我们认为,slice不改变原来数组的方式更加“安全”。改变原始组数,是一种“副作用”。
让我们来仔细研究一下“副作用”以便加深理解。那么,我们在纯函数定义中提到的万分邪恶的副作用到底是什么?
副作用是在计算结果的过程中,系统状态的一种变化,或者与外部世界进行的可观察的交互。
副作用可能包含,但不限于:
function getName(obj){ return obj.name; } function getAge(obj){ return obj.age; } function selfIntroduction(people){ console.log(getName(people)); console.log(getAge(people)); } var Lee = { name: 'LYY', age: 25 }; selfIntroduction(Lee);
运行结果
LYY25
显然selfIntroduction
这个函数不是纯函数,它依赖于getName
、getAge
两个函数,如果我不小心改变了其中某个函数的功能,这将使得selfIntroduction
这个函数出现错误。你现在可能感觉自己不会犯这样的错误,但当网页变得复杂,且由多人维护的时候,这将是个很难发现的bug。
var memoize = function(f) { var cache = {}; return function() { var arg_str = JSON.stringify(arguments); cache[arg_str] = cache[arg_str] ? cache[arg_str] + '(from cache)' : f.apply(f, arguments); return cache[arg_str]; }; }; var squareNumber = memoize(function(x){ return x*x; }); console.log(squareNumber(4)); console.log(squareNumber(4)); console.log(squareNumber(5)); console.log(squareNumber(5));
执行结果:
1616(from cache)2525(from cache)
以上分析提到了纯函数的很多优点,但是,这并不是要求我们编写的每一个函数都是纯函数。函数越“纯”,对环境依赖越少,往往意味着要输入更多参数。
var pureHttpCall = memoize(function(url, params){ return function() { return $.getJSON(url, params); }});
这里有趣的地方在于我们并没有真正发送 http 请求——只是返回了一个函数,当调用它的时候才会发请求。这个函数之所以有资格成为纯函数,是因为它总是会根据相同的输入返回相同的输出:给定了 url 和 params 之后,它就只会返回同一个发送 http 请求的函数。这种技巧结合 及 代码组合会使我们的JS代码清晰、可维护。
我写过一篇博客,你可以点击阅读。 代码组合其实比较简单,现在举例如下。
var compose = function(f, g) { return function(x) { return f(g(x)); }; }; function getPeople(){ return {}; } function namePeople(p){ p.name = 'Lee'; return p; } var definePeople = compose(namePeople, getPeople); var people = definePeople(); console.log(people);
执行结果:
Object {name: “Lee”} 看,代码组合就是字面意思。var getServerStuff = function(callback){ return ajaxCall(function(json){ return callback(json); });};//如果你仔细分析这段代码,它就等价于var getServerStuff = ajaxCall;
分析过程:
// 这行return ajaxCall(function(json){ return callback(json);});// 等价于这行return ajaxCall(callback);// 那么,重构下 getServerStuffvar getServerStuff = function(callback){ return ajaxCall(callback);};// ...就等于var getServerStuff = ajaxCall;
还有这种控制器:
var BlogController = (function() { var index = function(posts) { return Views.index(posts); }; var show = function(post) { return Views.show(post); }; var create = function(attrs) { return Db.create(attrs); }; var update = function(post, attrs) { return Db.update(post, attrs); }; var destroy = function(post) { return Db.destroy(post); }; return {index: index, show: show, create: create, update: update, destroy: destroy};})();
我们可以直接把它重写成:
var BlogController = {index: Views.index, show: Views.show, create: Db.create, update: Db.update, destroy: Db.destroy};
当你滥用函数式编程时,很可能使得你的代码很难懂。所以,我的建议是,代码保持最直接简洁的状态,尽量使用纯函数(容易维护)、适当情况下使用函数式编程(容易看懂)。
转载地址:http://isefn.baihongyu.com/