编写不可变和函数式 JavaScript
不变的概念和技术
不变的概念和技术
在 JavaScript 和 React 中使用不可变实践的原因:
-
降低害虫入侵的表面积。
- 原因很简单:输入数据进入函数/方法后,会产生可预测的输出。纯函数正是遵循这一原则的一种方式。另一个例子是将隐式编程应用于高阶函数。
-
创建数据和变更历史记录;这对于跟踪数据流甚至进行调试等操作都很有用。
- Redux 就是一个运用了许多此类概念的系统的例子,它处理数据变更的方法创建了一个可以逐步执行的变更历史记录。
纯函数:
- 无副作用;返回数据时不会修改其作用域之外的任何数据。
- 不出所料,相同的输入会返回相同的值(输出)。
因此,只要函数中不引入任何未知因素(X因素),输出就是“可预测的”;函数(func1)的唯一输入就是数据。如果引入了API数据或来自另一个函数(func2)的数据,而这些数据也会根据输入而变化,那么就不能再确定输入是“可预测的”了。
常量赋值:
- const 在防止重新赋值和重新声明方面非常有效。
const variable = 'hello world'
try{
variable = 'Hello World!'
}catch(error){
console.log(error) // TypeError: invalid assignment to const 'variable'
}
- 单独使用 const 并不能解决这个问题,防止变量重赋值只解决了一半的问题,可能连修改问题的一半都不到……但就变量数据而言,它肯定占了一半。
ECMAScript 提案中有一个很棒的提议,即在赋值语句的右侧添加一个关键字,以防止数据被篡改。它可能看起来像这样:
const obj = immut { a: 1, b: 2 }
obj['a'] = 3 // Error Thrown
console.log(obj['a']) // 1
冷冻物体
- 对物体进行浅层冷冻,以防止简单物体发生意外变异。
const obj = {
zoo: 'animals'
}
Object.freeze(obj)
obj['zoo'] = 'noAnimals'
console.log(obj['zoo']) // 'animals'
价差操作商
- 利用对象和数组的扩展运算符可以很好地创建这些源数据的浅拷贝,然后将旧数据的副本与新数据应用到新对象或数组中。
const arr = [ 1, 2, 3, 4 ]
const newArr = [ ...arr, 'newData' ]
console.log(arr) // [ 1, 2, 3, 4 ]
console.log(newArr) // [ 1, 2, 3, 4, 'newData' ]
高阶函数(HOF)
- 高阶函数(HOF)是一个很棒的工具,但它也遵循不可变性原则。HOF 接收一个函数作为参数,并返回一个函数作为参数。如果您有兴趣深入了解 HOF,我建议您阅读《Eloquent JavaScript》第五章。
以下是一个利用 HOF 行为来进一步遵循代码不可变性概念的示例:
const arr = [ 1, 2, 3 ]
const newArr = arr.map(ele => ele * 2)
console.log(newArr) // [ 2, 4, 6 ]
console.log(arr) // [ 1, 2, 3 ]
隐性编程(无点)
所以,无点风格是一种函数式编程概念,它允许进行抽象。然而,这种风格可能会被过度使用,导致一些陷阱
,甚至仅仅因为缺少参数命名而造成混淆……它甚至因为这种过度使用/误用而被戏称为“无点风格”。我认为,如果使用得当,它能够将参数和实现抽象成一个单一的函数。
const arr = [ 1, 2, 3 ]
const addTwo = (ele) => ele + 2
const twoAddedPointFree = arr.map(addTwo)
console.log(twoAddedPointFree) // [ 3, 4, 5 ]
// You can even reuse it!
const fourAdded = twoAddedPointFree.map(addTwo)
console.log(fourAdded) // [ 5, 6, 8 ]