【问题标题】:Javascript function loading (by reference?)Javascript函数加载(通过引用?)
【发布时间】:2016-07-22 10:22:26
【问题描述】:

虽然我有多年的编程经验(使用多种语言),但我的背景不是 Javascript。除此之外,今天的 Javascript 并不是我多年前第一次使用的 Javascript。它更加复杂和强大。也就是说,我正在努力理解一些函数负载动态。

函数实际返回某些内容的函数调用很直观,但 Javascript 似乎对我无法理解的函数做了一些事情。我可以只是复制/粘贴代码,或者我可以尝试找出如何在我自己的代码中重用这种模式。

例如,下面的 Mongoose 调用会查找 User 模型中的所有记录,并且调用的结果以某种方式最终出现在传递函数的第二个参数中(通过引用?)。

User.find({}, function(err, users) {  // Get all the users
  if (err) throw err;

  console.log(users); // Show the JSON Object    
});

这是另一个在数组上使用简单 forEach 的示例。不知何故,forEach 填充了“用户”参数。

users.forEach(function(user) {
        console.log(user.username, ': Admin = ', (user.admin ? 'Yes' : 'No'));
});

谁能解释一下,和/或给我一个关于它如何/为什么起作用的好指南?

我在 Node.js 中看到了相同的模式,这有点像绊脚石。

我是否遗漏了一些明显的东西,或者这只是函数式编程的一个特点?

乔恩

【问题讨论】:

  • 研究javascript callbacks.
  • Continuation Passing Style,如果您想更全面地了解该技术。
  • 这种风格并没有什么新东西。最早的 javascript 版本使用回调作为事件处理程序。
  • 感谢围绕回调的 cmets。我很感激花时间做出回应。我以为我知道回调是如何工作的,但我在这里看不到回调相关性。这不是关于在某些其他功能之后(在被调用的函数内)执行的函数,也不是用于在上下文中驱动某些顺序同步处理。我认为@naomik 有一个很好的观点,我不知道 CPS。
  • 见下面的 cmets。我现在可以看到回调相关性了。

标签: javascript node.js functional-programming return-by-reference


【解决方案1】:

在 Javascript 中,函数也是对象,可以存储在变量中。传递给另一个函数的函数通常称为“回调”(这也用于其他语言,但我们不会去那里)。

查看Array.prototype.forEachpolyfill 可能会有所帮助,尤其是触发回调的行。

由于 Javascript 函数也是对象,它们有自己的方法,特别是 callapply,它们触发函数,甚至为该函数设置 this 值。

回调示例(我知道这很愚蠢...这是fiddle):

function callIf(val, callbackFn) {
    // "arguments" is special in javascript, and it's not an array (although it does have an index selector).
    // I can call Array's slice method passing "arguments" as the "this" of the function
    var args = Array.prototype.slice.call(arguments, 2);
    if(val) {
        callbackFn.apply(this, args);
    }
}

var values = [
    "Hop",
    "on",
    "Pop",
    "Sam",
    "I",
    "Am"
];


values.forEach(function(val) {
    // note: referencing inner "log" function instead of "console.log" because "console.log" require's the "this" to be "console".

    callIf(val.length < 3, log, val + " is a small word.");

    function log(val) {
        console.log(val);
    }
});

旁注:

如果你来自静态类型语言背景,并且第一次接触到 Javascript 作为动态类型语言,我给你的建议是:不要担心,拥抱 Javascript 带来的灵活性,但仍然保持一致性和良好的编程纪律。强调简单性和可读性。玩得开心:)

【讨论】:

  • 谢谢 - 非常有帮助
【解决方案2】:

这称为Continuation Passing Style。它有时用于封装异步行为,例如您提供的 Mongoose 示例,但有时它可以以同步方式使用,例如 .forEach 示例。

要了解它是如何工作的,如果我们自己编写 forEach 就很容易了。

function forEach(xs, f)
  for (var i=0, len=xs.length; i<len; i++) {
    f(x[i]);
  }
}

forEach([1,2,3], function(x) { console.log(x); })

所以它的工作方式应该很容易看出:我们可以看到xs 被设置为我们的数组[1,2,3],我们在函数内部执行了一个常规的for 循环。然后我们看到f 在循环中的每个元素都被调用一次。

这里真正的强大之处在于函数是 JavaScript 中的 first-class members,这使得 higher-order functions 的使用成为可能。这意味着.forEach 被认为是一个高阶函数,因为它接受一个函数作为参数。

事实证明,forEach 可以通过很多不同的方式实现。这是另一个。

forEach(xs, f) {
  if (xs.length > 0) {
    f(xs[0]);
    forEach(xs.slice(1), f);
  }
}

这里的想法是您应该熟悉在 JavaScript 中发送函数。您甚至可以返回函数作为应用另一个函数的结果。

function add(x) {
  return function(y) {
    return x + y;
  }
}

function map(xs, f) {
  function loop(ys, xs) {
    if (xs.length === 0)
      return ys;
    else
      return loop(ys.concat(f(xs[0])), xs.slice(1));
  }
  return loop([], xs);
}

map([1,2,3], add(10)); //=> [11,12,13]

不久之后,您将深入了解functional paradigm 并学习各种其他新事物。

函数!

【讨论】:

  • forEach 和 CPS 关系不大?
  • 所以对回调的关注令人困惑。对于我问题中的 forEach 示例,用户不是一个函数,它是一个对象,但它也可以很容易地成为一个数字。 CPS 有点启发,但关注回调对我没有帮助。
  • 对!我明白了,我一直在混淆用户被分配一个值,而用户在回调范围内有一个值。虽然回调是隐藏的,所以并不完全清楚。
【解决方案3】:

你需要javascript callbacks

基本思想是将一个函数作为参数传递给另一个函数,然后在需要时调用它。

function basic( callback ){
  console.log( 'do something here' );

  var result = 'i am the result of `do something` to be past to the callback';

  // if callback exist execute it
  callback && callback( result );
}

这是 javascript 的核心概念之一。但我建议您也看看 Promises 以了解 ajax http 请求等异步操作。它不是当前ES5 规范的一部分,但您可以找到许多用于此的库和 polyfil。

function get(url) {
  // Return a new promise.
  return new Promise(function(resolve, reject) {
    // Do the usual XHR stuff
    var req = new XMLHttpRequest();
    req.open('GET', url);

    req.onload = function() {
      // This is called even on 404 etc
      // so check the status
      if (req.status == 200) {
        // Resolve the promise with the response text
        resolve(req.response);
      }
      else {
        // Otherwise reject with the status text
        // which will hopefully be a meaningful error
        reject(Error(req.statusText));
      }
    };

    // Handle network errors
    req.onerror = function() {
      reject(Error("Network Error"));
    };

    // Make the request
    req.send();
  });
}

// Use it!
get('story.json').then(function(response) {
  console.log("Success!", response);
}, function(error) {
  console.error("Failed!", error);
});

【讨论】:

  • 即使是 Promise 也使用回调,所以你应该首先了解回调的一般概念。
  • 谢谢——也很有用
【解决方案4】:

这些回调看起来很相似,但它们的用途完全不同。在第一个示例中,回调用于检索结果,因为User.find 是一个异步函数。异步性质也是 Nodejs 回调参数顺序约定背后的原因。回调的第一个参数总是用于错误。

在第二个例子中,使用回调的主要原因是创建一个本地作用域,这在你想在循环中执行一些异步操作时非常有用。例如:

users.forEach(function(user) {
    Model.find({},function(er,rows){
        if(er){
            return handle(er);
        }
        OtherModel.find({userid: user.id},function(er,result){
            if(er){
                return handle(er);
            }
            console.log(result);
        });
    });
});

上面的示例可能不适用于 C 样式循环,因为在执行 OtherModle.find 时,使用 var 定义的变量已经被数组的最后一项覆盖。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-10-08
    • 2013-11-24
    • 1970-01-01
    • 1970-01-01
    • 2011-02-20
    • 1970-01-01
    • 1970-01-01
    • 2016-06-30
    相关资源
    最近更新 更多