聊聊Chrome Devtools的Timeline

最近在进行移动端Web页面的性能调优时用到了Chrome Devtools的Timeline。Timeline主要是针对浏览器渲染引擎的相关数据进行记录。关于Timeline的介绍可以看官方文档。下面主要说说实际应用时如何来分析Timeline的数据,然后优化页面。 Timeline的使用场景 Timeline的使用场景一个是在页面发生明显的卡顿时,比如CSS动画或者页面滚动时,用来分析卡顿的原因。还有一个场景便是进行页面渲染性能的评估,看页面渲染性能是否有优化的空间,或者通过截图观察页面渲染的过程,优化Speed Index来提升用户体验。 关于渲染 我们知道,如果以每秒16-24帧的速度连续播放图片,那人就会产生动画的幻觉。在UI动画中,因为目前主流显示设备的刷新率达到了60Hz,我们追求的的帧率是60fps。那理想情况下,每一帧的时间应该是1/60=16.67ms。 又因为JavaScript是单线程事件驱动的模型。所以浏览器在一帧之内要完成JavaScript脚本的运行,然后计算,合成这帧画面,并渲染。 Long Frame 所谓的Long Frame,也就是我们在Timeline的FPS中被标注为红色(如下图)的那些帧,便是用时超过正常标准的那些帧。 long frame Long Frame往往意味着卡顿,因为帧率低于60fps,甚至30fps。我们会通过查看这帧中的JS运行情况,浏览器渲染情况,以及浏览器用户触发事件的情况来分析卡顿的原因。 Timeline中有价值的数据 JS调用栈 JS Call Stack 如果是JS运行时间过长,比如超过10ms,那我们就有必要检查这个JS的调用栈,可以通过查看Details一栏中的Bottom-up选项卡来看到自顶向下的调用信息。分析那个函数调用占用了过多的时间,并加以解决。 浏览器渲染信息 浏览器渲染每一帧画面,需要经过这些阶段。 render pipeline 其中我们需要避免的主要就是Layout,如果触发了Layout的重新计算,那就意味着你的页面需要重绘,这是一个非常expensive的任务。如果我们在CSS动画时使用了GPU加速,那这些动画就不会造成重绘,而是作为一个单独的层在Compose阶段进行组合。 如果是浏览器渲染用时过多,我们就需要检查代码中是否触发了不必要的浏览器重绘。这方面的优化主要是针对CSS的优化,是一个专门的专题。一般的措施是采用CSS3 transform进行动画,开启硬件加速来避免浏览器重绘。 事件输入 我们可以在Flame Chart的Interaction部分看到浏览器在这段时间触发的事件。 用户注册的事件,都有对应的回调函数。因此如果这个回调函数包含大量的逻辑,并且这个事件触发非常频繁的话,比如scroll或者touchmove,那我们就需要对事件触发的频率进行限制。在防止回调函数频繁触发层面,对应的办法有JS层面的节流函数。在限制事件注册的数量层面,我们可以使用事件代理,尽量避免在一个DOM元素上注册多个触发频繁的事件。 题外话:在调试移动端页面时,我发现Chrome在Timeline里标出了pinch这样的事件。说明浏览器的渲染层是支持这样的高级事件的。但在DOM的标准中没有找到这样的事件。需要大家用touch事件来合成。Chrome的这个做法不知道应该理解成浏览器面向未来的支持,还是浏览器私有的实现。当然,在移动端的操作系统层面,识别pinch应该不是一个难事。DOM的touch event中没有支持pinch,swipe等事件,这个问题值得思考。 其他数据 Timeline中还有很多有价值的数据,比如CPU占有率,JS的Heap大小(可以简单的理解成JS的内存占用情况),DOM节点的数量,事件监听器的数量等等。

November 29, 2016

Unix·Talk 聊聊工程

Unix·Talk是今年开始实行的木犀团队系列技术分享,面向的是团队内所有的技术同学,尤其是新人。在这个系列分享里,我们会聊聊计算机硬件,工程化实践,互联网文化,当然也会聊聊和Unix操作系统有关的一切。 工程是什么 首先请大家观察这两张图: 如果说左边的小草房是课程作业的话,那右边的高楼大厦,就是我们所说的工程。 大家可以思考一下,建造两者的过程有何不同。建左边的小房子也许不需要太多的材料,也不需要对材料进行深度的加工。在设计上,只要有基本的构想即可,不需要精准的蓝图。在施工上也相对随性一些。 而右边的高楼大厦,需要钢筋混凝土这种深度加工的材料去构建,需要严密的蓝图和施工周期。同时也需要数百倍于左图的人力去建造。 从产出的规模和流程的复杂度来说,软件工程师和建筑工程师或者电气工程师一样,都在一套成熟的流程和体系下,打造大规模的产品。 所以,在互联网行业,工程就是一套完整的产品研发方法论。同时也包括项目开发周期中的项目管理,前期估算,后期测试等流程。 那为什么我们需要这套方法论呢?很简单。如果没有这套方法论指导,我们就无法顺利的开发出大型的软件,或者,我们的开发效率会很低。我们需要用工具来加速我们的开发,自动化一些无聊的工作。我们需要用设计模式来解耦我们的代码,让代码的可维护性更高。我们也需要项目管理,来让不同分工的工程师协同工作,顺利完工。 而这套方法论,学校里是很少有讲到的。这也是因为,工程相关的方法论需要工程师在项目开发的过程中不断的实践总结,毕竟纸上得来终觉浅,而工程这种和实际问题特别相关的方向,则更需要在实际的项目才能有更深入的理解。 工程的几个关键词 工具 俗话说,没有金刚钻,就不揽瓷器活。无论是客户端还是服务端,大家都有专属的神兵利器。 其中大家使用频率最高的便是编辑器或者IDE。前端有Sublime,安卓有Android Studio,iOS有Xcode,后端有Vim。强大的IDE往往 包含了从代码提示到编译打包的一条路服务。而Sublime这样的轻量级编辑器,需要我们进行各种DIY,打造成自己想要的样子。好用的编辑器往往会提高开发的效率,带来生产力的提高。 在客户端开发中,打包构建工具也是一个重要的类别。好用的打包工具不仅仅可以降低打包的时间,在前端等领域,好用的打包工具甚至代表了一种工具生态,串联起了一个完整的工具链。 开发中用到的工具还有很多。很多小工具的诞生,往往是为了解决某一个具体问题的。程序员往往希望一切机械的事务都可以自动化。并且也喜欢将繁杂的数据可视化。这两个动机驱动了很多小工具的诞生。 趁手的工具代表了先进的生产力。这也是构建大型工程时必须借助的垫脚石。工程化程度高的技术团队,往往都有着自己的先进工具。 编码规范 编码规范包括代码风格,目录结构等等。这些编码规范的存在,一方面是约束团队成员的代码质量,使得一些低级错误和语言中的bad practice得到提醒和控制。一方面,代码规范中的一些代码风格相关的部分,是为了让团队成员的代码风格统一,这样有利于后期代码库的维护和迭代。 我们可以使用一些Lint工具和持续集成等技术来将编码规范落地。一个团队的编码规范应该被写在一个单独的仓库或者文档里,以供团队成员参考。 设计模式 设计模式有很多,从常见的工厂模式,单例模式,观察者模式,装饰器模式等到应用架构层面的MVC,MVVM等。 设计模式主要帮我们将一个大型的系统解耦成不同的层次。比如将数据层和视图层分离,并用一个中间层将二者联系起来。 因为大型的系统的模块数量众多,不同模块之间关系复杂。我们要用设计模式来将不同的模块划入不同的层次。 而观察者模式这样的设计模式主要是为了使模块之间可以实现松耦合下的通信。虽然模块之间有着各种的依赖和调用关系,但我们并不打算让不同模块直接相互调用,而是实现一套接口,并借助中间人通信。这样如果某个模块被替换,只要新的模块实现了相同的接口,整个系统依然可以正常运行。 在开发软件时,我们一般都遵循一套成熟的模式,在此基础上,完成业务代码的编写。设计模式也是大型软件的基石。 对于设计模式,关键点往往不是你能说出几个设计模式,而是你懂得在什么情况下使用合适的设计模式。 版本控制 现在主流的版本控制工具是Git。Git主要的作用是版本控制,同时也帮助我们进行多人的协作。 没有Git的话,如果我们需要保留多个不同版本的代码,就需要手动保存。在多人协作时,没有Git也是无法想象的。比如用FTP进行协作,每次都覆盖式的保存,这样的的代码管理无疑是非常原始的。 事实上Git的Branch以及Github的pull request等等机制造就了一套独特的Git工作流。这套工作流无疑是团队协作的最佳选择之一。 抽象 其实编程的精华便在于对过程(precedure)以及数据的抽象。这是一种编程的内功。通过对过程的抽象,我们可以将程序中可复用的模块提取成单独的函数,然后进行组合,这样可以大大增强程序的表现力。通过对数据的抽象,我们可以封装数据的细节,从一个更通用的维度来对数据进行操作。 对于抽象,推荐大家看《计算机程序的构造与解释》这本书。当然抽象要合理才行,过度的抽象也会使代码的易读性受损。 测试 测试主要是为了保证软件的质量。在软件被开发完成之后,会进行一系列的测试,以保证软件的高可用性。一般团队会有专门的测试工程师。负责测试用例的编写。 对于后端这样主要负责数据处理的逻辑,我们可以编写自动化的单元测试,并且进行持续集成。单元测试以一个模块为单元进行测试,这样我们在错误发生时,可以准确的定位到具体的模块。单元测试也被用在测试驱动的开发中,测试驱动的开发就是先写测试用例,然后编写可以通过这个测试用例的代码。 对于客户端代码来说,因为UI复杂多变。所以自动化的单元测试只有在对框架/库的代码进行测试时比较实际,在业务逻辑层面,比如视图层,我们一般采用人工测试。当然现在也有自动化的UI测试方案。 项目管理 项目管理,是学校软件工程课程中主要涉及的。这个主要涉及传统软件的开发流程。从需求分析,成本估算,开发流程到最后的测试。 互联网的开发模型上主要还是比较敏捷的迭代。通常以一个月甚至几周的周期进行迭代。其中Web应用的特点便是可以随时发布更新,所以在这方面显得更突出一些。 关于项目管理或者软件工程,大家可以去看《人月神话》,《构建之法》等书。虽然这些书主要针对的是传统软件工程,但我们在互联网产品的开发中还是可以从中汲取一些经验的。 适合我们团队的一套工程化流程 技术选型-选用最合适的技术栈,当然为了把玩新技术而选用比较新的技术栈也是可以的。 合适的工具-鼓励大家,没有在开源社区找到符合自己要求的工具时,打造自己专属的工具。 Git协作-Git工作流 代码规范 项目架构-合适的抽象,不仅仅是MVC等架构,也包括其他可以复用的组件和Utility模块。 代码审查 单元测试(后端) 人肉或自动测试(客户端)

November 19, 2016

聊聊CSS Modules

CSS在工程化上的一些问题 关于React的CSS in JS,有一个著名的talk,由Facebook的工程师vjeux带来。 里面最有名的一张slide是这样的: 里面列举了CSS的一些问题。其中,Dead Code Elimination,Minification,和Sharing Constants这些问题我们已经通过在我们的工作流中加入SASS和PostCSS这样的CSS预处理器解决了。 然而还有一些问题没有解决,比如全局命名空间。同一个document下的所有CSS的类名,都是在同一个“作用域”下的,因此我们常常要考虑如何避免命名冲突问题。现有的解决办法主要是靠BEM这样的命名惯例,或者是用多层CSS父子选择器来模拟命名空间。然而这样的办法对工程师有许多的限制。多级选择器有比较高的优先级,不容易维护。 解决全局作用域:Webpack css-loader Webpack的css-loader首先做出了解决全局作用域的尝试。解决办法就是在写CSS类名时加入:local(...)这样的标记。 比如: :local(.className) { background: red; } :local .className { color: green; } :local(.className .subClass) { color: green; } :local .className .subClass :global(.global-class-name) { color: blue; } 会被转化为: ._23_aKvs-b8bW2Vg3fwHozO { background: red; } ._23_aKvs-b8bW2Vg3fwHozO { color: green; } ._23_aKvs-b8bW2Vg3fwHozO ._13LGdX8RMStbBE9w-t0gZ1 { color: green; } ._23_aKvs-b8bW2Vg3fwHozO ._13LGdX8RMStbBE9w-t0gZ1 .global-class-name { color: blue; } 这里的办法就是把CSS类名转化为hash字符串,这样就可以保证每个类名都是独一无二的,自然也就不用在意命名冲突的问题了。只要在类名在当前模块内不会相互冲突就可以了。 CSS Modules 上述的办法,还是有一些不便。大多数情况下,比如在JavaScript中,变量都默认是局部变量。你想要声明一个全局变量,只能去全局作用域声明,或者把变量挂到local上(非严格模式下,不写var声明的是全局变量这种坑就不说了)。 Webpack的开发者之后将css-loader中的local变成了默认设定,于是CSS Modules这个规范就呼之欲出了。 CSS Modules规范。我们可以通过css-loader?...

November 12, 2016

猪场实习感受-项目管理

这次实习,我主要学到的倒不是前端方面的东西,而主要是关于一个产品的迭代的流程。 一个产品迭代的流程大致是这样的: 总的来说就是PM提出需求,交互出交互稿,然后这时研发和视觉可以同时开始进行。最后进行测试,测试通过后上线。这就是一个互联网产品版本迭代的过程。 时间规划 在上一个版本的开发过程中,PM和各个负责人就可以开始讨论下一个版本的需求,然后项目经理和大家一起确定这个版本的研发、测试和上线的时间节点。这个时间一旦确定,就必须严格执行,如果有需求无法完成要移到下一个版本做,必须经各个负责人确认后向大家发邮件说明。 评审 在我实习时,我作为开发,主要参加的就是交互的评审。这个评审的时间点在即将进入研发之前。其实主要就是交互设计师讲解交互稿,相关的开发人员提出自己的疑问,完善交互的可行性。 按理来说应该还有需求评审、设计评审和代码评审。这些在实习时,比如代码评审因为时间的原因,没有做。在木犀的项目实践中,我觉得我们可以加入这些评审。时间的节点在视觉和开发完成,即将进入测试之前。 JIRA JIRA是一款很有名的项目管理工具。在公司,我们一般是按需求创建Story,然后把交互、视觉和开发等任务关联到这个需求上。JIRA还有一个作用就是用来管理Bug。发现Bug的人可以提交Bug并指派给相关的责任人。JIRA有各种面板和筛选,可以方便的管理项目。 我们暂时可以用Tower的任务来发挥JIRA的作用。 测试 测试主要是由专职的测试进行。测试负责编写测试用例,以及进行测试。 当然,开发人员自己的自测是第一道防线。在开发完全之后,会进行冒烟测试,由测试提供用例,开发自行测试。用例主要和这一个版本的需求有关,主要就是检测一下需求中的功能是否被实现。只有冒烟测试通过了,才能进入真正的测试阶段。 在测试人员进行测试的阶段。开发人员往往还要配合进行若干次的回归测试。同时要及时解决测试提出的Bug。 首先测试开发环境下的项目,然后是测试环境,最后是预发布环境。预发布环境测试通过且Bug都解决的情况下,就可以上线了。 对于我们这样的小团队来说,专职的测试是不太可能有的。主要就是在项目组中加入一位兼职的同学担任测试这个角色,然后各个模块的同学做到充分的自测。视觉、产品和交互进行走查(就是以用户的角度使用产品,来看是否有问题)。这样就可以保证较好的产品质量。

October 3, 2016

一个组件的诞生(下)

上一篇中我们实现了一个通用的组件模型。但这个模型在前端这种用户交互密集的应用场景下,会遇到更多的挑战。 数据驱动 你接到了这样一个需求,要在banner上加几个指示的圆点(indicator),当前页面是第几个页面,第几个圆点就会高亮。你想着这个需求很简单啊,刷刷刷就写好了。 HTML <div class="banner"> <li> <img src="{{ url1 }}"/> <img src="{{ url2 }}"/> </li> <div class="button-left"></div> <div class="button-right"></div> <div class="indicator"> <li class="item"></li> <li class="item"></li> <li class="item"></li> </div> </div> JS import * as _ from "utility" var banner = new Banner({ el: document.querySelector("template"), data: { index:0, url1: "/somewhere/1.jpg", url2: "/somewhere/2.jpg" }, events: { ".left-button.click": this.onSwitch.bind(this, 1), ".right-button.click": this.onSwitch.bind(this, -1) }, methods: { onSwitch: function(event, direction) { // 切换banner逻辑 data.index += direction var indicators = document....

September 30, 2016

一个组件的诞生(上)

你是一个新手前端工程师,今天你接到了一个需求,写一个banner。banner嘛,大家都知道,就是一个用来展示图片的东西,不同的图片可以滚动切换。 你心想着这很简单啊,于是刷刷刷写了一个。 JS var banner = { init: function() { this.addEvent(); }, addEvent: function() { var buttonLeft = document.querySelector(".button-left"); var buttonRight = document.querySelector(".button-right"); buttonLeft.addEventListener("click", function() { this.switchClassName(1) }) buttonRight.addEventListener("click", function() { this.switchClassName(-1) }) }, switchClassName: function(direction) { // 切换Class类名 } } banner.init(); HTML <div class="banner"> <li> <img src="1.jpg"></img> </li> <li> <img src="2.jpg"></img> </li> <li> <img src="3.jpg"></img> </li> <div class="button-left"></div> <div class="button-right"></div> </div> 正当你觉得自己可以刷知乎的时候,主管把你叫到一边,告诉你: 如果你这个页面上需要有两个banner,怎么办呢?复制粘贴吗? 如果在需要在某个时间点从页面上移除banner,怎么办呢?手动remove节点吗,销毁对象吗? 如果banner中图片的URL是从服务端API请求到的动态的数据,难道要用一个个用修改图片的src的方法来显示这些数据吗? 听到这里,你的内心是崩溃的。 面向对象大法 这时,你想起了大学时学过的面向对象。你把你的组件改成了这样: JS...

September 29, 2016

《编码》导读

《编码》这本书被列了入我们团队技术方向培养方案的必读书目,可见这本书的地位。因此,我觉得有必要来撰写一篇导读,来帮助大家看透一些问题,把这本书上的知识和课本中的知识对应起来,并且形成一个体系。 《编码》讲的是什么呢?笼统的说,就是用自底向上的角度一层层的解析计算机的原理。比如第一章到第三章将的是编码的本质——信息和符号的对应。第四章到第六章讲的是电信号可以用来表示一种编码,从而传递信息。第七章到第九章介绍了二进制,也就是计算机系统中最基本的编码形式。第十章:逻辑与开关和第十一章:门,介绍了布尔代数,以及如何使用门电路进行布尔代数的运算。第十二章和第十三章介绍了二进制加法器和减法器,加法器已然是现代CPU中运算单元的雏形了。第十四章介绍的反馈与触发器是实现计算机中储存芯片的基础。第十五章字节与十六进制是为理解后续的存储器数据格式而准备的。第十六章讲了存储器的组织,这便是现代计算机系统中存储系统的原理。第十七章,是全书最难的一章,讲了如何制作一个CPU。CPU本质上就是一个可以编程的运算器。第十八章介绍了从算盘到机械结构到电子管再到晶体管的技术革命,晶体管、超大规模集成电路使得现代CPU的出现成为了可能。第十九章介绍了Intel 8080CPU的指令集。第二十章介绍了字符的存储格式,这可以帮助你理解计算机是如何存储文本的。第二十一章:总线,介绍了计算机系统中CPU、内存和I/O设备如何通信。第二十二章是一个简短的操作系统简史,注意到从这里我们涉及了软件层面的内容。第二十三章定点数和浮点数比较独立,主要讲了计算机是如何存储浮点数的。第二十四章和第二十五章则介绍了编程语言和图形学基础。 我们可以清晰的看到从硬件到软件,从具体到抽象,从底层到顶层的一个脉络。 这本书其实包含了我们计算机专业中《数字逻辑》、《计算机组成原理》、《微机系统与接口原理》三门课的主干内容。作者没有试图灌输大量的理论知识,而是试图将这些知识串联起来,包括最后的操作系统和编程语言,其实和计算机硬件是有着不可分割的关系的。在《编码》这本书中你就可以很自然的在章节的推进中体会到这种联系。 下面我会对一些章节进行详细的解读。主要是解释难点,拓展一些知识,并且写清这一章节的知识对应我们的哪门专业课,而专业课和这本书的要求有何不同等等。 我希望这本书可以让大家看清计算机的本质,计算机的本质其实就是一个处理信息的机器。你输入数据,计算机输出数据。首先我们需要一套编码系统来表示这些数据,而二进制的编码正好与半导体的性质相吻合,我们可以用布尔代数来对现实问题进行建模,通过门电路来表示这个布尔代数,从而制造出可以进行某种运算的电路。而如果这个电路可编程,再加上输入输出接口,那就是一个CPU了。至于是半导体和二进制之间孰先孰后呢,你可以做进一步的研究(从书中来看,第一台计算机ENIAC是采用十进制的,所以看来应该是半导体在先,二进制在后)(所以今后如果计算机的物理介质有了更新,我们也许就会换一种新的编码方案了)。 在书中大量讲到的门电路、继电器等等属于实现层面,大家了解一下就好。我们不是电气工程师,我们是软件工程师。当然理解硬件层面细节对于写好代码是很有帮助的。这主要解释的是一个“为什么”的问题。 你了解了CPU的指令集,和CPU的工作方式,你就理解为什么我们在程序运行时需要“栈”这个数据结构。 你了解了字符的编码以及浮点数的编码,你就不会奇怪于为什么0.1+0.2不等于0.3。 我们要关注的主要是如何用编码来抽象问题。比如用二进制来表示数据,进行运算。比如用指令来对CPU进行编程。比如 第一章:至亲密友、第二章:编码与组合和第三章:布莱叶盲文与二进制码 前几章就举了几个编码的例子,让你知道——编码可以用来传递信息。然后布莱叶盲文是一种二进制的编码,因为和计算机的二进制特点符合,所以被拉出来讲了。接下去几章就是铺垫一些电学基础,让你知道我们可以用开关的开和闭,来表示1和0的编码。这样后面就可以借助这个特性,拿开关和电线来组装计算机了。 第四章:手电筒的剖析、第五章:绕过拐角的通信 第五章里最重要的概念就是接地。大地电阻很大,但大地是导体。电子只要流动就能形成电能,电子是哪里来的?是不是一个环路?这个不重要。接地处电子往大地那里移动,电势就低。电池那边的电子就可以往接地处移动了。 有人说,那我一个电路,中间拿掉一个导线,为什么不能接通?因为空气的电阻太大了··· 电子没法移动到空气中。 第十章:逻辑与开关与第十一章:门 第十章首先介绍的内容是:布尔代数。 布尔代数,其实在我们的《离散数学》课里就接触过。注:如果没学过离散数学,可以买《离散数学及其应用》这本书学习,这里说到的形式逻辑主要指命题逻辑《离散数学》里一开始讲的逻辑,比如这种形式的: 我们知道一个命题可能是真(T),也可能是假(F)。那如果我们把T和F换成1和0,然后将命题逻辑中的合取,析取,否定等关系换了一种表示形式,这就成了布尔代数。 布尔代数中每个变量都是一个{0,1}的集合。命题逻辑中的合取在布尔代数中用·或者AND表示,而析取则用+或者OR表示。否定则用表示。 在第十一章中,有一个布尔表达式: (M·N·(W+T)+(F·N·(1-W))+B) 我们代入每个变量之中,运用布尔代数运算符的规则,就可以计算出这布尔表达式的值是True或者是False。 这就是问题的关键了,布尔代数是一个抽象的计算模型。我们可以将现实世界的问题,抽象成布尔表达式(当然这个最终是因为我们可以用命题逻辑来表示现实世界的问题)。 讲到这里,似乎和计算机没有什么关系。但最精彩的部分就在下面,我们可以用布尔代数设计电路。 一个机械开关,有开启和闭合两种状态,可以用来表示0或者1。这个是《编码》前几章大力铺垫的一个事实。既然这样,那我们就可以用两个开关的并联或者串联来表示AND和OR逻辑。更进一步,我们可以用开关和电线表示任何布尔表达式。 值得注意的是,用布尔代数可以用某种电路来等价的表示这个事实。从在19世纪50年代乔治·布尔发明布尔代数开始,一直到20世纪30年代才被信息论之父香农发现。尽管逻辑电路所需的开关和电线在19世纪都已经存在了。可见这种等价关系的革命性意义。 当然,反过来,布尔表达式也可以指导我们设计电路。事实上这个是目前所有逻辑电路设计的理论基础。 《数字逻辑》这门课讲布尔代数,主要是作为设计电路的一种工具。假如我们要设计电路,解决这个现实问题。这个问题被抽象成几个输入和输出。这些输入和输出是可能是0或者1。我们知道不同变量输入时对应的输出,也就是所谓的真值表。利用真值表,我们可以写出一个布尔表达式。但这个布尔表达式可以化简。我们可以用卡诺图等方式进行化简,化简的目的是简化电路。最终,化简的电路可以转化为门电路。 《编码》里只是简单的介绍了布尔代数和门电路,没有深入的介绍电路的设计。这是比较明智的,因为如何设计电路不是我们要关心的话题。《数字逻辑》中在电路设计这块涉及的还有功能表到布尔表达式的转换,布尔表达式的化简等等。有兴趣的同学可以了解一下。 总结一下,这两章对我们最大的启发就是:逻辑电路设计的理论基础是布尔代数。布尔代数可以用逻辑电路进行等价表示。而逻辑电路则是计算机所有硬件的基础。 第十二章:二进制加法器 这章一开始说: 如果我们可以造出加法器,同样地,就可以利用加法来实现减法、乘法和除法,计算按揭付款,引导火箭飞到火星、下棋,以及填写我们的话费账单 大家可能觉得这是夸张的说法,但其实加法器的确就是CPU中最重要的部分。实现了加法器,再加上存储系统,和控制器,就是一个基本的计算机了。 所以这章内容可以说是《编码》这本书介绍的如何制造一台计算机中的第一次飞跃(第二次飞跃是第十七章,自动操作)。 说的很神奇,其实加法器就是由简单的门电路构成的一个电路而已。 我们已经知道了各种门电路的输入输出,那问题就是,如何来利用门电路,设计一个相对复杂的电路呢? 这部分的知识对应的是大学中的《数字逻辑》课程。电路的设计有一套成熟的方法,当然我们先不考虑那么多,先来看看《编码》的作者是如何设计这个加法器电路的。 对于一个电路,我们首先要确定输入和输出,并写出一个真值表。 我们进行二进制数的相加时,会分别相加两个二进制数的每一位,如果有进位,则将进位加入下一位二进制数的相加中。 这样,我们就可以将两个二进制数的加法,转化为多次的一位二进制数加法。一位二进制数加法的输入是两个个加数。输出是一位的二进制数,以及一个进位数。比如输入是1加1,输出结果是0,进位位是1。 现在我们就可以列出两个真值表,一个是加法的真值表,一个是进位的真值表。 +加法 0 1 0 0 1 1 1 0 +进位 0 1 0 0 0 1 0 1 好了,现在我们可以进入第二步,根据真值表,设计电路。 我们要设计这样一个电路,输入A和B,都是一位二进制数,输出结果C和进位数D,也是都是一位二进制数。 我们观察真值表的输入和输出,发现进位表的真值和与门是一样的。而加法表的真值和异或门是一样的。 P141页的半加器,就是简单地将一个异或门和一个与门连到相同的输入端。异或门输出的就是加法结果,与门输出的则是进位的结果。 为什么这个叫半加器呢,因为这个半加器还无法完成真正的加法,因为它的输入中没有考虑进位。真正的一位加法器,输入有两个加数,以及进位位,输出结果和进位位。 P142页顶部的图就是全加器。它的原理就是级联两个半加器,将第一个半加器的结果和输入的进位位相加,对于两个半加器输出的进位位,则用一个或门来处理。或门在这里其实也是起一个加法器的作用,只不过两个级联的半加器的进位输出不会同时为1*(如果前一个半加器产生进位,那结果必然是0,那后一个半加器就不可能产生进位了)*,因此不用考虑输入都为1的情况。 P144页底部的图,讲8个全加器连接起来,前一个全加器的进位位输出是后一个全加器的进位位输入,第一个全加器的进位输入是0。这样我们就得到了一个8位加法器。...

September 15, 2016

2016 JSConf China见闻

今年9月3号-4号的JSConf是我第二次参加开发者大会。这次JSConf可以说是被我期待很久的。两天,十多个Topic,来自各大国外公司的大神,Vue、RxJS、WebVR等话题,让我非常期待。 然而最终的结果只能说是差强人意。真正有干货的主要是Ben Lesh的RxJS以及James Kyle的How to build a complier,然而恰恰因为他们都是比较活跃的工程师,所以他们这两个演讲我都在youtube上看过了。至于Vue,并没有讲很多的干货,主要还是来圈粉的。 其他的talk里面,PM2作者的分享还是非常solid的,他当场开源了grid control这个项目。朴灵的分享尽管主要是打广告,但是还是要我知道了Node性能调优里内存泄露、CPU占用和GC频繁三个方向。 WebVR和3D这些,说实话只能做到Demo级别的分享。这也是免不了的,外行只能看热闹。主要是给大家演示一下,如果能有启发就再好不过了。 不过这次大会上我的确看到了很多微博上关注的圈内人,等以后我在圈子里混熟了,水平更好了。那也许可以来参会和大家聊聊。 其实FEDay的质量明显要更高一些,尽管有翻译英文演讲这种笑话,但是干货明显更多,整体的技术气氛也更好一些。 总之,对于国内的技术大会,今后还是要慎重的选择。不然还不如在家看youtube上大神的分享呢。

September 7, 2016

Regular Devtools开发手记

Regular Devtools的前辈们 Dan Abramov的Redux Devtools把前端框架开发者工具推向了主流视野,他在React Europe 2015上的这个演讲被疯转。Redux Devtools可以说是独领风骚,开启了新一代前端框架开发者工具的潮流。 React Devtools也可以算是前开发者工具的祖师爷之一了。React Devtools不但有常用的组件树查看功能,还能直接查看JSX代码,筛选组件,双向修改state,查看组件源码等等。 而Regular Devtools主要参考的则是Vue Devtools。Vue Devtools是一个轻量,简洁的Devtools,只实现了开发者工具中最核心的功能。而Vue与Regular在某些方面比较相似,所以Vue Devtools就成为了我开发过程中的一个重要参考。 ...

July 28, 2016

发布Regular Developer Tools

为什么我们需要开发者工具? 现在五花八门的开源前端工具里面,有一些致力于解决前端不够工程化的问题,比如Webpack、Gulp、PostCSS。有些则致力于加强编程语言的特性,比如Babel。而有一部分工具则是为了提高开发者的体验,比如React Devtools、React Hotloader和Redux Devtools。提高开发者体验的工具,从某种角度来说,其实和其他直接参与到前端开发工作流中的工具一样,最终都是为了提高生产力。 React Devtools已经足够惊艳,而Redux Devtools带来的Time-travel Debugging和React Hotloader的热更新则让人直呼这是黑魔法。 所以,被这些开发者工具惯坏的我们,怎么能接受Regular没有Devtools的现实呢? Regular Devtools初印象 Regular Devtools是一款Chrome拓展。主要的功能是展示组件树,以及查看每个组件的数据。开发者对组件的操作,造成组件树和数据的变化,都会在Devtools中实时展现。 比如这样: 更强大的是,你可以检查当前组件对应的DOM节点,这个节点会在Chrome Devtools中的Element Tab中出现,就像你平时习惯的右键inspect一样。 反之,如果你正在检查一个DOM元素,此时你可以切换到Regular Tab,Regular DevTools会自动选中那个DOM节点对应的Regular组件(如果那个DOM节点是Regular组件渲染出的)。 比如这样: 特性 对Regular Devtools有了初步印象之后,下面就详细介绍一下它的功能 左侧Element View显示当前页面的Regular组件树,右侧State View显示被选中组件的状态。 Element View和State View都会随着页面上组件和状态的变化实时更新。 提供DOM节点和Regular组件的双向查找:可以点击State View中的Inspect查找当前Regular组件对应的DOM节点。 也可以在Element选项卡中选中DOM节点后切换到Regular选项卡,如果被选中的DOM节点由Regular组件渲染,那么这个Regular节点会被自动选中。 页面刷新时Devtools会自动重新加载,你也可以通过顶栏右侧的按钮手动重载。 通过include方式被引入的组件将会在组件树中注明,并且在组件树种作为视觉父节点this.$outer子节点展示。 安装 读到这里,想必你一定蠢蠢欲动了,那就快来体验一下Regular DevTools吧。 Step 1 克隆Regular Devtools仓库 Step 2 打开Chorme的chrome://extensions/页面 Step 3 点击Load unpacked extension··· Step 4 选择你刚刚克隆的仓库文件夹 Step 5 大功告成! 打开一个使用了Regular组件的页面,打开Chrome Devtools,选择Regular选项卡,如果一切顺利,你将在左边的Element View看到当前页面的组件树,然后就可以开始愉快的开发了! 注意,你的项目必须使用Regular v0.4.5或更高的版本 如果你手头上没有使用最新版Regular的项目,可以先拿这个Regular Todo例子尝鲜 开发手记 对这款Devtools的技术实现有兴趣的同学可以浏览Regular Devtools开发手记这篇博客。 已知的问题 这些问题主要是由于某些特殊组件$outer属性的指向和正常组件不同造成的。...

July 26, 2016