面向对象编程(OOP)通过封装变化使得代码更易理解。函数式编程(FP)通过最小化变化使得代码更易理解。-- Michacel Feathers(Twitter)
总所周知 JavaScript 是一种拥有很多共享状态的动态语言,慢慢的,代码就会积累足够的复杂性,变得笨拙难以维护。面向对象设计能帮我们在一定程度上解决这个问题,但是还不够。
由于有很多的状态,所以处理数据流和变化的传递显得尤为重要,不知道你们知道响应式编程与否,这种编程范式有助于处理 JavaScript 的异步或者事件响应。总之,当我们在设计应用程序的时候,我们应该考虑是否遵守了以下的设计原则。
我这能这么跟你说,一旦你学会了函数式编程,这些问题迎刃而解,本来函数式编程就是这个思想,一旦你掌握了函数式,然后你再学习响应式编程那就比较容易懂了,这是我亲身体会的。我之前在学 Rxjs 的时候是真的痛苦,说实话,Rxjs 是我学过最难的库了,没有之一。在经历过痛苦的一两个月之后,有些东西还是不能融会贯通,知道我最近研究函数式编程,才觉得是理所当然。毫无夸张,我也尽量在后面的文章中给大家介绍一下 Rxjs,这个话题我也在公司分享过。
简单来说,函数式编程是一种强调以函数使用为主的软件开发风格。看到这句我想你还是一脸懵逼,不知道函数式编程是啥,不要着急,看到最后我相信你会明白的。
还有一点你要记住,函数式编程的目的是使用函数来抽象作用在数据之上的控制流和操作,从而在系统中消除副作用并减少对状态的改变。
下面我们通过例子来简单的演示一下函数式编程的魅力。
现在的需求就是输出在网页上输出 “Hello World”。可能初学者会这么写。
document.querySelector('#msg').innerhtml = '<h1>Hello World</h1>'
这个程序很简单,但是所有代码都是死的,不能重用,如果想改变消息的格式、内容等就需要重写整个表达式,所以可能有经验的前端开发者会这么写。
function printMessage(elementId, format, message) {
document.querySelector(elementId).innerHTML = `<${format}>${message}</${format}>`
}
printMessage('msg', 'h1', 'Hello World')
这样确实有所改进,但是任然不是一段可重用的代码,如果是要将文本写入文件,不是非 HTML,或者我想重复的显示 Hello World。
那么作为一个函数式开发者会怎么写这段代码呢?
const printMessage = compose(addTodom('msg'), h1, echo)
printMessage('Hello World')
解释一下这段代码,其中的 h1 和 echo 都是函数,addToDom 很明显也能看出它是函数,那么我们为什么要写成这样呢?看起来多了很多函数一样。
其实我们是讲程序分解为一些更可重用、更可靠且更易于理解的部分,然后再将他们组合起来,形成一个更易推理的程序整体,这是我们前面谈到的基本原则。
compose 简单解释一下,他会让函数从最后一个参数顺序执行到第一个参数,compose 的每个参数都是函数,不明白的可以查一下,在 redux 的中间件部分这个函数式精华。
可以看到我们是将一个任务拆分成多个最小颗粒的函数,然后通过组合的方式来完成我们的任务,这跟我们组件化的思想很类似,将整个页面拆分成若干个组件,然后拼装起来完成我们的整个页面。在函数式编程里面,组合是一个非常非常非常重要的思想。
好,我们现在再改变一下需求,现在我们需要将文本重复三遍,打印到控制台。
var printMessaage = compose(console.log, repeat(3), echo)
printMessage(‘Hello World’)
可以看到我们更改了需求并没有去修改内部逻辑,只是重组了一下函数而已。
可以看到函数式编程在开发中具有声明模式。为了充分理解函数式编程,我们先来看下几个基本概念。
函数式编程属于声明是编程范式:这种范式会描述一系列的操作,但并不会暴露它们是如何实现的或是数据流如何传过它们。
我们所熟知的 SQL 语句就是一种很典型的声明式编程,它由一个个描述查询结果应该是什么样的断言组成,对数据检索的内部机制进行了抽象。
我们再来看一组代码再来对比一下命令式编程和声明式编程。
// 命令式方式
var array = [0, 1, 2, 3]
for(let i = 0; i < array.length; i++) {
array[i] = Math.pow(array[i], 2)
}
array;// [0, 1, 4, 9]
// 声明式方式 [0, 1, 2, 3].map(num => Math.pow(num, 2))
可以看到命令式很具体的告诉计算机如何执行某个任务。
而声明式是将程序的描述与求值分离开来。它关注如何用各种表达式来描述程序逻辑,而不一定要指明其控制流或状态关系的变化。
为什么我们要去掉代码循环呢?循环是一种重要的命令控制结构,但很难重用,并且很难插入其他操作中。而函数式编程旨在尽可能的提高代码的无状态性和不变性。要做到这一点,就要学会使用无副作用的函数--也称纯函数
纯函数指没有副作用的函数。相同的输入有相同的输出,就跟我们上学的函数一样。
常常这些情况会产生副作用。
举一个简单的例子
var counter = 0
function increment() {
return ++counter;
}
这个函数就是不纯的,它读取了外部的变量,可能会觉得这段代码没有什么问题,但是我们要知道这种依赖外部变量来进行的计算,计算结果很难预测,你也有可能在其他地方修改了 counter 的值,导致你 increment 出来的值不是你预期的。
对于纯函数有以下性质:
但是在我们平时的开发中,有一些副作用是难以避免的,与外部的存储系统或 DOM 交互等,但是我们可以通过将其从主逻辑中分离出来,使他们易于管理。
现在我们有一个小需求:通过 id 找到学生的记录并渲染在浏览器(在写程序的时候要想到可能也会写到控制台,数据库或者文件,所以要想如何让自己的代码能重用)中。
// 命令式代码
function showStudent(id) {
// 这里假如是同步查询
var student = db.get(id)
if(student !== null) {
// 读取外部的 elementId
document.querySelector(`${elementId}`).innerHTML = `${student.id},${student.name},${student.lastname}`
} else {
throw new Error('not found')
}
}
showStudent('666')
// 函数式代码
// 通过 find 函数找到学生
var find = curry(function(db, id) {
var obj = db.get(id)
if(obj === null) {
throw new Error('not fount')
}
return obj
})
// 将学生对象 format
var csv = (student) => `${student.id},${student.name},${student.lastname}`
// 在屏幕上显示
var append = curry(function(elementId, info) {
document.querySelector(elementId).innerHTML = info
})
var showStudent = compose(append('#student-info'), csv, find(db))
showStudent('666')
如果看不懂 curry (柯里化)的先不着急,这是一个对于新手来说比较难理解的一个概念,在函数式编程里面起着至关重要的作用。
可以看到函数式代码通过较少这些函数的长度,将 showStudent 编写为小函数的组合。这个程序还不够完美,但是已经可以展现出相比于命令式的很多优势了。
我们看到纯函数的输出结果是一致的,可预测的,相同的输入会有相同的返回值,这个其实也被称为引用透明。
引用透明是定义一个纯函数较为正确的方法。纯度在这个意义上表面一个函数的参数和返回值之间映射的纯的关系。如果一个函数对于相同的输入始终产生相同的结果,那么我们就说它是引用透明。
这个概念很容易理解,简单的举两个例子就行了。
// 非引用透明
var counter = 0
function increment() {
return ++counter
}
// 引用透明
var increment = (counter) => counter + 1
其实对于箭头函数在函数式编程里面有一个高大上的名字,叫 lambda 表达式,对于这种匿名函数在学术上就是叫 lambda 表达式,现在在 Java 里面也是支持的。
不可变数据是指那些创建后不能更改的数据。与许多其他语言一样,JavaScript 里有一些基本类型(String,Number 等)从本质上是不可变的,但是对象就是在任意的地方可变。
考虑一个简单的数组排序代码:
var sortDesc = function(arr) {
return arr.sort(function(a, b) { return a - b })
}
var arr = [1, 3, 2] sortDesc(arr)
// [1, 2, 3] arr
// [1, 2, 3]
这段代码看似没什么问题,但是会导致在排序的过程中会产生副作用,修改了原始引用,可以看到原始的 arr 变成了 [1, 2, 3]。这是一个语言缺陷,后面会介绍如何克服。
如果你真的做出了一些东西,在面对那些令人眼花缭乱的理论知识,或是和你相似甚至比你做的更糟糕的人时大可不必谦虚。在一天结束之时,正是那些在战壕中的开发者——构建、测试和开发了代码的人,真正做了事情。
这些事情可以帮助新手在他们漫长的旅程中学习编程。我知道我还有更多东西需要学习,并将继续学习如何永远地学习。最重要的事情说三遍,请继续,不要放弃,不要放弃,不要放弃。
Javascript代码异步执行的场景,比如ajax的调用、定时器的使用等,在这样的场景下也经常会出现这样那样匪夷所思的bug或者糟糕的代码片段,那么处理好你的Javascript异步代码成为了异步编程至关重要的前提
以买苹果为例说明程序员如何解决问题。程序员需要对问题进行透彻的分析,理清其涉及的所有细节,预测可能发生的所有意外与非意外的情况,列出解决方案的所有步骤,以及对解决方案进行尽量全面的测试。而这些正是我认为编程难的地方。
Google Blockly 是一款基于Web的、开源的、可视化程序编辑器。你可以通过拖拽块的形式快速构建程序,而这些所拖拽的每个块就是组成程序的基本单元。可视化编程完成
成为伟大的程序员,需要付出许多编程之外的努力。我们的大脑是有限的,每天要应付的问题复杂到足以让人精神崩溃。当工作不顺利时,多少都会有些冒名顶替症候群的感觉。
推荐8款最好用的前端开发工具供美工或者前端开发人员使用,当然若你是NB的全栈工程师也可以下载使用。Web前端开发最常见的编程软件有以下几种: 在前端开发中,有一个非常好用的工具,Visual Studio Code,简称VS code
学编程现在看起来挺简单,因为网上有丰富的各种资源。然而当你实际去学的时候就发现,还是很难!对我来说也一样。但从某天起,我决定认认真真学编程一年。后来又过了一年,又过了一年又一年……我好像有点感悟。
命名最好遵循驼峰法和下划线法,并且要清楚的表达变量的意思。相对于驼峰法而言,我更喜欢下划线法。下划线法可以更清楚的看出这个变量表示的意思。比如aBigGreenBanana和一个a_big_green_banana。
每隔几个月就会出现一篇文章表明:CSS并不是真正的编程语言。以编程语言的标准来说,CSS过于困难。使用这门语言会很有创造性:事实确实如此,CSS不同于传统的编程,且具有缺陷,同任何标准化编程语言相比
内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!