前端 ES6 之 Promise 实践应用与控制反转
Promise 主要是为解决程序异步处理而生的,在现在的前端应用中无处不在,已然成为前端开发中最重要的技能点之一。它不仅解决了以前回调函数地狱嵌套的痛点,更重要的是它提供了更完整、更强大的异步解决方案。
同时 Promise 也是前端面试中必不可少的考察点,考察内容可深可浅,因此熟练掌握它是每个前端开发者的必备能力。
Promise 相对于 callback 模式的优势,网上的介绍文章已经多如牛毛,本文我将不再重点赘述。本文我主要会在介绍 Promise 的基础使用上,重点介绍其典型的场景应用,以及一些重难点场景分析,主要目的是提高对 Promise 的理解及对其灵活的运用能力。
Promise 含义及基本介绍
首先 Promise 也是一个类或构造函数,是 JS 原生提供的,和我们自定义的类一样,通过对它进行实例化后,来完成预期的异步任务处理。
Promise 接受异步任务并立即执行,然后在任务完成后,将状态标注成最终结果(成功或失败)。
Promise 有三种状态:初始化时,刚开始执行主体任务,这时它的初始状态时 pending(进行中) ;等到任务执行完成,这时根据成功或失败,分别对应状态 **fulfilled(成功)**和 rejected(失败) ,这时的状态就固定不能被改变了,即 Promise 状态是不可逆的。
基本用法
Promise 就是一个类,所以使用时,我们照常 new 一个实例即可。
const myPromise = new Promise((resolve, reject) => {// 这里是 Promise 主体,执行异步任务ajax('xxx', () => { resolve('成功了'); // 或 reject('失败了')})
})
上面创建好 Promise 实例后,里面的主体会立即执行,比如,如果是发送请求,则会立即把请求发出去,如果是定时器,则会立即启动计时。至于请求什么时候返回,我们就在返回成功的地方,通过 resolve() 将状态标注为成功即可,同时 resolve(data) 可以附带着返回数据。 然后在 then() 里面进行回调处理。
const myPromise = new Promise((resolve, reject) => {// 这里是 Promise 主体,执行异步任务ajax('xxx', () => { resolve('成功了');})
})
myPromise.then((data) => { // 处理 data 数据
})
这里需要注意的是当初始化 Promise 实例时,主体代码是同步就开始执行了的,只有 then() 里面的回调处理才是异步的,因为它需要等待主体任务执行结束。技能考察时常常会通过分析执行顺序考察此处。 如下面的代码将输出 1、3、2。
const myPromise = new Promise((resolve, reject) => {// 这里是 Promise 主体,执行异步任务console.log(1);ajax('xxx', () => { resolve('成功了');})
}).then(() => {console.log(2);
})
console.log(3);// 最终输出 1、3、2
如果我们在调用 then() 之前,Promise 主体里的异步任务已经执行完了,即 Promise 的状态已经标注为成功了。那么我们调用 then 的时候,并不会错过,还是会执行。但需要记着,即使主体的异步任务早就执行完了,then() 里面的回调永远是放到微任务里面异步执行的,而不是立马执行。
比如我们在主体里面仅执行一块同步代码,从而不需要等待,下面代码 then() 将依然最后输出。因此我们也常常利用这种方式构建微任务(相对应的利用 setTimeout 构建宏任务):
const myPromise = new Promise((resolve, reject) => {// 主体只有同步代码,则 Promise 状态会立马标注为成功console.log(1);resolve();
}).then(() => {console.log(2);
})
console.log(3);
// 最终输出为 1、3、2
Promise 异常处理
- 方式一:通过 then() 的第 2 个参数
const myPromise = new Promise(...);
myPromise.then(successCallback, errorCallback);
这种方式能捕获到 promise 主体里面的异常,并执行 errorCallback。但是如果 Promise 主体里面没有异常,然后进入到 successCallback 里面发生了异常,此时将不会进入到 errorCallback。因此我们经常使用下面的方式二来处理异常。
- 方式二:通过 catch() (常用方案)
const myPromise = new Promise(...);
myPromise.then(successCallback).catch(errorCallback);
这样不管是 Promise 主体,还是 successCallback 里面的出了异常,都会进入到 errorCallback。这里需要注意,按这种链式写法才正确,如果按下面的写法将会和方式一类似,不能按预期捕获,具体原因在后面的链式调用里面说明。
const myPromise = new Promise(...);
myPromise.then(successCallback);
myPromise.catch(errorCallback);
- 方式三:try…catch
try catch 是传统的异常捕获方式,这里只能捕获同步代码的异常,并不能捕获异步异常,因此无法对 Promise 进行完整的异常捕获。
链式调用
熟悉 JQuery 的同学应该很了解链式调用,就是在调用了对象的一个方法后,此方法又返回了这个对象,从而可以继续在后面调用对象的方法。Promise 的链式调用,每次调用后,会返回一个新的 Promise 实例对象,从而可以继续 then()或者其他 API 调用,如上面的方式二异常处理中的 catch 就属于链式调用。
const myPromise = new Promise((resolve) => {resolve(1)
}).then((data) => {return data + 1;
})).then((data) => {console.log(data)
};
// 输出 2
这里需要注意的是,每次 then() 或者 catch() 后,返回的是一个新的 Promise,和上一次的 Promise 实例对象已经不是同一个引用了。而这个新的 Promise 实例对象包含了上一次 then 里面的结果,这也是为什么链式调用的 catch 才能捕获到上一次 then 里面的异常的原因。
下面的代码非链式调用,每次 then 都是针对最初的 Promise 实例最后输出为 1。
const myPromise = new Promise((resolve) => {resolve(1)
})
myPromise.then((data) => {return data + 1;
})
romise.then((data) => {console.log(data);
})
// 输出 1
常用 API
我再对一些常用 API 进行一下简单说明和介绍,Promise API 和大部分类一样,分为实例 API 或原型方法(即 new 出来的对象上的方法),和静态 API 或类方法(即直接通过类名调用,不需要 new)。注意实例 API 都是可以通过链式调用的。
实例 API(原型方法)
- then()
Promise 主体任务和在此之前的链式调用里的回调任务都成功的时候(即前面通过 resolve 标注状态后),进入本次 then() 回调。
- catch()
Promise 主体任务和在此之前的链式调用里的出现了异常,并且在此之前未被捕获的时候(即前面通过 reject 标注状态或者出现 JS 原生报错没处理的时候),进入本次 catch()回调。
- finally()
无论前面出现成功还是失败,最终都会执行这个方法(如果添加过)。比如某个任务无论成功还是失败,我们都希望能告诉用户任务已经执行结束了,就可以使用 finally()。
静态 API(类方法)
- Promise.resolve()
返回一个成功状态的 Promise 实例,一般常用于构建微任务,比如有个耗时操作,我们不希望阻塞主程序,就把它放到微任务去,如下输出 1、3、2,即 console.log(2) 将放到最后微任务去执行:
console.log(1);
Promise.resolve().then(() => {console.log(2); // 作为微任务输出 2
})
console.log(3);
- Promise.reject()
这个与 Promise.resolve 使用类似,返回一个失败状态的 Promise 实例。
- Promise.all()
此方法接收一个数组为参数(准确说是可迭代参数),数组里面每一项都是一个单独的 Promise 实例,此方法返回一个 Promise 对象。这个返回的对象含义是数组中所有 Promise 都返回了(可失败可成功),返回 Promise 对象就算完成了。适用于需要并发执行任务时,比如同时发送多个请求。
const p1 = new Promise(...);
const p2 = new Promise(...);
const p3 = new Promise(...);
const pAll = Promise.all([p1, p2, p3]);
pAll.then((list) => {// p1,p2,p3 都成功了即都 resolve 了,会进入这里;// list 按顺序为 p1,p2,p3 的 resolve 携带的返回值
}).catch(() => {// p1,p2,p3 有至少一个失败,其他成功,就会进入这里;
})
注意 Promise.all 是所有传入的值都返回状态了,才会最终进入 then 或 catch 回调。
Promise 的参数也可以如下常量,它会转换成立即完成的 Promise 对象:
Promise.all([1, 2, 3]);
// 等同于
const p1 = new Promise(resolve => resolve(1));
const p2 = new Promise(resolve => resolve(2));
const p3 = new Promise(resolve => resolve(3));
Promise.all([p1, p2, p3]);
- Promise.race()
与 Promise.all() 类似,不过区别是 Promise.race 只要传入的 Promise 对象,有一个状态变化了,就会立即结束,而不会等待其他 Promise 对象返回。所以一般用于竞速的场景。
接下来,来看看 Promise 具体的使用场景。
Promise 最佳实践介绍
Promise 的 API 不多,使用也不复杂,简单场景一看就明白,不过对于一些复杂的代码模块,不够熟悉的同学就会感觉比较绕。比如这些实际应用中的经验。
异步 Promise 化的两个关键
实际应用中,我们尽量将所有异步操作进行 Promise 的封装,方便其他地方调用。放弃以前的 callback 写法,比如我们封装了一个类 classA,里面需要有一些准备工作才能被外界使用,以前我们可能会提供 ready(callback) 方法,那么现在就可以这样 ready().then()。
另外,一般开发中,尽量将 new Promise 的操作封装在内部,而不是在业务层去实例化。
如下面代码:
// 封装
function getData(){const promise = new Promise((resolve,reject)=>{ajax(xxx, (d) => {resolve(d);})});return promise
}// 使用
getData().then((data)=>{console.log(data)
})
其实处理和封装异步任务关键就是两件事
- **定义异步任务的执行内容。**如发一个请求、设一个定时器、读取一个文件等;
- **指出异步任务结束的时机。**如请求返回时机、定时器结束的时机、文件读取完成的时机,其实就是触发回调的时机。
当通过 new Promise 初始化实例的时候,就定义了异步任务的执行内容,即 Promise 主体。然后 Promise 给我们两个函数 resolve 和 reject 来让我们明确指出任务结束的时机,也就是告诉 Promise 执行的内容和结束的时机就行了,不用像 callback 那样,需要把处理过程也嵌套写在里面,而是在原来 callback 的地方调用一下 resolve(成功)或 reject(失败)来标识任务结束了。
在实际开发中,不管业务模块或者老代码多么复杂,只需要抓住上述两点去进行改造,就能正确地将所有异步代码进行 Promise 化。 所有异步甚至同步逻辑都可以 Promise 化,只要抓住 任务内容和 任务结束时机这两点就可很清晰的来完成封装。
如何避免冗余封装?
现在很多类库已经支持返回 Promise 实例了,尽量避免在外面重复包装,所以在使用时仔细看官方说明,有的库既支持 callback 形式,也支持 Promise 形式。
下面代码为冗余封装:
function getData() {return new Promise((resolve) => {axios.get(url).then((data) => {resolve(data)})})
}
另一个案例就是,有时我们会需要构建微任务或者将同步执行的结果数据,以 Promise 的形式返回给业务,会容易写成下面的冗余写法:
function getData() {return new Promise((resolve) => {const a = 1;const b = 2;const c = a + b;resolve(c);})
}
优化写法应该如下,即用 Promise.resolve 快速构建一个 Promise 对象:
function getData() {const a = 1;const b = 2;const c = a + b;return Promise.resolve(c);
}
异常处理
前面 API 的介绍中已经有说明,尽量通过 catch() 去捕获 Promise 异常,需要说明的是,一旦被 catch 捕获过的异常,将不会再往外部传递,除非在 catch 中又触发了新的异常。
如下面代码,第一个异常被捕获后,就返回了一个新的 Promise,这个 Promise 对象没有异常,将会进入后面的 then() 逻辑:
const p = new Promise((resolve, reject) => {reject('异常啦'); // 或者通过 throw new Error() 跑出异常
}).catch((err) => {console.log('捕获异常啦'); // 进入
}).catch(() => {console.log('还有异常吗'); // 不进入
}).then(() => {console.log('成功'); // 进入
})
如果 catch 里面在处理异常时,又发生了新的异常,将会继续往外冒,这个时候我们不可能无止尽的在后面添加 catch 来捕获,所以 Promise 有一个小的缺点就是最后一个 catch 的异常没办法捕获(当然实际出现异常的可能性很低,基本不造成什么影响)。
使用 async await
实际使用中,我们一般通过 async await 来配合 Promise 使用,这样可以让代码可读性更强,彻底没有"回调"的痕迹了。
async function getData() {const data = await axios.get(url);return data;
}
// 等效于
function getData() {return axios.get(url).then((data) => {return data});
}
对 async await 很多人都会用,但要注意几个非常重要的点。
- await 同一行后面的内容对应 Promise 主体内容,即同步执行的
- await 下一行的内容对应 then()里面的内容,是异步执行的
- await 同一行后面应该跟着一个 Promise 对象,如果不是,需要转换(如果是常量会自动转换)
- async 函数的返回值还是一个 Promise 对象
比如下面写法就是不正确的:
async function getData() {// await 不认识后面的 setTimeout,不知道何时返回const data = await setTimeout(() => {return;}, 3000)console.log('3 秒到了')
}
正确写法是:
async function getData() {const data = await new Promise((resolve) => {setTimeout(() => {resolve();}, 3000)})console.log('3 秒到了')
}
Promise 高级应用
提前预加载应用
有这样一个场景:页面的数据量较大,通过缓存类将数据缓存在了本地,下一次可以直接使用缓存,在一定数据规模时,本地的缓存初始化和读取策略也会比较耗时。这个时候我们可以继续等待缓存类初始完成并读取本地数据,也可以不等待缓存类,而是直接提前去后台请求数据。两种方法最终谁先返回的时间不确定。那么为了让我们的数据第一时间准备好,让用户尽可能早地看到页面,我们可以通过 Promise 来做加载优化。
策略是页面加载后,立马调用 Promise 封装的后台请求,去后台请求数据。同时初始化缓存类并调用 Promise 封装的本地读取数据。最后在显示数据的时候,看谁先返回用谁的。
中断场景应用
实际应用中,还有这样一种场景:我们正在发送多个请求用于请求数据,等待完成后将数据插入到不同的 dom 元素中,而如果在中途 dom 元素被销毁了(比如 react 在 useEffect 中请求的数据时,组件销毁),这时就可能会报错。因此我们需要提前中断正在请求的 Promise,不让其进入到 then 中执行回调。
useEffect(() => {let dataPromise = new Promise(...);let data = await dataPromise();// TODO 接下来处理 data,此时本组件可能已经销毁了,dom 也不存在了,所以需要在下面对 Promise 进行中断return (() => {// TODO 组件销毁时,对 dataPromise 进行中断或取消})});
我们可以对生成的 Promise 对象进行再一次包装,返回一个新的 Promise 对象,而新的对象上被我们增加了 cancel 方法,用于取消。这里的原理就是在 cancel 方法里面去阻止 Promise 对象执行 then()方法。
下面构造了一个 cancelPromise 用于和原始 Promise 竞速,最终返回合并后的 Promise,外层如果调用了 cancel 方法,cancelPromise 将提前结束,整个 Promise 结束。
function getPromiseWithCancel(originPromise) {let cancel = (v) => {};let isCancel = false;const cancelPromise = new Promise(function (resolve, reject) {cancel = e => {isCancel = true;reject(e);};});const groupPromise = Promise.race([originPromise, cancelPromise]).catch(e => {if (isCancel) {// 主动取消时,不触发外层的 catchreturn new Promise(() => {});} else {return Promise.reject(e);}});return Object.assign(groupPromise, { cancel });
}// 使用如下
const originPromise = axios.get(url);
const promiseWithCancel = getPromiseWithCancel(originPromise);
promiseWithCancel.then((data) => {console.log('渲染数据', data);
});
promiseWithCancel.cancel(); // 取消 Promise,将不会再进入 then() 渲染数据
Promise 深入理解之控制反转
熟悉了 Promise 的基本运用后,我们再来深入点理解。Promise 和 callback 还有个本质区别,就是控制权反转。
callback 模式下,回调函数是由业务层传递给封装层的,封装层在任务结束时执行了回调函数。
而 Promise 模式下,业务层并没有把回调函数直接传递给封装层( Promise 对象内部),封装层在任务结束时也不知道要做什么回调,只是通过 resolve 或 reject 来通知到 业务层,从而由业务层自己在 then() 或 reject() 里面去控制自己的回调执行。
这里可能理解起来有点绕,换种等效的简单理解:我们知道函数一般是分定义 + 调用步骤的,先定义,后调用。谁调用了函数,就表示谁在控制这个函数的执行。
那么我们来看 callback 模式下,业务层将回调函数的定义传给了封装层,封装层在内部完成了回调函数的调用执行,业务层并没有调用回调函数,甚至业务层都看不到其调用代码,所以回调函数的执行控制权在封装层。
而 Promise 模式下,回调函数的调用执行是在 then() 里面完成的,是由业务层发起的,业务层不仅能看到回调函数的调用代码,也能修改,因此回调函数的控制权在业务层。
手动实现 Promise 类的思路
现在我们已经熟悉了 Promise 的详细使用方式,假设让你回到 Promise 类出现之前,那时的 ES6 还没出现,你为了淘汰 callback 的回调写法,准备自己写一个 Promise 类,你会怎么做?
其实这就是常见面试手写 Promise 题目。我们只要抓住 Promise 的一些特点和关键点就能比较顺利实现。
首先 Promise 是一个类,构造函数接收参数是一个函数,而这个函数的参数是 resolve 和 reject 两个内部函数,也就是我们需要构建 resolve 和 reject 传给它,同时让它立即执行。另外咱这个类是有三种状态及 then 和 catch 等方法。根据这些就能快速先把类框架创建好。
class MyPromise () {constructor (fun) {this.status = 'pending'; // pending、fulfilled、rejectedfun(this.resolve, this.reject); // 立即执行主体函数,参数函数可能需要 bind(this)}resolve() {} // 定义 resolve,内容待定reject() {} // 定义 reject,内容待定then() {}catch() {}
}
有了雏形之后,再根据对 Promise 的理解逐步完善即可,如 resolve 和 reject 里面我们肯定是要去修改 status 状态的; 而 then() 里面我们需要接收并保存传进来的回调等等。 完整案例可在网上搜索,重点是理解它的实现思路。
总结
今天我们对 Promise 进行了基本 API 介绍,然后重点对其实际应用进行了介绍和解析。相信通过本文的学习,可以提升你对 Promise 的理解和运用能力。
同时文中的一些实际场景举例是非常典型的应用场景,比如 async await 和手写 Promise 是很容易被考察的点。并且考察方式变化很多,万变不离其宗,抓住文中重点内容,做到举一反三不是问题。
最后可以看一个有点难度的 Promise 执行顺序分析题目:
function promise2() {return new Promise((resolve) => {console.log('promise2 start');resolve();})
}
function promise3() {return new Promise((resolve) => {console.log('promise3 start');resolve();})
}
function promise4() {return new Promise((resolve) => {console.log('promise4 start');resolve();}).then(() => {console.log('promise4 end');})
}
async function asyncFun() {console.log('async1 start');await promise2();console.log('async1 inner');await promise3();console.log('async1 end');
}
setTimeout(() => {console.log('setTimeout start');promise1();console.log('setTimeout end');
}, 0);
asyncFun();
promise4();
console.log('script end');
最后
最近找到一个VUE的文档,它将VUE的各个知识点进行了总结,整理成了《Vue 开发必须知道的36个技巧》。内容比较详实,对各个知识点的讲解也十分到位。




有需要的小伙伴,可以点击下方卡片领取,无偿分享
相关文章:
前端 ES6 之 Promise 实践应用与控制反转
Promise 主要是为解决程序异步处理而生的,在现在的前端应用中无处不在,已然成为前端开发中最重要的技能点之一。它不仅解决了以前回调函数地狱嵌套的痛点,更重要的是它提供了更完整、更强大的异步解决方案。 同时 Promise 也是前端面试中必不…...
LightGBM
目录 1.LightGBM的直方图算法(Histogram) 直方图做差加速 2.LightGBM得两大先进技术(GOSS&EFB) 2.1 单边梯度抽样算法(GOSS) 2.2 互斥特征捆绑算法(EFB) 3.LightGBM得生长策略(leaf-wise) 通过与xgboost对比,在这里列出lgb新提出的几个方面的技术 1.Ligh…...
Science:北京脑研究中心李莹实验室揭示性满足感的分子机制
短暂的社交经历(例如,性经历)可导致内部状态的长期变化并影响社会行为,如交配、攻击。例如,在成功交配射精后,许多物种迅速表现出对交配倾向的抑制有数小时、数天或更长时间,这种效应称为性满足…...
Element UI框架学习篇(三)
Element UI框架学习篇(三) 实现简单登录功能(不含记住密码) 1 准备工作 1.1 在zlz包下创建dto包,并创建userDTO类(传输对象) package com.zlz.dto;import lombok.Data;/* DTO 数据传输对象 用户表的传输对象 调用控制器传参使用 VO 控制器返回的视图对象 与页面对应 PO 数据…...
尚硅谷的尚融宝项目
先建立一个Maven springboot项目 进来先把src删掉,因为是一个父项目,我们删掉src之后,pom里配置的东西,也能给别的模块使用。 改一下springboot的版本号码 加入依赖和依赖管理: <properties><java.versi…...
12 Day:内存管理
前言:今天我们要完成我们操作系统的内存管理,以及一些数据结构和小组件的实现,在此之前大家需要了解我们前几天一些重要文件的内存地址存放在哪,以便我们更好的去编写内存管理模块 一,实现ASSERT断言 不知道大家有没有…...
linux基本功系列之lsof命令实战
文章目录前言一. lsof命令介绍二. 语法格式及常用选项三. 参考案例3.1 显示系统打开的文件3.2 查找某个文件相关的进程3.3 列出某个用户打开的文件信息3.4 列出某个程序进程所打开的文件信息3.5 查看某个进程号打开的文件3.6 列出所有的网络连接3.7 列出谁在使用某个端口3.8 恢…...
基础篇:02-SpringCloud概述
1.SpringCloud诞生 基于前面章节,我们深知微服务已成为当前开发的主流技术栈,但是如dubbo、zookeeper、nacos、rocketmq、rabbitmq、springboot、redis、es这般众多技术都只解决了一个或一类问题,微服务并没有一个统一的解决方案。开发人员或…...
【软件测试】软件测试工作上95%会遇到的问题,你遇到多少?
目录:导读前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结(尾部小惊喜)前言 1、测试负责人要进行…...
4.5.4 LinkedList
文章目录1.特点2.常用方法3.练习:LinkedList测试1.特点 链表,两端效率高,底层就是链表实现的 List接口的实现类,底层的数据结构为链表,内存空间是不连续的 元素有下标,有序允许存放重复的元素在数据量较大的情况下,查询慢&am…...
Python之FileNotFoundError: [Errno 2] No such file or directory问题处理
错误信息:FileNotFoundError: [Errno 2] No such file or directory: ../AutoFrame/temp/report.xlsx相对于当前文件夹的路径,其实就是你写的py文件所在的文件夹路径!python在对文件的操作时,需要特别注意文件地址的书写。文件的路…...
C语言中耳熟能详的printf与scanf
没有什么比时间更有说服力了,因为时间无需通知我们就可以改变一切了。---余华《活着》大家好,今天给大家分享的是C语言中的scanf与printf函数,一提起这两个函数,大家可能觉得这不就是打印和输入嘛?有什么可以说的&…...
【数据结构】复杂度讲解
目录 时间复杂度与空间复杂度:: 1.算法效率 2.时间复杂度 3.空间复杂度 4.常见时间复杂度以及复杂度OJ练习 时间复杂度与空间复杂度:: 什么是数据结构? 数据结构中是计算机存储,组织数据的方式,指相互之间存在一种或多种特定关…...
JAVA-线程池技术
目录 概念 什么是线程? 什么是线程池? 线程池出现背景 线程池原理图 JAVA提供线程池 线程池参数 如果本篇博客对您有一定的帮助,大家记得留言点赞收藏哦。 概念 什么是线程? 是操作系统能够进行运算调度的最小单位。&am…...
【C++】从0到1入门C++编程学习笔记 - 提高编程篇:STL常用算法(算术生成算法)
文章目录一、accumulate二、fill学习目标: 掌握常用的算术生成算法 注意: 算术生成算法属于小型算法,使用时包含的头文件为 #include <numeric> 算法简介: accumulate // 计算容器元素累计总和 fill // 向容器中添加元…...
【C++】static成员
💙作者:阿润菜菜 📖专栏:C 目录 概念 特性 出个题 概念 声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量; 用static修饰的成员函数,称之为静态…...
Python Scrapy 爬虫简单教程
1. Scrapy install 准备知识 pip 包管理Python 安装XpathCssWindows安装 Scrapy $>- pip install scrapy Linux安装 Scrapy $>- apt-get install python-scrapy 2. Scrapy 项目创建 在开始爬取之前,必须创建一个新的Scrapy项目。进入自定义的项目目录中&am…...
【DOCKER】容器概念基础
文章目录1.容器1.概念2.特点3.与虚拟机的对比2.docker1.概念2.命名空间3.核心概念3.命令1.镜像命令2.仓库命令1.容器 1.概念 1.不同的运行环境,底层架构是不同的,这就会导致测试环境运行好好的应用,到了生产环境就会出现bug(就像…...
第九层(16):STL终章——常用集合算法
文章目录前情回顾常用集合算法set_intersectionset_unionset_difference最后一座石碑倒下,爬塔结束一点废话🎉welcome🎉 ✒️博主介绍:一名大一的智能制造专业学生,在学习C/C的路上会越走越远,后面不定期更…...
一起学习用Verilog在FPGA上实现CNN----(六)SoftMax层设计
1 SoftMax层设计 1.1 softmax SoftMax函数的作用是输入归一化,计算各种类的概率,即计算0-9数字的概率,SoftMax层的原理图如图所示,输入和输出均为32位宽的10个分类,即32x10320 本项目softmax实现逻辑为: …...
3.3.1_1 检错编码(奇偶校验码)
从这节课开始,我们会探讨数据链路层的差错控制功能,差错控制功能的主要目标是要发现并且解决一个帧内部的位错误,我们需要使用特殊的编码技术去发现帧内部的位错误,当我们发现位错误之后,通常来说有两种解决方案。第一…...
Java - Mysql数据类型对应
Mysql数据类型java数据类型备注整型INT/INTEGERint / java.lang.Integer–BIGINTlong/java.lang.Long–––浮点型FLOATfloat/java.lang.FloatDOUBLEdouble/java.lang.Double–DECIMAL/NUMERICjava.math.BigDecimal字符串型CHARjava.lang.String固定长度字符串VARCHARjava.lang…...
C# SqlSugar:依赖注入与仓储模式实践
C# SqlSugar:依赖注入与仓储模式实践 在 C# 的应用开发中,数据库操作是必不可少的环节。为了让数据访问层更加简洁、高效且易于维护,许多开发者会选择成熟的 ORM(对象关系映射)框架,SqlSugar 就是其中备受…...
Android 之 kotlin 语言学习笔记三(Kotlin-Java 互操作)
参考官方文档:https://developer.android.google.cn/kotlin/interop?hlzh-cn 一、Java(供 Kotlin 使用) 1、不得使用硬关键字 不要使用 Kotlin 的任何硬关键字作为方法的名称 或字段。允许使用 Kotlin 的软关键字、修饰符关键字和特殊标识…...
LRU 缓存机制详解与实现(Java版) + 力扣解决
📌 LRU 缓存机制详解与实现(Java版) 一、📖 问题背景 在日常开发中,我们经常会使用 缓存(Cache) 来提升性能。但由于内存有限,缓存不可能无限增长,于是需要策略决定&am…...
Web后端基础(基础知识)
BS架构:Browser/Server,浏览器/服务器架构模式。客户端只需要浏览器,应用程序的逻辑和数据都存储在服务端。 优点:维护方便缺点:体验一般 CS架构:Client/Server,客户端/服务器架构模式。需要单独…...
绕过 Xcode?使用 Appuploader和主流工具实现 iOS 上架自动化
iOS 应用的发布流程一直是开发链路中最“苹果味”的环节:强依赖 Xcode、必须使用 macOS、各种证书和描述文件配置……对很多跨平台开发者来说,这一套流程并不友好。 特别是当你的项目主要在 Windows 或 Linux 下开发(例如 Flutter、React Na…...
WebRTC调研
WebRTC是什么,为什么,如何使用 WebRTC有什么优势 WebRTC Architecture Amazon KVS WebRTC 其它厂商WebRTC 海康门禁WebRTC 海康门禁其他界面整理 威视通WebRTC 局域网 Google浏览器 Microsoft Edge 公网 RTSP RTMP NVR ONVIF SIP SRT WebRTC协…...
多元隐函数 偏导公式
我们来推导隐函数 z z ( x , y ) z z(x, y) zz(x,y) 的偏导公式,给定一个隐函数关系: F ( x , y , z ( x , y ) ) 0 F(x, y, z(x, y)) 0 F(x,y,z(x,y))0 🧠 目标: 求 ∂ z ∂ x \frac{\partial z}{\partial x} ∂x∂z、 …...
LeetCode - 148. 排序链表
目录 题目 思路 基本情况检查 复杂度分析 执行示例 读者可能出的错误 正确的写法 题目 148. 排序链表 - 力扣(LeetCode) 思路 链表归并排序采用"分治"的策略,主要分为三个步骤: 分割:将链表从中间…...
