写在2016年的春节

2015年又结束啦。按照惯例,总要在年末记上一笔流水账。

跟2014年一样,依旧是过得忙忙碌碌。团队从7个人一下子扩张到近20人。
相比于2014年,多了一点不一样的色彩。跟很多很多人沟通,学习。对于所做的事情,对于所遇到的人。都有了新的理解。这,应该就是所谓的 “成长” 吧。

遇到了某个小妞,我觉得可能是过去二十多年里最幸运的事情之一了。
有很多感谢的话要说,就都先憋着吧。今年的生日过的很开心,谢谢。
忙忙碌碌的工作,搞得我有点迷失了自己,对于什么是重要的什么是不重要的。我觉得有必要需要重新静下心来思考思考了。

希望呢,2016年不要那么忙了。能有精力抽出来,考完驾照,买辆车。练练吉他,写首歌。多把精力陪陪身边的人,带着我们家小妞去很多有意思的地方,把我们家小妞带回家 :) ,这也算是新年的愿望吧,哈哈。
认认真真的生活,好好的对待身边的人。
踏踏实实的专注于手头的工作。
其他呢,看运气嘞 ~

coffeescript 词法分析

无论是对我们的代码解释执行,还是编译执行,词法分析都是作为第一个环节开始的。
词法分析的过程,可以理解为将字符序列转换为单词(Token)序列的过程。
本文中以如下的coffeescript代码为示例:

# Assignment:
number = 42
opposite = true
# Conditions:
number = -42 if opposite
# Functions:
square = (x) -> x * x

这里的单词(Token)是指一系列最小的对程序来说有意义的单元,例如"number", "=", "(换行)", "(空格)"等等。
对于coffeescript而言,有10中类型的token
1. identifier(标识符):脚本中各种独立的标示符,例如number,opposite等等
2. comment(注释)
3. whitespace(空格)
4. line(换行)
5. heredoc(本地注释):以"""或者'''开头的代码注释
6. string(字符串):匹配的coffeescript中得字符串,例如'test'(coffeescript中得字符串用单引号围起来)
7. number(数字):匹配的数字,包括了8进制数字和16进制的数字
8. regex(正则表达式)
9. js(js):匹配的正则为/^[^\\](?:\.[^\`])*`/,我觉得我的解释太TM多余了,还是看代码来的直观
10. literal(加减乘除,位运算,与或之列的符号)
输入的是如上的一串coffeescript的代码,输出则是按照这是种token分类好的程序的最小单元的一个数组,提供给语法分析器继续进行分析。
例如:


Enjoy it !
转载请注明出处
botobe.net
本文Github链接

coffeescript 编译调试剖析

工欲善其事,必先利器。我们用node-inspector来调试coffeescript的编译。
用全局的方式安装node-inspector

sudo npm install node-inspector -g 

新建一个叫coffee得目录,用来作为我们的工作目录。
在目录中新建一个package.json文件,可将如下内容填入其中(截止目前为止我们用coffeescript最新的1.8版本进行调试)

{
  "name": "coffee-debugger",
  "version": "0.0.0",
  "private": true,
  "dependencies": {
    "coffee-script": "1.8.0"
  }
}

再命令行中运行如下命令安装当前工程依赖的coffeescript

sudo npm install 

在我们的工作目录中,新建一个用来测试的coffeescript脚本main.coffee
这个时候工程中的目录结构应该是这样

下边就可以正式的开始进入主题了,打一个命令行,启动node-inspector

node-inspector

再打开一个命令行,并且进入到coffee目录中

node --debug-brk node_modules/coffee-script/bin/coffee -c main.coffee

在浏览器中打开URL 127.0.0.1:8080/debug?port=5858 就可以开始调试coffeescript的编译过程了(-c开关表示将main.coffee编译成为main.js脚本)

Enjoy it !
转载请注明出处
botobe.net
本文Github链接

[美团外卖] 长期诚聘前端,高级前端,前端技术专家

我们是谁?

我们是美团网的前端团队,负责美团外卖业务
我们是有趣的年轻人,待人真诚,做事靠谱,创业团队氛围,简单可依赖

工作职责?

指导初级工程师
持续优化,建设前端基础设施
支持美团外卖各个业务线前端开发需求,为用户呈现最好的界面交互

职位要求?

能独立完成前端开发工作;
了解对各种情形下的前端解决方案;
精通 HTML/CSS/Javascript 等前端技术,能轻松写出符合 W3C 标准、兼容主流浏览器的代码;

本科以上学历,计算机、数学等相关专业毕业(或者计算机基础非常扎实);
熟练的使用一门后台技术( node.js/python/PHP/java ),因为我们觉得,行于前端,而不应止于此;

同时,我们坚信一点,闻道有先后,术业有专攻,上边的都可以无视
我们欣赏的同学,需要有强大的自驱力;积极主动,有想法;并且知道如何不断持续的提升自己;精于问道,勤于实践
对技术有一定的热忱和激情,发自内心的热爱这份职业

我们提供了什么?

一份会令你满意的薪 [ 15k起 ],和能力成正比;优秀的同学会有期权激励
一套程序猿的顶级梦幻装备, Retina 屏幕的 MBP ,一把超舒适的人体工程学座椅,以及 27 寸外接大屏
一群聪明的小伙伴们
一个能够充分发挥你才能的轻松,愉快的工作环境
一家满了极客氛围的公司

所以?

坐标帝都,望京

如果你觉的似乎和我们存在了那么一点儿的相似和联系,就可以过来和我们聊聊吧
不用担心档期的问题,这个职位我们会长期为你留着!
邮件发送简历至: xukai#meituan.com => (# 改为 @)

原地满血复活

当我下定决心开始做一个有趣而有意义的事情时,我都没有想到自己的动力会那么大,被自己都吓到了~ 哈哈 :)

在博客杂草重生的荒废了n个月之后,趁着最近略有闲暇,噌噌噌的花了两个星期的业余时间,把博客重新又搞起来了。
新在Github上开了一个repo,把原先的数据库由mongodb改成了mysql。于是乎还是发现mysql用起来比较顺手啊~ VPS由原先的burstNet升级到了linode,express更新到了最新的版本,作为老本行的前端也做了深层次的优化。
瞬间有种鸟枪换炮,犀利哥走上了巴黎时装周感觉~

当然,博客最重要的还是内容啦。
从2010年6月份开始,到2014年的10月份,将近四年的时间遇到过不少人不少事,有傻逼的,又聪明的,有二逼的,有装逼的,有真诚的,有需要感谢的,有永远不想见的,有开心的,有难过的,有值得骄傲的,有痛苦的 ... ...
博客换过域名,换过内容,换过主题,换过技术架构。
总之贯穿了我毕业后的整个职业历程,也记录下了我的成长。看着自己从一个整天自怨自艾的小青年变成今天的自己。想想还是蛮欣慰的。这些年,没有白白的从指缝间溜走。
大大小小的写了约有四十来篇文章,网站迁移过几回,丢失了不少文章和图片。懒得去翻硬盘里那些百八十年的老数据去找了。以往对数据备份完全没啥概念的我,这回可是深有体会了。纸上得来终觉浅,这就算是成长的学费了吧。

总体看来,技术文章的数量在不断的减少,但是质量越来越高了,多了些体会和感悟。
重新上路,相聚无时,后会有期!

过年啦!

已经是年初五了,假期的日子过得就是这么的迅速啊,总是有昨天才放假的感觉。
回顾过去的2013年,有很多想说的话,留给2014年的结尾吧!

大家新年快乐,大吉大利!

2014.02.04
此文仅以刷一下微不足道的存在感
Merci !

记在2013的双12

又到一年的双12,在历史上那场著名的双12后52年,我也开始了星辰大海的征途。
不记得何时开始,给人们最大的关于12.12的印象莫过于12.12活动了,暂且抽象的理解为给我庆生了吧,哈哈。

对于2013年,遇到了一些人,奇葩也好,特殊也罢,凭借我一凡夫俗子的理解能力是真心无法理解他们的境界。我无法想象,这人是怎么做到,刚刚是还是一个表情,扭头过后又是另外 一种心态的。一切来得太过突然,着实让我吃了一惊。

认真对待我做的事情,真诚对待身边的每个人,扪心自问,我问心无愧!

n年后再回头看看这些事情,对于生活来讲也算是一种历练吧,过了就过去了。
希望上诉的言论不会让人为这是在抱怨,如果非要下个定义,我认为应该是写实。我不会去抱怨什么,因为最起码没人欠我钱,也没什么可以抱怨的了。
还能每天享受着灿烂的阳光,拥有者想走就走的自由,服雾这大帝都的空气的时候还有活蹦乱跳的拥有个健健康康的我,哈哈。
最重要的,在我困难,迷惑,郁闷,压抑的时候,爸爸妈妈还有媳妇儿会始终会站在我身边继续给我动力和支持。
关于我的生日,也谢谢你们每年总是第一时间送来祝福。

139邮箱,招商银行也会发来祝福短信。因为我知道,这些都是脚本,程序自动完成的。他们背后站着的都是一台台冷冰冰的机器。如果我从未用过他们的服务,也会接到这样的祝福吗?本质上就是不纯粹的。
就好比逢年过节的大伙们群发短信一样,好歹你也加上个祝福对象的名字哉!!再加上一句真心的问候,手指挪挪的功夫,否则意义在哪里?!
把一毛钱省着留给路边的乞丐吧,还能到他一句谢谢。要不就去买纸巾上厕所的时候擦屁股也比这样来的实在。

感谢那些给我梦想,给我机会的人。能回报你们的就是我的汗水和心底的一声谢谢。
也谢谢那些把我的梦想和计划推向边缘的人,让我认清了原来我们貌似平静的生活不是一个乌托邦。
骚年,一切才刚刚开始,我们的征途星辰大海!

本文Github链接

自定义你的javascript事件

浏览器内置了一些javascript的方法,比如说click, mouseover, mousein ... 可以在用户进行了一些浏览器内置的默认行为(点击,鼠标滑过DOM)的时候会触发。
说道自定义javascript事件,给大家印象比较深的也许是jQuery的bind方法,可以添加一些用户自定义的事件,再用trigger的方法来触发之。
基本的实现代码如下:

/**
 * 事件管理组件
 * 版权所有(C) 2013 EchoFUN (xukai.ken@gmail.com)
 * 
 * 2013.12.12 初始化文件。
 * 
 * Example:
 *   var test = document.getElementById('test');
 *   evt(test).bind('custom', function() {
 *
 *   })
 *
 *   evt(test).trigger('custom');
 *
 */

;var cacheId = 'cache' + setTimeout(function() {}, 0);

var evt = (function(global, cacheId) {
  'use strict';
  var isIE = !!document.attachEvent;
  var typeExpress = /^[a-z]+$/i;

  // 缓存所有的事件。
  var events = {};

  // 产生一个唯一的键值。
  var globalId = 1;

  function wrap(node) {
    return new _wrap(node);
  }

  function _wrap(node) {
    var _node = (node && node.nodeType == 1) ? node : '';
    if (!_node) {
      return 'Invalidate node !';
    }

    _node[cacheId] || (_node[cacheId] = globalId++);
    this._node = _node;
  }

  function _type(o) {
    var type = {}.toString.call(o);
    return type.slice(8, -1).toLowerCase();
  }

  var proto = _wrap.prototype;
  proto.bind = function(type, handle, capture) {
    if (_type(type) !== 'string' 
      || !typeExpress.test(type) 
      || _type(handle) !== 'function') {
      return 'Invalidate params !';
    }

    var node = this._node;
    if (isIE) {
      node.attachEvent('on' + type, handle);
    } else {
      node.addEventListener(type, handle, capture);
    }

    // 如果还未开始对这个对象有缓存,则加入缓存序列。
    if (!events[node[cacheId]]) {
      events[node[cacheId]] = {};
    }
    var evts = events[node[cacheId]];
    if (evts && evts[type]) {
      evts[type].push(handle);
    } else {
      evts[type] = [handle];
    }
    return this;
  };

  proto.unbind = function(type, handle, capture) {
    if (_type(type) !== 'string' 
      || !typeExpress.test(type) 
      || (handle && (_type(handle) !== 'function'))) {
      return 'Invalidate params !';
    }
    var node = this._node;
    var evts = events[node[cacheId]];
    if (!evts[type]) {
      return 'Not exist specified event !';
    }

    if (isIE) {
      node.detachEvent('on' + type, handle);
    } else {
      node.removeEventListener(type, handle, capture || false);
    }
    if (handle) {
      for (var i = 0; i < evts[type].length; i++) {
        if (evts[type][i] == handle) {
          evts[type].splice(i, 1);
        }
      }
    } else {
      evts[type] = [];
    }
    return this;
  };

  proto.trigger = function(type) {
    if (_type(type) !== 'string') {
      return 'Invalidate params !';
    }

    var args = Array.prototype.slice.call(arguments);
    args.shift();
    var evts = events[this._node[cacheId]];
    if (evts[type]) {
      var evtsList = evts[type];
      for (var i in evtsList) {
        evtsList[i].apply(this._node, args);
      }
    }
    return this;
  };

  return wrap;
})(this, cacheId);

完整示例请移步Github event.js文件

对javascript的事件自定义,只是其庞大事件系统的冰山一角。
口水横飞的讲了这么多,不知道你会不会有这样的疑问。
自定的这些事件有个蛋用啊!?

网上关于自定义事件的方法一抓能有一把,但是总结为何这样做的文章却很少。
具我在项目中使用情况看来,可以总结出起码有如下两种情形:
a). 在制作一些前端组件的时候,可以为特定的事件添加钩子
举个栗子:
人人网的表情组件(PS. 居然注释部分没有压缩掉哈,剩下了我的口水了。)
开发人员可以对new了XN.ui.emoticons这么个对象后的返回值添加各种事件。组件中已经在各个关键节点(渲染结构,绑定事件,展示)的地方fireEvent了这些事件。
这样就可以进行无侵入式的编程。在不改变原有逻辑的情况下,添加一些自己想要的行为,岂不爽哉!?

b). 模块化开发中,可以方面的为各个模块之间进行解耦
现在的前端工程里,有追求的前端们(咳咳,你懂的!)大多使用了common.js的规范来组织前端代码,各个模块的数据和操作都可能是独立的,但是模块和模块之间的沟通通过什么方式来进行呢?
(自定义事件!)
恭喜,都会抢答了。
细细说来,有点类似于前些文章中聊到的观察者模式,在我发布了一篇微博的时候,需要出发右侧的微博数目。这个时候,我就可以在发布框的模块中,trigger或者是fireEvent一下事先已经绑定在右侧数目计数器上的一个自定义的事件,这个举例比较简单,如果模块功能很复杂,就能做到充分的解耦。
能力有限,欢迎各路大神继续补充和拍砖。

写在结尾处:
之前的文章里很少有提及CSS的部分,之所以不涉及这块,并非不是很了解。奈何没有像@张鑫旭单口相声般的牛逼技能,写出来的文章就像懒婆娘的裹脚布一样。这个也是需要我学习和将强的地方啦,哈哈。

Enjoy it !
转载请注明出处
botobe.net
本文Github链接

2013.12.13
最近情绪不佳,有点小郁闷!
依旧Merci !

单例模式的在前端开发中的应用

公司项目中使用到了单例模式,于是撰文一篇。
单例模是Gof提出的一个简单的设计模式。

在前端项目中用到单例模式,给整个前端工程带来的优势是节省了浏览器的CPU的使用,在占用内存方面未必占有优势
从算法的角度看来,算是以空间换时间的一个经典的例子。

常见的地方一般使用在,前端创建需要大量重复使用的一段具有类似功能DOM的时候。
例如微博中的转发的弹框。

让我们先来看看创建了这么一个转发弹框的基本流程。
a). 构建HTML
b). 拼装模板(插入一些动态数据)
c). 绑定自定义事件
d). 插入文档流
顺序可以改变。

这些流程的操作是需要消耗系统的CPU资源的。
让我们考虑在这个弹框足够复杂的情况下。
如果你使用的是chrome神器,自然不会感觉到速度上的差异。但是如果你使用的是IE6之流的老古董,会体验到明显的感觉到差异。
作为一个苦逼的前端,如果你开发的是像google,taobao那样的产品,那么恭喜你。你可以横着脑袋,翘着二郎腿对用户说“赶紧给老子去升级你的浏览器,否则滚蛋!”。
不过我相信98%的前端同学面临的选择题都是:要么用户留下,要么你滚蛋。
那你会选哪个捏?

开个玩笑,言归正传。
实现前端开发中的单例模式的方式有很多种,但是总体来说都是有着类似的思路。
即,在页面上执行一次需要的逻辑(例如上边所说的a, b, c步骤),将执行的结果进行缓存在一个闭包的变量中(防止污染全局变量)。而后,每次的调用只需要执行d步骤就可以了,如果有额外的逻辑,可能还要在d步骤之后或者之前再加上个b步骤(如果每次的展示界面略有差异的话)。

巴巴儿地扯了半天,就像我们浏览某榴网站一样,有些同学可能要抱怨了 —— “没代码你说个j8!“
于是,借花献佛的引用了在腾讯AlloyTeam【Javascript设计模式1】-单例模式中的代码。

var singleton = function( fn ){
  var result;
  return function(){
    return result || ( result = fn .apply( this, arguments ) );
  }
}

这是采用了高阶函数的方式来实现单例模式。
按照这篇文章原作者的思路(可理解为伪代码),调用的方式为。

var createMask = singleton( function(){
  return document.body.appendChild( document.createElement('div') );
})

按照我们所讲的a, b, c, d的四四中执行方式来讲,d步骤和b步骤进行了合并。这种方式并不是太好。
私下认为可以把调用方式改为如下,可能会更加形象。

var getMask = singleton(function() {

  // 执行a, b, c步骤。
  var mask = document.createElement('div');
  console.log('A div has created for once !');
  return mask;
});

var blankMask = getMask();
document.body.appendChild(blankMask);

window.setTimeout(function() {
  blankMask.parentNode.removeChild(blankMask);

  window.setTimeout(function() {
    document.body.appendChild(getMask());
  }, 1000);
}, 1000);

结语:
而前端环境的局限性在于变量的生存周期再一次页面的重新加载中。
而不像后台语言中能做到的那样,一次创建,随意使用。
使用单例模式,能使得代码更易于维护,再者算是满足了程序猿们的小小精神洁癖吧,哈哈,何乐而不为呢?

Enjoy it !
转载请注明出处
botobe.net
本文Github链接

2013.11.06
一切安好。
Merci !

Chrome自定义Userscript脚本

随着现代浏览器的不断升级,浏览器给我们带来的已经不仅是浏览网页的体验。而是提供了各种的接口,来满足用户多样化的需求。从这种角度看,浏览器已经渐渐的在脱离它本来的初衷,而是渐渐的朝着Web平台的方向在发展。

本文我们来介绍的是Chrome自己提供的Userscript脚本的机制,用户可以在浏览器中加入自己写的脚本达到各种的需求。删除页面上牛皮癣似的广告啦,添加一些自定义的数据啦,当然还有个貌似最重要的在某神奇的车票网站上刷票啦!
这一切都可以通过添加Userscript脚本的方式来实现!!
还在等什么!?是不是突然发现无比的神奇呢?是不是觉得自己已经摆脱了屌丝的命运,俨然化身成互联网的神了呢?
哈哈,自己DIY,一切就是那么简单!

首先要创建一个以.user.js为后缀名的js脚本文件,例如创建了一个名为home.user.js的文件。
安装脚本的方式也是非常的简单,在浏览器的地址栏中输入“chrome://extensions/”,你就可以看到当前你的Chrome中所包含的扩展包。将我们的home.user.js文件拖拽入其中。

会出现

的提示,说明插件已经安装完成。
这是最新的Chrome为开发者内建了这种简单的扩展机制,非常的方便。

这只是添加了这么一个Userscript空格的脚本,基本上没什么作用。我们也无法看到任何的效果。
下边我们来往home.user.js中添加代码。

从用途上来讲,Userscript代码的结构可以分为两大部分,第一部分是Userscript的注释。用来告诉浏览器,你的这段脚本的作者,名称,日期,作用范围,版本等等一些基本的信息。第二部分则是开发者自己编写的功能代码。
让我们用一个最简单的删除Gmail顶部的牛皮癣小广告的作为例子。

头部的注释的一个简单范例如下所示:

// ==UserScript==
// @name           Just for testing Chrome APP
// @description    Yes
// @version        1.0
// @author         Kai.XU
// @namespace      http://botobe.net/
// @include        https://mail.google.com/*
// ==/UserScript==

让我们一条条的做个解释。

首先外层的

// ==UserScript==
//
// ==/UserScript==

固定格式,浏览器会读取这段注释中的基本信息。
@name@description@version@author用来记录名称,描述和当前软件的版本号以及开发者的名称。
@namespace是浏览器在识别查件的时候,当插件名一样时用以区分不同插件的命名空间。
在头部注释中,比较重要的,是@include@exclude,前者用来表示当前的脚本所能作用的域,后者表示在前者包括的内容中再剔除后者表示的域。
例如:

// @include    *
// @exclude    http://127.0.0.1:3000/*

则表示,该脚本能作用的地址为除了127.0.0.1:3000域名下的所有地址。

脚本内容中能够使用包括当前页面提供的,以及Chrome原生支持的所有API,文档可以参考这里
好了,现在我们可以编写删除Gmail中广告部分的代码了,如下所示

var removeAdd = function() {
  var mqEl = document.getElementsByClassName('mq');
  if (mqEl && mqEl.length != 0) {
    mqEl = mqEl[0];
    mqEl.remove();
  } 
  setTimeout(removeAdd, 500);
};

removeAdd();

OK,到此为止,插件差不多完成了。用上述的步骤,重新安装Userscript,是不是发现打开Gmail的时候,广告不见了呢?
如果需要调试我们的插件,可以打开控制台 ——> source ——> content script来进行调试。

当然,这里还需要注意一个问题,就是这段代码究竟是在页面加载到何时的情况下运行的呢?
可以写个简单的测试脚本:

document.addEventListener('DOMContentLoaded', function() {
  console.log('Document load event fired!');
}, false);

window.addEventListener('load', function() {
  console.log('Window onload event fired!');
}, false);

再将home.user.js脚本中打印一串“User script loaded!”。在不断地刷新页面,可以得出结论,userscript应该是放在window.onload中执行的,位于domready时间之后,因此可以放心的编写安全的代码了。

Enjoy yourself !
转载请注明出处
botobe.net
本文Github链接

2013.06.28
一切安好。
Merci !

下一页