夯实基础-JavaScript异步编程

2023-10-26

异步编程

JavaScript中异步编程问题可以说是基础中的重点,也是比较难理解的地方。首先要弄懂的是什么叫异步?

我们的代码在执行的时候是从上到下按顺序执行,一段代码执行了之后才会执行下一段代码,这种方式叫同步(synchronous)执行,也是我们最容易理解的方式。但是在某些场景下:

  1. 网络请求:常见的ajax
  2. IO操作:比如readFile
  3. 定时器:setTimeout

上面这些场景可能非常耗时,而且时间不定长,这时候这些代码就不应该同步执行了,先执行可以执行的代码,在未来的某个时间再来执行他们的handler,这就是异步。

通过这篇文章我们来了解几个知识点:

  1. 进程线程区别
  2. 消息队列与事件循环
  3. JavaScript处理异步的几种方法
  4. generator与async/await的关系

基础知识

先做些准备工作,补一补一些非常重要的前置的概念。

进程与线程

一个程序(program)至少包含一个进程(process),一个进程至少包含一个线程(thread)。

进程有以下特点:

  1. 一个进程可以包含一个或多个线程。
  2. 进程在执行过程中拥有独立的内存单元。
  3. 一个进程可以创建和撤销另一个进程,这个进程是父进程,被创建的进程称为子进程。

线程有以下特点:

  1. 线程不能独立运行,必须依赖进程空间。
  2. 线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源。
  3. 一个线程可以创建和撤销另一个线程;同一个进程中的多个线程之间可以并发执行。
从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但操作系统并没有将多个线程看做多个独立的应用,来实现进程的调度和管理以及资源分配。 这就是进程和线程的重要区别。

画张图来简单描述下:
clipboard.png
所有的程序都要交给CPU实现计算任务,但是CPU一个时间点只能处理一个任务。这时如果多个程序在运行,就涉及到了《操作系统原理》中重要的线程调度算法,线程是CPU轮转的最小单位,其他上下文信息用所在进程中的。

进程是资源的分配单位,线程是CPU在进程内切换的单位。

JavaScript单线程

浏览器内核是多线程,在内核控制下各线程相互配合以保持同步,一个浏览器通常由以下常驻线程组成:

  1. GUI 渲染线程
  2. JavaScript引擎线程
  3. 定时触发器线程
  4. 事件触发线程
  5. 异步http请求线程

Javascript是单线程的,那么为什么Javascript要是单线程的?

这是因为Javascript这门脚本语言诞生的使命所致:JavaScript为处理页面中用户的交互,以及操作DOM树、CSS样式树来给用户呈现一份动态而丰富的交互体验和服务器逻辑的交互处理。如果JavaScript是多线程的方式来操作这些UI DOM,则可能出现UI操作的冲突; 如果Javascript是多线程的话,在多线程的交互下,处于UI中的DOM节点就可能成为一个临界资源,假设存在两个线程同时操作一个DOM,一个负责修改一个负责删除,那么这个时候就需要浏览器来裁决如何生效哪个线程的执行结果。当然我们可以通过锁来解决上面的问题。但为了避免因为引入了锁而带来更大的复杂性,Javascript在最初就选择了单线程执行。

阻塞和非阻塞

这时候再理解阻塞非阻塞就好理解了,对于异步任务,单线程的JavaScript如果什么也不干等待异步任务结束,这种状态就是阻塞的;如果将异步消息放到一边,过会再处理,就是非阻塞的。

请求不能立即得到应答,需要等待,那就是阻塞;否则可以理解为非阻塞。

生活中这种场景太常见了,上厕所排队就是阻塞,没人直接上就是非阻塞。

事件循环(event-loop)

因为JavaScript是单线程的,每个时刻都只能一个事件,所以JavaScript中的同步和异步事件就有了一个奇妙的执行顺序。

JavaScript在运行时(runtime)会产生一个函数调用栈,先入栈的函数先被执行。但是有一些任务是不需要进入调用栈的,这些任务被加入到消息队列中。当函数调用栈被清空时候,就会执行消息队列中的任务(任务总会关联一个函数,并加入到调用栈),依次执行直至所有任务被清空。由于JavaScript是事件驱动,当用户触发事件JavaScript再次运行直至清空所有任务,这就是事件循环。

函数调用栈中的任务永远优先执行,调用栈无任务时候,遍历消息队列中的任务。消息队列中的任务关联的函数(一般就是callback)放入调用栈中执行。

举两个例子:异步请求

function ajax (url, callback){
    var req = new XMLHttpRequest();

    req.onloadend = callback;
    req.open('GET', url, true);
    req.send();
};

console.log(1);
ajax('/api/xxxx', function(res){
    console.log(res);
});
console.log(2);

一个开发经常遇到的业务场景,异步请求一个数据,上述过程用图表示:
clipboard.png
图中三条线分别表示函数执行的调用栈,异步消息队列,以及请求所依赖的网络请求线程(浏览器自带)。执行顺序:

  1. 调用栈执行console.log(1);
  2. 调用栈执行ajax方法,方法里面配置XMLHttpRequest的回调函数,并交由线程执行异步请求。
  3. 调用栈继续执行console.log(2);
  4. 调用栈被清空,消息队列中并无任务,JavaScript线程停止,事件循环结束。
  5. 不确定的时间点请求返回,将设定好的回调函数放入消息队列。
  6. 事件循环再次启动,调用栈中无函数,执行消息队列中的任务function(res){console.log(res);}

定时器任务:

console.log(1);
setTimeout(function(){
    console.log(2);
}, 100);
setTimeout(function(){
    console.log(3);
}, 10);
console.log(4);

// 1
// 4
// 3
// 2

跟上面的例子很像,只不过异步请求变成了定时器,上述代码的指向过程图:
clipboard.png
执行顺序如下:

  1. 调用栈执行console.log(1);
  2. 执行setTimeout向消息队列添加一个定时器任务1。
  3. 执行setTimeout向消息队列添加一个定时器任务2。
  4. 调用栈执行console.log(4);
  5. 调用栈执行完毕执行消息队列任务1。
  6. 调用栈执行完毕执行消息队列任务2。
  7. 消息队列任务2执行完毕调用回调函数console.log(3);
  8. 消息队列任务1执行完毕调用回调函数console.log(2);

通过上面例子可以很好理解,就像工作中你正在做一件事情,这时候领导给你安排一个不着急的任务,你停下来跟领导说'等我忙完手里的活就去干',然后把手里的活干完去干领导安排的任务。所有任务完成相当于完成了一个事件循环。

macrotasks 和 microtasks

macrotask 和 microtask 都是属于上述的异步任务中的一种,分别是一下 API :

  • macrotaskssetTimeoutsetIntervalsetImmediate, I/O, UI rendering
  • microtasksprocess.nextTick(node), PromisesObject.observe(废弃), MutationObserver

setTimeout 的 macrotask ,和 Promise 的 microtask 有什么不同呢:

console.log('script start');
setTimeout(function() {
  console.log('setTimeout');
}, 0);
Promise.resolve().then(function() {
  console.log('promise1');
}).then(function() {
  console.log('promise2');
});
console.log('script end');

// "script start"
// "script end"
// "promise1"
// "promise2"
// "setTimeout"

这里的运行结果是Promise的立即返回的异步任务会优先于setTimeout延时为0的任务执行。

原因是任务队列分为 macrotasks 和 microtasks,而Promise中的then方法的函数会被推入 microtasks 队列,而setTimeout的任务会被推入 macrotasks 队列。在每一次事件循环中,macrotask 只会提取一个执行,而 microtask 会一直提取,直到 microtasks 队列清空

所以上面实现循环的顺序:

  1. 执行函数调用栈中的任务。
  2. 函数调用栈清空之后,执行microtasks队列任务至清空。
  3. 执行microtask队列任务至清空。

并发(Concurrency)

并发我们应该经常听过,跟他类似的一个词叫并行。

并发:多个进程在一台处理机上同时运行,一个时间段内处理多件事情,宏观上好比一个人边唱边跳,微观上这个人唱一句跳一步。(可以类比时间片轮转法,多个线程同时占用一个CPU,外部看来可以并发处理多个线程)

并行:多态拥有相同处理能力的处理机在同时处理不同的任务,好比广场上多个大妈同时再调广场舞。(多个CPU同时处理多个线程任务)

在JavaScript中,因为其是单线程的原因,所以决定了其每时刻只能干一件事情,事件循环是并发在JavaScript单线程中的一种处理方式。

但是在日常开发中我们肯定见过,同时发送多个请求。这种情况下多个网络线程和js线程共同占用一个CPU,就是并发。

异步解决方法

虽然已经理解了JavaScript中运行异步任务的过程,但是这样显然对开发不友好,因为我们通常并不知道异步任务在何时结束。所以前人开发了多种处理异步的方法。每种方法我们都从三个角度考虑其优缺点:

  1. 单个异步写法是否简便。
  2. 多个异步按顺序执行。
  3. 多个异步并发执行。

回调函数 (callback)

一种最常见的处理异步问题的方法,将异步任务结束时候要干的事情(回调函数)作为参数传给他,等任务结束时候运行回调函数。我们常用的$.ajax()setTimeout都属于这种方式,但是这样的问题很明显:多个异步任务按顺序执行非常恐怖。

// 著名的回调金字塔
asyncEvent1(()=>{
    asyncEvent2(()=>{
        asyncEvent3(()=>{
            asyncEvent4(()=>{
                ....
            });    
        });
    });
});

上面这种情况非常难以维护,在早期Node项目中经常出现这种情况,有人对上面小改动:

function asyncEvent1CB (){
    asyncEvent2(asyncEvent2CB);
}

function asyncEvent2CB (){
    asyncEvent3(asyncEvent3CB);
}

function asyncEvent3CB (){
    asyncEvent4(asyncEvent4CB);
}

function asyncEvent4CB () {
    // ...
}

asyncEvent1(asyncEvent1CB);

这样讲回调函数分离出来,逻辑清晰了一些,但是还是很明显:方法调用顺序是硬编码,耦合性还是很高。而且一旦同时发送多个请求,这多个请求的回调函数执行顺序很难保证,维护起来非常麻烦。

这就是回调函数的弊端

  1. 虽然简单,但是不利于阅读维护。
  2. 多层回调顺序执行耦合性很高。
  3. 请求并发回调函数执行顺序无法确定。
  4. 每次只能指定一个回调函数,出现错误程序中断易崩溃。

虽然回调函数这种方式问题很多,但是不可否认的是在ES6之前,他就是处理异步问题普遍较好的方式,而且后面很多方式仍然基于回调函数。

事件监听(litenter)

JavaScript是事件驱动,任务的执行不取决代码的顺序,而取决于某一个事件是否发生。DOM中有大量事件如onclickonloadonerror等等。

$('.element1').on('click', function(){
    console.log(1);
});

$('#element2').on('click', function(){
    console.log(2);
});

document.getElementById('#element3').addEventListener('click', function(){
    console.log(3);
}, false);

例如上面这段代码 你无法预知输出结果,因为事件触发无法被预知。跟这个很像的还有订阅者发布者模式:

github上有个有意思的小demo。注册在发布者里面的回调函数何时被触发取决于发布者何时发布事件,这个很多时候也是不可预知的。

回调函数与事件监听的区别:

  1. 回调函数多是一对一的关系,事件监听可以是多对一。
  2. 运行异步函数,在一个不确定的时间段之后运行回调函数;不确定何时触发事件,但是触发事件同步响应事件的回调。
  3. 事件监听相对于回调函数,可配置的监听(可增可减)关系减少了耦合性。

不过事件监听也存在问题:

  1. 多对多的监听组成了一个复杂的事件网络,单个节点通常监听了多个事件,维护成本很大。
  2. 多个异步事件仍然还是回调的形式。

Promise

promise出场了,当年理解promise花了我不少功夫。Promise确实跟前两者很不一样,简单说下promise。

  1. Promise中文可以翻译成承诺,现在与未来的一种关系,我承诺我会调用你的函数。
  2. Promise三种状态:pending(进行中),fulfilled(已成功),rejected(已失败),其状态只能从进行中到成功或者是失败,不可逆。
  3. 已成功和已失败可以承接不同的回调函数。
  4. 支持.then链式调用,将异步的写法改成同步。
  5. 原生支持了race, all等方法,方便适用常见开发场景。

Promise对于异步处理已经十分友好,大多生产环境已经在使用,不过仍有些缺点:

  1. Promise一旦运行,不能终止掉。
  2. 利用Promise处理一个异步的后续处理十分简便,但是处理多个请求按顺序执行仍然很不方便。

Generator

中文翻译成'生成器',ES6中提供的一种异步编程解决方案,语法行为与传统函数完全不同。简单来说,我可以声明一个生成器,生成器可以在执行的时候暂停,交出函数执行权给其他函数,然后其他函数可以在需要的时候让该函数再次运行。这与之前的JavaScript听起来完全不同。

这里我们来据几个例子,正常的ajax调用写法看起来如下:

// 使用setTimeout模拟异步
function ajax (url, cb){
    setTimeout(function(){
        cb('result');
    }, 100);
}

ajax('/api/a', function(result){
    console.log(result);
});

// 'result'

一旦我们想要多个异步按顺序执行,简直是噩梦。这里使用generator处理异步函数利用了一个特点:调用next()函数就会继续执行下去,所以利用这个特点我们处理异步原理:

  1. 将异步逻辑封装成一个生成器。
  2. 将生成器的异步部分yield出去。
  3. 在异步的回调部分调用next()将生成器继续进行下去。
  4. 这样同步,异步,回调分离,处理异步写起来非常简便。

我们对上面的例子加以改进:

// 使用setTimeout模拟异步
function ajax (url, cb){
    setTimeout(function(){
        cb(url + ' result.');
    }, 100);
}

function ajaxCallback(result){
    console.log(result);
    it.next(result);
}

function* ajaxGen (){
    var aResult = yield ajax('/api/a', ajaxCallback); 
    console.log('aResult: ' + aResult);
    var bResult = yield ajax('/api/b', ajaxCallback); 
    console.log('bResult: ' + bResult);
}

var it = ajaxGen();
it.next();

// /api/a result.
// aResult: /api/a result.
// /api/b result.
// bResult: /api/b result.

运行下上面代码,可以看到控制台输出结果居然跟我们书写的顺序一样!我们稍加改动:

// 使用setTimeout模拟异步
function ajax (url, cb){
    setTimeout(function(){
        cb(url + ' result.');
    }, 100);
}

function run (generator) {
    var it = generator(ajaxCallback);
    
    function ajaxCallback(result){
        console.log(result);
        it.next(result);
    }
    
    it.next();
};

run(function* (cb){
    var aResult = yield ajax('/api/a', cb); 
    console.log('aResult: ' + aResult);
    var bResult = yield ajax('/api/b', cb); 
    console.log('bResult: ' + bResult);
});

简单几下改造便可以生成一个自执行的生成器函数,同时也完成了异步场景同步化写法。generator的核心在于:同步,异步,回调三者分离,遇到异步交出函数执行权,再利用回调控制程序生成器继续进行。上面的run函数只是一个简单的实现,业界已经有CO这样成熟的工具。实际上开发过程中通常使用generator搭配Promise实现,再来修改上面的例子:

// 使用setTimeout模拟异步
function ajax (url){
    return new Promise(function(resolve, reject){
        setTimeout(function(){
            resolve(url + ' result.');
        }, 100);
    });
}

function run (generator) {
    var it = generator();
    
    function next(result){
        var result = it.next(result);
        if (result.done) return result.value;
        result.value.then(function(data){
            console.log(data);
              next(data);
        });
    }
    
    next();
};

run(function* (){
    var aResult = yield ajax('/api/a'); 
    console.log('aResult: ' + aResult);
    var bResult = yield ajax('/api/b'); 
    console.log('bResult: ' + bResult);
});

使用Promise来代替callback,理解上花费点时间,大大提高了效率。上面是一种常见,之前我用过generator实现多张图片并发上传,这种情况下利用generator控制上传上传数量,达到断断续续上传的效果。

进化到generator这一步可以说是相当智能了,无论是单个异步,多个按顺序异步,并发异步处理都十分友好,但是也有几个问题:

  1. ES6浏览器支持问题,需要polyfill和babel的支持。
  2. 需要借助CO这样的工具来完成,流程上理解起来需要一定时间。

有没有更简便的方法?

async/await

理解了上面的generator,再来理解async/await就简单多了。

ES2017 标准引入了 async 函数,使得异步操作变得更加方便。async 函数是什么?一句话,它就是 Generator 函数的语法糖。

再看一遍上面的例子,然后修改上面的例子用async/await:

// 使用setTimeout模拟异步
function ajax (url){
    return new Promise(function(resolve, reject){
        setTimeout(function(){
            console.log(url + ' result.');
            resolve(url + ' result.');
        }, 100);
    });
}

async function ajaxAsync () {
    var aResult = await ajax('/api/a'); 
    console.log('aResult: ' + aResult);
    var bResult = await ajax('/api/b'); 
    console.log('bResult: ' + bResult);
}

ajaxAsync();

可以明显的看到,async/await写法跟generator最后一个例子很像,基本上就是使用async/await关键字封装了一个自执行的run方法。

async函数对 Generator 函数的改进,体现在以下四点。

  1. 内置执行器:Generator 函数的执行必须靠执行器,所以才有了co模块,而async函数自带执行器。也就是说,async函数的执行,与普通函数一模一样,只要一行。
  2. 更好的语义:async和await,比起星号和yield,语义更清楚了。async表示函数里有异步操作,await表示紧跟在后面的表达式需要等待结果。
  3. 更广的适用性:co模块约定,yield命令后面只能是 Thunk 函数或 Promise 对象,而async函数的await命令后面,可以是 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时等同于同步操作)。
  4. 返回值是 Promise:async函数的返回值是 Promise 对象,这比 Generator 函数的返回值是 Iterator 对象方便多了。你可以用then方法指定下一步的操作。

这里async/await不做深入介绍,详情移步阮一峰老师的博客

Web worker

一个很不常用的api,但是是一个异步编程的方法,跟以上几种又不太一样。

你可能会遇到一个非常耗时的计算任务,如果在js线程里运行会造成页面卡顿,这时使用web worker,将计算任务丢到里面去,等计算完成再以事件监听的方式通知主线程处理,这是一个web work的应用场景。在这时候,浏览器中是有多个线程在处理js的,worker同时可以在创建子线程,实现js'多线程'。web worker的文档。实战的话看这篇

与前面几种方法不同的是,我们绞尽脑汁想把异步事件同步化,但是web worker却反其道而行,将同步的代码放到异步的线程中。

目前,web worker通常用于页面优化的一种手段,使用场景:

  1. 使用专用线程进行数学运算:Web Worker最简单的应用就是用来做后台计算,而这种计算并不会中断前台用户的操作。
  2. 图像处理:通过使用从<canvas>或者<video>元素中获取的数据,可以把图像分割成几个不同的区域并且把它们推送给并行的不同Workers来做计算。
  3. 大量数据的检索:当需要在调用 ajax后处理大量的数据,如果处理这些数据所需的时间长短非常重要,可以在Web Worker中来做这些,避免冻结UI线程。
  4. 背景数据分析:由于在使用Web Worker的时候,我们有更多潜在的CPU可用时间,我们现在可以考虑一下JavaScript中的新应用场景。例如,我们可以想像在不影响UI体验的情况下实时处理用户输入。利用这样一种可能,我们可以想像一个像Word(Office Web Apps 套装)一样的应用:当用户打字时后台在词典中进行查找,帮助用户自动纠错等等。

总结

JavaScript中的异步编程方式目前来说大致这些,其中回调函数这种方式是最简单最常见的,Promise是目前最受欢迎的方式。前四种方式让异步编码模式使我们能够编写更高效的代码,而最后一种web worker则让性能更优。这里主要是对异步编程流程梳理,前提知识点的补充,而对于真正的异步编程方式则是以思考分析为主,使用没有过多介绍。最后补充一个连接:JavaScript异步编程常见面试题,帮助理解。

参考

  1. 《你所不知道JavaScript》
  2. 《JavaScript高级程序设计》
  3. 浏览器进程?线程?傻傻分不清楚!
  4. 线程和进程的区别是什么?
  5. 并发模型与事件循环
  6. 理解 JavaScript 中的 macrotask 和 microtask
  7. 【转向Javascript系列】深入理解Web Worker

转载于:https://my.oschina.net/u/3501740/blog/1813328

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

夯实基础-JavaScript异步编程 的相关文章

  • 如何动态更改表格单元格文本颜色?

    我需要一个解决方案来通过 javascript 动态更改表格单元格的文本颜色 文本可以是以下颜色 蓝色 绿色 红色和黑色 表示例
  • 自定义 Javascript EventManager - 请帮我完成

    我正在尝试创建一个自定义 javascript EventManager 类 我采用了 Grant Skinner 在他的 essel js 框架中使用的格式来创建类 并且需要坚持使用它 在这一点上我真的迷失了 我认为 至少在概念意义上 我
  • 使用 Selenium 自动化结帐流程时出现 403

    我正在尝试使用 python 和 selenium 创建一个脚本来自动执行 bestbuy ca 的结帐过程 我一直到达最后阶段 您可以单击以查看最终订单 但当我尝试单击到最后一步时 收到以下 403 禁止消息 如网络响应中所示 是否有服务
  • JavaScript 调用函数

    我最近一直在测试一些代码 试图更好地理解 javascript 然后我遇到了call 我无法很好地理解的功能 我有以下代码 function hi console log hi var bye function param param2 c
  • jquery/javascript setInterval

    目前我正在开发一个用户通知提醒消息功能 我设法使用setInterval控制我的 Ajax 调用 检查是否有用户的通知消息 但我的问题是我只想要通知消息 在页面上出现一次 现在它在屏幕上显示多个通知警报消息 我知道你可以使用setTimeo
  • 在 ES6 Node.js 中导入“.json”扩展名会引发错误

    我们正在尝试使用 Node js 导出和导入 ES6 模块的新方法 对于我们来说 从package json文件 下面的代码应该做到这一点 import name version from package json 但是 执行时会抛出以下错
  • Flot 中轴的逗号分隔数字

    有没有办法让 Flot 使轴编号以逗号分隔 例如 用 1 000 000 代替 1000000 您可以通过使用轴的tickFormatter 属性来做到这一点 xaxis tickFormatter function val axis in
  • 如何从主体上的 onClick 事件获取鼠标单击的绝对位置?

    我试图获取鼠标单击相对于浏览器 主体的绝对位置 顶部和左侧 not主体内的任何父元素 我有一个绑定到 body 的侦听器 但 e pageX 和 e pageY 为我提供了相对于 div 的位置 请注意 我可以利用 jQuery 和 YUI
  • JS 中的触摸板滚动检测,无库

    我正在制作自己的小型 Javascript 库 可以轻松地将您网站 和我的网站 的默认滚动条替换为自定义滚动条 其中一部分意味着为 BODY 元素提供 overflow hidden 样式来隐藏正常的滚动条 但是 这会阻止除代码中完成的滚动
  • JS 是否支持使用键函数而不是比较器进行排序?

    JavaScript 的array sort https developer mozilla org en US docs Web JavaScript Reference Global Objects Array sort Syntax方
  • Postman如何发送请求? ajax,同源策略

    我发现了这个非常有用的 Chrome 扩展程序 名为 Postman 这是一个非常有用的扩展 特别是当您正在编写 RESTful 应用程序时 我感到困惑的一件事是这个插件 扩展如何能够在不同的域上成功发送 POST 请求 我尝试像这样使用
  • CKFinder 如何在选择图像(文件:选择)时获取尺寸 URL 和尺寸(宽度/高度)?

    我正在使用 CkFinder 3 成功上传图像后 我需要能够在用户单击 选择 按钮后进行检测 文件名 ID url 原始图像的宽度和高度 目前我正在使用files choose但我找不到有关 cb 事件的信息 知道如何解决吗 代码示例将不胜
  • ReactJs / Typescript:如何扩展状态接口

    我有以下内容 interface EditViewState
  • 有没有办法在 Blazor 中隐藏 div?

    我正在使用 Blazor 并且想在按下导航栏切换器图标时隐藏侧边栏 列表项崩溃了 但问题是 div 仍然存在 div class page div class sidebar div class nav top row pl 4 navba
  • 如何检测鼠标指针位于浏览器关闭按钮上时的事件? [关闭]

    Closed 这个问题需要多问focused help closed questions 目前不接受答案 换句话说 这是用于检测事件的 javascript jquery 代码当鼠标指针位于浏览器的关闭按钮 X按钮 上时 或者当鼠标指针进入
  • 在画布中的鼠标位置放大/缩小

    我正在尝试使用 p5 js 实现缩放功能 当前缩放级别以及 x 和 y 位置存储在controls view目的 默认位置或 0 0 位置位于左上角 问题是调整放大 缩小时的 x 和 y 位置值 以便无论视图的当前位置是什么 它都会停留在缩
  • html5 canvas贝塞尔曲线获取所有点

    我喜欢从贝塞尔曲线中得到一些点 我发现 在javascript中查找三次贝塞尔曲线的所有点 https stackoverflow com questions 15397596 find all the points of a cubic
  • 无法在heroku上推送node.js应用程序

    我尝试在heroku 上推送我的node js 应用程序 但是 无法检测到此应用程序的默认语言 我什至尝试过heroku buildpacks set heroku nodejs 但还是无法推动 Counting objects 31 do
  • 监听鼠标事件……除了 div 的溢出:滚动滚动条?

    关于如何监听 mousedown 的任何建议 document exceptdiv 的溢出 滚动滚动条 我不确定滚动条是什么元素is为了参考它 您可以使用以下命令自行检查目标 document on mousedown function e
  • 推荐的增长缓冲区的方法?

    假设我正在 Node js 中构造一个可变长度的字符串或一系列字节 buf write 的文档说 https nodejs org api buffer html buffer buf write string offset length

随机推荐

  • Sort 【HDU - 5884】【哈夫曼树】

    题目链接 一开始看到题的时候 竟然读成了是按照升序排序的一串数 害得我WA了两发 还以为是补0补错了 研究了一会补0发现好像没有多大问题 然后就继续了 直到再看了遍题 发现好像是没有给你拍好序的 然后AC 这道题其实哈夫曼树不难 就是补0思
  • 【信创】麒麟操作系统配置在线源及手动查找所需软件包

    获取操作系统信息 命令 nkvers 关注倒数第2行 示例中大版本 V10 小版本 SP2 CPU架构 aarch64 root localhost nkvers Kylin Linux Version Release Kylin Linu
  • ThreadLocal原理以及其安全问题

    ThreadLocal 是一个线程内共享数据的类 其原理是在线程有一个 ThreadLocalMap key是ThreadLocal对象 value是自定义的数据 所以在同一个线程中 用同一个threadlocal去get数据 能取到同样的
  • ansible 离线部署

    1 安装 python 环境 wget https mirrors bfsu edu cn anaconda archive Anaconda3 2022 10 Linux x86 64 sh sh Anaconda3 2022 10 Li
  • VUE+echart绘制地图(伪3D)

    这里以宝鸡地图为示例 其他地图只需更换地图JSON 地图JSON获取通过阿里的datav 地址 阿里云地图获取工具 和data数值即可 效果图如下 首先我们需要创建一个div来盛放地图 这里的div必须给出对应的宽和高 不然地图无法显示 t
  • JavaSE核心API

    还在更新中 没有JavaSE基础的小伙伴可以先看看这篇哦 写的非常的详细 Java语言基础 文章目录 API介绍以及文档的使用 文档注释的规范 Javadoc生成项目文档 String的介绍 重写equals方法 字符串常量池 String
  • 帮你快速理解什么是MFC(Windows环境下)

    我是荔园微风 作为一名在IT界整整25年的老兵 今天总结一下Windows环境下的MFC到底是一个什么技术 早在1998年 MFC绝对是技术界的一个热门名词 现在似乎提的人很少 但其他MFC的很多程序仍在世界上各个角落运行着 做为一名系统架
  • 静态测试 vs 动态测试

    静态测试 静态测试又可分为代码走查 Walkthrough 代码审查 Inspection 技术评审 Review 代码走查 Walkthrough 开发组内部进行的 采用讲解 讨论和模拟运行的方式进行的查找错误的活动 代码审查 Inspe
  • (二)Jupyter Notebook, numpy, matplotlib的使用

    笔记 机器学习入门专栏笔记对应jupyternotebook以及封装的各种算法个人笔记 如有错误 感谢指出 机器学习文档类资源 CSDN文库 二 Jupyter Notebook numpy matplotlib的使用 下载anaconda
  • 微信扫码登录详细操作流程(微信公众平台开发)

    在平常的业务开发中 经常会涉及到扫码登录的案例 下面我将对扫码登录流程做简要概述 1 概念 首先需要清楚的是扫码登录大体上有两种实现方式 重点 一种是基于微信公众平台的扫码登录 另一种是基于微信开放平台的扫码登录 注意这两个平台一定要区分开
  • org.mybatis.generator.exception.XMLParserException: XML Parser Error on line 12: 对实体 “useUnicode“ 的

    org mybatis generator exception XMLParserException XML Parser Error on line 12 对实体 useUnicode 的引用必须以 分隔符结尾 在使用mybatis逆向工
  • shell编程基础

    1 它必须以如下行开始 必须放在文件的第一行 bin sh 符号 用来告诉系统执行该脚本的程序 本例使用 bin sh 编辑结束并保存后 如果要执行该脚本 必须先使其可执行 chmod x filename 此后在该脚本所在目录下 输入 f
  • 华为OD机试 - 字符串变换最小字符串(Java)

    题目描述 给定一个字符串s 最多只能进行一次变换 返回变换后能得到的最小字符串 按照字典序进行比较 变换规则 交换字符串中任意两个不同位置的字符 输入描述 一串小写字母组成的字符串s 输出描述 按照要求进行变换得到的最小字符串 备注 s是都
  • Oracle环境变量配置

    情况描述 最近在配置plsql环境后 plsql总是连接不上 由于对自己的记忆力过度自信 导致这个简单的问题不能得到解决 现在记录下来作为以后的参考 一 客户端安装 官网下载orcle对应版本的客户端 然后执行安装 二 环境变量 需要配置以
  • Futter 屏幕适配框架flutter_ScreenUtil 用法

    参考 前言 各位同学大家好 大家在做app开发的时候都会遇到屏幕适配的问题 安卓里面有dp iOS里面有pt 单位给我们用来处理屏幕适配 除此之外安卓还有 autosize等框架给我们使用 iOS也对应屏幕适配方案给我们使用 那么在flut
  • 基于Python的接口自动化unittest测试框架和ddt数据驱动详解

    这篇文章主要介绍了基于Python的接口自动化unittest测试框架和ddt数据驱动详解 本文给大家介绍的非常详细 对大家的学习或工作具有一定的参考借鉴价值 需要的朋友可以参考下 目录 引言 一 unittest测试框架 二 ddt数据驱
  • 用QML实现简单音视频播放器的实践

    用QML的MediaPlayer控件配合VideoOutput对可以对音频文件和视频文件进行播放 代码如下 VideoOutput id video out anchors fill parent source mediaPlayer Me
  • 给定一个无序整数数组,找出两个数字满足他们的和等于目标数字

    给定一个整数数组numbers 从数组中找出两个数满足相加之和等于目标数target 假设每个输入只对应唯一的答案 而且不可以使用重复的元素 返回两数下标值 以数组的形式返回 原始暴力算法 这个好想 建立两个嵌套的for循环 从头到尾遍历数
  • 【JDK新特性】一篇搞懂Lambda表达式 & 函数式接口

    必看 原创声明 转载请注明作者 文章来源 给伙伴们聊一下刷题事项 Lambda表达式 概述 Lambda是JDK8的语法糖 它可以对某些匿名内部类的写法进行简化 它是函数式编程的一个重要体现 让我们不用关注什么是对象 重点关注我们对数据做了
  • 夯实基础-JavaScript异步编程

    异步编程 JavaScript中异步编程问题可以说是基础中的重点 也是比较难理解的地方 首先要弄懂的是什么叫异步 我们的代码在执行的时候是从上到下按顺序执行 一段代码执行了之后才会执行下一段代码 这种方式叫同步 synchronous 执行