All Posts

[译] 寻找时间成为一名更优秀的程序员

原文链接 https://medium.freecodecamp.com/finding-time-to-become-a-better-developer-eebc154881b2#.t1uuonhtf 没有时间做任何事。这就是你感受,不是吗?没有时间学习你觉得你需要学会以跟上潮流的知识。没有时间回过头去重构那些丑陋的代码。它至少能工作,何况截止日期马上就要到了。没有时间编写单元测试。没有时间为今后要维护你代码的伙计编写文档或注释。没有时间思考。没有时间呼吸。没时间! 好吧…如果你能花时间阅读本文,我保证你自己会为那些重要的事找到更多时间。 我曾经以为成为一名伟大程序员的唯一途径就拼命工作。我的健康,友谊,家庭都因此受到影响。理解以下5个关于时间管理的真相拯救了我。 1. 你不需要为了赶潮流而学习每一个新事物 你不需要 毫无疑问一个优秀的程序员应该保持不断的学习,但是你把学习的焦点放在哪儿能在很大程度上影响你需要花费的时间。 “老的事物已经死去,(终归老去的)新事物万岁!” 首先,不要被出现在那些每37秒就宣布一个新标准的博客上的头条所愚弄。这些新技术,新框架,新特性中的大多数永远都不会被认可与采纳,因此你根本不需要了解它们。那些真正能够脱颖而出的也会经历比博客圈和发明厂商宣传的长的多的时间才会被采用。公司投资于他们的技术栈 —— 和那些小创业公司不一样,他们不可能说变就变。所以,放松一下,你的职业生涯很安全。 把你的学习聚焦在三方面,以下面列出的顺序为优先级: 1. 基础 —— 当你有了非常扎实的基础知识之后,学习新技能会变得非常容易。举个例子,如果你对JavaScript有着深入的了解,你就可以飞速的掌握任何新的JavaScript框架。如果你深入学习了面向对象编程,你也能快速掌握任何新的面向对象语言。深入学习基础知识会极大的提高你的学习效率。始终把提高对基础知识的掌握放在第一位。 2. 你最常使用的技术栈的最新版本/特性 —— 存在一套你每天都会使用的技术栈。这些是可以帮你养活你及你家人的工具。当这些工具的新版本发布时,投入时间学习是很值得的。 3. 由市场领导者主导的流行技术 —— 如果一个颇具规模的公司,比如Google,Facebook或是Microsoft提出某项新技术并已经开始小有名气,这也值得你的关注。市面上曾经有许多JavaScript框架争夺人们的注意力,然后Angular和React出现了并将那些框架彻底消灭。我不是说今后不会有搅局者出现成为下一个焦点,但实际情况往往是杂牌技术只不过是一些噪音。 学习时间应该是你日程安排的一部分。每天抽出一点时间来学习。这不需要很长的时间,即使是每天花25分钟阅读和练习也可以快速积累。 2. 编写优秀的代码比编写糟糕的代码需要更少的时间,但感觉上并不是这样 时间是一个扁圆 你很可能喜欢开发完几个新功能后当你试着运行并且发现似乎可以正常工作时那一刻的感受。但那只是你时间投入的开始。在一个功能上投入的时间包括了后期调试的时间,重构花费的时间以及解决由于开发时的不良设计导致的其它问题的时间。当你开始以这种方式认识你的时间投入时,你会发现,从长远来看,更少的错误和更好的设计是值得投资的。 你可以做两件事来减少代码中的错误并实现更好的设计。 1. 使用测试驱动开发 首先编写测试,然后编写代码以使测试通过。这不仅会减少bug还会导致更好的设计,因为当你按照可测试的方式组织代码时,你最终会得到更小,更简单的,依赖更少的函数。 2. 使用迭代的设计方式 在你的代码能真正工作之前,不要花时间试图让你的代码变完美。你永远不可能在脑子里把它设计的完全正确。你必须敲击键盘来生成实现预期功能的代码。问题是程序员尝尝犯两个常见的错误:要么时花了太多时间思考而没有足够的时间动手,要么就是不去优化他们最初的方案。遵循最初由Kent Beck提出的口头禅:“make it work, make it right, make it fast” —— 按照话中的顺序。 3. 7x24的工作不会让你成为英雄,管理预期才会 下班到家,开始工作! 这一条几乎杀了我。我曾经同意并承诺过我老板或客户提出的任何疯狂的时间表。我害怕说“不”。我害怕让任何人失望。为了交付我可以做任何事。我曾经睡在办公桌下,有过多次长达40+小时的马拉松式的编程经历。 起初我是一颗耀眼的明星。别人对我大加赞赏,我感觉自己像是个英雄。但是我设置了一个不可能实现的预期。不可能长久的像那样工作。最终我开始疲惫不堪,生病并错过截止日期。我开始得到不可靠的名声。这是个坏消息。 最后我终于明白真正的英雄是那些持续可靠的人。他们说到做到。要成为那样的英雄的唯一途径就是管理预期。 你需要控制时间表以便总是可以按时交付高质量的工作。起初这会非常困难。这意味着必须说“不”并把需求打回。 在最开始,你的老板和客户不会被你的拒绝刺激到。但一旦你证明自己是值得信赖的,一切都将开始改变。 随着时间的推移,其它程序员会迟到,交付马虎的工作或是筋疲力竭变得不可靠。这时你就会成为团队中真正的英雄。事实上,学会这一点让我成为我的领域里最受欢迎的咨询顾问。由于狠抓预期管理,我在质量和时效性方面建立了良好的声誉。 4. 不是所有花在“改善”代码上的时间都会得到相同的回报 花费时间是一种投资。和所有投资一样,投资回报率是一个合理的期望。你的所得至少应该和投入一样多,希望是越多越好。 我们已经谈论过“make it work, make it right, make it fast”。这个是不错的口头禅但这里有一个陷阱:“right”并不意味着完美,“fast”也不是说越快越好。

koa框架源码阅读笔记 之 lib/application.js

最近在学习ES6 Generator特性时发现了koa这个基于Generator的Web框架,它可以让开发者以一种“同步的方式”编写包含各种异步请求的Web应用。下面是关于它的一段中文介绍: 由 Express 原班人马打造的 koa,致力于成为一个更小、更健壮、更富有表现力的 Web 框架。使用 koa 编写 web 应用,通过组合不同的 generator,可以免除重复繁琐的回调函数嵌套,并极大地提升常用错误处理效率。Koa 不在内核方法中绑定任何中间件,它仅仅提供了一个轻量优雅的函数库,使得编写 Web 应用变得得心应手。 从介绍中可以看出这又是一个小而美的框架。到GitHub项目页面上看了一下发现源码里只有4个JS文件,总代码量只有1571行(以2015年12月26日最新稳定版1.1.2为准)。于是决定把代码clone下来学习一下,本篇会先从整个框架的入口文件lib/application.js说起。 总体说明 lib/application.js文件export出的是一个构造函数,用来创建一个koa应用。一个koa应用最常用的方法有2个: - listen(port) 执行listen后会通过http.createServer启动一个服务器并监听指定端口 - use(middleware) 注册一个中间件,一个koa应用可以注册多个中间件, 处理请求时会按照中间件注册的顺序执行这些中间件。 更多详细信息可以参考GitHub文档页面。 代码注释 function Application() { if (!(this instanceof Application)) return new Application; this.env = process.env.NODE_ENV || 'development'; this.subdomainOffset = 2; this.middleware = []; this.proxy = false; this.context = Object.create(context); this.request = Object.create(request); this.response = Object.create(response); } 以上就是Application构造函数的定义,很简洁,主要做了以下几件事情: - 通过instanceof判断来支持不带new关键字的调用。 - 设置应用运行环境,会从环境变量NODE_ENV读取,默认值为development - 声明this.

在JDF项目中使用ES6新特性

Note: 目前虽然JDF已经支持使用ES6开发脚本,但线上使用还在前期摸索阶段,请大家根据自己项目实际情况评估使用情况 最近利用业余时间给JDF增加了ES6代码的支持。背后的原理是在项目构建阶段利用Babel将.babel文件转译成ES5代码。关于Babel的更多使用方法可以参考其官网的配置文档。 下面开始正式介绍在利用ES6特性开发前的准备工作: - 升级JDF到最新版本(>= 1.8.2) - 进入JDF项目目录,安装基本的Babel preset和plugin。关于preset和plugin的更多说明请参考文档 npm install babel-preset-es2015 npm install babel-plugin-transform-es3-member-expression-literals npm install babel-plugin-transform-es3-property-literals 配置项目的.gitignore文件,忽略node_modules目录。在文件中添加以下内容: **/node_modules/ 引入es6-base.js(包含 es5-shim和babel-polyfill) 至此,所有的准备工作就完成了。接下来就可以利用ES6的各种新特性开发了。需要注意的是所有包含了ES6特性的脚本文件扩展名必须是.babel,否则JDF是不会对其进行编译的。 最后是一份简单的FAQ: Q: ES6都有哪些新特性,有没有推荐的学习资料? 当然有,中文版的有阮一峰出品的ECMAScript 6入门。 英文的有: - ES6 In Depth来自Mozilla团队博客的一系列文章,每篇讲解一个新特性。建议按时间顺序阅读。 - Understanding ECMAScript 6来自Nicholas C. Zakas大神的一本ES6小书。 Q: 我想使用额外的Babel plugin或preset该怎样配置? 只需在package.json中指定需要额外引入的plugin或preset,例如: "babel": { "plugins": ["syntax-async-functions","transform-regenerator"], "presets": ["stage-0"] } 并在JDF项目的目录中安装相关npm package即可。 Q: .babel文件编译出来的代码我看不懂,出了问题如何调试呢? 我们看到的经过Babel转译后的JS是这样的: 这里包含了大量的Babel生成的代码,非常不利于调试。不过不用担心,JDF在本地开发模式中启用了sourceMap,你只需在Chrome开发者工具中的Sources Tab中在你要调试的JS文件目录下找到同名的.babel文件即可。相关JS中的报错信息,都会被映射到转译前的.babel文件的对应位置。 关于sourceMap的更多信息请参考这里。

[译] TrimPath模板引擎语法

本文翻译自JavaScript Template Syntax 表达式与表达式修饰符 ${expr} ${expr|modifier} ${expr|modifier1|modifier2|...|modifierN} ${expr|modifier1:argExpr1_1} ${expr|modifier1:argExpr1_1,argExpr1_2,...,argExpr1_N} ${expr|modifier1:argExpr1_1,argExpr1_2|...|modifierN:argExprN_1,argExprN_2,...,argExprN_M} expr可以是不包含右花括号}的任何合法JavaScript表达式 修饰符的格式是modifierName[:argExpr1[,argExpr2[,argExprN]]] argExpr可以是任何合法的expr Examples: ${customer.firstName} ${customer.firstName|capitalize} ${customer.firstName|default:"no name"|capitalize} ${article.getCreationDate()|default:new Date()|toCalenderControl:"YYYY.MM.DD",true,"Creation Date"} ${(lastQuarter.calcRevenue() - fixedCosts) / 1000000} 这里有一份内置的修饰符列表,你也可以通过TrimPath提供的API来创建自定义的修饰符。 表达式还可以写成${% customer.firstName %}这种形式,多出的%字符允许你的表达式中出现花括号}。例如: Visit our ${% emitLink('Solutions and Products', { color: 'red', blink: false }) %} 表达式中的空白字符是可选的,你也可以写成下面的格式: ${%customer.firstName%} ${%customer.firstName|capitalize%} 声明 声明标签可以嵌套使用。 控制流 {if testExpr} {elseif testExpr} {else} {/if} testExpr可以是任何不包含}的JavaScript表达式 testExpr不需要被(和)包围 示例:

AngularJS中的依赖注入

本文译自https://docs.angularjs.org/guide/di 依赖注入 依赖注入(DI)是一种解决组件如何获取其依赖这一问题的设计模式。 Angular injector子系统负责创建组件,解决它们的依赖,并按要求将它们提供给其他组件。 如果想深入了解DI,可以查看依赖注入的Wikipedia以及Martin Fowler的这篇Inversion of Control。 DI in a Nutshell 一个组件要想获取它的依赖可以通过以下三种途径: 直接创建这个依赖的一个实例,通常使用new操作符 通过全局变量查找已经被创建的依赖 依赖可以作为参数传递给需要它的组件 前两种获取依赖的方式并不理想,因为这会导致依赖关系被硬编码进代码中。这会导致更新依赖关系变得很复杂。在测试时尤其如此,我们通常会提供mock过的依赖关系以隔离不同组件间的测试,硬编码会导致每次测试都需要修改相关的源码。 相比之下,第三种方式是最可行的,因为它将解决依赖关系的责任从组件中移除。依赖只是简单的作为参数传递给组件: function SomeClass(greeter) { this.greeter = greeter; } SomeClass.prototype.doSomething = function(name) { this.greeter.greet(name); } 在上边的例子中,SomeClass并不关心如何创建或找到greeter,它只是在初始化时从参数列表里取出需要的依赖即可。 但这样做的问题是,寻找/创建依赖的责任被转交给了调用SomeClass的代码。 为了统一管理依赖关系,每个Angular应用都有一个injector。injector是一个服务定位器,它负责查找或创建依赖。 下面是一个使用injector服务的例子: // Provide the wiring information in a module var myModule = angular.module('myModule', []); 下面的代码告诉injector如何创建greeter服务。需要注意的是greeter又依赖于$window服务。greeter实际上是一个包含greet方法的对象。 myModule.factory('greeter', function($window) { return { greet: function(text) { $window.alert(text); } }; }); 下面的代码演示了如何创建一个injector并通过它来请求greeter服务:

AngularJS源码阅读之ngModel

数据的双向绑定可能是Angular最为人们熟知的特性之一。举个最简单的例子 可以在result页面中看到,每当在input中输入时,$scope中对应model的值也改变了。反之,当用户点击Set按钮在$scope中更新了model的值时,input输入框中的内容也对应更新了。 所有的这些魔法只需要我们在input元素上指定一个ng-model属性。可见ng-model这个directive是双向绑定这一特性不可缺少一点。本文就继续从源代码入手看看ngModel的实现方式以及其它directive是如何与ngModel交互的。 ngModel被定义在input.js。从名字上可看出,该文件还定义input这个directive,从这点也可以看出ngModel与input之间的紧密关系。 注: 文中所有涉及到的AngularJS源码均来自angular-1.2.8版本。 var ngModelDirective = function() { return { require: ['ngModel', '^?form'], controller: NgModelController, link: function(scope, element, attr, ctrls) { // notify others, especially parent forms var modelCtrl = ctrls[0], formCtrl = ctrls[1] || nullFormCtrl; formCtrl.$addControl(modelCtrl); scope.$on('$destroy', function() { formCtrl.$removeControl(modelCtrl); }); } }; }; 可以看出ngModel依赖于ngModelController以及可选的formController。ngModel在link方法中只做了2件事: 如果声明了ngModel的元素出现在一个form中,那么就向上层form注册自身 注册了$destroy事件的监听器,该事件触发时告知上层form移除对自身的引用 link方法如此简单以至于我们还没看到关于数据绑定的任何信息。看来问题的答案都藏在ngModelController中了。接下来看看ngModelController中都发生了什么。 var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$parse', function($scope, $exceptionHandler, $attr, $element, $parse) { this.

Angular中的Controller

在Angular中,一个Controller是一个JavaScript构造函数。当一个Controller通过ng-controller附加到某个DOM元素上时,Angular会使用相应Controller的构造函数来初始化一个新的Controller对象。一个新的child scope会被创建并作为可注入的参数(就是我们看到的$scope)传递给该Controller的构造函数。 使用Controller: 为$scope对象设置初始状态 为$scope对象添加行为 不要使用Controller: 修改DOM —— Controllers应该只负责业务逻辑。将任何UI的逻辑放进Controller都会极大地破坏其可测试性。Angular中可以使用数据绑定用来更新页面中的数据或使用directive来封装DOM操作。 格式化input数据 —— 使用angular form controls 过滤输出数据 —— 使用Angular中的filter机制 跨Controller之间的代码或状态共享 —— 使用Angular中的service机制 管理其他组件的生命周期

AngularJS源码阅读之ngRepeat

注: 文中所有涉及到的AngularJS源码均来自angular-1.2.8版本。 注2: 本文结构为一段代码配一段说明,代码在上,说明在下。 从3月3号到现在,进入新公司也有1个月了。过去这一个月里主要负责公司内部某个DevOps工具开发过程中的前端部分。由于涉及到较多的网页交互,该项目的前端部分使用了AngularJS这个框架。本人这正好借助这个机会进一步了解了AngularJS的相关知识。 这两天项目主体完成,准备上线,算是有了一些自由时间,正好借此机会学习一下AngularJS内部的实现机制。 本篇来说说最常见的directive之一 ngRepeat 该文件的整体结构如下: var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) { var NG_REMOVED = '$$NG_REMOVED'; var ngRepeatMinErr = minErr('ngRepeat'); return { transclude: 'element', priority: 1000, terminal: true, $$tlb: true, link: function($scope, $element, $attr, ctrl, $transclude){ ..... } }; function getBlockStart(block) { return block.clone[0]; } function getBlockEnd(block) { return block.clone[block.clone.length - 1]; } }]; ngRepeatDirective是一个数组,它会被传递给directive方法用来生成ngRepeat这个directive。可以看到ngRepeat依赖于$parse和$animate两个服务。$parse用来将字符串解析成javascript函数。$animate用来给DOM改变附加上动画效果。文件最后声明了两个helper方法,会在稍后的分析中介绍到。 接下来把最大的篇幅交给这个directive的核心——link方法。 var expression = $attr.ngRepeat; var match = expression.match(/^\s*([\s\S]+?)\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?\s*$/), trackByExp, trackByExpGetter, trackByIdExpFn, trackByIdArrayFn, trackByIdObjFn, lhs, rhs, valueIdentifier, keyIdentifier, hashFnLocals = {$id: hashKey}; if (!

AngularJS Directive之compile与link

刚开始学习AngularJS Directive时,总是不太能搞明白compile和link两个属性的含义和区别。最近写的多一些,读的资料也多了一些,渐渐有了一些理解,简单记录下来: compile属性是一个函数。该函数会针对每一个directive的实例执行一次,在compile阶段: 不能访问scope 元素还没有被插入document 可以修改template,最终结果会被angular缓存 link属性可以有两种配置方式。 link属性可以被设置为一个对象,包含pre, post两个属性,分别对应一个函数(preLink, postLink)。preList,postLink的区别在于执行顺序。preLink是自顶向下的,先父节点,后子节点,postLink正好相反。 .directive{ return { link: { pre: function preLink(){ ... }, post: function postLink() { ... } } } } link属性也可以被直接设置为一个函数,则默认相当于设置了postLink函数。 .directive{ return { link: function link(){ ... } } } 等于 .directive{ return { link: { post: function post() { ... } } } } 由此可见,如果直接将link属性设置为一个函数,则link阶段的执行顺序是自底向上的。 link阶段会针对每一个被RENDERED的directive实例执行一次。此阶段: 可以访问scope(因为controller已经初始化完成) 元素已经被插入document中 不可以修改template(angular会直接使用之前缓存的内容)

使用CSS3实现逐格动画

首先我们有这样一张照片: 照片长度为1184px,高度为75px,共分为24格。 首先我们将DIV大小设置成单格照片的大小,49x75。这样在初始状态下看到的就是第一格的照片。 接下来定义的是最终状态也就是最后一格的状态,通过偏移量背景调整到最后一格。 最后就是定义动画效果,在下面的设置中时长2秒,动画会无限循环下去。 @keyframes wave { to { background-position: -1184px 0 } } @-webkit-keyframes wave { to { background-position: -1184px 0 } } #hahaha { margin: 50px auto; width: 49px; height: 75px; background: url('https://raw.github.com/loveky/loveky.github.io/master/assets/images/css3-stop-animation-background.png') 0 0; -webkit-animation: wave 2s infinite steps(24); animation: 2s wave infinite steps(24); } 效果如下: 由于照片是自己剪辑拼成的,所以偏移量不是太精确。。。