初学者JavaScript OOP与功能

时间:2016-05-14 21:01:57

标签: javascript oop functional-programming underscore.js

我刚刚开始研究不同的编程风格(OOP,功能,程序)。

我正在学习JavaScript并开始使用underscore.js并且在文档中出现了this小部分。 文档说下划线.js可以用于面向对象或功能样式,并且这两者都可以产生相同的结果。

_.map([1, 2, 3], function(n){ return n * 2; });
_([1, 2, 3]).map(function(n){ return n * 2; });

我不明白哪一个是功能性的,哪一个是OOP,即使经过对这些编程范式的一些研究,我也不明白为什么。

6 个答案:

答案 0 :(得分:26)

编程范式

面向对象编程(OOP)和功能编程(FP)是编程范例。粗略地说,遵循编程范式是编写符合特定规则集的代码。例如,将代码组织成单元称为OOP,避免副作用称为FP。

每种编程范例都由特定功能组成,但是您最喜欢的语言不必将所有功能都归入一个范例。事实上,OOP可以没有inheritanceencapsulation,因此我们可以说JavaScript(JS)是一种带有继承且没有封装的OOP语言。

既然你已经对编程范式有所了解(希望如此),那么让我们快速浏览一下OOP和FP的基础知识。

面向对象编程

在OOP中,对象是一个包含信息和操作的框,应该引用相同的概念。信息通常称为“属性”,操作通常称为“方法”。属性允许跟踪对象的状态,方法允许操纵对象的状态。

在JS中,您可以向对象发送消息以执行特定方法。以下代码是JS中message passing的示例。它显示了一个“点”对象,它有两个属性,“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

这种简单案例涉及的功能并不多。在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

这个定义虽然很肤浅。例如,Haskell是一种纯函数式语言,它实现了更多的概念,如函数组合,仿函数,currying,monad等等......这使得代码更加简洁和优雅。

结论

实际上,OOP和FP是两个没有共同点的不同概念,我甚至会说没有什么可比较的。因此,我相信你从Underscore.js文档中读到的是对语言的误用。

您不应该在此库的范围内学习编程范例。实际上,使用Underscore.js编写代码的方式使其类似于OOP和FP,但这只是外观问题。因此,引擎盖下没有什么令人兴奋的事情: - )

请参阅维基百科深入阅读。

答案 1 :(得分:11)

功能:您将对象传递给函数并执行操作

_.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

答案 2 :(得分:8)

对于什么是“功能”而不是“功能”没有正确的定义,但通常功能语言强调数据和功能的简单性。

大多数函数式编程语言都没有属于对象的类和方法的概念。函数在明确定义的数据结构上运行,而不是属于数据结构。

第一个样式_.map_命名空间中的一个函数。它是一个独立的函数,您可以将其返回或作为参数传递给另一个函数。

function compose(f, g) {
  return function(data) {
    return f(g(data));
  }
}

const flatMap = compose(_.flatten, _.map);

对于第二种样式,不可能这样做,因为方法实例本质上与用于构造对象的数据相关联。所以我要说第一种形式是更多功能。

在任何一种情况下,一般的函数式编程风格是数据应该是函数的最后一个参数,这使得更容易理解或部分应用早期的参数。 Lodash/fpramda通过为地图添加以下签名来解决此问题。

_.map(func, data);

如果函数是curry,你可以通过传递第一个参数来创建函数的特定版本。

const double = x => x * 2;
const mapDouble = _.map(double);

mapDouble([1, 2, 3]);
// => [2, 4, 6]

答案 3 :(得分:2)

FP

在FP中,函数接受输入并产生输出,并确保相同的输入将产生相同的输出。为此,函数必须始终为其操作所依据的值具有参数,并且不能依赖状态。即,如果一个函数依赖状态,并且状态发生变化,则该函数的输出可能会不同。 FP不惜一切代价避免这种情况。

我们将显示FP和OOP中map的最低实现。在下面的此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 ]

以这种样式,将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 ]

这是FP中通话的基本方法-

// ? function to call
             // ? argument(s)
someFunction(arg1, arg2)

对于FP来说,值得注意的是map有两个(2)参数arrfn,而map的输出仅取决于这些输入。您将在下面的OOP示例中看到这种情况的变化。


OOP

在OOP中,对象用于存储状态。调用对象的方法时,该方法(函数)的上下文被动态地 绑定到接收对象,即this。由于this是一个 changing 值,因此即使给出了相同的输入,OOP也不能保证任何方法都将具有相同的输出。

请注意,map如何仅接受下面的一(1)个自变量fn。我们如何仅使用map fn?我们将map做什么?如何为map指定目标? FP认为这是一场噩梦,因为该函数的输出不再仅取决于其输入-现在更难以确定map的输出,因为它取决于{{1 }}-

this

这是在OOP中进行动态调用的基本方法-

            // ? 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 ]

重新访问FP

在第一个FP示例中,我们看到// ? state // ? bind state to `this` in someAction // ? argument(s) to action someObj.someAction(someArg) .concat-这些OOP动态调用不是吗?它们是,但是特别是这些不会修改输入数组,因此可以安全地用于FP。

话说回来,各种风格的呼唤可能会让人有些眼花ore乱。 OOP赞成在方法的参数之间中显示方法(函数)的“中缀”表示法-

.slice

这也是JavaScript的运算符的工作方式-

// ? arg1
     // ? function
                       // ? arg2
user .isAuthenticated (password)

FP倾向于使用“前缀”表示法,其中函数始终位于其参数之前。在理想的世界中,我们可以在 any 位置调用OOP方法和运算符,但不幸的是JS不能这样工作-

// ? arg1
   // ? function
      // ? arg2
   1  +  2

通过将// ? invalid use of method .isAuthenticated(user, password) // ? invalid use of operator +(1,2) .conat之类的方法转换为函数,我们可以以更自然的方式编写FP程序。请注意,前缀表示法的一致使用如何使您更容易想象计算如何进行-

.slice

方法转换如下-

function map (arr, fn) {
  if (isEmpty(arr))
    return []
  else
    return concat(
      [ fn(first(arr)) ]
      , map(rest(arr, 1), fn)
    )
}

map([1, 2, 3], function(n){ return n * 2; })
// => [ 2, 4, 6 ]

这开始显示FP的其他优势,在这些优势中,功能保持较小并专注于一项任务。并且由于这些功能仅在其输入上起作用,因此我们可以轻松地在程序的其他区域中重用它们。

您的问题最初是在2016年提出的。此后,现代JS功能使您可以用更优雅的方式编写FP-

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
}

使用expressions代替statements进行进一步细化-

const None = Symbol()

function map ([ value = None, ...more ], fn) {
  if (value === None)
    return []
  else
    return [ fn(value), ...map(more, fn) ]
}

const result =
  map([1, 2, 3], function(n){ return n * 2; })

console.log(result)
// [ 2, 4, 6 ]

语句依赖于副作用,而表达式直接求值。表达式在代码中留下的潜在“漏洞”较少,在这些漏洞中,语句可以随时执行任何操作,例如抛出错误或退出函数而不返回值。


带有对象的FP

FP并不意味着“不要使用对象”,而是要保留能够轻松推理程序的能力。我们可以编写相同的const None = Symbol() const map = ([ value = None, ...more ], fn) => value === None ? [] : [ fn(value), ...map(more, fn) ] const result = map([1, 2, 3], n => n * 2) console.log(result) // [ 2, 4, 6 ]程序,以产生我们在使用OOP的错觉,但实际上,它的行为更像FP。它看起来像方法调用,但是实现仅依赖于局部变量,而不依赖于动态状态(map)。

JavaScript是一种丰富,富有表现力的多范式语言,可让您编写适合自己需求和偏好的程序-

this

答案 4 :(得分:1)

严格来说,他们都是OOP。但是,第一种方法(_.map)也称为效用函数。该函数是_对象的方法,但该语句不会创建_构造函数的新实例,并且在方法调用之后返回值。由于返回的值是一个Array对象,您还可以调用它上面的所有数组方法。这意味着它仍然是OOP,因为在JavaScript中几乎所有东西都是对象。

第二个创建_对象的新实例,它使方法(下划线对象的方法)可链接,您应该使用value方法获取包装值。

该段落本质上意味着在第一个片段中没有创建下划线对象,而在第二个片段中,在场景后面创建了一个下划线对象。

这里的OOP /功能是指样式,而不是JavaScript开发人员的实用视角。

答案 5 :(得分:0)

两个UIAlertController都有效,两个代码都基于concpet,value =>地图功能的价值。

但是,两者都可以看到OOP,因为object.map风格。

我不会想要通过Underscore了解函数式编程。