Zhang Xiao

Zhang Xiao

45 posts published

📍 beijing

海报分享图片生成服务在狐友的落地实践

项目背景狐友作为搜狐的一款社交产品,在流量传播上有着旺盛的需求点。而在流量传播所需的众多载体之中,海报图片以其简单的分享形式、可定制的视觉体验、自带二维码识别导流等特点,成为了社交产品高频必备的流量载体。 作为狐友的前端开发,生成海报图片就成为了我们工作中持续不断的一个重要需求点。以下是狐友目前的产品前端服务矩阵和海报图片的产品形式。 图 1 狐友产品前端服务矩阵和海报图片的产品形式海报图片实现现状从上图1可以看到,生成海报图片对于狐友产品矩阵来说是一个高频强需求。海报图片作为分享载体,对于各平台的分享流程对接也非常畅通和直观,例如不同于小程序卡片分享只能拘泥于微信平台,网页分享的链接形式不够直观。 而在海报图片这个重要环节,长期的主要技术手段一直是通过各客户端开发在本地设备上进行绘制,但这种方案存在如下的劣势困扰着我们: 各端无法复用:如图2, 如果要全平台都要做图片分享,那么需要各端分别开发,即使生成的图片一模一样,也要开发iOS、Android、H5、小程序一共4遍,整体开发各端无法互相复用。长图大图崩溃:客户端限于设备平台或系统限制,对于长图的生成并不友好,会出现长图因为内存或算力限制无法生成的情况,其中小程序尤为明显,在微信的框架下很容易长图生成造成程序直接崩溃。开发效率较低:客户端本地绘制海报图片,一般需要手写原生代码效率不高。小程序端虽然使用wxml-to-canvas(H5端使用html-to-canvas)来绘制减轻了一些手写命令式绘制代码的负担,但这种标记语言转canvas在实现上也存在缺陷,相比HTML+CSS的表达力还是非常受限。所以,海报图片在代码层面开发效率比较低。为了解决以上问题,我们开始着手调研并实践落地了一套全新的海报图片统一服务,

如何写好一篇技术文章

背景应团队同学提议,写了这篇如何写好一篇技术文章的文章。鉴于水平实在有限,亚历山大之余,也很高兴分享一下自己对技术写作的一些思考。 希望大家可以从中得到一些对自己有用的关于写作的启示和方法,就能达到本文的目的了。那么,写好一篇技术文章对我们有什么用呢?我觉得有三点:第一点,可以更好地分享传播技术方案,让读者认同赞赏你的文章;第二点,通过文字表达提升个人的表达能力,在做技术分享和演讲时更具魅力;第三点,通过整理技术文章,主动对技术深入思考剖析,提升个人的技术广度和深度能力。 那么,接下来我重点分享下我实践中的写作流程。 写作流程写作流程,大体上可以分为:写作前的准备工作、写作中的注意事项和技巧以及写作后的提升工作。通过对这三个阶段的打磨,相信你就可以写出一篇”令人满意“的技术文章。 写作前所谓万事开头难,所以写作前的充分准备工作,可以帮助你快速进入写作状态,准确明晰写作内容。让我们通过以下手段,告别写作开始时的”提笔皱眉“,体验一把”下笔如有神“。 确定立意标题当然,立意和标题应该是先确定清楚的,要非常明确地知道自己要写什么内容,这个”什么“其实并没有那么简单,可以参考以下罗列的要素: 内容传达的目的是什么?内容关键字是什么?内容触达的范围是什么?内容的重点是什么?

2021年12月

平凡与不平凡— 纪念毛星云同学并与君共勉 前端技术新闻React conf 2021 回顾 React 18介绍和并发新特性:React 18 在没有重大break changes的情况下支持了并发特性,这意味着用户可升级的工作量只是和以往大版本更新相似。React 18已经发布RC版本可试用,预计2022年初就会发布正式版本。Streaming server rendering with Suspense:使用Suspense控制组价级别的流式渲染,使得用户更快看到内容,更快进行交互。创立第一个React working group:集合各领域专家、开发者、库作者以及教育人员,共同合作逐步改善新的APIs,例如useId, useSyncExternalStore, 和 useInsertionEffect。React Developer Tooling:将会推送更易用更多功能的开发工具来帮助开发者debug React应用。React without memo:解决React常见痛点,介绍如何通过auto-memoizing compiler来减少模板优化代码,使开发者更加聚焦业务本身。React docs keynote:

js正则字符串

正则字符串是什么? 这个词是我自己这么叫的,就是可以用字符串来表示的正则。。。一般我们写正则,直接写在两个斜线中间:/这里是正则表达式/ 可是你要想写个动态正则,你就得借助字符串来完成了,因为字符串可以动态生成,并且当做参数去构造出正则的对象。但是这个参数直接加双引号变成:"/这里是正则表达式/" 这样就可以了吗? 如果去尝试一下,就知道咿~不对。。。为什么不对呢?先来看看escape这个东西吧。 一直对escape这个词有陌生感,因为他的英文是逃避躲开的意思,而程序中它有另一个名字叫做转义。 其实顾名思义,转义就是转换意义的意思,也算式一种避开本身意义的行为吧。那么什么是转义呢?为什么需要转义呢? 因为在特定环境中,字符串中的【某些字符或者字符组合】有某个意义,而希望它表示其他意义的时候,就需要转义了 感觉说了废话,所以上例子: "Hello "World."" 由于字符串中的两个双引号,和字符串开头和结尾的双引号引起了歧义,所以程序解释器不知道这个字符串合适结束,而你又想表示确确实实看到的这个由这些字符和四个双引号组成的字符时,就需要转义,让解释器明白我只是想表示双引号这个字符而不是它本来的字符串识别符。所以变成下面就可以了: "

一次记住js的6个正则方法

我时常感到困惑,为什么有些知识我总是觉得模糊,其实就是想的少,总结的少,大多数人也如此,有疑惑不清楚就找出来,想明白或者想不明白都记录下来自己的收获,比扭头忘记还是要好很多吧。。好我觉得js中的正则我不是很清楚,那么来看一下吧。 首先6这个具象的数字可以帮助我们整体记忆了,666哈哈 范围 js中有两个类可以让正则发挥作用 创建 var re = /ab+c/ 方式一:正则表达字面量,这种直接是常量的表示用法可以让js解析器提高性能 var re = new RegExp('ab+c') 方式二:构造函数,这种方式可以在runtime的时候动态确定正则是什么,更加灵活 常用特殊字符 来记忆一些常用特殊字符,这个是正则本身的范畴了,是不是总觉得记不住?其实我也记不住,每次都是去搜索和online验证来完成一些任务。我也困恼过,其实最后还是因为自己写的少吧,唯手熟尔。。。下面的总结不写具体内容,只列出具体特殊字符和分类,可以尝试一下说出他们的意义,我觉得比看表格更有利于记忆。。。 匹配量的:* + ? {n} {n,} {n,m}

闭包,能吃么

最近simona在面试,一面动不动就问闭包,这个包子啊,好吃么?哈哈,其实吧,很多时候程序员一直用着闭包,也知道有这么回事儿,可是有时候用语言去表达也是醉醉的。。。。所以,叙述不出来没事儿,也不代表能力,抽个空,总结一下也就完事儿了,是吧?so,让我们来吃掉这个包子~ 好的,开始认知: 是什么 闭包是指有权访问另一个函数作用域中的变量的函数(红宝书如是说) 怎么创建 在一个函数内部创建另一函数 (红宝书如是说) 有什么用 函数执行完后,让函数的内部变量不被当做垃圾收回的一种手段 (我自己如是瞎说哈哈) 好了,就这么些东西,好像不是很好吃啊,干巴巴的。。。那我们就深入一些,把包子馅给吃掉吧~ ######函数执行发生了什么? 看了一下红包书,说了很多东西,其实我们可以更感性的理解一下。说一下我的理解: 函数就是程序执行的一个提前写好的计划清单:plan 要开始做这个计划就需要在plan上写上我的计划需要的东西,例如我们要计算今天的花费: 早餐=5元 中餐=15元 晚餐=借的别人的钱

记一次webpack打包优化

开始接触vue的时候大概就是开始接触webpack的时候,用vue-cli直接脚手架工程后就开始开发了,用了一段时间后发现vendor.js越来越大,居然到了M的级别。看了一下vue-cli的默认配置,vendor是把node_modules里的依赖都打进vendor中,我觉得太大了这样,不如手动维护控制包的粒度,多分出几个包,充分利用浏览器并发请求资源的好处,而不是把所有东西都打进vendor中。 那个阶段开发任务重,没太多时间细细纠结,反正是迷迷糊糊看着webpack的配置文档和各种问题解决的帖子把上面提到的事儿做了。效果出来了,也就没再管过。这几天突然发现打包的地方还是有很多可以优化的点儿,便优化并记录一下。 这里着重的就是三个东西来完成此次优化: webpack.optimize.CommonsChunkPlugin OptimizeCssAssetsPlugin BundleAnalyzerPlugin 首先,记录一下目前线上的包有什么问题(这个项目是一个多入口的vue项目): 只抽离了vendor.js,却没有抽离各个入口的共有代码 每个入口模块的css也没有抽离出共有代码,导致500k的样式文件在每个入口的css文件里都有一份,浏览器缓存用不起来 css文件没有压缩,去注释等优化工作,在线上直接裸奔,传输的大小过大 有些基础库虽然不是每个入口包都需要的,但迟早是需要的,是否也应该进一步优化出来成为一个单独的包引用,而不是打在各个入口包里 所以接下来我们就利用工具解决上面的问题到达优化的目的。 CommonsChunkPlugin 就是常用的提取各个包共有模块的工具,不过我觉得这个插件设计的不是那么易于理解和使用,文档看了有些懵。。。不过我还是按照自己的实验和理解来记录一下如何使用他。 在分析之前,

从插件组件说到vue的slot上监听

最开始写网页的时候我很崩溃,为什么写的代码很快就不能看了,一坨一坨的,对自己写的东西觉得非常没有安全感,以至于很长一段时间里我都在做重复而难以维护的事情。我感觉前端开发有两件事儿是特别需要重视的,一个就是可维护性,一个就是复用性。 可维护性有很多因素,我觉得最重要的就是模块化,遥想现在的老系统的代码都是要么js一坨,要么script标签引入一坨而且顺序还得小心翼翼,真是太惨了。。。 而可复用性就是今天要记录的一些东西,虽然说的是vue里具体的解决方案。可是很多东西都是相通的。 比如最开始的时候: 第一天,我有一个table页面,好的,我html一画,js数据一拿,事件一监听完工; 第二天,我又有一个这样的table页面,差不多,好的,我把昨天写的copy过来,改改,完工; 第三天,我又有一个这样的table页面,也差不多,好的个鬼。。。。我要疯了,难道我还要copy吗?好吧,忍了。。。copy去 第四天,好了我没有同样的页面,总算松了一口气,可是需求让我把每个table都加上排序功能,好的,啊。。不对,我要加在哪里。。。第四天我总于崩溃了。。。 后来,我发现了有jq插件这种好东西,

提交表单数据的不同

前后端通信的时候,我们肯定要定义传输的格式,以便进行交流,我们利用http进行数据传输时有不少提交数据的方式,之前也就是用别人封装好的,也没有太总结过~~废话少说,listing it~ 在http的协议里有个header字段来进行数据格式的约束,就是Content-Type 字段。它有三个指令: midea-type: 资源类型MIME charset:字符集 boundary:简单来说用来区分资源的分块标识 例如 Content-Type: text/html; charset=utf-8 Content-Type: multipart/form-data; boundary=something 我们主要来总结下常见的MIME type: text/html text/css application/javascript application/json multipart/form-data application/x-www-form-urlencoded 后三种就是我们常见的表单提交的三种payload格式,一个是json字符串,一个是分片表单提交,一个是url表单提交。 后两种有什么区别呢?一般要提交文件的时候是要用分片的方式提交的,

new Promise vs Promise.resolve

异步嘛,新项目上了ES6,反正就是用,虽然没有用async/await,感觉promise也可以处理大部分情况了,毕竟项目的异步数据流没有那么复杂,这里记录一下下,有时候封装异步操作为promise,看到别人的代码有用new的,有直接调resolve的,故查了一下两者的区别。 Promise.resolve(x); 和 new Promise(function(r){ r(x); }); 是有一样的效果的,都是返回一个promise,来让使用者使用。只是Promise.resolve返回的是一个已经resolved的promise,而new Promise返回的是一个既没有resolve也没有reject的promise。 在stackoverflow上有人提到了对异常的捕获也不一样,我没有太读懂,回头在补充吧~ 通常Promise.resolve的用例就是把objects或者是thenables的对象转换成promise对象来使用。

ES6箭头函数

ES6的箭头函数其实就是词法作用域的一种方式,它本身没有this,所以会向上寻找有this的作用域当做自己的作用域;而普通函数的this是运行时动态绑定的,也就是指向调用者的作用域(上下文环境) 所以呢? function Person() { var self = this; self.age = 0; setInterval(function growUp() { self.age++; }, 1000); } // 可以改成这么写,是不是简化了很多? function Person() { this.age = 0; setInterval(() => { this.age++; // 这个this指向的就是箭头函数外面的this }, 1000); } // 那如果这样呢?套嵌箭头函数呢? function Person() { this.age = 0; setInterval(() => { setTimeout(() => { this.age++; // 这个this呢?由于它会像父级作用域寻找,发现是箭头函数,

vue-cli定制脚手架

年初的时候公司的老后台系统实在难以维护和继续在其上开发了,因为这个系统被很多人写过页面,有前端有后端,编写前端代码时都非常随意,加之没有模块化,复用性和可维护性都极低,便下定决定,重新搞一套。 经过一段时间的调研选择了vue全家桶+elementUI来开发后台系统,让交互体验更好,让开发体验更好,让生产效率提高。 从零搭建其实考虑的事情还挺多的,比如: 如何管理代码仓库 开发环境,测试环境搭建 如何接入公司的打包上线流程 如何目录划分 如何划分模块 登录和权限如何做 这篇文章来记录下和脚手架相关的改造,首先其实就是上了vue-cli来做,可是呢?由于预计项目会有很多页面,这些页面其实是分模块的,不同模块的页面之前其实关系不大。所以我觉得一个用户其实大部分时候只会用到其中一个模块的页面,如果把所有页面做成一个单页应用很多资源加载就不是很必要了,所以第一个改造就是:做成多入口打包,也就是做成多个单页应用,每个模块一个入口。 /build/utils exports.getEntries = function (globPath) { var entries = {} glob.sync(globPath).forEach(function (entry) { var basename

Date

嗯嗯,今天复习一下js中原生的Date对象~不不。。是预习。。。 翻了一下MDN,没错Date对象有一大坨方法,先不管那么多,先来了解一下构造方法: 四种用法: new Date(): 默认是当前时间 new Date(value):value是时间戳 new Date(dateString):这个dateString比较多,不太好记忆,用到的时候试试就好啦,该字符串必须能被Date.parse()识别才行哦,不过文档上是不推荐在ES5之前使用的,因为哥浏览器实现差异大, 手动解析会更好一些。 new Date(year, month, day, hour, minutes, secondes, milliseconds) Date拥有的方法众多,我觉得很有用的是几个get方法: 年:getFullYear() 月:getMoth() + 1 注意它从0开始的 日:getDate() 注意不是getDay, getDay返回的是星期中的第几天(0-6)

DOM之度量

一个网页就是一个图纸,前端工程师的任务就是把图纸中所有元素的尺寸和大小规划好,然后把它们安放在图纸相应的位置。有时候我们需要度量元素的位置和坐标,那么我们来复习一下和度量有关的那些事儿吧~翻译原文的链接here css盒模型 一个文档例子 <div id="example"> ## Introduction The contents. </div> 这个文档盒子绝对定位,有borders、paddings、margins、和滚动条: #example { position: absolute; width: 300px; height: 200px; left: 160px; top: 160px; padding: 20px; margin: 20px; overflow: auto; border: 25px solid #F0E68C; } 然后我们看看css样式对这个盒子起的作用,看上面的数字标注,很清晰吧。

DOM之样式

jQuery有css这个方法很便利的获取或者设置文档的样式,如果原生的方式呢?可以回顾一下js是如果操作DOM样式的。 className className属性总是和html上的class特性保持一致的。用法例如: <body class="class1 class2"> <script> alert(document.body.className) document.body.className += ' class3' </script> </body> 如果想要删除一个class,可以把字符串中的class字符replace成空,也可以应空格split字符串后删除class再join回来。多数js框架都会提供内置方法来做这些工作,例如jq中: $( "p" ).removeClass( "myClass noClass" ).addClass( "yourClass" ); style

DOM之访问

今天无意中看到一篇文章觉得可以很好的回顾一下关于DOM基础知识,故翻译了一下~原文链接是here 所有的起源都来源于document这个对象。这个对象提供了提供了很多方法来搜索和修改元素。 根元素:documentElement和body DOM的根元素总是document.documentElement, 它是一个可以引用最开始的HTML标签的特殊属性。另一个起点的属性是document.body,它表示了BODY标签。 这两个进入点都是有效的,但是document.body可以为null,比如这种情况,在HEAD标签里试图访问body就会是null,因为此时还没有解析到body标签。 <!DOCTYPE HTML> <html> <head> <script> alert("Body from HEAD: "+document.body) // null </script> </head> <body>

从快排递归说起

快排最简洁的是递归写法,可是当我问自己你真的想明白到底发生了什么吗?如果你有些不能肯定,那么我们一起来看看到底发生了什么。 其实快排的原理用语言描述起来挺容易的,简单说就是在数组中找一个值作为对比值(常常找中间那个元素),然后把数组中小于此值的值放入一个数组,把大于此值得放入另一个数组。然后针对这两个数组重复递归上面的过程,把所有的值连接起来就是最后的结果了。 让我们来写写代码: var qsort = function(arr) { if(arr.length <= 1) { return arr; } var pivot = arr.splice(Math.floor(arr.length/2), 1)[0], left = [], right =[]; for(var i=0; i<arr.length; i++) { if(arr[i] < pivot)

mobile

移动端适配页面快速搭建

遥想去年入前端坑的时候,就很快遇到了移动端页面的开发,天(er)真(bi)的我立马上去量设计稿,然后撸起袖子就是干!可是打开调试一看乱套了,先不说布局上的问题,就是大小看起来也不太对啊,太大了!然后就去问老司机,他说尺寸你要除以2才行,我心里挺郁闷的,为啥啊。。当时羡慕紧,就先这么搞,然后加上百分比布局,完成了第一次的开发任务。。。 但是!这只是叫完成,我非常不喜欢心里有梗的感觉,我觉得这事儿没这么简单,而且随着后期开发的进行,有一天设计mm给我说你这个线好粗啊,能不能再细点儿,我心中一万个不(cao)情(ni)愿(ma)飞过,我这已经是1px了啊。。。 还有最关键的我发现用百分比布局有个极大的缺陷,就是当你感知设计的设计意图的时候,(我们的设计师同学们的设计意图有多重要,我觉得好的设计师一定是设计意图非常犀利的),有些设计元素并不是单纯的左对齐,右对齐,或者和某个元素有某种视觉上的联系。也就是说,这个时候百分比布局就要头疼了,这个元素到底是百分之多少呢,用尺子量一下,可以啊,可是你把设备屏幕放大或者缩小看看,哭还是不哭?。。。。 是的,

React

React起手-组合vs继承

当然,React也是提倡组合优于继承的,这里对一些新手来说,往往他们喜欢用继承,我们来看一下如果用组合来更好的来解决问题吧~ 这里的这个例子所实现的功能,当时用vue1实现的时候特别困难,如今vue2应该也支持了相应的功能,不过当我看到入门文章的这里的时候,我感觉这是React很强大的地方,简单来说就是,可以进行依赖注入,注入什么呢?当然什么都可以,这就给UI组件强大的灵活能力,即父组件只用实现那些不变的部分,那些变化的部分我只需要抽取出来行程单独的子组件,这样父组件在不知道他内部某些部分是什么样子的时候就可以进行抽象。 还是先看例子来理解一下: function FancyBorder(props) { return ( <div className={'FancyBorder FancyBorder-' + props.color}> {props.children} </div> ); } function WelcomeDialog() { return ( <FancyBorder color="blue"> <h1 className="

React

React起手-提炼状态

官方文档叫Lifting State up,不知道我翻译的是否妥当,哈哈哈。 组件之间的数据有些肯定是共享的,也就是说这个共享数据不管在什么地方被改变了,所有组件都要及时的反应到自身上,官方推荐提炼共有状态到他们最近的祖先上。举个例子来理解这个东东,例如有个温度计,当温度到100以上时候水开,反之不开。 function BoilingVerdict(props) { if (props.celsius >= 100) { return <p>The water would boil.</p>; } return <p>The water would not boil.</p>; } 然后我们添加一个计算器来输入温度 class Calculator extends React.Component

React

React起手-表单

对于表单的处理,react的处理和原生很相像,所以没有什么多说的,就是看例子吧~ 以下就是input, textarea, select的用法,其实都一样,把state的值挂载到相应位置的value上,并且监听相应的事件即可,注意事件的写法onChange驼峰哦。回调函数的第一个参数是event哦,原生event对象可以做一些例如消除默认行为和停止冒泡的事情。这些被控制的组件叫做controlled components。 然后你会发现这也太麻烦啦,每一个表单控件我都得写一个handler区处理它,这样的话不仅麻烦而且和原生代码以及非react得库难以整合,所以有uncontrolled components, 来改善这一情况,会另起文章来记录~ class NameForm extends React.Component { constructor(props) { super(props); this.state = {value: ''}; this.handleChange = this.handleChange.bind(this); this.handleSubmit = this.handleSubmit.bind(this); } handleChange(event) { this.

React

React起手-列表渲染

列表渲染也很简单,利用map方法返回一个新的渲染列表即可,例如: const numbers = [1, 2, 3, 4, 5]; const listItems = numbers.map((number) => <li>{number}</li> ); ReactDOM.render( <ul>{listItems}</ul>, document.getElementById('root') ); 基础列表组件的构造中,有一个重要的属性值key需要你进行指定,这个很重要,和帮助框架进行性能优化有关,具体深入原因后续会继续了解,先来看例子: function NumberList(props) { const numbers = props.numbers; const listItems

React

React起手-事件处理

React的事件处理和DOM的事件处理是很相似的,只是有一些语法上的区别: React的事件名是驼峰的,不是小写的 在JSX语法中,你传递一个fucntion作为时间处理器,而不是一个string 举个例子: <button onClick={activateLasers}> Activate Lasers </button> 而且如果你想拿到事件对象event,这个对象是React按照w3c标准完成的,所以不用担心浏览器的兼容性,可以像如下这样: function ActionLink() { function handleClick(e) { e.preventDefault(); console.log('The link was clicked.'); } return ( <a href="#" onClick={handleClick}> Click me </a>