博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
JavaScript-可维护代码编写,函数式编程与纯函数
阅读量:3661 次
发布时间:2019-05-21

本文共 5603 字,大约阅读时间需要 18 分钟。

JavaScript-可维护代码编写,函数式编程与纯函数

JavaScript是函数式编程与面向对象编程的混合编程语言,加上本身一些可扩展性(比如:函数参数个数及类型的不确定),使得JavaScript非常灵活,当然也可以说非常不可控。正是这个特点,使得一个团队维护一个共同的前端项目时,JavaScript代码可能非常难以读懂。试想,你新加入一个团队,让你去读别人的代码本身就不太容易,如果团队的注释习惯及编程风格不太好,真的很难读懂每个js文件到底在干什么。因此,编写可维护的高质量代码真的非常重要

1. 问题来源

阅读代码比阅读文章困难是因为,一段代码可能会依赖很多函数,你为了了解每个函数的作用不得不跳到其他代码块,这不符合我们的阅读习惯。 偶尔在一本书中看到这样一句话:“面向对象语言的问题是,它们永远都要随身携带那些隐式的环境。你只需要一个香蕉,但却得到一个拿着香蕉的大猩猩…以及整个丛林”。所以,尝试在我们的代码中更巧妙利用JavaScript的特点,使用函数式编程,可以使得我们的JS代码可维护性更高,可读性更强。

2. 使用纯函数来提高JS代码可维护性

首先我们来了解一下什么是函数式编程。

在函数式编程语言中,函数是第一类的对象,也就是说,函数 不依赖于任何其他的对象而可以独立存在,而在面向对象的语言中,函数 ( 方法 ) 是依附于对象的,属于对象的一部分。这一点就 决定了函数在函数式语言中的一些特别的性质,比如作为传出 / 传入参数,作为一个普通的变量等。

我们常听到这样一种说法,在JavaScript中函数是“一等公民”。当我们说函数是“一等公民”的时候,我们实际上说的是它们和其他对象都一样,你可以像对待任何其他数据类型一样对待它们——把它们存在数组里,当作参数传递,赋值给变量…等等。而函数式编程的一个基础就是纯函数

纯函数是这样一种函数,即相同的输入,永远会得到相同的输出,而且没有任何可观察的副作用。

2.1 理解纯函数

实际上我们可以这样理解,纯函数就是这样一种函数,它不依赖于外部环境(例如:全局变量、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不改变原来数组的方式更加“安全”。改变原始组数,是一种“副作用”。

2.2 非纯函数可能带来的“副作用”

让我们来仔细研究一下“副作用”以便加深理解。那么,我们在纯函数定义中提到的万分邪恶的副作用到底是什么?

副作用是在计算结果的过程中,系统状态的一种变化,或者与外部世界进行的可观察的交互。

副作用可能包含,但不限于:

  • 列表内容
  • 更改文件系统
  • 往数据库插入记录
  • 发送一个 http 请求
  • 可变数据
  • 打印/log
    获取用户输入
  • DOM 查询
  • 访问系统状态
    这个列表还可以继续写下去。概括来讲,只要是跟函数外部环境发生的交互就都是副作用——这一点可能会让你怀疑无副作用编程的可行性。函数式编程的哲学就是假定副作用是造成不正当行为的主要原因。
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这个函数不是纯函数,它依赖于getNamegetAge两个函数,如果我不小心改变了其中某个函数的功能,这将使得selfIntroduction这个函数出现错误。你现在可能感觉自己不会犯这样的错误,但当网页变得复杂,且由多人维护的时候,这将是个很难发现的bug。

2.3 纯函数编程的优点

  1. 可缓存性(Cacheable)
    纯函数总能够根据输入来做缓存。实现缓存的一种典型方式是 memoize 技术:
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)
  1. 可移植(Portable)
    纯函数是完全自给自足的,它需要的所有东西都能轻易获得。仔细思考思考这一点…这种自给自足的好处是什么呢?首先,纯函数的依赖很明确,因此更易于观察和理解——没有偷偷摸摸的小动作。这使得你在阅读这种代码的时候更容易,一个函数完成一个功能,不再依赖其他函数或者变量。
  2. 可测试(Testable)
    第三点,纯函数让测试更加容易。因为只要每次输入相同,纯函数将输出相同的结果,不需要多次测试同一个输入。
  3. 合理性(Reasonable)
    很多人相信使用纯函数最大的好处是引用透明性(referential transparency)。如果一段代码可以替换成它执行所得的结果,而且是在不改变整个程序行为的前提下替换的,那么我们就说这段代码是引用透明的。
    由于纯函数总是能够根据相同的输入返回相同的输出,所以它们就能够保证总是返回同一个结果,这也就保证了引用透明性。
  4. 并行代码(Parallel)
    最后一点,也是决定性的一点:我们可以并行运行任意纯函数。因为纯函数根本不需要访问共享的内存,而且根据其定义,纯函数也不会因副作用而进入竞争态(race condition)。
    并行代码在服务端 js 环境以及使用了 的浏览器那里是非常容易实现的,因为它们使用了线程(thread)。不过出于对非纯函数复杂度的考虑,当前主流观点还是避免使用这种并行。

3. 不要滥用函数式编程或者纯函数

以上分析提到了纯函数的很多优点,但是,这并不是要求我们编写的每一个函数都是纯函数。函数越“纯”,对环境依赖越少,往往意味着要输入更多参数。

3.1 可以提纯的情况举例

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”}
看,代码组合就是字面意思。

3.2 滥用函数式编程的举例

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/

你可能感兴趣的文章
PAT乙级_1077 互评成绩计算 (20 分)_python
查看>>
PAT乙级_1088 三人行 (20 分)_python
查看>>
PAT乙级_1089 狼人杀-简单版 (20 分)_python
查看>>
PAT乙级_1092 最好吃的月饼 (20 分)_python
查看>>
操作系统页表&进程调度Tips
查看>>
RT-Thread 学习笔记:一、通俗易懂学会创建线程
查看>>
转义序列
查看>>
约分最简分式
查看>>
时间换算
查看>>
逆序的三位数
查看>>
JS下拉框实现省市联动
查看>>
JS实现文字无缝滚动
查看>>
JavaScript高级学习(三)
查看>>
JavaScript高级学习(四)
查看>>
JS遍历DOM树
查看>>
JavaScript高级学习(五)——正则表达式
查看>>
验证密码的强中弱程度——正则表达式
查看>>
验证表单——正则表达式
查看>>
Centos7下spinnaker安装至k8s
查看>>
linux常用命令
查看>>