网站搜索

typescript - 如何在 JavaScript 中将面向对象转换为函数式


我有这段 JavaScript 代码

class StringFormatter {

 init(locale) {
  this.locale = locale;
 }

 getA() {
  return 'A' + this.locale
 }

 getB() {
  return 'B' + this.locale
 }

 getC() {
  return 'C' + this.locale
 }

}


// when page just render
let stringManager = new StringFormatter();
stringManager.init('enUS');
// call it in other components 
let result = stringManager.getA();

所以当我使用这个面向对象的类时,我只需要在页面首次渲染时通过init设置一次locale。然后我之后调用getA、getB 或 getC方法的所有代码都会适应这个locale。

现在由于我想把这个转向更函数式编程的方式,我该如何编写它,同时保持相同的概念。现在我只能想到这个解决方案

 getA(locale) {
  return 'A' + locale
 }

 getB(locale) {
  return 'B' + locale
 }

 getC(locale) {
  return 'C' + locale
 }

let result = getA('enUS');

但是这个要求我每次调用函数getA、getB或getC时都要传入locale。

有没有办法我们仍然可以配置本地设置,但只配置一次,不需要每次都传给getA, getB 或 getC函数,并且不需要以面向对象的方式编写?

答案#1

使用闭包:

let getA;
let getB;
let getC;

function initStringFormatter (x) {
    let locale = x;

    getA = function () { return 'A' + locale }
    getB = function () { return 'B' + locale }
    getC = function () { return 'C' + locale }
}

initStringFormatter('enUS');

getA();

从理论上讲,闭包可以提供与面向对象编程中的对象属性/变量/成员完全相同的功能。闭包和对象之间有一一对应的关系。唯一的区别是闭包使用作用域作为将变量附加到函数的机制,而对象则使用绑定将变量附加到方法上。

当然,将面向对象编程与函数式编程混合是没有问题的。即使是 Lisp 也有面向对象编程库。因此,一个常见的 JavaScript 习惯用法是返回一个对象,而不是将函数名称作为全局变量:

function initStringFormatter (locale) { // another trick is to just use
                                        // the argument directly instead of
                                        // creating another variable.

    function a () { return 'A' + locale }
    function b () { return 'B' + locale }
    function c () { return 'C' + locale }

    return {
        getA: a,
        getB: b,
        getC: c
    }
}

let stringManager = initStringFormatter('enUS');

stringManager.getA();

确实。在 JavaScript 中看到函数式编程(FP)程序员以这种方式使用对象很常见:只是将对象作为函数的命名空间。状态管理可以完全使用闭包来完成,而不是使用对象属性。

附注:函数式编程足够强大,以至于像 Lisp 这样的语言不需要在语言中内置面向对象编程,面向对象编程可以作为一种设计模式和/或库——Lisp 的标准面向对象库是 CLOS:Common Lisp 对象系统

使用闭包的唯一缺点是所有闭包本质上都是私有的(从技术上讲,它们不是私有的,它们只是函数的局部变量——私有/公共是绑定的概念,全局/局部是作用域的概念)。但是,如果你有过任何大量的面向对象编程经验,你会认识到这是在面向对象世界中的良好实践,即永远不要公开访问变量。在使用闭包时,对变量的所有访问都必须通过函数进行——同样,在面向对象的世界中,这将是常见的 getter/setter 设计模式,被认为是良好实践。

但是请注意,这并不是 100% 纯函数式编程。不过没关系。还有其他不纯的函数式编程语言。JavaScript 仍然允许修改封闭变量。然而,上面的函数是纯函数式编程,因为 locale 变量不能被修改。

要让 JavaScript 成为 100% 纯函数式编程很简单。只需在代码中禁止使用所有 let 和 var,并始终使用 const。起初会感觉有些奇怪,但你可以只使用 const 和函数参数(默认就是常量)来编写任何程序。像 JavaScript 和 Go 这样的语言给你一个逃生出口,可以使用变量以直接的方式管理基于状态的逻辑。

答案#2

你想要一个想法吗?

Thunk 会延迟计算,直到需要其结果时才进行,从而提供参数的惰性求值。(ramda 的 thunk 定义)

const thunkGetFromLocale = (locale) => () => (toGet) => toGet + locale

let thunk = thunkGetFromLocale('en-US')()
console.log(thunk('A'))
console.log(thunk('B'))
console.log(thunk('C'))

thunk = thunkGetFromLocale('vi-VN')()
console.log(thunk('A'))
console.log(thunk('B'))
console.log(thunk('C'))