前两讲我们介绍了前端开发领域常见的开发模式和封装思想,这一讲,我们将该主题升华,聊一聊软件开发灵活性和高定制性这个话题。
业务需求是烦琐多变的,因此开发灵活性至关重要,这直接决定了开发效率,而与灵活性相伴相生的话题就是定制性。本讲主要从设计模式和函数式思想入手,从实际代码出发,来阐释灵活性和高定制性。
设计模式
设计模式——我认为这是一个“一言难尽”的概念。维基百科对设计模式的定义为:
在软件工程中,设计模式(Design Pattern)是对软件设计中普遍存在(反复出现)的各种问题,所提出的解决方案。这个术语是由埃里希·伽玛(Erich Gamma)等人在 1990 年代从建筑设计领域引入到计算机科学的。设计模式并不是直接用来完成代码的编写,而是描述在各种不同情况下,要怎么解决问题的一种方案。
设计模式一般认为有 23 种,这 23 种设计模式的本质是面向对象设计原则的实际运用,是对类的封装性、继承性和多态性,以及类的关联关系和组合关系的总结应用。
事实上,设计模式是一种经验总结,它就是一套“兵法”,最终是为了更好的代码重用性、可读性、可靠性、可维护性。我认为设计模式不能只停留在理论上,而是应该结合到实际代码当中。在平常开发中,“也许你不知道,但是已经在使用设计模式了”。
下面我们将从前端中最常见的两种设计模式展开讲解。
代理模式
代理模式大家应该都不陌生,ES.next 提供的 Proxy 特性让我们实现代理模式变得更加容易。关于 Proxy 特性的使用这些基础内容这里不过多赘述,我们直接来看一些代理模式的应用场景。
一个常见的代理模式应用场景是针对计算成本比较高的函数,我们可以通过对函数进行代理,来缓存函数对应参数的计算返回结果。在函数执行时,优先使用缓存值,否则返回执行计算值,如下代码:
利用上述实现思想,我们还可以很轻松地实现一个根据调用频率来进行截流的函数代理,如下代码实现:
我们再看一个 jQuery 中的一个例子,jQuery 中方法接受一个已有的函数,并返回一个带有特定上下文的新函数。比如对于向一个特定对象的元素添加事件回调,如下代码:
上述代码中的因为是在中执行,不再是预期之中的“当前触发事件的元素”,我们可以存储 this 指向来完成:
也可以使用 jQuey 中的代理方法。如下代码:
其实,jQuery 源码中的实现也并不困难:
上述代码中我们模拟了方法,以保证 this 上下文的准确。
事实上,代理模式在前端中的使用场景非常多。我们熟悉的 Vue 框架,为了完成对数据的拦截和代理,以便结合观察者模式,对数据变化进行响应,在最新版本中,也使用了 Proxy 特性,这些都是代理模式的典型应用。
装饰者模式
简单来说,装饰者模式就是在不改变原对象的基础上,对其对象进行包装和拓展,使原对象能够应对更加复杂的需求。这有点像高阶函数,因此在前端开发中很常见,如下面代码:
react-redux 类库中的方法,对相关 React 组件进行包装,以拓展新的 Props。另外,这种方法在 ant-design 中也有非常典型的应用,如下面代码:
如上代码,我们将一个 React 组件进行“装饰”,使其获得了表单组件的一些特性。
事实上,我们将上述介绍的两种模式相结合,很容易衍生出 AOP 面向切面编程的概念。如下代码:
如上代码,我们对函数原型进行了扩展,在函数调用前后分别调用了相关切面方法。一个典型的场景就是对表单提交值进行验证。如下代码:
至此,我们对前端中常见的两种设计模式进行了分析,实际上,在前端中还处处可见观察者模式等经典设计模式的应用,我们将在下一讲中,进行更多说明。
函数式思想应用
前面我们介绍了设计模式相关内容,事实上,设计模式和面向对象话题相伴相生,而面向对象和函数式思想“相互对立”,互为补充。函数式思想在前端领域同样应用颇多,这里我们简单对函数式思想的基础应用进行说明。
函数组合的简单应用
纯函数是指:
一个函数如果输入参数确定,输出结果是唯一确定的,那么它就是纯函数。
同时,需要强调的是纯函数不能修改外部变量,不能调用 Math.radom() 方法以及发送异步请求等,因为这些操作都不具有确定性,可能会产生副作用。
纯函数是函数式编程中最基本的概念。另一个基本概念是——高阶函数:
高阶函数体现了“函数是第一等公民”,它是指这样的一类函数:该函数接受一个函数作为参数,返回另外一个函数。
我们来看一个例子:这个函数接受一个数组作为参数,它会挑选出数组中数值小于 10 的项目,所有符合条件的值都会构成新数组被返回:
另外一个需求,挑选出数组中非数值项目,所有符合条件的值都会构成新数组被返回,如下函数:
上面两个函数都是比较典型的纯函数,不够优雅的一点是 filterLowerThan10 和 filterNaN都有遍历的逻辑,都存在了重复的 for 循环。它们本质上都是遍历一个列表,并用给定的条件过滤列表。那么我们能否用函数式的思想,将遍历和筛选解耦呢/p>
好在 JavaScript 对函数式较为友好,我们使用 Filter 函数来完成,并进行一定程度的改造,如下代码:
继续延伸我们的场景,如果输入比较复杂,想先过滤出小于 10 的项目,需要先保证数组中每一项都是 Number 类型,那么可以使用下面的代码:
我们通过组合,实现了更多的场景。
curry 和 uncurry
继续思考上面的例子,filterLowerThan10 还是硬编码写死了 10 这个阈值,我们用 curry 化的思想将其改造,如下代码:
上面代码中我们提到了 curry 化这个概念,简单说明:
curry 化,柯里化(currying),又译为卡瑞化或加里化,是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果的新函数的技术。这个技术由克里斯托弗·斯特雷奇以逻辑学家哈斯凯尔·加里命名的。
curry 化的优势非常明显:
-
提高复用性
-
减少重复传递不必要的参数
-
动态根据上下文创建函数
其中动态根据上下文创建函数,也是一种惰性求值的体现。比如这段代码:
声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!