文同步本人掘金平台原创翻译的文章:https://juejin.cn/post/6844903834246971400
你是否遇到过"callbacks"一词,但是不知道这意味着什么?别着急。你不是一个人。许多JavaScript的新手发现回调也很难理解。
尽管callbacks可能令人疑惑,但是你仍然需要彻底了解它们,因为它们是JavaScript中的一个重要的概念。如果你不知道callbacks,你不可能走得很远。
这就是今天的文章(要讲的)!你将了解callbacks是什么,为什么它们很重要,以及如何使用它们。
备注:你会在这篇文章中看到ES6箭头函数。如果你不是很熟悉它们,我建议你在往下读之前复习一下ES6这篇文章(只了解箭头函数部分就可以了)。
callback是作为稍后要执行的参数传递给另一个函数的函数。(开发人员说你在执行函数时“调用”一个函数,这就是被命名为回调函数的原因)。
它们在JavaScript中很常见,你可能自己潜意识的使用了它们而不知道它们被称为回调函数。
接受函数回调的一个示例是addEventLisnter:
const button=document.querySelector('button')
button.addEventListener('click', function(e) {
// Adds clicked class to button
this.classList.add('clicked')
})
复制代码
看不出是回调函数吗?那么,这种写法怎样?
const button=document.querySelector('button')
// Function that adds 'clicked' class to the element
function clicked (e) {
this.classList.add('clicked')
}
// Adds click function as a callback to the event listener
button.addEventListener('click', clicked)
复制代码
在这里,我们告诉JavaScript监听按钮上的click事件。如果检测到点击,则JavaScript应触发clicked函数。因此,在这种情况下,clicked是回调函数,而addEventListener是一个接受回调的函数。
现在,你明白什么是回调函数了嘛?:)
我们来看另外一个例子。这一次,假设你希望通过过滤一组数据来获取小于5的列表。在这里,你将回调函数传递给filter函数:
const numbers=[3, 4, 10, 20]
const lesserThanFive=numbers.filter(num=> num < 5)
复制代码
现在,如果你想通过命名函数执行上面的代码,则过滤函数将如下所示:
const numbers=[3, 4, 10, 20]
const getLessThanFive=num=> num < 5
// Passing getLessThanFive function into filter
const lesserThanFive=numbers.filter(getLessThanFive)
复制代码
在这种情况下,getLessThanFive是回调函数。Array.filter是一个接受回调的函数。
现在明白为什么了吧?一旦你知道回调函数是什么,它们就无处不在!
下面的示例向你展示如何编写回调函数和接受回调的函数:
// Create a function that accepts another function as an argument
const callbackAcceptingFunction=(fn)=> {
// Calls the function with any required arguments
return fn(1, 2, 3)
}
// Callback gets arguments from the above call
const callback=(arg1, arg2, arg3)=> {
return arg1 + arg2 + arg3
}
// Passing a callback into a callback accepting function
const result=callbackAcceptingFunction(callback)
console.log(result) // 6
复制代码
请注意,当你将回调函数传递给另一个函数时,你只传递该函数的引用(并没有执行它,因此没有括号())
const result=callbackAcceptingFunction(callback)
复制代码
你只能在callbackAcceptingFunction中唤醒(调用)回调函数。执行此操作时,你可以传递回调函数可能需要的任意数量的参数:
const callbackAcceptingFunction=(fn)=> {
// Calls the callback with three args
fn(1, 2, 3)
}
复制代码
这些由callbackAcceptingFunction传递给回调函数的参数,然后再通过回调函数(执行):
// Callback gets arguments from callbackAcceptingFunction
const callback=(arg1, arg2, arg3)=> {
return arg1 + arg2 + arg3
}
复制代码
这是回调的解剖。现在,你应该知道addEventListener包含一个event参数:)
// Now you know where this event object comes from! :)
button.addEventListener('click', (event)=> {
event.preventDefault()
})
复制代码
唷!这是callbacks的基本思路!只需要记住其关键:将一个函数传递给另一个函数,然后,你会想起我上面提到的机制。
旁注:这种传递函数的能力是一件很重要的事情。它是如此重要,以至于说JavaScript中的函数是高阶函数。高阶函数在编程范例中称为函数编程,是一件很重大的事情。
但这是另一天的话题。现在,我确信你已经开始明白callbacks是什么,以及它们是如何被使用的。但是为什么?你为什么需要callbacks呢?
回调函数以两种不同的方式使用 -- 在同步函数和异步函数中。
如果你的代码从上到下,从左到右的方式顺序执行,等待上一个代码执行之后,再执行下一行代码,则你的代码是同步的。
让我们看一个示例,以便更容易理解:
const addOne=(n)=> n + 1
addOne(1) // 2
addOne(2) // 3
addOne(3) // 4
addOne(4) // 5
复制代码
在上面的例子中,addOne(1)首先执行。一旦它执行完,addOne(2)开始执行。一旦addOne(2)执行完,addOne(3)执行。这个过程一直持续到最后一行代码执行完毕。
当你希望将部分代码与其它代码轻松交换时,回调将用于同步函数。
所以,回到上面的Array.filter示例中,尽管我们将数组过滤为包含小于5的数组,但你可以轻松地重用Array.filter来获取大于10的数字数组:
const numbers=[3, 4, 10, 20]
const getLessThanFive=num=> num < 5
const getMoreThanTen=num=> num > 10
// Passing getLessThanFive function into filter
const lesserThanFive=numbers.filter(getLessThanFive)
// Passing getMoreThanTen function into filter
const moreThanTen=numbers.filter(getMoreThanTen)
复制代码
这就是为什么你在同步函数中使用回调函数的原因。现在,让我们继续看看为什么我们在异步函数中使用回调。
这里的异步意味着,如果JavaScript需要等待某些事情完成,它将在等待时执行给予它的其余任务。
异步函数的一个示例是setTimeout。它接受一个回调函数以便稍后执行:
// Calls the callback after 1 second
setTimeout(callback, 1000)
复制代码
如果你给JavaScript另外一个任务需要完成,让我们看看setTimeout是如何工作的:
const tenSecondsLater=_=> console.log('10 seconds passed!')
setTimeout(tenSecondsLater, 10000)
console.log('Start!')
复制代码
在上面的代码中,JavaScript会执行setTimeout。然后,它会等待10秒,之后打印出"10 seconds passed!"的消息。
同时,在等待setTimeout10秒内完成时,JavaScript执行console.log("Start!")。
所以,如果你(在控制台上)打印上面的代码,这就是你会看到的:
// What happens:
// > Start! (almost immediately)
// > 10 seconds passed! (after ten seconds)
复制代码
啊~异步操作听起来很复杂,不是吗?但为什么我们在JavaScript中频繁使用它呢?
要了解为什么异步操作很重要呢?想象一下JavaScript是你家中的机器人助手。这个助手非常愚蠢。它一次只能做一件事。(此行为被称为单线程)。
假设你告诉你的机器人助手为你订购一些披萨。但机器人是如此的愚蠢,在打电话给披萨店之后,机器人坐在你家门前,等待披萨送达。在此期间它无法做任何其它事情。
你不能叫它去熨衣服,拖地或在等待(披萨到来)的时候做任何事情。(可能)你需要等20分钟,直到披萨到来,它才愿意做其他事情...
此行为称为阻塞。当你等待某些内容完成时,其他操作将被阻止。
const orderPizza=flavour=> {
callPizzaShop(`I want a ${flavour} pizza`)
waits20minsForPizzaToCome() // Nothing else can happen here
bringPizzaToYou()
}
orderPizza('Hawaiian')
// These two only starts after orderPizza is completed
mopFloor()
ironClothes()
复制代码
而阻止操作是一个无赖。
为什么?
让我们把愚蠢的机器人助手放到浏览器的上下文中。想象一下,当单击按钮时,你告诉它更改按钮的颜色。
这个愚蠢的机器人会做什么?
它专注于按钮,忽略所有命令,直到按钮被点击。同时,用户无法选择任何其他内容。看看它都在干嘛了?这就是异步编程在JavaScript中如此重要的原因。
但是,要真正了解异步操作期间发生的事情,我们需要引入另外一个东西 -- 事件循环。
为了设想事件循环,想象一下JavaScript是一个携带todo-list的管家。此列表包含你告诉它要做的所有事情。然后,JavaScript将按照你提供的顺序逐个遍历列表。
假设你给JavaScript下面五个命令:
const addOne=(n)=> n + 1
addOne(1) // 2
addOne(2) // 3
addOne(3) // 4
addOne(4) // 5
addOne(5) // 6
复制代码
这是JavaScript的待办事项列表中出现的内容。
相关命令在JavaScript待办事项列表中同步出现。
除了todo-list之外,JavaScript还保留一个waiting-list来跟踪它需要等待的事情。如果你告诉JavaScript订购披萨,它会打电话给披萨店并在等候列表名单中添加“等待披萨到达”(的指令)。与此同时,它还会做了其他已经在todo-list上的事情。
所以,想象下你有下面代码:
const orderPizza (flavor, callback) {
callPizzaShop(`I want a ${flavor} pizza`)
// Note: these three lines is pseudo code, not actual JavaScript
whenPizzaComesBack {
callback()
}
}
const layTheTable=_=> console.log('laying the table')
orderPizza('Hawaiian', layTheTable)
mopFloor()
ironClothes()
复制代码
JavaScript的初始化todo-list如下:
订披萨,拖地和熨衣服!
然后,在执行orderPizza时,JavaScript知道它需要等待披萨送达。因此,它会在执行其余任务时,将“等待披萨送达”(的指令)添加到waiting list上。
JavaScript等待披萨到达
当披萨到达时,门铃会通知JavaScript,当它完成其余杂务时。它会做个**心理记录(mental note)**去执行layTheTable。
JavaScript知道它需要通过在其 mental note 中添加命令来执行layTheTable
然后,一旦完成其他杂务,JavaScript就会执行回调函数layTheTable。
其他所有内容完成后,JavaScript就会去布置桌面(layTheTable)
我的朋友,这个就被称为事件循环。你可以使用事件循环中的实际关键字替换我们的管家,类比来理解所有的内容:
JavaScript的事件循环
如果你有20分钟的空余时间,我强烈建议你观看Philip Roberts 在JSconf中谈论的事件循环。它将帮助你理解事件循环的细节。
哦~我们在事件循环绕了一大圈。我们回正题吧。
之前,我们提到如果JavaScript专注于按钮并忽略所有其他命令,那将是不好的。是吧?
通过异步回调,我们可以提前提供JavaScript指令而无需停止整个操作。
现在,当你要求JavaScript查看点击按钮时,它会将“监听按钮”(指令)放入waiting list中并继续进行杂务。当按钮最终获得点击时,JavaScript会激活回调,然后继续执行。
以下是回调中的一些常见用法,用于告诉JavaScript要做什么...
// Callbacks in event listeners
document.addEventListener(button, highlightTheButton)
document.removeEventListener(button, highlightTheButton)
// Callbacks in jQuery's ajax method
$.ajax('some-url', {
success (data) { /* success callback */ },
error (err) { /* error callback */}
});
// Callbacks in Node
fs.readFile('pathToDirectory', (err, data)=> {
if (err) throw err
console.log(data)
})
// Callbacks in ExpressJS
app.get('/', (req, res)=> res.sendFile(index.html))
复制代码
这就是它(异步)的回调!
希望你清楚callbacks是什么以及现在如何使用它们。在开始的时候,你不会创建很多回调,所以要专注于学习如何使用可用的回调函数。
现在,在我们结束(本文)之前,让我们看一下开发人员(使用)回调的第一个问题 -- 回调地狱。
回调地狱是一种多次回调相互嵌套的现象。当你执行依赖于先前异步活动的异步活动时,可能会发生这种情况。这些嵌套的回调使代码更难阅读。
根据我的经验,你只会在Node中看到回调地狱。在使用前端JavaScript时,你几乎从不会遇到回调地狱。
下面是一个回调地狱的例子:
// Look at three layers of callback in this code!
app.get('/', function (req, res) {
Users.findOne({ _id:req.body.id }, function (err, user) {
if (user) {
user.update({/* params to update */}, function (err, document) {
res.json({user: document})
})
} else {
user.create(req.body, function(err, document) {
res.json({user: document})
})
}
})
})
复制代码
而现在,你有个挑战 -- 尝试一目了然地破译上面的代码。很难,不是吗?难怪开发者在看到嵌套回调时会不寒而栗。
克服回调地狱的一个解决方案是将回调函数分解为更小的部分以减少嵌套代码的数量:
const updateUser=(req, res)=> {
user.update({/* params to update */}, function () {
if (err) throw err;
return res.json(user)
})
}
const createUser=(req, res, err, user)=> {
user.create(req.body, function(err, user) {
res.json(user)
})
}
app.get('/', function (req, res) {
Users.findOne({ _id:req.body.id }, (err, user)=> {
if (err) throw err
if (user) {
updateUser(req, res)
} else {
createUser(req, res)
}
})
})
复制代码
更容易阅读了,是吧?
还有其他解决方案来对抗新版JavaScript中的回调地狱 -- 比如promises和async / await。但是,解释它们是我们另一天的话题。
今天,你了解到了回调是什么,为什么它们在JavaScript中如此重要以及如何使用它们。你还学会了回调地狱和对抗它的方法。现在,希望callbakcs不再吓到你了。
你对回调还有任何疑问吗?如果你有,请随时在下面发表评论,我会尽快回复你的。【PS:本文译文,若需作者解答疑问,请移步原作者文章下评论】
感谢阅读。这篇文章是否帮助到你?如果有,我希望你考虑分享它。你可能会帮助到其他人。非常感谢!
篇文章介绍了中继器的增和删两个功能,这篇文章将介绍使用中继器进行评论的编辑功能。
效果显示地址:http://www.iqiyi.com/w_19saxn0qtt.html
(1)增加一个编辑的按钮,和一个对应编辑的文本框,思路:用户点击编辑按钮的时候,头像、姓名、时间和评论的内容都进入到对应的编辑处,即第二张图片圈起来的部分。
(2)给添加的元件起名字,需要注意的是,起的名字不可以和之前两个的名字相同,在这里我分别起的名字为:touxiang1,mingzi1,shijian1,neirong1,起什么样的名字都可以,只要不和上面的名字重复就可以。
(1)给编辑按钮添加事件,当点击编辑的时候,选中的行,mingzi1=item.name,shijian1=item.time,neirong1=item.name,touxiang1=item.img。
添加事件的方法在上文中,一步一步的介绍过,这里不再对其细节进行赘述。
需要注意的是,在编辑评论的时候,我们可能点击多个评论,但最后我们要修改的是最后点击的评论。所以需要添加事件,点击编辑的时候,取消对之前所有行的标记,只标记最后选择的一行。
(2)给编辑按钮添加事件,当点击更新按钮的时候,更新的内容到达需要编辑的位置。
(3)添加全局变量,点击ok即可完成对编辑评论。
本文由 @月半警长 原创发布于人人都是产品经理。未经许可,禁止转载
题图来自Unsplash,基于CC0协议
阵子在逛张戈博客的时候,发现他的博客评论按钮挺好的,一来可以有效防止垃圾评论,二来减少一个评论提交步骤从而有效提高用户体验,所以我也把自己博客的评论按钮修改为滑动/拉链解锁后自动提交评论,今天就把折腾的过程分享一下。
一、不想折腾代码的只需两步即可,具体如下:
1、下载本站已经折腾好的Three主题滑动解锁自动提交代码压缩包,内含张戈博主分享的myqaptcha代码和Three主题需要修改的替换文件。
下载地址
2、将本站分享的压缩包解压后,把myqaptcha文件夹上传到Three主题目录下;把“Three主题修改文件”文件夹内的comments.php和functions.php替换Three主题目录下的comments.php和functions.php文件,把comments-ajax.js替换Three主题目录下JS文件夹内的comments-ajax.js文件。
二、想折腾代码的具体步骤如下(适用于其他WordPress主题):
1、跟上面第一步一样,下载张戈博主分享的myQaptcha代码,下载解压后,将得到的myqaptcha文件夹整体上传到Three主题目录下备用。
2、编辑Three主题目录下的functions.php,在<?php 之后添加如下代码保存即可:
// 滑动提交评论
include("myqaptcha/myQaptcha.php");
3、修改评论框的提交按钮代码
打开Three主题目录下的comments.php,将以下代码:
<p class="form-submit">
<input id="submit" name="submit" type="submit" tabindex="5" value="提交评论">
<?php comment_id_fields(); do_action('comment_form', $post->ID); ?>
</p>
替换为以下代码:
<div id="autosubmit"></div>
<p style="display:none;">
<input id="submit" name="submit" type="submit" tabindex="5" value="提交评论">
<?php comment_id_fields(); do_action('comment_form', $post->ID); ?>
</p>
4、修改comments-ajax.js代码
目的是为了滑动模块后,不管是评论提交成功还是失败,模块都应该恢复到未拉动的状态,以备再次评论。
把以下代码:
/** Ajax */
$.ajax( {
url: ajax_php_url,
data: $(this).serialize(),
type: $(this).attr('method'),
error: function(request) {
$('#loading').slideUp();
$('#error').slideDown().html('<img src="' + pic_no + '" style="vertical-align:middle;" alt=""/> ' + request.responseText);
setTimeout(function() {$submit.attr('disabled', false).fadeTo('slow', 1); $('#error').slideUp();}, 3000);
},
success: function(data) {
$('#loading').hide();
comm_array.push($('#comment').val());
$('textarea').each(function() {this.value=''});
var t=addComment, cancel=t.I('cancel-comment-reply-link'), temp=t.I('wp-temp-form-div'), respond=t.I(t.respondId), post=t.I('comment_post_ID').value, parent=t.I('comment_parent').value;
替换为以下代码:
/** Ajax */
$.ajax( {
url: ajax_php_url,
data: $(this).serialize(),
type: $(this).attr('method'),
error: function(request) {
$('#loading').slideUp();
$('#error').slideDown().html('<img src="' + pic_no + '" style="vertical-align:middle;" alt=""/> ' + request.responseText);
setTimeout(function() {$submit.attr('disabled', false).fadeTo('slow', 1); $('#error').slideUp();}, 3000);
$(".QapTcha").html('');$(".QapTcha").QapTcha();
},
success: function(data) {
$('#loading').hide();
comm_array.push($('#comment').val());
$('textarea').each(function() {this.value=''});
var t=addComment, cancel=t.I('cancel-comment-reply-link'), temp=t.I('wp-temp-form-div'), respond=t.I(t.respondId), post=t.I('comment_post_ID').value, parent=t.I('comment_parent').value;
$(".QapTcha").html('');$(".QapTcha").QapTcha();
很明显,也就新增了2行JS代码,作用就是为了在aja评论成功或失败后,复原滑动模块,让滑动模块可以再次使用!
至此,Three主题评论按钮修改为滑动/拉链解锁后自动提交评论的功能已经实现了,欢迎大家测试使用。
特别感谢:本文技术和myQaptcha代码均来自于张戈博主的《WordPress评论滑动/拉链解锁myQaptcha修改为自动提交的方法》。
*请认真填写需求信息,我们会在24小时内与您取得联系。