JS:同步、异步与回调
JS以单线程执行代码。这会让很多人认为JS代码是按顺序自上而下一句接着一句依次执行的。这个说法也没错,试看:<div id="mydiv">请稍候……</div>
<script>
let 函数1 = () => {
mydiv.innerText += '\n我是函数1';
};
let 函数2 = () => {
mydiv.innerText += '\n我是函数2';
};
函数1();
函数2();
</script>
执行函数1和函数2,其结果是我们预期的:
我是函数1
我是函数2
不过,当函数1要做的事情很耗时,运行结果会如何呢?我们给函数1加一个定时器,假装它要做的事情耗时1秒钟:
<div id="mydiv">请稍候……</div>
<script>
let 函数1 = () => {
setTimeout( () => {
mydiv.innerText += '\n我是函数1';
}, 1000);
};
let 函数2 = () => {
mydiv.innerText += '\n我是函数2';
};
函数1();
函数2();
</script>
执行结果:
我是函数2
我是函数1
这次,结果和上一次相反。原因:JS运行代码的次序虽然是自上而下一句一句交付执行,但这仅限于分派任务,执行结果的快慢要根据各个代码块处理事件的耗时情况而定。上例,函数1延时一秒然后再处理输出事件,可JS的流水线节奏紧凑,该事件的代码交付执行执行之后,余下的代码紧跟着交付。函数2没有延时输出,所以它放到执行线程之后立马就出结果,而函数1则等完自己那一秒时间过了才输出自己的处理结果。
这里面,要牵出两个概念,同步与异步。分派任务,即代码模块交付执行,虽然也确实存在先后次序问题,可是这个环节都是一步到位、瞬间完成,用“秒杀”这个新词汇都不能精准描述这个环节,因此这个环节可视作是同步的。JS流水线是个单线程构架,各个工位上,如果大家也能在相同的时间内完成各自生产环节的工作,那是很理想的状态,通常也都是这样,但是,非常遗憾,一些应用场景下,有些工位上工作复杂、处理加工环节耗时不确定,这就来大麻烦了:我们知道,如果事件间有前后依赖关系(生产线上都这样),前面环节结果尚未出来,后面的环节就不能进行,否则出错或根本无法干下去。异步概念因此引出:各个代码块运行耗时的不同,运行结果不能在同一时间拿到,这可以叫做运行结果异步。运行结果异步完成的现象不是好现象,比如上例,函数1运行耗时较长,假如函数2的运行需要函数1的结果或结果的一部分,则函数2的运行结果就不靠谱,在JS里运行出错,余下的代码块拒绝运行。为避免出错,我们应有异步交付执行的机制,等上一个环节出了结果,再去运行下一个环节。我们改进一下上面的代码:
<div id="mydiv">请稍候……</div>
<script>
let 函数1 = (回调) => {
setTimeout( () => {
mydiv.innerText += '\n我是函数1';
回调();
}, 1000);
};
let 函数2 = () => {
mydiv.innerText += '\n我是函数2';
};
函数1(函数2);
</script>
函数1加了一个“回调”参数,这不是普通的参数,它实际上是一个函数,看它在函数1的调用方式 “回调();” 就知道。回调函数写在定时器里,放在输出结果语句之后。然后,这个非常重要,函数1和函数2不是依次运行,而是,函数1把函数2作为自己的运行参数运行,函数2此时就是函数1里的“回调”那个参数。这就彻底解决了异步问题:函数2的执行放在函数1执行结果出来之后再执行。
不知不觉,我们已经引入了JS的高阶函数,函数1就是,因为它有回调函数。以其他函数做参数的函数就叫高阶函数。而回调函数(callback)很多编程语言也都存在,在JS世界,回调函数主要用于解决环节运行结果异步的问题,一般就命名为 callback 而不是我们在函数1里的“回调”。
以其他函数做参数的函数就叫高阶函数。
我咋觉得这个说法是反的,函数2是依赖函数1的结果的,所以函数1应该是函数2的函数啊。当然可能只是个表达方法,实际做法中是顺的。 这个一点都没接触过,看看这个学习一下真好。异步的处理也能做到,JS才没什么缺项了{:4_204:} 黑黑的讲解一步步非常清晰透彻,让那么难懂的东西一下就被说明白了,太赞了{:4_199:} 红影 发表于 2023-9-10 11:11
黑黑的讲解一步步非常清晰透彻,让那么难懂的东西一下就被说明白了,太赞了
嗯,尽量通俗去讲解,但不一定专业、精准。官网的描述很专业,只是,多数人看不懂。 红影 发表于 2023-9-10 11:09
以其他函数做参数的函数就叫高阶函数。
我咋觉得这个说法是反的,函数2是依赖函数1的结果的,所以函数1 ...
这里的 函数2() 是个普通函数,而 函数1() 有回调参数,在实例化运行 函数(1() 时把 函数2() 作为自己的参数,所以是高阶函数。 红影 发表于 2023-9-10 11:10
这个一点都没接触过,看看这个学习一下真好。异步的处理也能做到,JS才没什么缺项了
异步处理是交互的核心,JS通过回调实现异步处理数据,交互功能才可以强大。比方讲,一个代码模块要读取某一个服务器上的数据,这些数据读到了才能运行其他的功能模块,这就需要等待,等待数据从服务器读取完毕,再去运行其他模块。
我们的播放器插件和粒子插件的使用,就用到这个异步,尽管我给出的帖子实例还没有用到回调,而是用到了资源文档的 onload 事件,该事件表示,资源文档加载完毕后再运行 onload({}) 花括号里的代码。这个也是实现异步处理数据的方法。还可以使用最笨的方法,用定时器延时,只是延时多长时间不好确定(所以说它笨)。 黑黑的讲解很清楚,只是我一次看了肯定不够记忆的。 马黑黑 发表于 2023-9-10 12:24
嗯,尽量通俗去讲解,但不一定专业、精准。官网的描述很专业,只是,多数人看不懂。
对的,还是喜欢这样的讲解,特别容易懂。 马黑黑 发表于 2023-9-10 12:26
这里的 函数2() 是个普通函数,而 函数1() 有回调参数,在实例化运行 函数(1() 时把 函数2() 作为自己的 ...
我连低阶的还没学会呢{:4_173:} 马黑黑 发表于 2023-9-10 12:33
异步处理是交互的核心,JS通过回调实现异步处理数据,交互功能才可以强大。比方讲,一个代码模块要读取某 ...
以前至少看你的代码里有onload 之类的,都不知道它们的作用呢。 红影 发表于 2023-9-10 14:16
以前至少看你的代码里有onload 之类的,都不知道它们的作用呢。
onload主要用于window对象和动态加载的元素(如img,script,CSS等) 红影 发表于 2023-9-10 14:14
我连低阶的还没学会呢
没有低阶,只有普通函数和高阶函数 红影 发表于 2023-9-10 14:14
对的,还是喜欢这样的讲解,特别容易懂。
能明白就好 小辣椒 发表于 2023-9-10 13:03
黑黑的讲解很清楚,只是我一次看了肯定不够记忆的。
这个知识点抽象、艰涩,能大概了解就行 俺飘过{:4_187:} 马黑黑 发表于 2023-9-10 16:00
onload主要用于window对象和动态加载的元素(如img,script,CSS等)
嗯嗯,是JS里的常用命令吧。 马黑黑 发表于 2023-9-10 16:00
没有低阶,只有普通函数和高阶函数
哦哦,就这两个分类啊,习惯性地以为高阶对应的是低阶{:4_173:} 马黑黑 发表于 2023-9-10 16:01
能明白就好
还好,黑黑讲解的能看明白{:4_187:} 红影 发表于 2023-9-10 20:26
还好,黑黑讲解的能看明白
那就好