int*_*sik 12 javascript oop functional-programming underscore.js
我刚刚开始研究不同的编程风格(OOP,功能,程序).
我正在学习JavaScript并开始使用underscore.js并在文档中出现了这一小部分.文档说,underscore.js可以用于面向对象或功能样式,并且这两者都可以产生相同的结果.
_.map([1, 2, 3], function(n){ return n * 2; });
_([1, 2, 3]).map(function(n){ return n * 2; });
Run Code Online (Sandbox Code Playgroud)
我不明白哪一个是功能的,哪一个是OOP,我不明白为什么,即使经过对这些编程范式的一些研究.
lea*_*eaf 27
面向对象编程(OOP)和功能编程(FP)是编程范例.粗略地说,遵循编程范式是编写符合特定规则集的代码.例如,将代码组织成单元称为OOP,避免副作用称为FP.
每种编程范例都由特定功能组成,但是您最喜欢的语言不必将所有功能都归入一个范例.事实上,OOP可以在没有继承或封装的情况下生存,因此我们可以说JavaScript(JS)是一种具有继承且没有封装的OOP语言.
现在您已经对编程范例(希望如此)有了一些了解,让我们快速了解一下OOP和FP的基础知识.
在OOP中,对象是一个包含信息和操作的框,应该引用相同的概念.信息通常称为"属性",操作通常称为"方法".属性允许跟踪对象的状态,并且方法允许操纵对象的状态.
在JS中,您可以向对象发送消息以执行特定方法.下面的代码是JS 中消息传递的一个示例.它显示了一个"点"对象,它有两个属性,"x"和"y",以及一个名为"translate"的方法."translate"方法根据给定的向量更新"point"的坐标.
point = {
x: 10, y: 10,
translate: function (vector) {
this.x += vector.x;
this.y += vector.y;
}
};
point.x; // 10
point.translate({ x: 10, y: 0 });
point.x; // 20
Run Code Online (Sandbox Code Playgroud)
这种简单案例涉及的功能并不多.在OOP中,代码通常被划分为类,并且通常支持继承和多态.但我不会进一步详述,因为我担心我已经超出了你的问题的范围.
在FP中,代码本质上是函数的组合.而且,数据是不可变的,这导致编写程序没有副作用.在功能代码中,函数无法更改外部世界,输出值仅取决于给定的参数.这允许对程序流程进行强有力的控制.
实际上,只要你处理副作用,JS就可以用作FP语言,没有内置机制.以下代码是这种编程风格的示例."zipWith"功能来自Haskell世界.它会使用给定的函数合并两个列表add(point[i], vector[i]).
zipWith = function (f, as, bs) {
if (as.length == 0) return [];
if (bs.length == 0) return [];
return [f(as[0], bs[0])].concat(
zipWith(f, as.slice(1), bs.slice(1))
);
};
add = function (a, b) {
return a + b;
};
translate = function (point, vector) {
return zipWith(add, point, vector);
};
point = [10, 10];
point[0]; // 10
point = translate(point, [10, 0]);
point[0]; // 20
Run Code Online (Sandbox Code Playgroud)
这个定义虽然很肤浅.例如,Haskell是一种纯函数式语言,它实现了更多的概念,如函数组合,仿函数,currying,monad等......这使得代码更加简洁和优雅.
实际上,OOP和FP是两个没有共同点的不同概念,我甚至会说没有什么可比较的.因此,我相信你从Underscore.js文档中读到的是滥用语言.
您不应该在此库的范围内学习编程范例.实际上,使用Underscore.js编写代码的方式使其类似于OOP和FP,但这只是外观问题.因此,在引擎盖下没有什么真正令人兴奋的:-)
请参阅维基百科深入阅读.
Rik*_*kin 12
功能:您将对象传递给函数并执行操作
_.map([1, 2, 3], function(n){ return n * 2; });
OOP:你在对象上调用函数并做东西
_([1, 2, 3]).map(function(n){ return n * 2; });
在两个示例中[1,2,3] (array)都是一个对象.
示例OOP参考:http://underscorejs.org/#times
对于什么是"功能"而不是"功能" 没有正确的定义,但通常功能语言强调数据和功能的简单性.
大多数函数式编程语言没有属于对象的类和方法的概念.函数在定义良好的数据结构上运行,而不是属于数据结构.
第一种样式_.map是_命名空间中的一个函数.它是一个独立的函数,您可以将其返回或作为参数传递给另一个函数.
function compose(f, g) {
return function(data) {
return f(g(data));
}
}
const flatMap = compose(_.flatten, _.map);
Run Code Online (Sandbox Code Playgroud)
对于第二种样式,不可能做同样的事情,因为方法实例本质上与用于构造对象的数据相关联.所以我要说第一种形式更具功能性.
在任何一种情况下,一般的函数式编程风格都是数据应该是函数的最后一个参数,这使得更容易理解或部分应用早期的参数.Lodash/fp和ramda通过具有以下地图签名来解决此问题.
_.map(func, data);
Run Code Online (Sandbox Code Playgroud)
如果函数是curry,则只需传递第一个参数即可创建函数的特定版本.
const double = x => x * 2;
const mapDouble = _.map(double);
mapDouble([1, 2, 3]);
// => [2, 4, 6]
Run Code Online (Sandbox Code Playgroud)
FP
在 FP 中,函数接受输入并产生输出,并保证相同的输入将产生相同的输出。为此,函数必须始终为其操作的值提供参数,并且不能依赖状态。即,如果一个函数依赖于状态,并且该状态发生变化,则该函数的输出可能会有所不同。FP 不惜一切代价避免了这种情况。
我们将展示mapFP 和 OOP的最小实现。在下面的这个 FP 示例中,请注意如何map仅对局部变量进行操作而不依赖于状态 -
const _ = {
// has two parameters
map: function (arr, fn) {
// local
if (arr.length === 0)
return []
else
// local
// local // local // local
return [ fn(arr[0]) ].concat(_.map(arr.slice(1), fn))
}
}
const result =
// call _.map with two arguments
_.map([1, 2, 3], function(n){ return n * 2; })
console.log(result)
// [ 2, 4, 6 ]Run Code Online (Sandbox Code Playgroud)
在这种风格中,map存储在_对象中无关紧要- 这不会因为使用了对象而使其成为“OOP”。我们可以很容易地写——
function map (arr, fn) {
if (arr.length === 0)
return []
else
return [ fn(arr[0]) ].concat(map(arr.slice(1), fn))
}
const result =
map([1, 2, 3], function(n){ return n * 2; })
console.log(result)
// [ 2, 4, 6 ]Run Code Online (Sandbox Code Playgroud)
这是在 FP 中调用的基本方法 -
// function to call
// argument(s)
someFunction(arg1, arg2)
Run Code Online (Sandbox Code Playgroud)
此处 FP 值得注意的是它map有两 (2) 个参数arr和fn,并且 的输出map仅取决于这些输入。您将在下面的 OOP 示例中看到这如何发生巨大变化。
面向对象编程
在 OOP 中,对象用于存储状态。当一个对象的方法被调用时,该方法(函数)的上下文作为动态绑定到接收对象this。因为this是一个变化的值,OOP 不能保证任何方法都会有相同的输出,即使给出相同的输入。
NB howmap只需要下面的一 (1) 个参数,fn. 我们怎么能map只使用一个fn?我们会map怎样?我如何指定目标map?FP 认为这是一场噩梦,因为函数的输出不再仅仅取决于它的输入——现在输出map更难确定,因为它取决于——的动态值this——
// function to call
// argument(s)
someFunction(arg1, arg2)
Run Code Online (Sandbox Code Playgroud)
这是 OOP 中动态调用的基本方法 -
// state
// bind state to `this` in someAction
// argument(s) to action
someObj.someAction(someArg)
Run Code Online (Sandbox Code Playgroud)
重温FP
在第一个FP例子中,我们看到的.concat和.slice-不是这些OOP动态调用?它们是,但特别是这些不会修改输入数组,因此它们可以安全地与 FP 一起使用。
也就是说,呼叫风格的混合可能有点碍眼。OOP 支持“中缀”表示法,其中方法(函数)显示在函数的参数之间-
// arg1
// function
// arg2
user .isAuthenticated (password)
Run Code Online (Sandbox Code Playgroud)
这也是 JavaScript 的运算符的工作方式 -
// arg1
// function
// arg2
1 + 2
Run Code Online (Sandbox Code Playgroud)
FP 倾向于“前缀”表示法,其中函数总是出现在其参数之前。在理想的世界中,我们可以在任何位置调用 OOP 方法和运算符,但不幸的是 JS 不能这样工作——
// invalid use of method
.isAuthenticated(user, password)
// invalid use of operator
+(1,2)
Run Code Online (Sandbox Code Playgroud)
通过将像.conat和.slice这样的方法转换为函数,我们可以以更自然的方式编写 FP 程序。请注意前缀符号的一致使用如何使想象计算的执行方式变得更加容易 -
function map (arr, fn) {
if (isEmpty(arr))
return []
else
return concat(
[ fn(first(arr)) ]
, map(rest(arr), fn)
)
}
map([1, 2, 3], function(n){ return n * 2; })
// => [ 2, 4, 6 ]
Run Code Online (Sandbox Code Playgroud)
方法转换如下 -
function concat (a, b) {
return a.concat(b)
}
function first (arr) {
return arr[0]
}
function rest (arr) {
return arr.slice(1)
}
function isEmpty (arr) {
return arr.length === 0
}
Run Code Online (Sandbox Code Playgroud)
这开始展示 FP 的其他优势,即功能保持较小且专注于一项任务。因为这些函数只对它们的输入进行操作,所以我们可以很容易地在程序的其他区域重用它们。
您的问题最初是在 2016 年提出的。从那时起,现代 JS 功能允许您以更优雅的方式编写 FP -
// constructor
function _ (value) {
// returns new object
return new OOP(value)
}
function OOP (arr) {
// dynamic
this.arr = arr
}
// only one parameter
OOP.prototype.map = function (fn) {
// dynamic
if (this.arr.length === 0)
return []
else // dynamic // dynamic
return [ fn(this.arr[0]) ].concat(_(this.arr.slice(1)).map(fn))
}
const result =
// create object
// call method on created object
// with one argument
_([1, 2, 3]).map(function(n){ return n * 2; })
console.log(result)
// [ 2, 4, 6 ]Run Code Online (Sandbox Code Playgroud)
// state
// bind state to `this` in someAction
// argument(s) to action
someObj.someAction(someArg)
Run Code Online (Sandbox Code Playgroud)
语句依赖于副作用,而表达式直接评估为一个值。表达式在代码中留下的潜在“漏洞”较少,其中语句可以随时执行任何操作,例如抛出错误或退出函数而不返回值。
带对象的 FP
FP 并不意味着“不使用对象”——它是关于保留轻松推理程序的能力。我们可以编写相同的map程序,给人一种我们正在使用 OOP 的错觉,但实际上它的行为更像 FP。它看起来像一个方法调用,但实现只依赖于局部变量而不依赖于动态状态(this)。
JavaScript 是一种丰富的、富有表现力的、多范式的语言,它允许您编写程序来满足您的需求和偏好——
// arg1
// function
// arg2
user .isAuthenticated (password)
Run Code Online (Sandbox Code Playgroud)