关于前端设计:用APICloud开发仿微信聊天App制作经验分享

作者:川哥哥 之前我始终用融云的接口做即时通信,本人也摸索了一段时间感觉融云做的挺好的。可是接口是免费的还有点小贵,就放弃融云了信心本人搭建一个后盾。 在网上查资料,去学校图书馆借书发现竟然还有《30天,App开发从0到1》这本书,官网有点牛批额。最终发现Workerman这个框架能够做即时通信,而且还容易上手。装好了后发现Workerman根底框架只能做服务推送,不能实现客户端之间互相通信。 而后持续找材料发现GatewayWorker能够实现通信,接口都封装好了文档也具体。本人把GatewayWorker环境弄好了后做了几个简略测试,发现客户端之间的确能够互相通信,还能够群聊。而后就开始设计数据库以及欠缺各种业务逻辑(业务逻辑代码只能写在Events.php外面)。 服务器也是买的最便宜的1G的内存1M的带宽凑合着用(百度云服务器46元买的),服务端还没做压力测试不晓得在线用户多了会不会蹦! 手机端通过屡次测试发现原生HTML5 WebSocket以及平台的其余网络通信模块通信体验很差,最终选用webSocket v1.1.2模块和服务器进行通信很稳固。 目前app次要的性能:单聊、群聊、好友、二维码辨认、音讯数据本地存储、音讯申请验证、意见反馈、音讯珍藏、微信登陆、截图。 音讯类型:语音、文字、表情、图片、地位。 平台用到的模块:webSocket、aMap、NVTabBar、db、fs、UIChatBox、FNScanner、wxPlus、trans、imageFilter、trans。 App开发制作零零散散花了半年的工夫,总体来讲前端功能模块设计要难些,很多性能比拟麻烦。后端比较简单最次要就是要把数据库表设计好,App图标和名字都是长期想的。 当初官网推出了AVM,等空了把App转成AVM多端版,目前还有局部性能未欠缺,必定还有很多bug,大家能够下载而后用微信登陆进行测试,欢送大家批评指正图片。 次要性能界面截图:

April 16, 2021 · 1 min · jiezi

javascript-设计模式修言小册干货长文建议收藏

前言最近拜读了一下修言大神的JavaScript 设计模式核⼼原理与应⽤实践, 对于现阶段的我,可以说受益匪浅,自己也学着总结下,分享下干货,力求共同进步! 在软件工程中,设计模式(design pattern)是对软件设计中普遍存在(反复出现)的各种问题,所提出的解决方案。 ——维基百科先提炼下,文章缺少小册前两章,概括来说: 技术寒冬,前端真的很缺人,经得起推敲的人太少;前端技术面很大,想把每一面都涵盖到很难,不建议大家死磕;追求极致设计,讲究道术结合;掌握核心科技,以‘不变’应万变;前端工程师,首先是软件工程师;这里强调一下以不变应万变的中不变的是什么,因为这关系到你的核心竞争力是什么在哪里。所谓‘不变的东西’说的驾驭技术的能力,具体来说分以下三个层次: 能用健壮的代码去解决具体问题;能用抽象的思维去应对复杂的系统;能用工程化的思想去规划更大规模的业务;这三种能力在你的成长过程中是层层递进的关系,而后两种能力可以说是对架构师的要求。能做到第一点,并且把它做到扎实、做到娴熟的人,已经堪称同辈楷模很多人缺乏的并不是这种高瞻远瞩的激情,而是我们前面提到的“不变能力”中最基本的那一点——用健壮的代码去解决具体的问题的能力。这个能力在软件工程领域所对标的经典知识体系,恰恰就是设计模式。所以说,想做靠谱开发,先掌握设计模式。 小册的知识体系与格局,用思维导图展示如下: 下面涉及到的是小册中细讲的设计模式; 目录:工厂模式单例模式原型模式修饰器模式适配器模式代理模式策略模式状态模式观察者模式迭代器模式工厂模式定义: 工厂模式其实就是将创建的对象的过程单独封装; 简单工厂模式结合定义我们来看一段需求,公司需要编写一个员工信息录入系统,当系统里面只创建自己的时候我们可以: const lilei = { name = 'lilei', age: 18, career: 'coder'}当然员工肯定不会是一个,并且会不断加入,所以使用构造函数写成: function User(name, age, career) { this.name = name; this.age = age; this.career = career;}const lilei = new User('lilei', 18, 'coder')const lilei = new User('hanmeimei', 20, 'product manager')// ...上面的代码其实就是构造器,关于构造器模式后面会有具体介绍,我们采用ES5的构造函数来实现,ES6的class其本质还是函数,class只不过是语法糖,构造函数,才是它的这面目。 需求继续增加,career字段能携带的信息有限,无法完整诠释人员职能,要给每个工种的用户添加上一个个性字段,来描述相应的职能。 function Coder(name, age){ this.name = name; this.age = age; this.career = 'coder'; this.work = ['敲代码', '摸鱼', '写bug'];}function ProductManager(name, age) { this.name = name; this.age = age; this.career = 'product manager'; this.work = ['订会议室', '写PRD', '催更']}function Factory(name, age, career) { switch(career) { case 'coder': return new Coder(name, age); break; case 'product manager': return new ProductManager(name, age); break; ... }}现在看至少我们不用操心构造函数的分配问题了,那么问题来了,大家都看到了省略号了吧,这就意味着每多一个工种就要手动添加一个类上去,假如有几十个工种,那么就会有几十个类?相对来说,我们还是需要不停的声明新的构造函数。 ...

November 5, 2019 · 3 min · jiezi

实践实现纯前端下的音频剪辑处理

前言最近在做一个项目,需要对webRTC录制的音频进行处理,包括音频的裁剪、多音频合并,甚至要将某个音频的某一部分替换成另一个音频。 原本笔者打算将这件工作交给服务端去完成,但考虑,其实无论是前端还是后台,所做的工作是差不多的,而且交给服务端还需要再额外走一个上传、下载音频的流程,这不仅增添了服务端的压力,而且还有网络流量的开销,于是萌生出一个想法:为什么音频处理这件事不能让前端来做呢? 于是在笔者的半摸索半实践下,产生出了这篇文章。废话少说,先上仓库地址,这是一个开箱即用的前端音频剪辑sdk(点进去了不如就star一下吧) ffmpegffmpeg是实现前端音频处理的非常核心的模块,当然,不仅是前端,ffmpge作为一个提供了录制、转换以及流化音视频的业界成熟完整解决方案,它也应用在服务端、APP应用等多种场景下。关于ffmpeg的介绍,大家自行google即可,这里不说太多。 由于ffmpeg在处理过程中需要大量的计算,直接放在前端页面上去运行是不可能的,因为我们需要单独开个web worker,让它自己在worker里面运行,而不至于阻塞页面交互。 可喜的是,万能的github上已经有开发者提供了ffmpge.js,并且提供worker版本,可以拿来直接使用。 于是我们便有了大体的思路:当获取到音频文件后,将其解码后传送给worker,让其进行计算处理,并将处理结果以事件的方式返回,这样我们就可以对音频为所欲为了:) 开启美妙之旅前的必要工作需要提前声明的是,由于笔者的项目需求,是仅需对.mp3格式进行处理的,因此下面的代码示例以及仓库地址里面所涉及的代码,也主要是针对mp3,当然,其实不管是哪种格式,思路是类似的。 创建worker创建worker的方式非常简单,直接new之,注意的是,由于同源策略的限制,要使worker正常工作,则要与父页面同源,由于这不是重点,所以略过 function createWorker(workerPath: string) { const worker = new Worker(workerPath); return worker;}postMessage转promise仔细看ffmpeg.js文档的童鞋都会发现,它在处理音频的不同阶段都会发射事件给父页面,比如stdout,start和done等等,如果直接为这些事件添加回调函数,在回调函数里去区分、处理一个又一个音频的结果,是不大好维护的。个人更倾向于将其转成promise: function pmToPromise(worker, postInfo) { return new Promise((resolve, reject) => { // 成功回调 const successHandler = function(event) { switch (event.data.type) { case "stdout": console.log("worker stdout: ", event.data.data); break; case "start": console.log("worker receive your command and start to work:)"); break; case "done": worker.removeEventListener("message", successHandler); resolve(event); break; default: break; } }; // 异常捕获 const failHandler = function(error) { worker.removeEventListener("error", failHandler); reject(error); }; worker.addEventListener("message", successHandler); worker.addEventListener("error", failHandler); postInfo && worker.postMessage(postInfo); });}通过这层转换,我们就可以将一次postMessage请求,转换成了promise的方式来处理,更易于空间上的拓展 ...

October 8, 2019 · 3 min · jiezi

JS的callapply与bind详解及其模拟实现

call, apply 与 bind1.call() 与 apply()call或apply会自动执行对应的函数 fun.call(thisNew[, arg1[, arg2[, ...]]])fun.apply(thisNew[, argsArray])thisNew: fun函数运行时指定的this值,可能的值为: 不传,或者传null,undefined, this指向window对象传递另一个函数的函数名fun2,this指向函数fun2的引用值为原始值(数字,字符串,布尔值),this会指向该原始值的自动包装对象,如 String、Number、Boolean传递一个对象,函数中的this指向这个对象用例: call: window.name = 'windowName'var obj = { name: 'objName'}function getName(p1, p2) { console.log('name === ', name) console.log('p1 === ', p1) console.log('p2 === ', p2)}getName('str1', 'str2')getName.call(obj, 'str1', 'str2')apply: Math.max.apply(null, [2, 3, 1, 4])2.bind()bind()方法会创建一个新函数,称为绑定函数。bind是ES5新增的一个方法,不会执行对应的函数,而是返回对绑定函数的引用。 fun.bind(thisNew[, arg1[, arg2[, ...]]]);用例: var $ = document.querySelectorAll.bind(document)3.三者异同相同: 改变函数体内 this 的指向不同: call、apply的区别:接受参数的方式不一样bind不立即执行。而apply、call 立即执行4.模拟实现call: Function.prototype.customCall = function () { if (typeof this !== 'function') { throw new TypeError('error!') } let context = arguments[0] || window context.fn = this let args = [...arguments].slice(1) let result = context.fn(...args) delete context.fn return result}apply: ...

August 28, 2019 · 1 min · jiezi

UI2CODE系列文章如何批量制造高质量样本

在 UI2CODE 项目中,我们大量使用了深度学习方法来做一些物体检测。而深度学习模型的训练,避免不了需要大量的样本,因此如何制造大量样本,来满足模型训练需要是我们必须要解决的一个问题。在这篇文章中,我们将介绍我们如何利用工具,批量泛化出大量样本,为模型训练提供数据保障。 1.样本现状我们的模型要解决的问题是在一个设计稿图片上识别出基础控件等信息,包括位置和类别。而它所需要的样本,主要存在两个问题: 数据量少:一个APP的页面是有限的,特别是针对单个APP做优化适配的时候,页面的数量是相对较少的,可能在几十到上百个。而模型的对样本数量的需求是巨大的,特别像较为复杂的模型,对数据量的要求至少是万级别的,单靠真实样本,是远远达不到要求的。标注成本高:物体检测的样本标注,不仅需要标注物体的类别,更需要标注出物体的具体位置,而一个样本上会存在多个物体标注。因此,这类样本打标成本非常大。2.样本获取途径获取样本,主要有几种途径。 对于真实样本,这类质量是最高的,要想训练出效果很好的模型,这类样本基本是必不可少的,但是由于这类样本数量少,成本高,因此还需要其他方法来补充样本量。 对于数据增广,这种方法简单快速,但是效果也有限,特别是对于我们 UI2CODE 里识别控件这个任务来说,做旋转等操作基本是无效的。 因此,我们需要利用样本Mock,来扩充我们的数据量,尽量模拟出质量又多,量又大的样本。这里我们选择的是利用Weex页面来进行样本的Mock泛化。(当然还有一些其它方法,比如利用 Android 的特性,在运行时的APP页面,抓取页面数据,经过过滤和清洗,得到带标注的样本,这里不做展开) 3.WEEX页面样本泛化在这里,我们介绍如何利用 Weex 页面,来批量泛化样本,并且得到样本标注的方法。 前端页面特点之所以选择使用前端页面来生成样本,是因为前端页面更多的是做一些数据展示,并且其拥有完整的 DOM 树,只要我们拿着DOM树就可以解析出里面的各个元素。 对于节点内容,只要我们改变元素内容即可。这样我们就可以由一个前端页面很方便地泛化出不同文字、不同图片的多个样本。 当然,我们的闲鱼APP上有大量的Weex活动页,这也是我们选择做Weex页面泛化的原因之一。 泛化思路我们需要的基础控件的分类有“文本”、“图片”、“Shape”这三类,对于一个页面来说,我们的文本和图片内容基本都是可替换的,因此我们解析出所有节点以后,对里面的文本和图片进行替换,再进行渲染就可以得到新的样本。 利用 Puppeteer 实现泛化要想得到Weex页面,需要有一个渲染容器,并且我们可以很方便地修改其内容。这里,我们选择了Google的Puppeteer,它是Google推出的可以运行 Chrome Headless 环境以及对其进行操控的js接口套装。通过它,我们可以模拟一个Chrome运行环境,并且进行操控。官方简介在这里. 首先启动一个不带界面的浏览器: const browser = await puppeteer.launch({ headless: true});启动一个页面,然后打开一个网站: const page = await browser.newPage();await page.goto(nowUrls, {waitUntil: ['load','domcontentloaded','networkidle0']});模拟IPhone6环境: await page.emulate({ 'name': 'iPhone 6', 'userAgent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1', 'viewport': { 'width': 750, 'height': 1334, 'deviceScaleFactor': 1, 'isMobile': true, 'hasTouch': true, 'isLandscape': false }});搜索所需控件: ...

August 8, 2019 · 2 min · jiezi

JavaScript-设计模式四适配者模式

适配器模式:将一个类(对象)的接口(方法或属性)转化成客户希望的另外一个接口(方法或属性),使得原本由于接口不兼容而不能一起工作的那些类(对象)可以正常协作。简单理解就是为兼容而生的 “转换器”。对于强迫症患者,那么多形状各异的接口,对应着不同数据线。如果可以有一个转接口实现集合兼容,岂不美哉。没错,这样一个适配器,你值得拥有。(各大厂商加把劲。。。) 生活小栗子: 港式插头转换器笔记本电源适配器Type-C 转接口模式特点新包装对象(适配器对象)实现对原对象接口的访问(接口名可不同)适配器对象不对请求数据做预处理,直接传入原对象接口处理适配器对象对外接口名统一,外部调用者可统一接口调用多对象方法模式实现实现方式:在不改变原有对象接口的基础上,定义一个包装对象,新对象转接调用原有接口,使外部调用者可以正常使用。第三方SDK的应用// 适配器实现地图SDK统一渲染var googleMap = { show: function(){ console.log('开始渲染谷歌地图'); }};var baiduMap = { display: function(){ console.log('开始渲染百度地图'); }};// 外部实际调用的适配器对象var baiduMapAdapter = { show: function() { return baiduMap.display(); }};// 外部调用者var renderMap = function(map) { map.show(); // 统一接口调用};renderMap(googleMap);renderMap(baiduMapAdapter);ES6实现适配器// 使用ES6改写适配器实现地图SDK统一渲染class googleMap { show() { console.log('开始渲染谷歌地图'); }}class baiduMap { display() { console.log('开始渲染百度地图'); }}class baiduMapAdapter extends baiduMap { constructor() { super(); } show() { this.display(); }}// 外部调用者function renderMap(map) { map.show(); // 统一接口调用}renderMap(new googleMap());renderMap(new baiduMapAdapter());jQuery中的应用适配器模式非常适用于跨浏览器兼容,例如强大的 jQuery 封装了事件处理的适配器,解决跨浏览器兼容性问题,极大简化我们日常编程操作。 ...

June 26, 2019 · 1 min · jiezi

react-hooks-全面转换攻略三-全局存储解决方案

针对 react hooks 的新版本解决方案一.redux维持原方案若想要无缝使用原来的 redux,和其配套的中间件 promise,thunk,saga 等等的话可以使用 redux-react-hook github 链接 redux-react-hook 一个简单的使用例子: import {useDispatch, useMappedState} from 'redux-react-hook';export function DeleteButton({index}) { // 类似于以前 react-redux 中的 connect 函数 const mapState = useCallback( state => ({ canDelete: state.todos[index].canDelete, name: state.todos[index].name, }), [index], ); // 获取 redux 的数据 const {canDelete, name} = useMappedState(mapState); // 获取 dispatch const dispatch = useDispatch(); // button click handle const deleteTodo = useCallback( () => dispatch({ type: 'delete todo', index, }), [index], ); return ( <button disabled={!canDelete} onClick={deleteTodo}> Delete {name} </button> );}使用方法和以前一致 ...

June 5, 2019 · 3 min · jiezi

阿里云前端技术周刊第三期

作者:兆庆 校对:染陌知乎:阿里云中台前端/全栈团队专栏 Github:阿里云前端技术周刊 给我们投稿:传送门 参与交流:传送门 前端速报Electron 发布 v5.0.0。更多阿里巴巴官方发布免费商用字体:阿里巴巴普惠体。更多Chrome 74 正式发布,已经支持 private class fields。更多VS Code Remote 发布!开启远程开发新时代。更多飞冰(ICE)4 月新动态。更多编者推荐UForm 一种新的表单解决方案。相信做中后台的前端在处理复杂表单时一定遇到很多问题,面向复杂场景的高性能表单解决方案(背景篇)一文列举了一些常见痛点,如果你在开发时也有类似的痛点,不妨试试 UForm 表单解决方案。它引入 Rxjs 将表单联动集中处理,避免在 JSX 中做各种判断;使用 JSchema 进行表单描述,再配合 React Hook 可以非常方便的做表单拆分,再也不用担心写出一个上千行的表单组件! build-your-own-x 内容非常丰富,通过代码示例教你造各种各样的轮子,不推荐重复造轮子,但轮子是怎么造的还是推荐了解一下~ 脑壳疼的Webpack-tapable Tapable 暴露很多有用的钩子函数,主要服务于 Webpack 的插件机制,类似于NodeJS 中的 EventEmitter,本文介绍了各个钩子函数的实现原理,对于理解 JavaScript 异步编程很有帮助。 如何用数据驱动效能提升? | 解密蚂蚁研发效能 埋头做业务的同时,也要看看能用技术赋能点什么,这篇文章提供了一个大致的方法论,如何用技术手段提升研发效能。 工具尝鲜Shepherd是一款便于我们为一款产品做新手指引的工具库,通过一些简单的api就可以实现该功能,目前对于Vue,React,Angular,Ember框架都有支持。 autosub是一个可以自动为视频添加字幕的工具。vscode-leetcode让你可以在 vscode 里刷LeetCode~关于我们我们是阿里云智能中台体验技术团队。详情 如有兴趣加入我们,简历/沟通请至:zhaoqing.szq@alibaba-inc.com

May 26, 2019 · 1 min · jiezi

阿里云前端技术周刊第六期

作者:靖鑫校对:染陌 知乎:阿里云中台前端/全栈团队专栏Github:阿里云前端技术周刊 给我们投稿:传送门参与交流:传送门 前端速报 V8引擎7.5版本发布,最新的V8引擎带来的新特性可以让我们预知未来Node及Chrome将会拥有的能力,本次它主要带来了WebAssembly的隐式缓存能力以及令人眼前一亮的数字分隔符,例如1_091_134_908点我阅读详情TypeScript 3.5即将于5月30日发布,优化构建速度,新增Omit帮助函数点我阅读详情开发指南算法数构 in JS dsa.js是一个非常实用的工具库,它实现了常见的算法和数据结构模型,例如快排、Map、LinkList,推荐它给大家有两个用意。 如果你是在校大学生,这是非常好的教学资料,它的源码可以反复阅读,祝你的数构成绩更上一层楼。如果你已经工作,每日限于业务之中,思考下除了每天都在引用的Array,Map以外,可否引入其他数据结构来为业务进行建模,从而让代码更加清晰和健壮。链接地址:https://github.com/amejiarosa... Web站点优化的最佳实践网站 web.dev是由Google出品的面向Web站点优化的网站,它为我们提供了常见场景(加载、SEO、安全、可访问性)的优化指南,循序渐进地从背景知识再延伸到解决方案,通读全站,你会如沐春风,会对自己的站点优化产生新的idea。链接地址:https://web.dev Throttle和debounce在React中的应用throttle和denounce是我们老生常谈的API,但是在我们日常的开发场景中,是非常实用的,一个常见的例子便是输入搜索框,这篇文章为我们介绍了在React中的的Throttle和debounce应用。链接地址:https://blog.bitsrc.io/improv... 如何伪造和获取用户真实的IP目光转向服务端,我们需要对来访的请求做一些分流、限流等逻辑,如何获取用户真实的IP成为了一个有意思的话题,本文为大家梳理获取IP的常见问题,并介绍了egg.js里的处理思路,对此感兴趣的同学不容错过。链接地址:https://www.yuque.com/egg/nod... 开源项目2分钟快速实现视差滚动 视差滚动效果在现代站点应用越来越多了,我们期望能够有一个简洁的,性能良好的,并且不依赖于特殊框架的工具库来快速实现,这不,simpleParallax便可以满足我们的愿望,原生JavaScript编写,使用CSS3 Transform实现,无副作用,在你的项目中试试吧。链接地址:https://github.com/geosigno/s... 基于WebGL实现的酷炫流体渲染效果 非常酷炫的流体粒子效果,强迫症必备,做一些酷炫的活动页如果能适当应用那便再好不过。链接地址:https://github.com/PavelDoGre... 跨界碰撞JavaScript 如何开发 IoT 应用 JavaScript在开发者圈里一直有着不错的生态,甚至有句玩笑话,Javascript will rule the world,随着网络的普及和成本的降低,越来越多的「传统设备」接入互联网变得不是那么遥远,越来越多的互联网企业也投入到这个领域的研发,物联网和边缘计算应运而起。对于JavaScript而言,如果能够快速扩展到这个端,想必是一件非常有意义的事情,本文会为你介绍JavaScript开发IOT应用的基本策略,或许对你而言,仅仅是多了一些API,多了一些事件,但是对整个世界,却是溢出了一片缤纷,话不多说,跟着兴趣前去了解吧。链接地址:https://www.infoq.cn/article/...*Mrzkp 有效提升你的职场写作能力做的再多,不表达出来也无济于事,作为一名职场人士,我们避免不了大大小小的写作与汇报,了解这篇读书笔记,可以给你提供一些写作锦囊,攒攒小技巧。链接地址:https://www.yuque.com/quxiaof... 关于我们我们是阿里云智能中台体验技术团队。详情 如有兴趣加入我们,简历/沟通请至:jingxin.sjx@alibaba-inc.com

May 26, 2019 · 1 min · jiezi

阿里云前端技术周刊第四期

作者:弱冠校对:兆庆 知乎:阿里云中台前端/全栈团队专栏 Github:阿里云前端技术周刊 给我们投稿:传送门 参与交流:传送门 前端速报本周举行的微软Build 2019 大会上宣布了一系列开发者工具和服务:WSL2、Windows Terminal、量子开发、VS Online 等。更多使用 VS Code 进行远程开发 更多Babylon.js 4.0发布: (非常)强大的 WebGL 图形引擎 更多What's New In DevTools (Chrome 74) 更多Node 12.2.0 (Current) Released [更多]()编者推荐Python - 100天从新手到大师不会一两门后端语言的前端不是好开发,如果你还不知道从哪开始学习,那就来看看这个教程吧 leetcode题解正在准备面试的小伙伴对leetcode可能不陌生,想知道别人是怎么解题的吗? 一名【合格】前端工程师的自检清单前端有三宝:HTML、CSS、JavaScript,通过这篇文章,可以对自己目前的知识储备有个简单的认知 Node.js最佳实践nodejs 上手很简单,但是真正在项目中使用的时候会发现要考虑很多问题:目录结构定义、编码风格、项目环境配置、异常处理、代码质量、安全等。这篇文章会告诉你怎么做 使用CSS Grid做响应式布局6 行 css 实现响应式布局 趣前端有意思的动效 传送门 用动画的形式呈现解LeetCode题目的思路 传送门在浏览器上使用JavaScript模块 传送门React Patterns and Templates 传送门Why, How, and When to Use Semantic HTML and ARIA 传送门关于我们我们是阿里云智能中台体验技术团队。详情 如有兴趣加入我们,简历/沟通请至:qianshan.wengqs@alibaba-inc.com

May 26, 2019 · 1 min · jiezi

阿里云前端技术周刊第二期

作者:也树 校对:染陌 素材:也树、英布《阿里云前端技术周刊》由阿里云智能商业中台体验技术团队整理编写。 知乎:阿里云中台前端/全栈团队专栏 Github:阿里云前端技术周刊 给我们投稿:传送门 参与交流:传送门 前端速报本周 Node.js 12 发布第一个 Current 版本,包括 V8 引擎升级、ES 新特性支持、启动及解析速度提升,以及诊断工具的优化升级等,官方原文介绍请看 Node.js 12 值得关注的新特性。著名 CSS 专家 Rachel Andrew 4 月份在加拿大 W3C交流会议 上关于 CSS 新功能的分享,包括 CSS-Grid-2、CSS Scroll Snap、Conic Gradients 等。演示文稿传送门本周 create-react-app 发布 3.0 版本,主要变化有升级 Jest,更好地支持 hooks、Typescript 以及浏览器兼容能力增强。完整 Changelog 编者推荐[useEffect 完整指南](https://overreacted.io/zh-han...React 团队核心成员 Dan Abramov 关于 useEffect API 的详细介绍与示例,有多个语言版本可供查看。 CSS layout cookbookMDN 为前端开发者总结的常用 CSS 布局方式,可以了解 CSS 新特性为我们的页面布局方式带来的改变。 趣前端quicklink预加载视窗内的链接对应下探页的静态资源,让你的页面切换如丝般顺滑。立即体验 VisBug通过这个 Chrome 插件,能让任意一个网页变成画板,肆意挥洒你的设计灵感吧! React95能够让你梦回 windows95 的 React 组件库。 ...

May 26, 2019 · 1 min · jiezi

阿里云前端技术周刊第五期

作者:雏恬校对:染陌 知乎:阿里云中台前端/全栈团队专栏Github:阿里云前端技术周刊 给我们投稿:传送门参与交流:传送门 前端速报GitHub Package Registry 发布!你依旧是那个我们所爱的 GitHub!更多Google I/O 2019 召开,Chrome 开发者团队发布了 “Web at Google I/O 2019” 到 YouTube 上,包含了此次大会有关 Web 技术的所有视频,值得一看。更多在 Microsoft Build 2019 上 Edge 浏览器宣布了一些新功能,其中包括能收藏网页信息的 Collections、保护用户隐私的 Privacy tools、与 Chrome 同源的开发者工具等等。更多趣前端基于 HTML5 Canvas 的交互式地铁线路图使用Canvas开发交互式地铁线路图,带你深入掌握Canvas开发技能。 Flutter and Chrome OS: Better Together使用 Flutter 开发 Chrome OS 应用。 LeetCode - 001 - 两数之和(two-sum)没事儿刷刷LeetCode,让思维保持活跃。 你知道 JavaScript 有 535 种方法刷新页面吗使用 JavaScript 有多少种方式重新加载页面? 编者推荐探索 Serverless 中的前端开发模式从前端开发模式的演进、基于 Serverless 的前端开发案例以及 Serverless 开发最佳实践等方面,与大家探讨 Serverless 中的前端开发模式。 ...

May 26, 2019 · 1 min · jiezi

Js-函数式编程

前言JavaScript是一门多范式语言,即可使用OOP(面向对象),也可以使用FP(函数式),由于笔者最近在学习React相关的技术栈,想进一步深入了解其思想,所以学习了一些FP相关的知识点,本文纯属个人的读书笔记,如果有错误,望轻喷且提点。什么是函数式编程函数式编程(英语:functional programming)或称函数程序设计、泛函编程,是一种编程范式,它将计算机运算视为函数运算,并且避免使用程序状态以及易变对象。即对过程进行抽象,将数据以输入输出流的方式封装进过程内部,从而也降低系统的耦合度。为什么Js支持FPJs支持FP的一个重要原因在于,在JS中,函数是一等公民。即你可以像对其他数据类型一样对其进行操作,把他们存在数组里,当作参数传递,赋值给变量…等等。如下:const func = () => {}// 存储const a = [func]// 参数 返回值const x = (func) => { …… …… return func}x(func)这个特性在编写语言程序时带来了极大的便利,下面的知识及例子都建立在此基础上。纯函数概念纯函数是这样一种函数,即相同的输入,永远会得到相同的输出,而且没有任何可观察的副作用。 副作用包括但不限于:打印/log发送一个http请求可变数据DOM查询简单一句话, 即只要是与函数外部环境发生交互的都是副作用。 像Js中, slice就是纯函数, 而splice则不是var xs = [1,2,3,4,5];// 纯的xs.slice(0,3);//=> [1,2,3]xs.slice(0,3);//=> [1,2,3]xs.slice(0,3);//=> [1,2,3]// 不纯的xs.splice(0,3);//=> [1,2,3]xs.splice(0,3);//=> [4,5]xs.splice(0,3);//=> []例子在React生态中,使用纯函数的例子很常见,如React Redner函数,Redux的reducer,Redux-saga的声明式effects等等。 React Render 在React中,Render返回了一个JSX表达式,只要输入相同,即可以保证我们拿到同样的输出(最终结果渲染到DOM上),而内部的封装细节我们不需要关心,只要知道它是没有副作用的,这在我们开发过程中带来了极大的便利。当我们的程序出问题时(渲染出来与预期不符合),我们只要关心我们的入参是否有问题即可。class Component extends React.Component { render() { return ( <div /> ) }}Redux的reducer Redux的reducer函数要求我们每一次都要返回一个新的state, 并且在其中不能有任何副作用,只要传入参数相同,返回计算得到的下一个 state 就一定相同。没有特殊情况、没有副作用,没有 API 请求、没有变量修改,单纯执行计算。这样做可以使得我们很容易的保存了每一次state改变的情况,对于时间旅行这种需求更是天然的亲近。特别是在调试的过程中,我们可以借助插件,任意达到每一个state状态,能够轻松的捕捉到错误是在哪一个节点出现。function todoApp(state = initialState, action) { switch (action.type) { case SET_VISIBILITY_FILTER: return Object.assign({}, state, { visibilityFilter: action.filter }) case ADD_TODO: return Object.assign({}, state, { todos: [ …state.todos, { text: action.text, completed: false } ] }) default: return state }}Redux-sage的声明式effects 许多时候, 我们会写这样的函数const sendRequest = () => { return axions.post(…)}这是一个不纯的函数,因为它包含了副作用,发起了http请求,我们可以这样封装一下:const sendRequestReducer = () => { return () => { return axios.post(…) }}ok, 现在是一个纯函数了,正如Redux-saga中的effects一样:import { call } from ‘redux-saga/effects’function* fetchProducts() { const products = yield call(Api.fetch, ‘/products’) // …}实际上call不立即执行异步调用,相反,call 创建了一条描述结果的信息。那么这样做除了增加代码的复杂度,还可以给我们带来什么?参考saga的官方文档就知道了, 答案是测试:这些 声明式调用(declarative calls) 的优势是,我们可以通过简单地遍历 Generator 并在 yield 后的成功的值上面做一个 deepEqual 测试, 就能测试 Saga 中所有的逻辑。这是一个真正的好处,因为复杂的异步操作都不再是黑盒,你可以详细地测试操作逻辑,不管它有多么复杂。import { call } from ‘redux-saga/effects’import Api from ‘…‘const iterator = fetchProducts()// expects a call instructionassert.deepEqual( iterator.next().value, call(Api.fetch, ‘/products’), “fetchProducts should yield an Effect call(Api.fetch, ‘./products’)")总结纯函数有着以下的优点 可缓存性 首先,纯函数总能够根据输入来做缓存。实现缓存的一种典型方式是 memoize 技术:var memoize = function(f) { var cache = {}; return function() { var arg_str = JSON.stringify(arguments); cache[arg_str] = cache[arg_str] || f.apply(f, arguments); return cache[arg_str]; };};var squareNumber = memoize(function(x){ return xx; });squareNumber(4);//=> 16squareNumber(4); // 从缓存中读取输入值为 4 的结果//=> 16squareNumber(5);//=> 25squareNumber(5); // 从缓存中读取输入值为 5 的结果//=> 25可移植性 纯函数因为不依赖外部环境,所以非常便于移植,你可以在任何地方使用它而不需要附带着引入其他不需要的属性。 可测试性 如上面提到的Redux reducer和Redux-saga一样, 它对于测试天然亲近。 并行代码 我们可以并行运行任意纯函数。因为纯函数根本不需要访问共享的内存,而且根据其定义,纯函数也不会因副作用而进入竞争态(race condition)。柯里化概念在计算机科学中,柯里化(英语:Currying),又译为卡瑞化或加里化,是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。var add = function(x) { return function(y) { return x + y; };};var increment = add(1);var addTen = add(10);increment(2);// 3addTen(2);// 12例子在Lodash类库中,就有这么一个curry函数来帮助我们处理科里化,关于如何实现一个curry函数,推荐大家参考这篇文章var abc = function(a, b, c) { return [a, b, c];}; var curried = .curry(abc); curried(1)(2)(3);// => [1, 2, 3] curried(1, 2)(3);// => [1, 2, 3] curried(1, 2, 3);// => [1, 2, 3] // Curried with placeholders.curried(1)(, 3)(2);// => [1, 2, 3]偏函数应用偏函数本身与科里化并不相关, 但在日常的编写程序中,或许我们使用更多的是偏函数,所以在这里简单的介绍一下偏函数偏函数应用是找一个函数,固定其中的几个参数值,从而得到一个新的函数。有时候,我们会写一个专门发送http请求的函数const sendRequest = (host, fixPath, path) => { axios.post(${host}\${fixPath}\{path})}但是大多数时候, host和fixPath是固定的, 我们不想每次都写一次host和fixPath,但我们又不能写死,因为我们需要sendRequest这个函数是可以移植的,不受环境的约束,那么我们可以这样const sendRequestPart = (path) => { const host = ‘…’ const fixPath = ‘…’ return sendRequest(host, fixPath, path)}总结科里化和偏函数的主要用途是在组合中,这一小节主要介绍了他们的使用方法和行为。组合 compose组合的功能非常强大, 也是函数式编程的一个核心概念, 所谓的对过程进行封装很大程度上就是依赖于组合。那么什么是组合?var compose = function(f,g) { return function(x) { return f(g(x)); };};var toUpperCase = function(x) { return x.toUpperCase(); };var exclaim = function(x) { return x + ‘!’; };var shout = compose(exclaim, toUpperCase);shout(“send in the clowns”);//=> “SEND IN THE CLOWNS!“上面的compose就是一个最简单的组合函数, 当然组合函数并不限制于传入多少个函数参数,它最后只返回一个函数,我个人更喜欢将它认为像管道一样,将数据经过不同函数的逐渐加工,最后得到我们想要的结果const testFunc = compose(func1, func2, func3, func4) testFunc(…args) 在js中, 实现compose函数比较容易const compose = (…fns) => { return (…args) => { let res = args for (let i = fns.length - 1; i > -1; i–) { res = fnsi } return res }}例子React官方推崇组合优于继承这个概念,这里选择两个比较典型的例子来看 React中的高阶组件 在React中,有许多使用高阶组件的地方,如React-router的withRouter函数,React-redux的connect函数返回的函数,// Navbar 和 Comment都是组件const NavbarWithRouter = withRouter(Navbar);const ConnectedComment = connect(commentSelector, commentActions)(Comment);而由于高阶函数的签名是Component => Component。所以我们可以很容易的将他们组合到一起,这也是官方推荐的做法// 不要这样做……const EnhancedComponent = withRouter(connect(commentSelector)(WrappedComponent))// ……你可以使用一个函数组合工具// compose(f, g, h) 和 (…args) => f(g(h(…args)))是一样的const enhance = compose( // 这些都是单独一个参数的高阶组件 withRouter, connect(commentSelector))const EnhancedComponent = enhance(WrappedComponent)Redux的compose函数 Redux的compose函数实现要比上面提到的简洁的多export default function compose(…funcs) { if (funcs.length === 0) { return arg => arg } if (funcs.length === 1) { return funcs[0] } return funcs.reduce((a, b) => (…args) => a(b(…args)))}这个实现咋看之下有点懵逼, 所以可以拆开来看一下composeFn = compose(fn1, fn2, fn3, fn4)那么reduce循环运行时, 第一次a就是fn1, b是fn2, 第二次a是(…args) => fn1(fn2(…args)), b是fn3, 第三次运行的时候则是a是(…args) => fn1(fn2(fn3(…args))), b是fn4, 最后返回了fn1(fn2(fn3(fn4(…args))))pointfree它的意思是说,函数无须提及将要操作的数据是什么样的。// 非 pointfree,因为提到了数据:wordvar snakeCase = function (word) { return word.toLowerCase().replace(/\s+/ig, ‘’);};// pointfreevar snakeCase = compose(replace(/\s+/ig, ‘’), toLowerCase);pointfree 模式能够帮助我们减少不必要的命名,让代码保持简洁和通用。对函数式代码来说,pointfree 是非常好的石蕊试验,因为它能告诉我们一个函数是否是接受输入返回输出的小函数。比如,while 循环是不能组合的。不过你也要警惕,pointfree 就像是一把双刃剑,有时候也能混淆视听。并非所有的函数式代码都是 pointfree 的,不过这没关系。可以使用它的时候就使用,不能使用的时候就用普通函数。总结有了组合, 配合上面提到的科里化和偏函数应用, 可以将程序拆成一个个小函数然后组合起来, 优点已经很明显的呈现出来,也很直观的表达出了函数式编程的封装过程的核心概念。范畴学函数式编程建立在范畴学上,很多时候讨论起来难免有点理论化,所以这里简单的介绍一下范畴。 有着以下这些组件(component)的搜集(collection)就构成了一个范畴:对象的搜集态射的搜集态射的组合identity 这个独特的态射对象的搜集 对象就是数据类型,例如 String、Boolean、Number 和 Object 等等。通常我们把数据类型视作所有可能的值的一个集合(set)。像 Boolean 就可以看作是 [true, false] 的集合,Number 可以是所有实数的一个集合。把类型当作集合对待是有好处的,因为我们可以利用集合论(set theory)处理类型。 态射的搜集 态射是标准的、普通的纯函数。 态射的组合 即上面提到的compose identity 这个独特的态射 让我们介绍一个名为 id 的实用函数。这个函数接受随便什么输入然后原封不动地返回它:var id = function(x){ return x; };functor在学习函数式编程的时候,第一次看到functor的时候一脸懵逼, 确实不理解这个东西是什么, 可以做什么,加上一堆术语,头都大了。在理解functor之前,先认识一个东西概念容器容器为函数式编程里普通的变量、对象、函数提供了一层极其强大的外衣,赋予了它们一些很惊艳的特性。var Container = function(x) { this.__value = x;}Container.of = x => new Container(x);//试试看Container.of(1);//=> Container(1)Container.of(‘abcd’);//=> Container(‘abcd’)Container.of 把东西装进容器里之后,由于这一层外壳的阻挡,普通的函数就对他们不再起作用了,所以我们需要加一个接口来让外部的函数也能作用到容器里面的值(像Array也是一个容器):Container.prototype.fmap = function(f){ return Container.of(f(this.__value))}我们可以这样使用它:Container.of(3) .fmap(x => x + 1) //=> Container(4) .fmap(x => ‘Result is ’ + x); //=> Container(‘Result is 4’)我们通过简单的代码就实现了一个链式调用,并且这也是一个functor。Functor(函子)是实现了 fmap 并遵守一些特定规则的容器类型。这样子看还是有点不好理解, 那么参考下面这句话可能会好一点:a functor is nothing more than a data structure you can map functions over with the purpose of lifting values from a container, modifying them, and then putting them back into a container. 都是些简单的单词,意会比起本人翻译会更容易理解。加上一张图: ok, 现在大概知道functor是一个什么样的东西了。作用那么functor有什么作用呢? 链式调用 首先它可以链式调用,正如上面提到的一样。 Immutable 可以看到, 我们每次都是返回了一个新的Container.of, 所以数据是Immutable的, 而Immutable的作用就不在这里赘述了。 将控制权交给Container 将控制权交给Container, 这样他就可以决定何时何地怎么去调用我们传给fmap的function,这个作用非常强大,可以为我们做空值判断、异步处理、惰性求值等一系列麻烦的事。例子上面作用的第三点可能直观上有点难以理解, 下面举三个简单的例子 Maybe Container 定义一个Maybe Container来帮我们处理空值的判断var Maybe = function(x) { this.__value = x;}Maybe.of = function(x) { return new Maybe(x);}Maybe.prototype.fmap = function(f) { return this.isNothing() ? Maybe.of(null) : Maybe.of(f(this.__value));}Maybe.prototype.isNothing = function() { return (this.__value === null || this.__value === undefined);}//试试看import _ from ’lodash’;var add = .curry(.add);Maybe.of({name: “Stark”}) .fmap(.prop(“age”)) .fmap(add(10));//=> Maybe(null)Maybe.of({name: “Stark”, age: 21}) .fmap(.prop(“age”)) .fmap(add(10));//=> Maybe(31)当然, 这里可以利用上面提到的科里化函数来简化掉一堆fmap的情况import _ from ’lodash’;var compose = _.flowRight;var add = .curry(.add);// 创造一个柯里化的 mapvar map = _.curry((f, functor) => functor.fmap(f));var doEverything = map(compose(add(10), _.property(“age”)));var functor = Maybe.of({name: “Stark”, age: 21});doEverything(functor);//=> Maybe(31)Task Container 我们可以编写一个Task Container来帮我们处理异步的情况var fs = require(‘fs’);// readFile :: String -> Task(Error, JSON)var readFile = function(filename) { return new Task(function(reject, result) { fs.readFile(filename, ‘utf-8’, function(err, data) { err ? reject(err) : result(data); }); });};readFile(“metamorphosis”).fmap(split(’\n’)).fmap(head);例子中的 reject 和 result 函数分别是失败和成功的回调。正如你看到的,我们只是简单地调用 Task 的 map 函数,就能操作将来的值,好像这个值就在那儿似的。(这看起来有点像Promise) Io Container 我们可以利用Io Container来做惰性求值import _ from ’lodash’;var compose = _.flowRight;var IO = function(f) { this._value = f;}IO.of = x => new IO( => x);IO.prototype.map = function(f) { return new IO(compose(f, this._value))};var io_document = new IO( => window.document);io_document.map(function(doc){ return doc.title });//=> IO(document.title)注意我们这里虽然感觉上返回了一个实际的值 IO(document.title),但事实上只是一个对象:{ __value: [Function] },它并没有执行,而是简单地把我们想要的操作存了起来,只有当我们在真的需要这个值得时候,IO 才会真的开始求值,functor 范畴functor 的概念来自于范畴学,并满足一些定律。 即functor 接受一个范畴的对象和态射(morphism),然后把它们映射(map)到另一个范畴里去。Js中的functorJs中也有一些实现了functor, 如map、filtermap :: (A -> B) -> Array(A) -> Array(B)filter :: (A -> Boolean) -> Array(A) -> Array(A)Monad普通functor的问题我们来写一个函数 cat,这个函数的作用和 Linux 命令行下的 cat 一样,读取一个文件,然后打出这个文件的内容import fs from ‘fs’;import _ from ’lodash’;var map = .curry((f, x) => x.map(f));var compose = .flowRight;var readFile = function(filename) { return new IO( => fs.readFileSync(filename, ‘utf-8’));};var print = function(x) { return new IO( => { console.log(x); return x; });}var cat = compose(map(print), readFile);cat(“file”)//=> IO(IO(“file的内容”))ok, 我们最后得到的是两层嵌套的IO, 要获取其中的值cat(“file”).__value().__value()问题很明显的出来了, 我们需要连续调用两次_value才能获取, 那么假如我们嵌套了更多呢, 难道每次都要调用一大堆__value吗, 那当然是不可能的。概念我们可以使用一个join函数, 来将Container里面的东西拿出来, 像这样var join = x => x.join();IO.prototype.join = function() { return this.__value ? IO.of(null) : this._value();}// 试试看var foo = IO.of(IO.of(‘123’));foo.join();似乎这样也有点麻烦, 每次都要使用一个join来剖析var doSomething = compose(join, map(f), join, map(g), join, map(h));我们可以使用一个chain函数, 来帮助我们做这些事var chain = .curry((f, functor) => functor.chain(f));IO.prototype.chain = function(f) { return this.map(f).join();}// 现在可以这样调用了var doSomething = compose(chain(f), chain(g), chain(h));// 当然,也可以这样someMonad.chain(f).chain(g).chain(h)// 写成这样是不是很熟悉呢?readFile(‘file’) .chain(x => new IO( => { console.log(x); return x; })) .chain(x => new IO( => { // 对x做一些事情,然后返回 }))ok, 事实上这就是一个Monad, 而且你也会很熟悉, 这就像一个Promise的then, 那么什么是Monad呢? Monad有一个bind方法, 就是上面讲到的chain(同一个东西不同叫法),function bind<T, U>(instance: M<T>, transform: (value: T) => M<U>): M<U> { // …}其实,Monad 的作用跟 Functor 类似,也是应用一个函数到一个上下文中的值。不同之处在于,Functor 应用的是一个接收一个普通值并且返回一个普通值的函数,而 Monad 应用的是一个接收一个普通值但是返回一个在上下文中的值的函数。上下文即一个Container。Promise是Monad需要被认为是Monad需要具备以下三个条件拥有容器, 即Maybe、IO之类。一个可以将普通类型转换为具有上下文的值的函数, 即Contanier.of拥有bind函数(即上面提到的bind, 而不是ES5的bind)那么Promise具备了什么条件?拥有容器 Promise, 即上面第一点Promise.resolve(value)将值转换为一个具有上下文的值, 即上面第二点。Promise.prototype.then(onFullfill: value => Promise) 拥有一个bind(then)函数, 接受一个函数作为参数, 该函数接受一个普通值并返回一个含有上下文的值。 即上面第三点不过Promise比Monad拥有更多的功能。如果then返回了一个正常的value, Promise会调用Promise.resolve将其转换为Promise普通的Monad只能提供在计算的时候传递一个值, 而Promise有两个不同的值 - 一个用于成功值,一个用于错误(类似于Either monad)。可以使用then方法的第二个回调或使用特殊的.catch方法捕获错误Applicative Functor提到了Functor和Monad而不提Applicative Functor就不完整了。概念Applicative Functor就是让不同 functor 可以相互应用(apply)的能力。 举一个简单的例子, 假设有两个同类型的 functor,我们想把这两者作为一个函数的两个参数传递过去来调用这个函数。// 这样是行不通的,因为 2 和 3 都藏在瓶子里。add(Container.of(2), Container.of(3));//NaN// 使用可靠的 map 函数试试var container_of_add_2 = map(add, Container.of(2));// Container(add(2))这时候我们创建了一个 Container,它内部的值是一个局部调用的(partially applied)的函数。确切点讲就是,我们想让 Container(add(2)) 中的 add(2) 应用到 Container(3) 中的 3 上来完成调用。也就是说,我们想把一个 functor 应用到另一个上。巧的是,完成这种任务的工具已经存在了,即 chain 函数。我们可以先 chain 然后再 map 那个局部调用的 add(2),就像这样:Container.of(2).chain(function(two) { return Container.of(3).map(add(two));});然而这样我们需要延迟Container.of(3)的建立, 这对我们来说是很不方便的也是没有必要的, 我们可以通过建立一个ap函数来达成我们想要的效果Container.prototype.ap = function(other_container) { return other_container.map(this.__value);}Container.of(2).map(add).ap(Container.of(3));// Container(5)注意上面的add是科里化函数, this.__value是一个纯函数。 由于这种先 map 再 ap 的操作很普遍,我们可以抽象出一个工具函数 liftA2:const liftA2 = (f, m1, m2) => m1.map(f).ap(m2)liftA2(add, Container.of(2), Container.of(3))应用正如我们上面所说, 我们可以独立创建两个Container, 那么在Task中也可以同时发起两个http请求,而不必等到第一个返回再执行第二个// Http.get :: String -> Task Error HTMLvar renderPage = curry(function(destinations, events) { / render page */ });Task.of(renderPage).ap(Http.get(’/destinations’)).ap(Http.get(’/events’))// Task("<div>some page with dest and events</div>")FunctorMonadApplicative Functor的数学规律Functor// identitymap(id) === id;// compositioncompose(map(f), map(g)) === map(compose(f, g));Monadbind(unit(x), f) ≡ f(x)bind(m, unit) ≡ mbind(bind(m, f), g) ≡ bind(m, x ⇒ bind(f(x), g))Applicative FunctorIdentity: A.of(x => x).ap(v) === vHomomorphism: A.of(f).ap(A.of(x)) === A.of(f(x))Interchange: u.ap(A.of(y)) === A.of(f => f(y)).ap(u)js 与 函数式和面向对象以下引用自文章漫谈 JS 函数式编程(一)面向对象对数据进行抽象,将行为以对象方法的方式封装到数据实体内部,从而降低系统的耦合度。而函数式编程,选择对过程进行抽象,将数据以输入输出流的方式封装进过程内部,从而也降低系统的耦合度。两者虽是截然不同,然而在系统设计的目标上可以说是殊途同归的。面向对象思想和函数式编程思想也是不矛盾的,因为一个庞大的系统,可能既要对数据进行抽象,又要对过程进行抽象,或者一个局部适合进行数据抽象,另一个局部适合进行过程抽象,这都是可能的。数据抽象不一定以对象实体为形式,同样过程抽象也不是说形式上必然是 functional 的,比如流式对象(InputStream、OutputStream)、Express 的 middleware,就带有明显的过程抽象的特征。但是在通常情况下,OOP更适合用来做数据抽象,FP更适合用来做过程抽象。当然由于Javascript本身是多范式语言, 所以可以在合适的地方使用合适的编程方式。总而言之, 两者互不排斥,是可共存的。尾递归优化由于函数式编程,如果尾递归不做优化,很容易爆栈, 这个知识点有很多文章提出来了, 这里推荐一篇文章声明式编程声明式主要表现在于只关心结果而不关心过程, 这里推荐一篇轻松易懂的文章 或者举个例子: 在JQ时代的时候, 假如我们需要渲染一个DOM, 并改变其文字颜色, 我们需要这样的步骤:找到DOM的class或者id根据class或者id找到DOM重新赋值DOM的style属性的color属性而在React中, 我们可以直接告诉JSX我们想要DOM的颜色变成红色即可。const textColor = ‘red’const comp = () => { return ( <div style={{ color: textColor }} /> )}而关于声明式和函数式, 我个人认为函数式和声明式一样, 也是属于关心结果, 但是函数式最重要的特点是“函数第一位”,即函数可以出现在任何地方。 两者其实不应该做比较。函数式编程在JS中的实践Undescore/Lodash/Ramda库 特别是Lodash, 打开node_modules基本都能看到Immutable-js 数据不可变ReactReduxES6 尾递归优化函数式编程在前端开发中的优势以下引用自知乎答案 优化绑定说白了前端和后端不一样的关键点是后端HTTP较多,前端渲染多,前端真正的刚需是数据绑定机制。后端一次对话,计算好Response发回就完成任务了,所以后端吃了二十年年MVC老本还是挺好用的。前端处理的是连续的时间轴,并非一次对话,像后端那样赋值简单传递就容易断档,导致状态不一致,带来大量额外复杂度和Bug。不管是标准FRP还是Mobx这种命令式API的TFRP,内部都是基于函数式设计的。函数式重新发明的Return和分号是要比裸命令式好得多的(前端状态可以同步,后端线程安全等等,想怎么封装就怎么封装)。封装作用接上条,大幅简化异步,IO,渲染等作用/副作用相关代码。和很多人想象的不一样,函数式很擅长处理作用,只是多一层抽象,如果应用稍微复杂一点,这点成本很快就能找回来(Redux Saga是个例子,特别是你写测试的情况下)。渲染现在大家都可以理解幂等渲染地好处了,其实函数式编程各种作用和状态也是幂等的,对于复杂应用非常有帮助。复用引用透明,无副作用,代数设计让函数式代码可以正确优雅地复用。前端不像后端业务固定,做好业务分析和DDD就可以搭个静态结构,高枕无忧了。前端的好代码一定是活的,每处都可能乱改。可组合性其实很重要。通过高阶函数来组合效果和效率都要高于继承,试着多用ramda,你就可以发现绝大部分东西都能一行写完,最后给个实参就变成一个UI,来需求改两笔就变成另外一个。总结函数式编程在JS的未来是大放异彩还是泯然众人,都不影响我们学习它的思想。本文里面有许多引用没有特别指出,但都会在底部放上链接(如介意请留言), 望见谅。参考&引用声明式编程和命令式编程有什么区别? 用 JS 代码完整解释 Monad 怎么理解“声明式渲染”? JavaScript函数式编程(二) JavaScript Functors Explained 前端开发js函数式编程真实用途体现在哪里? js 是更倾向于函数式编程了还是更倾向于面向对象?或者没有倾向?只是简单的提供了更多的语法糖? 漫谈 JS 函数式编程(一) 有哪些函数式编程在前端的实践经验? 前端使用面向对象式编程 还是 函数式编程 针对什么问题用什么方式 分别有什么具体案例? 什么是 Monad (Functional Programming)? Monads In Javascript Functor、Applicative 和 Monad JavaScript 让 Monad 更简单 函数式编程 ...

March 31, 2019 · 6 min · jiezi

Serverless 风暴来袭,前端工程师如何应对?

阿里妹导读:尽管大部分前端的工作并不涉及server,但最近半年serverless这个词汇以及其引发的热烈的讨论,深深触动了阿里巴巴高级前端技术专家伐薪。作为接触前端十余载的老开发,伐薪认为serverless可能会是接下来引起前端领域革命性变化的技术之一。今天,伐薪将为大家梳理serverless的历史发展进程以及对前端的影响,希望对前端工程师有所启发。上图是serverless 这个词最近5年在 google 的搜索趋势,可以看到最近半年已经达到巅峰。历史上前端领域的重要技术革命Ajax 的诞生先来回顾一下前端技术领域的重要历史节点,第一个节点是2005年,google的Jesse James Garrett 发表了一篇文章——《Ajax:Web应用程序的新方法》,首次发布了Ajax 这个新的词汇(准确说并不是新的技术,只是新的词汇),当时我还在读大二,虽然ajax不是什么新的技术,只是对XmlHttpRequest等技术的包装,但是这个技术被google宣传之后成为全球web开发的标杆,间接促进了富客户端应用(RIA)和单页应用(SPA)的流行,这些应用大都具备丝滑般的体验(局部刷新),并一直伴随着web 2.0的发展,ajax的深入人心,使得前端js的工作更加复杂和重要,专业分工越来越细,间接促进了专职的前端开发人员这一角色诞生,在此之前,web开发并不区分服务端和浏览器端的工作,因此ajax是前端领域的第一次重要事件。Nodejs 对前端规范化和工程化的促进接下来对前端变化最大的一个里程碑事件是2009年诞生的 nodejs(包括common js及npm)的出现和流行,它对前端领域的重要意义并不仅仅是让前端可以快速用js写server那么简单,个人认为它最大的贡献反而是commonjs、npm以及其便捷开发体验促进的前端工程化,它使得前端开始从刀耕火种的和传统软件工程格格不入的部署方式,发展为接近传统企业应用的研发模式,在此之前,前端开发在资源引用、依赖管理以及模块规范上缺乏有效的工具和标准,但是nodejs流行以后,基于commonjs的模块及npm的包部署和依赖管理成为主流(类似java的maven体系),并诞生了多种基于nodejs开发的cli工具辅助前端开发(如grunt、gulp),npm目前是全球最大的包管理仓库,并且成为前端项目的包依赖管理事实标准。而webpack的出现,又使得前端代码的部署更加简便,让前端可以以类似java jar包的形式发布应用(bundle),而不管项目中是何种类型的资源。React 的组件化及vdom理念第三个革命性事件是2013年开始出现的react,尽管web components标准在此之前早已发布,但是真正让组件化理念深入人心并且应用最广的库是react,它还至少有两点特性足以让它成为历史上最具前瞻性的前端库,第一个特性是vdom的出现,在此之前,所有的ui库,都直接与dom关联,但是react在UI创建与渲染引擎之间,增加了一个中间层——vdom(一个使用轻量级json描写UI结构的协议),除了改善了其本身的dom diff性能之外,还有一个重大意义就是UI的编写与渲染开始分离,一次编写,多端渲染的UI得以实现,这个多端包括server端、移动端、pc端以及其他需要展示UI的设备,之后的react native以及weex都是这一分层思想的受益者。除了vdom之外,react还有一个重要的理念非常超前,即UI是一个函数(类),函数输入一个state,一定返回确定的视图,在此之前,大部分框架和库,都会把UI分离成一个html片段(通常支持模板写法以渲染数据),一个为该html片段绑定事件的js,尽管这样比较好理解,但是react对UI这种抽象却反映了UI的实际本质,并且这种函数式理念,在后面可以看到,将与faas及serverless技术产生美妙的碰撞。react 的诞生对此后,甚至此前的框架和库都产生了深远的影响,包括不限于angular和vue都陆续采纳了它很多技术思想,并且成为前端开发领域目前已经趋于稳定的屈指可数的几个技术选型之一。再来总结一下,ajax使得前端的角色逐渐分离出来,nodejs促进了前端的开发模式向传统编程语言靠近(工程化),react的出现,基本结束了后端常常对前端”技术变化快“的吐槽,至此,前端的技术体系逐步成熟和标准化。serverless 理念与前端的关系那么为什么说下一次对前端技术领域有较大影响的理念是serverless呢,事实上,尽管serverless这个词汇由亚马逊提出来还不到几年,但是这个理念并不是什么爆炸性的新理念,在早期,cdn还不普及的时候,web工程师会把js资源和视图文件(可能是静态也可能是动态的)传到服务器,那个时候前端是需要关心服务器的,但是cdn及回源策略的普及,工程及搭建系统的大规模使用,使得前端可以快速把一个js或者静态文件扔到cdn节点,通过回源机制(cdn回源到一个动态服务),半动态的视图层渲染也成为可能,在这整个过程,前端开发无需关心任何服务器的知识,也不知道cdn有多少节点,如何做负载均衡,做gslb的,也不需要知道qps多少,一个cdn可以放各种业务各种开发的资源,可以说cdn是serverless理念的的先行者。回到应用部署,在前几年nodejs刚流行的时期,已有开发者意识到应用与机器的部署与运维成本对业务方会是个问题,出现了一些容器化的思想,比如cbu在15年出的naga,在这个naga容器里,业务逻辑是一个个插件,容器负责请求的路由分发,负载及稳定性管理,业务方只需要编写并上传最直接的业务代码即可,对业务方来说是实现了serverless的理念,因为naga的维护者帮你解决了部署及运维的问题。再说对前端息息相关的页面搭建系统以及bff层,无论是各种搭建系统(如斑马、积木盒子、TMS),还是基于graphQl的平台,还是通过web ide快速编写api gateway的产品——如cbu的mbox,都让业务开发只关心业务逻辑,无需关心部署运维知识,它们一定程度上体现了serverless的理念。serverless 将对前端的影响综上所述,前端早已与serverless产生了联系,但是很多人还没感知,接下来,serverless显示化地爆发将给前端带来更为深远的影响,主要体现在三个方面。前端将会重新回归到web应用工程师这一职能在最前面说了,前端是社会分工的细化,大约起源于2007年左右,在此之前是没有专门的前端开发角色的,通常称作web工程师或网站工程师,早期的网页大都是服务器渲染,使用asp、php、jsp等server page技术,js仅仅是web工程师需要掌握的小小技能之一,但是随着web 2.0及互联网、移动互联网、电子商务的发展,需要专门的人专注于编写具备很好兼容性和体验的UI,因此逐渐产生了专注于浏览器及移动端的前端工程师。但是前端技术领域逐渐趋于稳定,伴随着十几年的发展,各种开箱即用的库、垂直方案以及工程手段唾手可得,甚至目前出现了一些辅助工具可以把设计师的视觉稿生成UI代码,前端可以安心并且以非常低的成本编写UI和业务逻辑,而不用耗费大量精力在选型、造轮、还原视觉、处理兼容性、性能优化、调试和部署上,这种情况,前后端工种分离造成的协同成本反而放大了,因为在前后端角色分离的情况下,后端往往还会充当bff层的角色,比如为前端表现层封装各种api gateway,经常出现相互等待、联调协议的情况,而且bff层通常只是一些数据的加工,其他的角色经过短期的培训可以快速上手,因此前端一直在尝试接入到server端的bff层。在15年前端开始推广使用nodejs来部署应用,阿里内外也出现了不少nodejs的框架,如业界的express,在生产环境,包括给买家、商家以及内部人员使用的系统,有不少使用了nodejs,但是到今年2019年,再来回顾一下,发现这个数字并没有超出预期。造成这一现象的原因,个人认为归根到底还是因为分工太细导致的前端对服务器知识的缺乏,nodejs本身的定位是服务器技术,本质上在服务器要面对的问题与java无异,现有的前端jd招聘的人才,鲜有能在服务器上工作游刃有余的人,除非专门招的nodejs人才,server服务的长期运行会暴露很多问题,比如接口很慢,进程core,cpu飙升,内存泄漏等,另外负载均衡、扩缩容,高并低延等知识,大部分前端都是没有这些经验的。云计算的本质就是要让业务开发专注于业务逻辑,业务之下的硬件及软件设施都是按需采买,开箱即用,而serverless理念及相关技术,将使得开发人员不再需要关心应用及机器的问题,甚至连流量能不能撑住也不用关心了,它能自动扩缩容,因此,未来web开发人员的运维成本会大幅降低,前端也可介入到bff层的开发,而后端可以聚焦于数据处理、业务逻辑与算法。这一变革符合研发效能提升的背景,未来的云设施将会做得非常厚,非常专业、稳定,而前台开发人员可以快速地,最低成本地在云设施上建立业务逻辑,前端和服务器的前端(对整个请求链路来说,前端是相对的,只要离客户请求更近的角色都可以称自己为前端),分工将没那么明确,以前的浏览器端的前端,将逐步承担一部分服务器端接入层以及bff层的工作,返璞归真,回到历史上曾经的web开发工程师角色,这是对前端最大的一个变革。当然,serverless技术让前端回归到传统的web层,不代表前端不用掌握服务器知识了,掌握操作系统内核及网络编程知识仍有助于你编写高性能、高可用的业务应用。实时SSR将成为展示端UI的主要开发模式最早的web开发,其实处理UI都是以服务器渲染为主,比如perl、php等动态网页技术,但是在前端逐渐成为一个工种开始负责了绝大部分UI开发,并且技术域逐渐缩小到客户端范围之后,网页静态化以及客户端的渲染逐渐成为主流。但是这种模式对用户体验肯定是有问题的,导致了较多的白屏时间,而由于新的前端库如react和vue在vdom这层的抽象,服务器渲染的技术成本更低,因此SSR在最近几年,又逐步开始流行。但是SSR的难点恰恰是前面说的,服务器端人才的匮乏,虽然nodejs和vdom的普及提升了SSR的实施效率,但由于服务器知识的缺乏,通常只有少部分具备综合知识的前端会深入的实践这一领域。serverless技术的普及将把这个问题消除掉,借助于serverless技术,前端可以快速搭建一个ssr的场景,在服务器取数,在服务器渲染,直出html给到客户端,而不用关心这个渲染服务能否扛得住流量,会不会挂掉,这些事情云设施供应商会去解决。在前面说过,react有一个核心理念就是把UI看成函数,如果说一个页面是多个组件组成的,那一个组件是函数,我们可以把一个页面看成是多个函数的组合,不同函数的组合,组合成不同的导购场景,如果把一个函数看成一个微服务,一个场景就是微服务的聚合,这恰恰是faas的理念。通过serverless低成本地实时ssr,可以让客户体验更好,借助算法和大数据,还可以快速实现UI的千人n面,构建真正的导购大脑。基于场景的云开发(web ide)将成为主流开发模式在提到serverless技术的时候,有一个关联的领域不得不提,那就是web ide,很多互联网大型企业把一个web ide当成了云的基础设施并且大力投资,这是为何?个人认为有两个原因。第一个是因为serverless目前在业界使用以垂直场景为主,他们有一个共同点,就是代码足够标准、规范,场景较为垂直,代码复杂度不是很大,而且借助web ide可以快速地与云平台结合,做到一键发布,因此这种场景,是比较适合轻量的在线编码到部署全链路打通的。另一个原因是,目前所有的云设施解决的是业务运行问题,但是软件开发有一个非常大但是大部分人可能会忽略的痛点,那就是环境问题,相信很多人都有那种clone别人的项目但是废了九牛二虎之力都无法启动的问题,因为要装各种环境啊?另外就是和别人联调的时候,是不是因为各种环境部署缺失的问题,联调效率很低?借助容器如docker等技术,软件的运行及调试环境,可以快速地移植给别人复用,而目前基于js的代码编辑器已经非常强大,vscode editor就是基于js编写并且沉淀出一个库 Monaco Editor,因此可以说,大部分认为web ide可能在语法提示、智能感知上比不上本地ide的想法是过时了。同时web ide可以快速地与平台集成,深入定制,打通业务平台,一键部署,极大地提升研发效率。web ide还能解决跨地办公的问题,因为解决了环境准备这一老大难问题,你可以在家里,在公司,甚至在火车上,快速编写并交付代码。因此未来的paas平台,都将关联一个深度定制的web ide,需要编写业务逻辑时,一个连接跳转到web ide即可编码,完全无需关心本地环境问题,做到真正的envless。比如你要开发一个TMS模块,那么点击”新建“,跳到了web ide,代码会帮你初始化好,点击运行,会在云端启动服务器运行该组件,编写好之后,一键即可发布到TMS。对于各种faas、api gateway系统以及其他云服务,都会一样,web ide将成为企业云化的基础设施。尽管阿里云目前还未发布媲美cloud9以及coding.net的web ide,但是很欣喜地看到集团内部已经涌现出一些优秀的产品,如aone的ide,以及数据平台的app studio,其体验已经接近业界顶级水准。结语在云计算领域,serverless将会掀起一场革命,即使看起来与这一领域关联不大的前端,也会经受即ajax、nodejs以及react之后的又一重大变革,你做好应对了吗?本文作者:伐薪阅读原文本文来自云栖社区合作伙伴“阿里技术”,如需转载请联系原作者。

March 13, 2019 · 1 min · jiezi

「面试题」如何实现一个圣杯布局?

前言最近,有个朋友向我诉苦说,面试的时候突然被问到了如何实现布局和原理,有点懵。之前JavaScript的部分回答得挺好的,偏偏在这里翻船了,完全没有思路,后面的面试状态一落千丈。结局也如他所料,没有被录取。我给这个朋友做了解答之后,回家整理出此文。希望其他小伙伴面试中,再被问及圣杯布局的时候,可以沉着作答。本文将介绍经典布局——圣杯布局的原理以及两种实现方法:浮动和flex。什么是圣杯布局?圣杯布局是为了讨论「三栏液态布局」的实现,最早的完美实现是由 Matthew Levine 在 2006 年写的一篇文章 《In Search of the Holy Grail》 ,它主要讲述了网页中关于最佳圣杯的实现方法。它有以下几点要求:上部(header)和下部(footer)各自占领屏幕所有宽度。上下部之间的部分(container)是一个三栏布局。三栏布局两侧宽度不变,中间部分自动填充整个区域。中间部分的高度是三栏中最高的区域的高度。接下来,将会介绍两种实现的方法。它们的最终效果都一样,如下图:实现方法1:浮动先上代码:<div class=“header”> <h4>header</h4></div><div class=“container”> <div class=“middle”> <h4>middle</h4> <p>middle-content</p> </div> <div class=“left”> <h4>left</h4> <p>left-content</p> </div> <div class=“right”> <h4>right</h4> <p>right-content</p> </div></div><div class=“footer”> <h4>footer</h4></div>.header, .footer { border: 1px solid #333; background: #ccc; text-align: center;}.footer { clear: both;}.container { padding:0 220px 0 200px; overflow: hidden;}.left, .middle, .right { position: relative; float: left; min-height: 130px;}.middle { width: 100%; background: blue;}.left { margin-left: -100%; left: -200px; width: 200px; background: red;}.right { margin-left: -220px; right: -220px; width: 220px; background: green;}解析一下思路:在html中,先定义好header和footer的样式,使之横向撑满。在container中的三列设为浮动和相对定位(后面会用到),middle要放在最前面,footer清除浮动。三列的左右两列分别定宽200px和220px,中间部分middle设置100%撑满这样因为浮动的关系,middle会占据整个container,左右两块区域被挤下去了接下来设置left的 margin-left:-100%;,让left回到上一行最左侧但这会把middle给遮住了,所以这时给外层的container设置 padding:0 220px 0 200px;,给left空出位置这时left并没有在最左侧,因为之前已经设置过相对定位,所以通过 left:-200px; 把left拉回最左侧同样的,对于right区域,设置 margin-right:-220px; 把right拉回第一行这时右侧空出了220px的空间,所以最后设置 `right:-220px;##把right区域拉到最右侧就行了。实现方法2:flex弹性盒子用弹性盒子来实现圣杯布局特别简单,只需要把中间的部分用flex布局即可。<div class=“header”> <h4>header</h4></div><div class=“container”> <div class=“left”> <h4>left</h4> <p>left-content</p> </div> <div class=“middle”> <h4>middle</h4> <p>middle-content</p> </div> <div class=“right”> <h4>right</h4> <p>right-content</p> </div></div><div class=“footer”> <h4>footer</h4></div>.header, .footer { border: 1px solid #333; background: #ccc; text-align: center;}.container { display: flex;}.left { width: 200px; background: red;}.middle { flex: 1; background: blue;}.right { width: 220px; background: green;}解析一下思路:header和footer同上面一样,横向撑满。footer不用再清浮动了container中的left、middle、right依次排布即可,不用特意将middle放置到最前面给container设置弹性布局 display:flex;left和right区域定宽,middle设置 flex:1; 即可总结总的来说,弹性布局是最适合实现圣杯布局的方法了,相较浮动,弹性布局的结构更清楚,更好理解,也不用担心移动端的适配问题。而浮动的方法,在面试中可能会遇到,主要考察对布局的理解能力。所以,建议大家可以把浮动的例子拷贝下来,自行模拟一把,以便加深理解。参考文献In Search of the Holy GrailPS:欢迎关注我的公众号 “超哥前端小栈”,交流更多的想法与技术。 ...

December 27, 2018 · 1 min · jiezi

前端mock数据server新概念 — 状态管理

前言针对目前前端mock数据server的不足,本王继承在同事的思想之上,为米娜桑做了一个简单易用的基于koa的前端mock工具 —— koa-mock-swich。What这是一个前端mock数据、并可以管理返回数据的server。Why为什么需要koa-mock-switch。目前开发过程中的mock数据方式,主流来说分为:1.后端mock数据即,局域环境有一个专门模拟数据用的数据库,然后,后端开发完接口以后,和线上一样地进行增删改查,最后返回给前端数据。缺点:时间上,前端在需要数据接口的时候,不得不等后端开发完接口以后,才能进行下一步开发。职责上,即使前端开发页面的效率很高,但是因为最后完成的时间肯定是在后端之后的,如果一个项目进度耽误了,前端的锅是背定了。2.前端搭建mock数据服务我们前端,一般都会自己用express或者koa搭建自己的本地前端mock数据服务,市面上也有很多现成的npm可以使用。优点:前后端并行开发。前后端只需要在开发之前,一起定义好接口规范即可。之后前端按照api文档模拟mock数据,自己可以躲在小黑屋独自开发,直到最后的联调。通过对比,我们发现前端搭建mock数据服务的方式无疑是前端开发的首选。但是,对于传统的前端mock服务,我们做的仅仅只有,前端页面发起请求,mock服务接收请求,根据请求路径寻找对应的mock文件,最后返回给前端。相信大多公司也是这么干的。那它有什么不足呢?考虑一下以下场景:如果我们想要返回不同的mock数据,开发者不得不手动的修改mock数据源文件,每次注释,解注释。状态少还可以,比如一个接口,成功或失败,在界面的显示需要不同,因此,我们就需要写完两组模拟数据,并注释一组比如失败,等到需要用失败的时候,解注释失败,注释成功。如果状态多呢?比如一个用户信息接口,用户分为企业用户和个人用户,然后,企业用户有四种状态:未实名、实名中、已实名、实名失败。默认模拟数据为企业用户->已实名,这个时候,我们想要测测所有的情况,那就得做7次注释加解注释的操作。版本迭代了,已实名还有分:初级会员、中级会员、高级会员、超级会员。我们以后每次改相关代码,为了避免出bug被测试看不起,就不得不所有的情况全都再测一遍。如果状态更多呢?有同学说,我三年的注释解注释工作经验,怕这百把十个操作?我就喜欢每次改完代码就一顿注释解注释操作,让老板看到,我工作是有多么饱和。我相信有些很有毅力的同学,会觉得这都不是事儿。但是,这么做的话,我们能保证我们不会漏掉任何一个有多个状态的接口吗?又有同学说:恩,这个不难,在每个有多个状态的mock文件中加个标记,比如本王宇宙最牛逼这行注释,然后全局搜索,就能知道哪些mock文件会有多状态了。那我们能保证我们不会把状态拼接错乱吗?比如,明明是个人用户,却不小心解注释了企业用户的某些状态。有同学说:小意思,写注释就好,想要多少写多少,下次一行行看注释就好了,吐了算我输。恩~~~对于这样的杠精,我只能说:回归正题,为了解决这些问题,koa-mock-switch诞生了。How那么,怎么设计koa-mock-switch这个server呢?首先,先说一下我们的期望,我们期望:1、有一个涉及多状态mock数据的管理页面,方便查看2、通过UI界面的操作就可以控制返回对应状态的mock数据其实这个方案并不是我首创的,最开始接触这个方案,是从我们部门同事那,原始版叫做mock-middleware。我先解释一下他的实现原理。前端项目browser -> node 算法:其实就是在express或者koa的node服务中,维护一个全局变量,我们叫$config,数据类型为对象,key为api的地址,value为返回的模拟数据。如果node端接收到浏览器的请求的话,先在$config中查找,看看是否存在当前api,有的话直接返回,没有的话,就寻找对应的mock文件,返回数据。同时,将api作为key,返回数据作为value存入$config。mock管理界面browser -> node 算法:为了达到通过UI界面的操作就可以控制返回对应状态的mock数据的效果,会有一个和项目无关的,专门用来管理mock返回数据的页面,我们就叫做mock-management-page吧,如图:这个页面的列表渲染,依赖与事先创建的mockSwitchMap。渲染完以后,只要切换状态,就会想node服务发起ajax请求,参数为api的地址以及对应的status(如成功或失败)。node端接收到后,读取该api的mock文件,根据需要的状态,更新$config。如此一来,我们就可以通过mock-management-page,在开发的时候,简单的点击一下按钮,就达到了切换返回数据的目的。从算法可以看出,mock-management-page可以发起ajax对应的status是单一的,会遇到什么问题呢?缺点很明显:1、不得在每次的返回函数中,根据key(即之前说的各种状态)进行人工处理。2、我们看到有段注释// ‘bankCardType’: ‘ENTERPRISE’,,我们依然用了传统的注释,解注释方式来切换返回数据。因为,我们之前说过mock-management-page可以发起ajax对应的status是单一的。如果我们一定要把它变为可切换方式,我们不得不这么写:我们发现,处理状态的过程又多了,最终导致该接口状态越多,处理逻辑约繁重,想想都觉得好心疼,做了这么多,回报却不是很大。但是,细心的同学可以发现,我们根据key(即之前说的各种状态)的名字规定,可以做些不同的处理,所以是不是存在某种方式,可以通过一个通用的数据处理方法,自动地根据key(即之前说的各种状态)的规则,处理后得到最终理想的数据呢?当然可以!最后,我们的任务就是:制定key规则;编写一个通用数据处理函数。Rule我们通过事先约定来规定mockSwitchMap的value,为了便于理解,我们回到Hello Kitty的例子,我们重新构造mockSwitchMap的value:我们[]代表数据的层级,用@代表状态,@作为状态选项,经过处理以后,会向上提升一层。/api/kitty的mock数据文件:如此,我们就可以非常灵活地管理我们想要返回的mock数据,并且,对于哪些mock接口具有多种状态一目了然。此外,如果不需要多状态的mock数据和传统mock文件一样,不需要做任何额外的处理,比如Tom的mock文件:npm安装npm install -D koa-mock-switchnode端使用方法const path = require(‘path’)// mock文件的根目录const mockRoot = path.join(__dirname, ‘./mock’)// require koa-mock-switchconst KoaMockSwitch = require(‘koa-mock-switch’)// mock管理列表const mockSwitchMap = require(’./mockSwitchMap.js’)/** * KoaMockSwitch(mockRoot, mockSwitchMap, apiSuffix) * @param mockRoot mock文件的根目录 * @param mockSwitchMap mock管理列表 * @param apiSuffix 客户端请求api的后缀,比如’/api/kitty.json’,apiSuffix就是’.json’ */const mock = new KoaMockSwitch(mockRoot, mockSwitchMap, ‘.htm’)// 启动mock服务mock.start(7878)还是对使用方法疑惑的同学,可以参考demo。项目地址demo项目中有demo演示,同学们可以自己clone后体验下。地址:koa-mock-switchdemo启动安装npm install第一个窗口shellnpm run mock第二个窗口shellnpm run demo

December 18, 2018 · 1 min · jiezi

封装框架的实践

最近在尝试着封装一个框架,碍于种种原因,先从简单的入手吧。基于vue和elementUI封装的框架,集成 数据存储localforage、字体图标库font-awesome、css拓展语言scss、网络请求axios等模块,为了让业务开发更专注于数据驱动。项目源码地址:https://gitee.com/g2333/data_…使用场景1. 环境 框架基于vue2.0开发,故开发环境也需要nodejs和vue-cli。2. 拓展和维护 为使框架本身易拓展和维护,项目采用vue-cli封装,在开发和使用过程都不打包,保持程序的可读性,同时也方便在引用该模块时可简单的修改配置文件和源码。3. 便捷使用 在一个全新的vue-cli初始化项目中, 安装模块(在vue项目路径下npm i modulecomponents), 引用模块(在vue项目的main.js中添加import ‘modulecomponents/index.js’) 测试使用(比如使用框架暴露的方法dataTool.alert(‘测试成功’))项目配置1. 依赖模块 框架本身依赖有如下模块: elementUI 框架的主力,用于组件封装和方法的调用、 localforage 数据存储,用于存储前端的大量数据、 font-awesome 字体图标库、 scss css拓展语言、 axios 网络请求2. 设置项目入口 修改package.json文件,添加main字段,指向项目入口(“main”: “mc/index.js”),修改private字段,设置为开源(“private”: false)3. 项目初始化 为了让框架方便引用,故在初始化文件index.js(框架项目开发过程使用indexdsForDev.js),自动引入依赖和全局变量的挂载4. 文件提交 设置项目.gitignore文件忽略node_modules避免在协同开发时因为环境不一致导致的webpack报错 设置项目.npmignore文件忽略发布时非必要的文件,减少模块的体积封装的模块1. 组件 组件基于elementUI封装,项目中封装的组件为避免命名冲突,都以mc-为前缀开头。 计划封装的组件有如下: 表格mc-table、 表单mc-form、 树列表mc-tree、 对话框mc-dialog、 上下文菜单mc-contentmenu、 按钮组mc-btns、 流图mc-flow、 下拉选框mc-select、 附件上传mc-upload//在界面上显示一个表单<mc-form :object=“form”></mc-form>//表单对象,描述表单的结构和数据form: new mc.Form({ structure: [{ label: ‘测试’, name: ’test’, }], data: { test: ‘hello world’, }}) 除框架封装的组件外,依旧支持使用elementUI组件2. 全局方法 为了方便开发,较为常用的方法被挂载在全局变量dataTool的属性中,比如 请求方法:ajax请求httpReq、文件导出exportFile、文件上传uploadFile; 提示类方法:警告弹框alert、边角提示notify、确认输入框confirm、锁屏加载loading等; 调用组件类方法:打开弹窗openDialog、关闭弹窗closeDialog、打开上下文菜单openContextmenu、关闭上下文菜单closeContextmenu等; 数据处理:对象类型的克隆和过滤objClone、时间格式的转化formatTime、cookie的添加setCookie等; 原型链上的方法:获取表格新增的一行数据Array.newTableRow、数组元素位置交换Array.swap等; 事件方法:注册事件addEvent、触发事件emitEvent、取消事件cancelEvent等;//打开上下文菜单,点击导出文件,将请求的内容导出成flow.json文件dataTool.openContextmenu(event,[{ text: ‘导出文件’, icon: ‘fa fa-download’, color: ‘blue’, click: ()=>{ const reqObj = {url:‘http://rap2api.taobao.org/app/mock/22119/FUNC=getFlow’, params: {}, type:‘mock’}; dataTool.httpReq(reqObj).then(res=>{ dataTool.exportFile({fileName: ‘flow.json’,data: JSON.stringify(res.CONTENT)}); }); }}])3. 配置文件 封装的组件各有一份默认配置文件,方便全局调整组件的参数。 封装的组件既支持组件类的默认参数修改,也兼容修改单个实例和继承组件类export default { //表单类的配置文件 btns: [], //表单底部栏按钮 topBtns: [], //表单顶部栏按钮 hiddenRows: [], //隐藏的行 topBtnStyle: ‘’, bottomBtnStyle: ’text-align:right’, dialogEdit: false, //是否开启普通字符串类型的弹窗编辑功能 showRules: true, //是否显示表单规则验证 style: “margin: 10px;”, inline: false, labelWidth: “50px”, labelPosition: “right”, size: “small”, autoComplete: ‘on’, spellcheck: false, readOnly: false, extBtnIcon: ’el-icon-more’, textArea: { size: { minRows: 1, maxRows: 10}, resize: ’none’, }, tag: { input: ‘’, type: ‘warning’, closeTransition: false, appendWord: ’ + New Tag’, }, inputStyle: ‘width:100%’, dataType: { //采用小写,减少枚举数量 bool: [‘bool’,‘boolean’,‘switch’], checkboxGroup: [‘checkboxgroup’,‘checkbox’], radio: [‘radio’], select: [‘singleenum’,‘multiselect’,‘multienum’], time: [’time’], date: [‘date’,‘datetime’,‘datetimerange’,‘daterange’], button: [‘button’,‘btn’], tag: [’tags’,’tag’], input: [’’,‘input’,‘string’,’text’,’textarea’,’number’,‘float’,‘password’,‘double’,‘int’,‘integer’,’long’,‘search’,’extinput’], component: [‘mc-table’], },}开发记录1. 项目结构 整体项目的规划整理在一个xmind文件中,方便记录开发进度和了解项目的整体大纲,这是图片版 http://qpic.cn/dDPbFwEeD (请在复制粘贴到浏览器的地址栏中访问)2. 使用文档 为了记录开发进度和形成规范,项目开发的使用说明和修改会记录在石墨文档https://shimo.im/sheet/K8QPjP…3. 版本控制 使用git作为版本控制,项目的源码托管在码云上https://gitee.com/g2333/data_… 既方便协同开发,也方便代码版本控制框架更新1. 项目更新 修改后的源码在测试成功后,修改package.json中的版本号,将代码推送到码云上,然后通过npm发布新版本2. 模块更新 通过npm update modulecomponents指令更新模块,即可使用最新版功能 ...

December 9, 2018 · 1 min · jiezi