Javascript函数加载(通过引用?)

Javascript function loading (by reference?)

本文关键字:引用 函数 加载 Javascript      更新时间:2023-11-09

虽然我有多年的编程经验(使用多种语言),但我的背景不是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填充了"user"参数。

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

有人能解释一下吗,和/或给我一个关于如何/为什么这样做的好指南吗?

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

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

Jon

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

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

由于Javascript函数也是对象,它们有自己的方法,特别是调用和应用,这些方法触发函数,甚至设置该函数的this值。

回调示例(我知道这很傻…这是小提琴):

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带来的灵活性,但仍要保持一致性和良好的编程纪律。强调简洁性和可读性。玩得开心:)

这被称为连续传递样式。它有时用于封装异步行为,如您提供的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中的一流成员,这使得可以使用更高阶的函数。这意味着.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]

不久,你将深入到功能范式中,学习各种其他新事物。

函数!

这些回调看起来很相似,但它们的用途完全不同。在第一个示例中,回调用于检索结果,因为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定义的变量将已经被数组的最后一项覆盖。

您需要javascript回调

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

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的核心概念之一。但我建议您也可以看看异步操作(如ajax http请求)的Promises。它不是当前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);
});