用any-loader封装jQuery的XHR —— 随便写着玩系列
哎,都说没人用JQuery啦,叫你别写这个。
其实我也是好高骛远使用过npm上某个和某个很出名的XHR库,嗯,认识我的人都知道我喜欢喷JQ,以前天天喷,见面第一句,你还用JQ,赶紧丢了吧。但我也是用过了npm那些脑残玩意,才知道,艾玛,其实$.ajax还不错,于是乎,我又开始天天喷那些某某知名类库。今天到处都能看到jQuery老矣的话题,饭否(不是jQuery能饭否,是用jQuery的人能饭否)?嗯,其实我觉得jQ还是可堪一用的,prop、attr、data,以及$.fn的扩展,这些方面还是很实用的,特别是基于此结合React.js用来做Web组件使用。
嗯,是的,自从2016年折腾过了一年用Reactjs大前端化(全站前后台都用),到今天为止我始终只是把React.js作为组件库来使用。彻底的纯前端化,所要解决的难题,太多太多,有一些问题可能都超越目前的技术极限。做网站做项目,不是一个纯粹的技术理想化的事情。传统服务器端输出有着大量的技术积累,框架积累,能够迅速的解决项目需求,满足市场发展需要,能确保公司、资金、项目、市场良性的互动。对任何团队,就算用antd这些全家桶,仍然要求团队的前端成员都有一年以上组件化开发的基础硬素质要求,而且还需要准备足够多的基础Component,不管antd多香多美好,符合你项目和行业需求的组件必然是高度定制化的。
今天看似各种大全,全家桶,界面组件库,嗯,似乎做网站,手机应用会越来越简单了,但我想说的是,少年,你还是太young了,直到出现某个大机构、团队提供AI自动化代码构成为止,编程的世界只会越来越繁杂,这些UI全家桶,只是逼着大家把用户体验、用户交互又推上了一个行业新高度,程序猿攻城狮每天的工作不变,但是代码层面要求控制的粒度只会更细,用户交互的体验感,只会越来越奇葩。这世界本就不存在某个框架或者类库,横空出世就让整个行业一片死寂,就像当年ROR,从1.0版本到后来的版本,复杂度呈几何倍增长,敏捷、轻量化也都是过去的幻影,最后发现那只是一种奢望,这是必然的,你控制的粒度越细,提供更多方便直观的接口和更高自由度的编码区间,实际项目代码的繁杂程度只会相应的成倍的增长。
刚开始,你或团队,靠着所谓先进工具,得到了生产力上的短时间提升,客户一片赞美,老板对你赞赏有加,提拔到关键岗位,给你管理职权,给你更多人力,管理更多项目,你也理所当然的将先进工具进行广泛的使用和传播。可这才是埋下了你或团队后来的败笔,后来的故事,往往都是,随着使用越多,各种曾经不曾有过的,不曾预期的问题都出现了,当然的,必然的,因为你们的大量广泛使用,形成自己的需求。最后各个项目纷纷暴雷,项目成员各自甩包,等等,最后,往往牺牲的就是你,背锅的就是你,因为是你最先带头搞的,也是你积极推广的,你看果然出事了吧,我早说了不行——你的竞争对手如是说。人生就是这样了,所谓新技术,新框架的学习热情慢慢逝去,个人生活也变得丰富多彩了,有老婆孩子了,要相夫教子了,要还房贷,maybe我应该转做管理吧,慢慢转型吧,所以技术是朝阳行业,让年轻人追去吧。
哎,怎么跑题跑这么远了呢,真是,年纪大了,得话痨病了,好以下回归正题。
因为要照顾整体团队的前端技术层次问题,所以我们还是选择以jQuery的ajax作为一个切入点。
基础准备
首先容我安利一番,什么是 any-loader?什么,你不喜欢看字?好好
码云仓库:https://gitee.com/janpoem/any-loader
基本的封装
import $ from 'jquery'; import _ from 'lodash'; import Loader from './Loader'; // 这里我把源代码放到本地,因为我们的开发环境里面webpack的编译环境比较老,babel 6的版本,babel-loader 还将 node_modules 排除了,所以放到本地,进行一些修改。后面 0.0.2版本会着手解决此问题。 import defineProps from 'define-props'; // 请求封装类 export class JQueryXhrRequest { id = ''; url = ''; method = 'get'; data = {}; dataType = 'json'; cache = false; constructor(args) { if (typeof args === 'function') args = args(); if (args instanceof JQueryXhrRequest) return args; if (_.isString(args)) args = {url: args}; if (!_.isObjectLike(args)) args = {}; this.url = _.toString(args.url) || ''; this.method = _.toLower(_.trim(args.method)) || 'get'; this.data = args.data || {}; this.dataType = _.toLower(args.dataType) || ''; this.cache = false; } getConfig() { return { url : this.url, method : this.method, date : this.data, cache : this.cache, dataType: this.dataType }; } } // 响应的封装类 export class JQueryXhrResponse { request = null; error = null; readyState = 0; statusCode = 0; statusText = ''; text = ''; json = null; xhr = null; constructor(request) { this.request = request; } bindResponse(jqXHR, error) { this.xhr = jqXHR; this.readyState = jqXHR.readyState; this.statusCode = jqXHR.status; this.statusText = jqXHR.statusText; this.text = jqXHR.responseText; this.json = jqXHR.responseJSON; return this; } } // Loader 的实现 export class JQueryXhrLoader extends Loader { newInput(input) { return new JQueryXhrRequest(input); } /** * * @param {JQueryXhrRequest} input * @param {JQueryXhrResponse} output * @returns {JQueryXhrResponse} */ newOutput(input, output) { return new JQueryXhrResponse(input); } /** * * @param {JQueryXhrRequest} input * @param {JQueryXhrResponse} output * @returns {Promise<any>} */ doLoad({input, output}) { return new Promise((resolve, reject) => { $.ajax(input.getConfig()) .done((jqData, textStatus, jqXHR) => { output.bindResponse(jqXHR); resolve(); }) .fail((jqXHR, textStatus, errorThrown) => { const error = new Error('jQuery ajax load failed!'); output.bindResponse(jqXHR, error); reject(error); }); }); } }
嗯,很简单,全程无脑闭着眼睛就写出来了。可能有人会觉得,哎呀,你很啰嗦哎,又是请求封装,又是响应封装,写个Ajax请求,要这么繁琐吗?嗯,年轻时的我也是这么想的,其实随着项目越来越复杂,就是有这个需求,而且,下面会变得越来越复杂。好吧,如果没兴趣,可以出门左转,不送。
嗯,好吧好吧,我还是再次解释解释吧,首先,其实我内心始终是抱着,可以的话,随时换jQ的Ajax为别的类库——当然,前提是那个类库做的足够好,足够完善,别再搞什么脑残设计。所以,我不希望在应用层直接使用jQ的方法。
其次,经过多次实战经验,我们往往是要对请求的各个细节,比如header,比如默认的queryString,进行一些项目内的微调和项目内的全局定制。当然我可以每个项目都写一个默认的$.ajax config的模板,事实上的确有项目是这样实施的,最后还是会发现,有时候为了制造一个临时的特殊请求,废了半天劲,这改那改。所以我们要用面向对象编程,公开的,大大方方将构造XHR请求的每个细节进行透析,让团队成员都能看得懂,可以插手进行修正以及调节。
还有,关于Request,其实包含的是一套URI解析合并的类库,之前我写了一个ke-url的,但实际使用起来,还是很多问题。当然,这也不是现在的话题了,无论如何,有了这个Request的类,只要确保接口的稳定性,内部实施细节如何调节都无所谓。
接着是调用的示例:
import {JQueryXhrLoader} from './loader/JQueryXhrLoader.js'; // 假定我们放在了 loader的目录里 const loader = new JQueryXhrLoader(); loader.load({url: 'http://localhost/a.php'}).then({input, output} => { console.log(output.json); }).catch(err => { console.log(err.message); })
嗯,这调用的代码,其实可能还不如直接用 $.ajax 的优雅,无所谓,这并不是重点。
标准化的JSON响应
其实我是不推荐直接这样去使用这个 jQ Loader的,他 output 是XHR底层属性东西居多,以实际项目而言,往往接口输出数据,都会有统一的格式,假定我的项目里,规定了接口必然按照如下接口进行输出:
{ "status": true "message": "提示消息", "data": {} }
status,必然只会是true or false,message 必然是字符串,data,必然是 object 的key/value结构,来存放接口自定义数据。
我们应当基于项目的实际接口需求,封装出一个进一步的Response 类,为了偷懒,我就随便一拍脑袋决定,Request 构造时,增加一个属性,statsJSON用于识别是否为这类型的Response的请求。
接着,基于上述的代码,我们开始着手改进上面的代码:
import $ from 'jquery'; import _ from 'lodash'; import Loader from './Loader'; // 这里我把源代码放到本地,因为我们的开发环境里面webpack的编译环境比较老,babel 6的版本,babel-loader 还将 node_modules 排除了,所以放到本地,进行一些修改。后面 0.0.2版本会着手解决此问题。 import defineProps from 'define-props'; // 请求封装类 export class JQueryXhrRequest { id = ''; url = ''; method = 'get'; data = {}; dataType = 'json'; cache = false; constructor(args) { if (typeof args === 'function') args = args(); if (args instanceof JQueryXhrRequest) return args; if (_.isString(args)) args = {url: args}; if (!_.isObjectLike(args)) args = {}; this.url = _.toString(args.url) || ''; this.method = _.toLower(_.trim(args.method)) || 'get'; this.data = args.data || {}; this.dataType = _.toLower(args.dataType) || ''; this.cache = false; // 改动#1:增加statusJSON属性判定 this.statusJSON = !!args.statusJSON; // 改动#1:如果 statusJSON,则dataType必须是json if (this.statusJSON) { this.dataType = 'json'; } } getConfig() { return { url : this.url, method : this.method, date : this.data, cache : this.cache, dataType: this.dataType }; } } // 响应的封装类 export class JQueryXhrResponse { request = null; error = null; readyState = 0; statusCode = 0; statusText = ''; text = ''; json = null; xhr = null; constructor(request) { this.request = request; } bindResponse(jqXHR, error) { this.xhr = jqXHR; this.readyState = jqXHR.readyState; this.statusCode = jqXHR.status; this.statusText = jqXHR.statusText; this.text = jqXHR.responseText; this.json = jqXHR.responseJSON; return this; } } // 改动#1:这里我们增加一个 JQueryXhrStatusJSONResponse 类,不在JQueryXhrResponse 的基础上去改了,这样更优雅一点,代码阅读起来也更清晰 export class JQueryXhrStatusJSONResponse extends JQueryXhrResponse { // 改动#1:增加了三个属性与相应的JSON相对应,并给定一个默认值。 status = false; message = ''; data = {}; bindResponse(jqXHR, error) { // 继承原来的方法,并在原来的基础,将json数据进行拆解,并写入对应的属性中。 super.bindResponse(jqXHR, error); if (_.isObjectLike(this.json)) { this.status = !!this.json.status; this.message = _.toString(this.json.message); this.data = _.merge(this.data, this.json.data); } return this; } } // Loader 的实现 export class JQueryXhrLoader extends Loader { newInput(input) { return new JQueryXhrRequest(input); } /** * * @param {JQueryXhrRequest} input * @param {JQueryXhrResponse} output * @returns {JQueryXhrResponse} */ newOutput(input, output) { // 改动#1:增加识别,如果 output 已经是JQueryXhrResponse实例,直接使用该实例 if (output instanceof JQueryXhrResponse) { return output; } // 改动#1:如果request.statusJSON为真,则构造一个 JQueryXhrStatusJSONResponse 实例 if (input.statusJSON) { return new JQueryXhrStatusJSONResponse(input); } return new JQueryXhrResponse(input); } /** * * @param {JQueryXhrRequest} input * @param {JQueryXhrResponse} output * @returns {Promise<any>} */ doLoad({input, output}) { return new Promise((resolve, reject) => { $.ajax(input.getConfig()) .done((jqData, textStatus, jqXHR) => { output.bindResponse(jqXHR); resolve(); }) .fail((jqXHR, textStatus, errorThrown) => { const error = new Error('jQuery ajax load failed!'); output.bindResponse(jqXHR, error); // 改动#1:因为这种数据结构的特殊性,我们希望这类型的请求,不要抛出错误,如果服务器出错,就当默认返回 status = false 的状态即可。 if (input.statusJSON) { resolve(); } else { reject(error); } }); }); } }
改动并不大,也不复杂,看起来简单易懂。
调用上:
import {JQueryXhrLoader} from './loader/JQueryXhrLoader.js'; // 假定我们放在了 loader的目录里 const loader = new JQueryXhrLoader(); loader.load({ url: 'http://localhost/a.php', statusJSON: true // 改动#1:声明这个请求为 statusJSON 格式 }).then({input, output} => { console.log(output.status); // 改动#1:这里我们输出这个 status 看看 }).catch(err => { console.log(err.message); })
嗯,虽然我说过,不想修改应用层的调用代码,但这样程度的修改,无论谁都是可以接受的。而且事实上真的不想改应用层的代码,我还有很多办法可以实施,这里就不啰嗦了。起码在调用声明上,增加一个属性,使代码清晰明确的看得出,这是申请 statusJSON的请求,这个调用的相应结果和程序执行,是可预期的。
并发限制
接下来,我们还贼心不死,某种程度上,无论如何我们希望能对并发请求做出一些限制,哪怕是最最简单的,排队等待,策略是,我们可以给Ajax进行分组,程序员在实例化Loader的时候自行指定分组名,同组内的所有Loader,都默认遵守该组的并发限制,当一个请求未返回时,后续新增的请求全部挂起不执行,存入队列,等待第一个请求响应结束,然后再依次加载挂起的请求(也必须one by one的进行,不能呼啦超一下子朝服务器端推送一大票请求出去)。
之前用React.js做纯前端时,中后期这个需求成为一个日益严峻的问题,因为一个数据,往往关联多个其他相关的碎片数据,纯前端化,意味着无法靠服务器端去做数据拼凑,哪怕服务器端只是读缓存,获取这些碎片数据,请求量还是会很多,而且当大量用户同时执行类似操作时,会瞬间造成服务器的压力,那么先发起请求的人,可能会迅速得到他们想要的结果,而越靠后的用户,因为服务器资源用于应答前面的用户的请求,导致越后面的用户,响应速度越慢。嗯,这不管是Java和PHP,还是C#,经过HTTP协议的数据传输,就是那么的不堪一击。
所以,既然那么贼心不死,我们就随便写个分组玩玩?
import $ from 'jquery'; import _ from 'lodash'; import Loader from './Loader'; // 这里我把源代码放到本地,因为我们的开发环境里面webpack的编译环境比较老,babel 6的版本,babel-loader 还将 node_modules 排除了,所以放到本地,进行一些修改。后面 0.0.2版本会着手解决此问题。 import defineProps from 'define-props'; // 请求封装类 export class JQueryXhrRequest { id = ''; url = ''; method = 'get'; data = {}; dataType = 'json'; cache = false; constructor(args) { if (typeof args === 'function') args = args(); if (args instanceof JQueryXhrRequest) return args; if (_.isString(args)) args = {url: args}; if (!_.isObjectLike(args)) args = {}; this.url = _.toString(args.url) || ''; this.method = _.toLower(_.trim(args.method)) || 'get'; this.data = args.data || {}; this.dataType = _.toLower(args.dataType) || ''; this.cache = false; // 改动#1:增加statusJSON属性判定 this.statusJSON = !!args.statusJSON; // 改动#1:如果 statusJSON,则dataType必须是json if (this.statusJSON) { this.dataType = 'json'; } } getConfig() { return { url : this.url, method : this.method, date : this.data, cache : this.cache, dataType: this.dataType }; } } // 响应的封装类 export class JQueryXhrResponse { request = null; error = null; readyState = 0; statusCode = 0; statusText = ''; text = ''; json = null; xhr = null; constructor(request) { this.request = request; } bindResponse(jqXHR, error) { this.xhr = jqXHR; this.readyState = jqXHR.readyState; this.statusCode = jqXHR.status; this.statusText = jqXHR.statusText; this.text = jqXHR.responseText; this.json = jqXHR.responseJSON; return this; } } // 改动#1:这里我们增加一个 JQueryXhrStatusJSONResponse 类,不在JQueryXhrResponse 的基础上去改了,这样更优雅一点,代码阅读起来也更清晰 export class JQueryXhrStatusJSONResponse extends JQueryXhrResponse { // 改动#1:增加了三个属性与相应的JSON相对应,并给定一个默认值。 status = false; message = ''; data = {}; bindResponse(jqXHR, error) { // 继承原来的方法,并在原来的基础,将json数据进行拆解,并写入对应的属性中。 super.bindResponse(jqXHR, error); if (_.isObjectLike(this.json)) { this.status = !!this.json.status; this.message = _.toString(this.json.message); this.data = _.merge(this.data, this.json.data); } return this; } } // Loader 的实现 export class JQueryXhrLoader extends Loader { newInput(input) { return new JQueryXhrRequest(input); } /** * * @param {JQueryXhrRequest} input * @param {JQueryXhrResponse} output * @returns {JQueryXhrResponse} */ newOutput(input, output) { // 改动#1:增加识别,如果 output 已经是JQueryXhrResponse实例,直接使用该实例 if (output instanceof JQueryXhrResponse) { return output; } // 改动#1:如果request.statusJSON为真,则构造一个 JQueryXhrStatusJSONResponse 实例 if (input.statusJSON) { return new JQueryXhrStatusJSONResponse(input); } return new JQueryXhrResponse(input); } /** * * @param {JQueryXhrRequest} input * @param {JQueryXhrResponse} output * @returns {Promise<any>} */ doLoad({input, output}) { return new Promise((resolve, reject) => { $.ajax(input.getConfig()) .done((jqData, textStatus, jqXHR) => { output.bindResponse(jqXHR); resolve(); }) .fail((jqXHR, textStatus, errorThrown) => { const error = new Error('jQuery ajax load failed!'); output.bindResponse(jqXHR, error); // 改动#1:因为这种数据结构的特殊性,我们希望这类型的请求,不要抛出错误,如果服务器出错,就当默认返回 status = false 的状态即可。 if (input.statusJSON) { resolve(); } else { reject(error); } }); }); } } // 改动#2:增加一个全局变量,存储所有的分组实例。 const JQueryXhrGroups = {}; // 改动#2:增加一个分组的类,容我偷懒,这个类还有一些值得优化的空间,但既然我们说了是随便写着玩系列,所以就这样吧 class JQueryXhrGroup { name = ''; // 分组名 loading = null; // 正在读取的stream.id waiting = []; // 挂起中的stream constructor(name) { name = _.trim(name); if (typeof JQueryXhrGroups[name] === 'undefined') { // 禁止这个实例的name变动 defineProps(this, { name: name }); JQueryXhrGroups[name] = this; } return JQueryXhrGroups[name]; } // 启动接口,这个实际上是取代Loader的 doLoad 实际执行方法,但我们不选择在 Loader 里面动手,写在外部也是很优雅的嘛 start(loader, stream) { const me = this; return new Promise(function (resolve, reject) { if (me.loading !== null) { me.waiting.push({ stream : stream, promise: new Promise((res, rej) => { res({resolve, reject}); }) }); } else { me.loading = stream.id; resolve(stream); } }); } // 结束一个stream.id,用于清空正在读取的ID,并且执行 下一个的等待中的任务。 done(id) { if (this.loading === id) { this.loading = null; } this.next(); return this; } // 执行下一个任务的实际实现,逻辑也很简单,从waiting队列里面将最前面的挤出来,然后找回挂起的Promise ,然后执行他,so easy next() { if (this.loading === null && this.waiting.length > 0) { const item = this.waiting.shift(); const {stream, promise} = item; promise.then(({resolve, reject}) => { resolve(stream); }) } } } // 改动#2:再次,我们再增加一个JQueryGroupedXhrLoader类,我不想改上面已经实现过的类,没工夫写那么多if else export class JQueryGroupedXhrLoader extends JQueryXhrLoader { // 改动#2:重载构造函数,默认将分组名作为第一个参数 constructor(groupName, args) { super(args); this.group = new JQueryXhrGroup(groupName); } /** * @param {{input: JQueryXhrRequest, output: JQueryXhrResponse, id: string}} loadStream * @returns {Promise<any>} */ // 改动#2:重载doLoad的实现,用JQueryXhrGroup.start方法来接管 doLoad 的实现 doLoad(loadStream) { // 改动#2:首先,我们给每个stream都基于分组名生成一个id if (typeof loadStream.id === 'undefined') { loadStream.id = _.uniqueId('jq_group_' + this.group.name); } return new Promise((resolve, reject) => { return this.group.start(this, loadStream).then(stream => { // 调用父类实现的 doLoad 方法 super.doLoad(stream).then(() => { resolve(stream); // 请求成功 }).catch(err => { reject(err); // 请求失败,还是要照例抛出错误的,但因为在父类已经进行了statusJSON的判定,所以只要请求为 statusJSON 那么不会走到这里来。 }).finally(() => { this.group.done(loadStream.id); // 最终,无论这个 加载是失败还是成功,我们都要对分组执行一次完成任务,以触发分组去执行下一个任务。 }); }) }) } }
so far so good,仅仅增加了两个类,按照我们事先设想的,稍微的挪移了一下,大功告成。那么以下是调用的代码:
import {JQueryXhrLoader, JQueryGroupedXhrLoader} from './loader/JQueryXhrLoader'; // 指定分组为Test const loader = new JQueryGroupedXhrLoader('Test'); // 第一个请求 loader.load({ url : 'http://localhost/a.php?temp=3', statusJSON: true }).then(({input, output}) => { console.log('a.php', output.data); // 第一个请求会执行到这里。 console.log((new Date()).valueOf()); // 输出一下客户端到达时间 }).catch(err => { // console.log(err.message); }); // 第二个请求,我们请求一个不存在的地址,并且去掉statusJSON的请求说明 loader.load({ url : 'http://localhost/c.php', // statusJSON: true }).then(({input, output}) => { console.log('c.php', output.data); }).catch(err => { console.log(err.message); // 第二个请求会执行到这里。 console.log((new Date()).valueOf()); // 输出一下客户端到达时间 }); // 第三个请求 loader.load({ url : 'http://localhost/a.php?temp=1', statusJSON: true }).then(({input, output}) => { console.log('a.php', output.data); // 第三个请求会执行到这里。 console.log((new Date()).valueOf()); // 输出一下客户端到达时间 }).catch(err => { console.log(err.message); });
ok,到此为止,另外附上a.php的源代码:
<?php header('Access-Control-Allow-Origin: *'); // webpack 和 php不是一个host $data = [ 'status' => true, 'message' => 'hello world!', 'data' => [ 'date' => date('Y-m-d H:i:s'), // 输出一下服务器时间 'ms' => microtime(true), // 在输出一下毫秒,方便观察时间差 ], ]; echo json_encode($data);
ok,上述请求客户端调试会看到如下截图:
好,搞完收工。
后记
今天话痨病发作了,就唠多两句吧。
之所以选择jQuery,其实是看中jQuery的人尽皆知。人尽皆知?咿,要知道什么呢?我有什么是不知道的?hum....
到目前为止,any-loader 0.0.1 版本还不算可以直接用于实际使用,还要编译输出一下。所以晚上为了折腾上述这些代码,倒是折腾了我半天,最后一口气将前端项目的大环境的babel版本升级到最新版本了(从babel-core 6跳跃到@babel/core 7)。说是折腾,其实我也是密谋很久了,新版babel编译速度要比旧版快多了,这次好了,彻底升到新版本,整个前端项目里面都可以直接使用async/await了。
忘记说了,去年某个时间点(大概就是去年现在这个时间点),我忽然心血来潮(也是因为招了比较新手向的前端工程师),决定将公司所有的前端项目融合在一个大的项目环境下,集中使用一套webpack配置,每个项目有自己的项目入口,有自己的项目版本配置信息,项目的JS和CSS以对应的版本配置的版本号进行文件命名(以做到发生重大异常和错误时,可以随时切换回旧版本),大前端项目也整合CDN同步的指令(npm指令),所以项目只要 release (webpack打包),直接一个指令同步到CDN,以及全局的项目版本声明文件(不要问我当初为何想设计这样一个系统,因为新人都很气人也很任性),最后release也push到代码仓库,所以就算CDN爆了,随时再执行一次同步指令,所有版本立刻恢复。
嗯,其实这个系统设计,只是我所有野心的一个起点,我的野心当然是彻底的用React.js做纯前端网站响应式兼容应用,但这需要很多组件储备。所以这一年,随着市场和需求的发展,这个项目里的React组件也越来越丰富。而且因为在一个统一的编译环境下,今天我为A项目写的组件,其他项目即可立刻获益,真是非常的爽快(这一年下来,我也是佩服自己当初的心血来潮)。组件即可随项目一起合并打包,也可以独立带版本号输出独立的js和样式文件,以作为插件库使用。时至今日,所积累的组件库也算是基本满足行业内的需求了。
从某个时间点开始(大概2015年下半年吧),做前端,不管你是专精CSS方向,还是JS方向,已经注定走上了一条不归路。注定了,必须以折腾为本命,而且不管用任何开发语言,都没有Web前端那么折腾,因为npm库的发展繁殖速度实在太快了,版本号迭代的速度已经到了令人发指的地步——这不,才将React 16.6.1整合到基础库,全部组件测试一遍,人家又出16.6.3了。前端之折腾,简直是要人命的,从当初的gulp、webpack、babel初代,乃至到今天的@babel,从最初的安哥拉JS(到今天我都鄙视这货),到今天的Vuejs和Reactjs双驾马车并驾齐驱。现在的技术潮流,尤其是前端领域,已经逼着你必须时刻怀抱着,随时干死自己,彻底推翻掉昨天的你,熬一个通宵折腾,将会是一个全新的你。
来吧,少年,为了那个全新的自我,开始折腾吧!
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
GO 互斥锁实现原理剖析
1. 前言 互斥锁是并发程序中对共享资源进行访问控制的主要手段,对此Go语言提供了非常简单易用的Mutex,Mutex为一结构体类型,对外暴露两个方法Lock()和Unlock()分别用于加锁和解锁。 Mutex使用起来非常方便,但其内部实现却复杂得多,这包括Mutex的几种状态。另外,我们也想探究一下Mutex重复解锁引起panic的原因。 按照惯例,本节内容从源码入手,提取出实现原理,又不会过分纠结于实现细节。 2. Mutex数据结构 2.1 Mutex结构体 源码包src/sync/mutex.go:Mutex定义了互斥锁的数据结构: type Mutex struct { state int32 sema uint32 } Mutex.state表示互斥锁的状态,比如是否被锁定等。 Mutex.sema表示信号量,协程阻塞等待该信号量,解锁的协程释放信号量从而唤醒等待信号量的协程。 我们看到Mutex.state是32位的整型变量,内部实现时把该变量分成四份,用于记录Mutex的四种状态。 下图展示Mutex的内存布局: Locked: 表示该Mutex是否已被锁定,0:没有...
- 下一篇
设计一个可拔插的 IOC 容器
前言 磨了许久,借助最近的一次通宵上线 cicada 终于更新了 v2.0.0 版本。 之所以大的版本号变为 2,确实是向下不兼容了;主要表现为: 修复了几个反馈的 bug。 灵活的路由方式。 可拔插的 IOC 容器选择。 其中重点是后面两个。 新的路由方式 先来看第一个:路由方式的更新。 在之前的版本想要写一个接口必须的实现一个 WorkAction;而且最麻烦的是一个实现类只能做一个接口。 因此也有朋友给我提过这个 issue。 于是改进后的使用方式如下: 是否有点似曾相识的感觉。 如上图所示,不需要实现某个特定的接口;只需要使用不同的注解即可。 同时也支持自定义 pojo, cicada 会在调用过程中对参数进行实例化。 拿这个 getUser 接口为例,当这样请求时这些参数就会被封装进 DemoReq 中. http://127.0.0.1:5688/cicada-example/routeAction/getUser?id=1234&name=zhangsan 同时得到响应: {"message":"hello =zhangsan"} 实现过程也挺简单,大家查看源码便...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS7编译安装Cmake3.16.3,解决mysql等软件编译问题
- Hadoop3单机部署,实现最简伪集群
- Mario游戏-低调大师作品
- CentOS8,CentOS7,CentOS6编译安装Redis5.0.7
- CentOS7,8上快速安装Gitea,搭建Git服务器
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装
- CentOS关闭SELinux安全模块
- CentOS8编译安装MySQL8.0.19
- SpringBoot2整合MyBatis,连接MySql数据库做增删改查操作
- Windows10,CentOS7,CentOS8安装MongoDB4.0.16