管理对象状态
程序的状态可以定义为任意时刻存储在所有对象中的数据快照。
javascript是对象状态安全方面做得最差的语言。
javascript的对象是高度动态的,其属性可以在任何时刻被修改、增加或删除
这本书希望有一种机制能够永远不能将对象的属性写入,这是病态的
基本类型
对于基本类型,可以用const实现不变
闭包实现私有变量
采用闭包的这种模块模式可以保证内部状态访问权限,但是可以动态地删除或替换它的方法
var a = (function(){
var name = '2233'
return {
getName : function() {
return name
}
}
}())
深冻结可变对象
我们可以通过控制一些如writable地隐藏对象元属性来实现阻止对象状态的改变。让我们冻结person对象
Object.freeze()方法可以将对象的属性的元属性设置为false阻止对象状态的改变。
var person = Object.freeze(new Person('Haskell','Curry'))
改方法能冻结继承来属性,包括原型链,但不能用于冻结嵌套对象属性。
我们可以使用递归去冻结对象
var isObj = val->val ** typeof val === 'object'
function deepFreeze(obj) {
if(isObj(obj)&&!Object.isFrozen(obj)) {
Object.keys(obj).forEach(name=>{
deepFreeze(obj[name])
})
Object.freeze(obj)
}
return obj
}
为什么需要创建永不可变的对象呢?这是因为严格的策略可以有效降JavaScript应用的复杂性。
对象方法的缺点
面向对象编程时通过调用对象的方法更改对象的内部状态,这种方式的缺点无法保证检索的状态输出一致,并可能破坏部分期望对象保持不变的功能(RNM扯淡呢),你可以选择写时复制策略,即写入时创建新的实例,从而保证写入对象的状态不变
setName(a) {
return new A(a)
}
这是一种弱智的做法
Lenses
Lensex被称为函数式引用,是函数式程序设计中用于访问和不可改变地操纵数据状态类型属性的解决方案
本质上讲,Lensex与写时复制的策略类似,它采用一个合理管理和赋值状态的内部存储组件。
Ramda.js
Ramda.js是一个函数式的javascript库,默认情况下,使用全局对象R公开所有功能
请看如下代码
var a = new A('2233')
var nameLens = R.lenseProp('name')
R.view(nameLens,person) // logs 2233
从实践角度,它类似一个get_name方法,那么它如何实现一个setter呢?
var newA = R.set(nameLens,'孙笑川',a)
newA.name = '孙笑川'
它会创建一个全新的对象副本,其中包含一个新的属性值,并保留原始实例状态。除此之外,它还支持嵌套属性
即使改变嵌套属性,它也返回一个新的对象
Lenses与函数式编程的分离对象与字段访问逻辑思想契合,也消除了对this的依赖。
函数
函数是函数式编程的核心,函数会向调用者返回一个经过计算的值或是undefined,函数只有返回一个有价值的结果(不是null或者undefined)才有意义。
无值函数在该范式下没有意义
一等函数
javascript中,术语是一等的,指的在语言层面讲函数视为真实的对象。
其中一个例子可以证明函数是一等公民
var multplier = new Function('a','b','return a * b')
在javascript中,任何函数都是Function
的实例,可通过访问.length获取形参的数量。
高阶函数
像是Array.sort这种通过传入一个自定义函数作为参数的Javascript函数,它属于高阶函数的一种类型。
接受一个函数为参数,然后返回一个函数或是函数的结果的函数我们称它为高阶函数。如Array.sort的comparator函数。
我们来看一些例子
function applyOperation(a,b,opt) {
return opt(a,b)
}
function add(a) {
return function(b) { return a + b }
}
add(3)(6)
函数具有一等性与高阶性,所以javascript函数具有值得行为,也就是说,函数是一个基于输入的但未知的值。
如需要打印住在美国的人员名单
function printPeopleInTheUs(people) {
for(let i=0;i<people.length;i++) {
var thisPerson = people[i]
if(thisPerson.addresse.country === 'US') {
console.log(thisPerson)
}
}
}
printPeopleInTheUs([p1,p2,p3])
假设现在需要支持打印生活再其他国家的人,可以通过高阶函数,抽象出应用于每个人的操作.
function printPeople(people,action) {
for(let i=0;i<people.length;i++) {
action(people[i])
}
}
var action = function(person) {
if(person.address.country === 'US') {
console.log(person)
}
}
printPeople(people,action)
实际上,这种抽象的思维是十分难转变过来的。
我们再基于函数的高阶特性讲printPeople重构
function printPeople(people,selector,printer) {
people.forEach(function(person) {
if(selector(person)) {
printer(person)
}
})
}
var inUs = person=>person.address.coutry === 'US'
printPeople(people,inUs,console.log)
forEach是函数式推荐的循环方式,另外,这一开始的硬编码灵活得多,因为可以轻易的选择条件以及打印的方式,第三章与第四章将紧紧的围绕这个主题,但重要的是,如何将线性的函数连接起来。
另外,函数式编程不给推荐使用this,因为它永远不会依赖上下文状态
网友评论