Code Monkey home page Code Monkey logo

blog's People

Contributors

chunpu avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

blog's Issues

深入理解jQuery中的data(data_user, data_priv)

data函数在jQuery中看起来很不起眼, 就像沙滩上一颗平凡的沙子, 但仔细一瞅, 却惊讶的发现data是jQuery中无比重要的一环, 甚至jQuery中各种事件都基于此


data在jQuery中有两种

一个是用来存数据的, 对应的对象分别是

  • 存储对象: data_user
  • 获取与设置方法: $.data(el, key, value)

另一个是用来存事件的, 如click事件那种, 对应的对象分别是

  • 存储对象: data_priv
  • 获取与设置方法: $._data(el, key, value)

data_userdata_priv, 就如其名, 一个是用户用的, 一个是jQuery私有的, 他们都是一个叫Data的实例对象


存数据的 data_user

data的使用方法简单无比

$('body').data('key', 'value')
// 或者
$.data(document.body, 'key', 'value')

你可能会说, 这函数也太弱了吧, 自己分分钟就可以写一个

$.fn.data = function(k, v) {
  if (arguments.length === 2) {
    return this.each(function() {
      this[key] = v
    })
  }
  return this[0][k]
}

这样写看上去没啥问题, 其实却有很多隐患, 比如这样

// case1
$('body').data('tagName', 'somevalue')
// case2
$('body').data('key', $('body'))

直接套在node对象上碰到原生的属性只会失效. 又如果值存为自己, 谁保不准会引用来引用去出问题呢, 而且直接放在节点对象上, 容易暴露逻辑, 被轻易修改

我们来看看jQuery怎么做的

$(document.body).data('aaa', 'value-aaa')
console.dir(document.body)

document.body

我们看到多出来暗色的属性-->jQuery加一串随机数

首先是暗色, 我们就知道此属性不能被枚举(enumerable为false), 是用definePropert*定义的属性

其次可以看出这个属性和jQuery.expando很像

expando是一个jQuery的唯一标示, 这个多出来的属性就是jQuery.expando再加上一串随机数产生的

为啥费这么大劲, 直接叫jquery-data啥的不好嘛? 这是因为一个网页能共存多个jQuery, 用$.noConflict来实现不冲突共存

同时我们能看到这个属性的值为3

我们再试试修改body上的data值

$(document.body).data('aaa', 'value-bbb')
$(document.body).add(document).data('bbb', 'value-xxx')

再看看这个属性的值, 还是3, 根本没有变

聪明的同学一定知道了, jQuery存data是用一个数组一样的东西存的, dom上只存这个数据的位置

而这个数组(array-like), 就是本文一开始提到的data_user的一个属性

先打印一下data_user存的东西

data_user

可以看到这里面存了2个属性, 一个就是expando, 也就是节点元素上多出来的那个奇怪属性, 另一个是cache, 不像数组, 但存着刚才我们看到的3, 3属性的值就是之前存入的data

知道这些我们就可以写出一个最简单的类似jQuery的data函数

;(function() {
  $.fn.data = dataFn
  var data = {
    expando: jQuery.expando + Math.random(),
    cache: []
  }
  var eo = data.expando
  function dataFn(k, v) {
    if (arguments.length === 2) {
      return this.each(function() {
        if (this[eo] >= 0) {
          data.cache[this[eo]][k] = v
        } else {
          var len = data.cache.length
          this[eo] = len
          data.cache[len] = {}
          data.cache[len][k] = v
        }
      })
    }
    var o = data.cache[this[0][eo]] || {}
    if (arguments.length === 0) {
      return o
    }
    return o[k]
  }
})()

存事件的 data_priv

那为啥jQuery的事件也和它有关呢?

了解jQuery事件的同学一定知道, 不管怎么给dom加事件, 在chrome的调试板上只能看到dom被绑了一个很奇怪的匿名函数

因为所有事件都被jQuery托管了, 但是都绑同一个函数, jQuery是怎么知道哪个handler对应哪个dom呢?

我们不妨再打印一下dom元素

$('a').click(alert)
console.dir($('a')[0])

我去, 怎么看上去和存了data一模一样啊, 也多了一个暗色奇怪字符串, 值也是3

我们也给a存下data看下

$('a').data('aaa', 222)

这下有俩奇怪字符串了!

其实它们就是data_userdata_priv这俩货各自的expando

事实上data和events本来就是完全一样的东西, 只是一个存在data_user中, 一个存在data_priv

打印一下data_priv

data_priv

可以看到data_priv确实和data_user一样, 只是存放的数据比较复杂, 是一些jQuery的events标示

jQuery提供了$._data这个方法(以后会被删掉), 让我们可以像$.data那样轻松的获取绑定在某节点上的handlers

比如我们看一下github在document上绑定的事件

github-data-priv

知道jQuery的event handlers存放和data一样, 借助data, 那我们自己模仿jQuery实现一个最简单最简陋的$.fn.on函数就像玩一样

;(function() {
  $.fn.on = onFn
  function onFn(ev, sel, handler) {
    return this.each(function() {
      this.addEventListener(ev, _handler)
      var o = {}
      o[ev] = {
        sel: sel,
        handler: handler
      }
      $.data(this, o)
    })
  }
  function _handler(e) {
    var _event = $.data(this)[e.type]
    var sel = _event.sel
    var elements = $(this).find(sel)
    var handlerQueue = []
    elements.each(function() {
      var cur = e.target
      if (cur === this || $(this).has(cur).length) {
        _event.handler.call(this, e)
      }
    })
  }
})()

jekyll小结

又玩了一会jekyll, 感觉挺不错的,但功能不全.

首先是因为Liquid这个模板并不是一个全功能的模板.使用起来并不是很舒畅.

然后就是它的Front-matter,也就是每篇markdown中最开头的头部信息.layout是必须写的,我们无法通过修改_config.yml来设置默认的layout.比如我设置

layout_defaults:
 -
  layout: page.html
 -
  path: _posts
  layout: post.html

这使得我每次写文章都要加上layout: post这句废话, 尽管我就是在_post文件夹下写的.这不是一个好的设计,而且破坏人们写作的心情,不少人提出了这个问题, 比如这个issuehttps://github.com/jekyll/jekyll/issues/453, 作者注意到了却没去实现.

jekyll允许通过插件来自定义新功能, 可惜github的jekyll是不支持的,这肯定不能支持, 不然写个死循环就要跑坏github的服务器了.所以jekyll虽然很纯净, 但少了一些基本的功能.

写完这篇我在手机上阅读时发现有横向滚动条,原来是没有断词

word-wrap: break-word;

加上自动断词即可解决.

横向滚动条在移动设备的响应式网页上是大忌.话说今天在写media query的css的时候,偶才知道屏幕分辨率不等同于浏览器分辨率

浏览器的分辨率通过window.screen.heightwindow.screen.width获取.

关于设备宽度和浏览器宽度的区别可以参照http://stackoverflow.com/questions/5716066/difference-between-width-and-device-width-in-css-media-queries

可以通过window.devicePixelRatio获取设备和浏览器像素密度的比例.

比如小米2的像素比是2, 小米2的分辨率是720 x 1280, 于是它的浏览器分辨率就是360 x 640.640 x 960的iphone也是2.但768 x 1024的ipad就是1, 因此用ipad横屏浏览桌面版网页是不会有x轴滑动条的.但ipad3则是2, 因为超过1000的分辨率是没有意义的.这是因为大多数定死宽度的网页都采用940-980px之间的数值.所以不要担心手机分辨率太高用media-query分辨不出来.屏幕越小, 分辨率越大, 则window.devicePixelRatio越大, 这样保证了使用viewport的时候字体大小是正常的.

尝试一下jekyll!

我觉得jekyll真棒啊,这是一个测试.

我是用了 redcarpet 这个markdown解析器, 据说是Github flavored markdown

现在我们来看下效果.注意,在_config.yml中的markdown值为redcarpet

function() {
  var love = true;
  if (love == true) {
    console.log("my love is " + love);
  }
}

It works!

nginx学习2

今天尝试写一个模块

ngx_http_module_t

这里面初始化所有8个成员全部是函数指针,同样是可选的,用NULL放弃使用回调。

create_loc_conf

此函数接受的参数是ngx_conf_t *, 也就是nginx配置结构体的指针

返回结果会被放在ctx->loc_conf[mi]中,mi是module index的缩写

返回一个有字符串和整数的结构体,

作用是告诉总的配置文件, 我需要一个啥结构体, 保存哪些信息

这个函数貌似不重要..不管了

merge_loc_conf

可以不写,和上面一样,没啥用

ngx_command_t

struct ngx_command_s {
    ngx_str_t             name;
    ngx_uint_t            type;
    char               *(*set)(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
    ngx_uint_t            conf;
    ngx_uint_t            offset;
    void                 *post;
};

其中主要是set这一个回调函数

set函数给这个模块加上了handler.

ngx_http_handler_pt handler

handler函数

核心部分

昨天没写出个模块,因为有太多很杂的东西了.

其实和大部分web程序一样, handler才是核心内容.

ngx_http_get_module_loc_conf(r, ngx_http_hi_module)这个函数可以(应该是把配置文件中的信息给r)

假设返回结果放到cglcf中了.

然后就是设置content-type,这永远都是必须的,注意nginx设置字符串全部是用data和len两个属性.

比如

r->headers_out.content_type.len = sizeof("text/html");
r->headers_out.content_type.data = (u_char *) "text/html";

当然也要设置状态码.

r->headers_out.status = NGX_HTTP_OK; // 这当然就是200

设置content_length_n, 不得不说我在nodejs和django中并没有发现返回数据的时候要写content-length.我猜这个content_length_n可能并不是http首部content-length.设置长度很简单

r->headers_out.content_lenght_n = cglcf->ecdata.len;

设置buffer.

b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));

b里面是什么东西呢,我也看不懂,大概是一些起始和末尾位置,chain位置.

设置out, 就是output啦.

out.buf = b; 
out.next = NULL; // out可以是一个buffer chain,这里表示后面没有了

调整b的pos地址,指向需要返回字符串的初始位置.

b->pos = cglcf->ecdata.data; // ecdata是网上抄的例子中的,实际自己取名

设置结束地址

b->last = cglcf->ecdata.data + (cglcf->ecdata.len)

可以把buffer放入memory

b->memory = 1;
b->last_buf = 1; // 这是最后一个buffer,我不知道这跟上面的next = NULL有啥区别

跟上面的发送头部一样先把头部发出去, 头部都存在r->headers_out中

rc = ngx_http_send_header(r);

如果成功的话, 继续发送output

return ngx_http_output_filter(r, &out);

这个filter啥意思呢?

其实filter有两个,一个是header filter,在r传进来之前就filter了header.

另一个filter就是这个body filter,是传输body的开始函数.

filter函数是链, 可以把它传给比如gzip这样的filter

交给filter, handler的任务就完成了.

总结一下

  1. 先设置头部,必要的有content-type, status, content-length.
  2. 设置要返回的buffer, 是通过设置buf的pos来定位字符串的.
  3. 发送头部
  4. 交给filter

明天计划写一个静态文件模块.

Harmony Generator, yield, ES6, co框架学习

harmony generator是ES6中的新玩具, 为了更好的避免回调金字塔, 学习这个特性是重要的, 这个特性在各大浏览器以及nodejs中已经都可以使用

nodejs中需要0.11.*版本, 官网找不到可以用nvm来下载(nvm install v0.11.9), 同时启用harmony generator还需要使用node --harmony, 为了方便, 我们一般会先取个别名alias node='node --harmony'

在chrome中使用该特性需要先打开chrome://flags/, 搜索harmony, enable之, 重启chrome即可

准备工作好了, 进入正题(建议使用chrome, 方便调试玩耍)

最简单的yield用法

function* Hello() {
  // 我习惯用大驼峰命名因为这就好比generator的构造函数
  yield 1
  yield 2
}

var hello = Hello() // hello 是一个generator
var a = hello.next() // a: Object {value: 1, done: false}
var b = hello.next() // b: Object {value: 2, done: false} 
var c = hello.next() // c: Object {value: undefined, done: true}

可以看到hello的原型链中总是有一个next函数, 每次运行都返回yield后面的值, 只是不是单纯的yield后面的值, 而是放在一个对象的value键中, 同时我们注意到对象中还有另一个键done, Hello函数中有两个yield, 因此前两个done的值为false, 表示我还没有结束呐!, 最后一个done为true, 表示我已经结束了! 如果继续运行hello.next()则会报错Uncaught Error: Generator has already finished

很明显的说yield就是相当于是另一种return, return使用时函数就结束了, 而使用yield的时候, 函数会卡在那个yield的地方, 等待下一个next

你可能会问,这到底有啥用呢? 难不成像那些教程中求Fibonacci函数那样一个一个next着玩?

代替回调金字塔

要理解一个语言,必须知道这门语言为什么会产生. 同理,理解这个新特性,也需要知道它是为了解决怎样的难题. 本文上来就说到harmony是用来解决回调金字塔的, 那究竟如何解决的呢

先写出一个需要耗时的异步函数: delay

funciton delay(time, cb) {
  setTimeout(function() {
    cb && cb()
  }, time)
}

我们并不是讨论同个耗时函数同时运行, 并取得最终回调的情景, 因为这种情景太简单了

var len = 3
function cb() {
  len --
  if (len === 0) {
    console.log('finish')
  }
}

delay(200, cb)
delay(1000, cb)
delay(500, cb)

我们讨论的是逐个按顺序运行

delay(200, function() {
  delay(1000, function() {
    delay(500, function() {
      console.log('finish')
    })
  })
})

上面程序看上去很短,不过是因为还没有加入参数和错误处理, 以及他的回调才3个,如果多起来函数就会斜的像金字塔了

利用harmony generator

首先我们需要改造delay函数

function delay(time) {
  return function(fn) {
    setTimeout(function() {
      fn()
    }, time)
  }
}

可以看到新delay变成了一个返回刚才delay的函数, 而且参数变成了一个, 就是time, 回调函数成了返回函数的参数

借助co函数(后面详解), 我们可以这样用

console.time(1)
co(function* () {
  yield delay(200)
  yield delay(1000)
  yield delay(500)
})(function() {
  console.timeEnd(1) // print 1: 1702.000ms 
})

这样就完成了顺序的异步执行, 写法非常简洁

co函数

为什么是co函数呢? 嗯, 这篇博文完全是为了看懂TJ大神的co框架, 即将大热的koa框架也是基于这个小小的函数

那co函数究竟是怎么写的呢?

最简单的co

function co(GenFunc) {
  return function(cb) {
    var gen = GenFunc()
    next()
    function next() {
      if (gen.next) {
        var ret = gen.next()
        if (ret.done) { // 如果结束就执行cb
          cb && cb()
        } else { // 继续next
          ret.value(next)
        }
      }
    }
  }
}

短短十行就完成了co的主要功能, 不过这个co是不支持任何参数的, 因此会如此简短

ret.value(next)这行代码可以说是co中最重要的地方, 这句话将使得以后大部分异步函数都变成这个鸟样! 比如读文件

function read(file) {
  return function(fn){
    fs.readFile(file, 'utf8', fn);
  }
}

也就是说使用co的异步函数都是返回一个函数, 且该函数的参数是异步执行后的回调, co通过不断的执行, 传递next, 执行, 传递next, ... , 结束

看起来异步函数都被包了一层, 换来的好处是参数少了一个(不用把回调函数写里面了), 但是回调的结果在哪里呢?

大多数时候需要顺序调用的异步函数都不会这么简单, 每一步都需要来自上一步的返回值

比如最简单的就是这样:

function delay(time, cb) {
  setTimeout(function() {
    cb && cb(time)
  }, time)
}

delay(200, function(time) {
  delay(time+100, function(time) {
    delay(time+100, function(time) {
      console.log(time) // print 400
    })
  })
})

那yield如何传值呢? 先来看这个小demo

var gen = (function* (num) {
  console.log(num) //  print 11
  var a = yield 1
  console.log(a) //  print 22
  var b = yield 1
  console.log(b) // print 33
})(11)

gen.next()
gen.next(22)
gen.next(33, 44)

可以看出, yield的返回值和yield后面的东西毫无关系(但不能没有, 不然是undefined), 返回值就是next函数中第一个参数

我们稍稍改一下co函数就能完成回调使用之前回调参数的需求

function co(GenFunc) {
  return function(cb) {
    var gen = GenFunc()
    next()
    function next(args) { // 传入args
      if (gen.next) {
        var ret = gen.next(args) // 使用args
        if (ret.done) {
          cb && cb(args)
        } else {
          ret.value(next)
        }
      }
    }
  }
}

就多了俩args, 于是我们就可以非常愉快的这么写!

function delay(time) {
  return function(fn) {
    setTimeout(function() {
      fn(time) // time为回调参数
    }, time)
  }
}

co(function* () {
  var a
  a = yield delay(200) // a: 200
  a = yield delay(a + 100) // a: 300
  a = yield delay(a + 100) // a: 400
})(function(data) {
  console.log(data) // print 400, 最后的值被回调出来
})

如何在回调中传err

有一种说法: 一个程序, 三分钟一的代码都是在错误处理

nodejs中也有大量的错误处理

在nodejs官方文档中就有大量这样的例子

fs.readFile('/etc/passwd', function (err, data) {
  if (err) throw err;
  console.log(data);
});

错误都放在回调函数中的第一个参数中, 如果不使用专门的框架, 我们需要大量的写这种代码

if (err) {
  throw ..
  next(err) ..
} else {
  next(null, err)
}

简直冗长无比, 但也没有办法, 因为回调函数第一个放错误已经从不成文规定变成了大多数项目的明文规定

但有了co函数我们就不必这么麻烦了

首先把delay改成标准的函数

function delay(time) {
  return function(fn) {
    setTimeout(function() {
      fn(null, time) // null 表示没有错误..
    }, time)
  }
}

改进co函数, 加上错误判断

function co(GenFunc) {
  return function(cb) {
    var gen = GenFunc()
    next()
    function next(err, args) {
      if (err) {
        cb(err)
      } else {
        if (gen.next) {
          var ret = gen.next(args)
          if (ret.done) {
            cb && cb(null, args)
          } else {
            ret.value(next)
          }
        }
      }
    }
  }
}

使用新的co函数

co(function* () {
  var a
  a = yield delay(200) // 200
  a = yield delay(a + 100) // 300
  a = yield delay(a + 100) // 400
})(function(err, data) {
  if (!err) {
    console.log(data) // print 400
  }
})

好了! co函数的雏形就这样完成了, TJ大大的真正的co框架还支持promise等写法, 不看看源码绝对可惜了

最后记一下函数是否是generatorFunction的判断方法

function isGeneratorFunction(obj) {
  return obj && obj.constructor && 'GeneratorFunction' === obj.constructor.name
}

Nginx模块开发之最简单的Hello模块

nginx模块开发并不是那么容易, 从行数上来讲, 淘宝给出的tengine给出的那个所谓hello模块的长度也到了245行, 要想真正独立写出这么多代码, 对于我来说是非常难的.

245行, 如果是nodejs, 已经可以写一个比较完善的文件服务器了. 要想完全理解这个hello模块, 有c基础的也怕是要花不少时间, 像我这样没有c经验的, 更是难上加难.

我决定写一个真正的hello模块,也就是最最简单的那种,能自己写出来,也算是至少nginx模块开发入门了.

这个hello模块作用就是当访问/test的时候, 返回一段固定的html代码(一个字符串).

此模块一共不到60行, 理解此模块比理解淘宝教程的模块要简单几倍.

先上全代码

#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>

static char *set(ngx_conf_t *, ngx_command_t *, void *);
static ngx_int_t handler(ngx_http_request_t *);

static ngx_command_t test_commands[] = {
  {
    ngx_string("test"),
    NGX_HTTP_LOC_CONF | NGX_CONF_NOARGS,
    set,
    NGX_HTTP_LOC_CONF_OFFSET,
    0,
    NULL
  },
  ngx_null_command
};

static ngx_http_module_t test_ctx = {
  NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL
};

ngx_module_t ngx_http_test_module = {
  NGX_MODULE_V1,
  &test_ctx,
  test_commands,
  NGX_HTTP_MODULE,
  NULL, NULL, NULL, NULL, NULL, NULL, NULL,
  NGX_MODULE_V1_PADDING
};

static char *set(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) {
  ngx_http_core_loc_conf_t *corecf;
  corecf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
  corecf->handler = handler;
  return NGX_CONF_OK;
};

static ngx_int_t handler(ngx_http_request_t *req) {
  u_char html[1024] = "<h1>This is Test Page!</h1>";
  req->headers_out.status = 200;
  int len = sizeof(html) - 1;
  req->headers_out.content_length_n = len;
  ngx_str_set(&req->headers_out.content_type, "text/html");
  ngx_http_send_header(req);

  ngx_buf_t *b;
  b = ngx_pcalloc(req->pool, sizeof(ngx_buf_t));
  ngx_chain_t out;
  out.buf = b;
  out.next = NULL;
  b->pos = html;
  b->last = html + len;
  b->memory = 1;
  b->last_buf = 1;

  return ngx_http_output_filter(req, &out);
}

这个文件的路径是src/http/module/ngx_http_test_module.c.

光是放这个是nginx的makefile是不知道的,它不会去编译新增的模块, 还需要在auto/modules这个文件中加入

if [ $HTTP_ACCESS = YES ]; then
    HTTP_MODULES="$HTTP_MODULES $HTTP_ACCESS_MODULE"
    HTTP_SRCS="$HTTP_SRCS $HTTP_ACCESS_SRCS"
fi
# 上面是原有的, 这里才是加上的

HTTP_MODULES="$HTTP_MODULES ngx_http_test_module"
HTTP_SRCS="$HTTP_SRCS src/http/modules/ngx_http_test_module.c"

auto是用来生成Makefile的很多shell脚本,Nginx没有用那些构建工具来制作自己的Makefile, 而是自己写了大量的shell脚本, 学习这些脚本对于自己的shell编程也是很有帮助的. nginx的编译的生成文件都在objs中, 清晰明了, 因此make clean也只是调用rm -rf objs即可, 非常简洁.

总之加上上面两句话, nginx就知道你要新增这个模块了, 顺序应该不是很要紧(其实我是没试过).

这样我们的模块依然不起作用, 还需要修改配置文件, nginx启动完全依靠那个conf/nginx.conf的配置文件!

server {
    listen       80;
    server_name  localhost;

    #charset koi8-r;

    #access_log  logs/host.access.log  main;

    location / {
        root   html;
        index  index.html index.htm;
    }
    location /test {
        test;
    }

我们在http中的server中加上location /test来插入我们的模块.

运行之, 在浏览器中访问你的域名/test就能看到This is Test Page几个大字, 因为是<h1>的嘛!

解释下这段代码吧.

#include <ngx_core.h>
#include <ngx_http.h>
#include <ngx_config.h>

包含三个关键头文件, 这没什么异议, 注意, 我们写的模块基本都是http的.

static char *set(ngx_conf_t *, ngx_command_t *, void *);
static ngx_int_t handler(ngx_http_request_t *);

声明两个函数, 这是两个非常重要的函数, 后面主要讲.

command注册

static ngx_command_t test_commands[] = {
  {
    ngx_string("test"),
    NGX_HTTP_LOC_CONF | NGX_CONF_NOARGS,
    set,
    NGX_HTTP_LOC_CONF_OFFSET,
    0,
    NULL
  },
  ngx_null_command
};

这是定义一个配置命令信息数组(为何是数组暂时还真不知道), 数组最后一个元素都是ngx_null_command.

结构体第一个参数尤为重要, 这里是test, 指的是我们在配置文件中输入test(不是路径的/test).这样指定后, nginx在读取配置的时候读到test命令, 才会把接管权给我们.也就是把请求转给我们去处理.

第二个参数代表我们的模块注册的是http location的命令, 并且接受0个参数.http location当然就是指这个命令触发是跟路径有关的.

事实上大量的模块触发都跟路径相关, 比如php, php就是接管所有后缀是.php的location.php的nginx模块配置如下所示

location ~ \.php$ {
  root /home/www;
  fastcgi_pass 127.0.0.1:3344;
  fastcgi_index index.php;
  fastcgi_param SCRIPT_FILENAME $document_root/$fastcgi_script_name;
  include fastcgi_params;
}

第三个参数是set, 这是一个函数指针,也就是我们一开始声明的两个关键函数中的一个,读到我们注册的test这个命令的时候触发的, 我们一般在set中写上托管http请求的handler函数.

第四个参数是offset类型, 用来结构体的偏移的, 不再hello模块的讨论范围, loc conf类型就直接是NGX_HTTP_LOC_CONF_OFFSET就行.

第五个参数就是具体offset的值, 我们这里只有一个命令, 没有参数, 输入0即可.

第六个参数NULL即可, 作用未知.

回调函数

写完命令注册, 我们还需要一个context,至于为何叫上下文我也不知道, 但这种把函数作为参数的方法在nodejs中一般叫回调函数,内核程序员喜欢叫它hook, 钩子函数, 我觉得也很形象.但是在hello模块中,我们可以完全不用这些回调机会.

static ngx_http_module_t test_ctx = {
  NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL
};

nginx一共提供8个回调机会, 具体是什么时候用不在hello模块讨论范围, 这里都设置为NULL即可.

模块结构体

最后写上真正暴露给外面的模块结构体

ngx_module_t ngx_http_test_module = {
  NGX_MODULE_V1,
  &test_ctx,
  test_commands,
  NGX_HTTP_MODULE,
  NULL, NULL, NULL, NULL, NULL, NULL, NULL,
  NGX_MODULE_V1_PADDING
};

其中NGX_MODULE_V1NGX_MODULE_V1_PADDING都是宏定义, 不必去管

只需知道第二个放上回调函数数组, 第三个是注册命令, 第四个是模块类型即可.后面7个NULL.

set函数

前面说到, set函数就是给配置信息挂上托管http请求的handler函数的.

static char *set(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) {
  ngx_http_core_loc_conf_t *corecf;
  corecf = ngx_http_conf_get_module_loc_conf(cf->pool, ngx_http_core_module);
  corecf->handler = handler;
  return NGX_CONF_OK;
}

返回类型是char *,它有两种返回值, 一个是NGX_CONF_OK也就是NULL, 另一个是NGX_CONF_ERROR也就是(void *) -1.

里面最重要的就是corecf->handler = handler了, 这句话把handler函数挂到corecf的handler属性上.

handler函数

这是最重要的函数, nodejs中写http服务器, 完全只要写个handler就行了.nginx却要写前面一堆废话.

static ngx_int_t handler(ngx_http_request_t *req) {
  char html[1024] = "<h1>This is Test Page</h1>";
  int len = sizeof(html) - 1;
  req->headers_out.status = 200;
  req->headers_out.content_length_n = len;
  ngx_str_set(&req->headers_out.content_type, "text/html");
  ngx_http_send_header(req);

  ngx_buf_t *b;
  b = ngx_pcalloc(req->pool, sizeof(ngx_buf_t));
  b->pos = html;
  b->last = html + len;
  b->memory = 1;
  b->last_buf = 1;
  ngx_chain_t out;
  out.buf = b;
  out.next = NULL;
  return ngx_http_output_filter(req, &out);
};

其实主菜才是最直观的, 前面是设置http的返回头部, 后面是设置http body.简单至极.每每写到handler部分都神清气爽, 感觉自己也会用c了..

补充一下. 我在用jekyll写博客的时候又出现的编译错误, 原因是使用了高亮shell, 不知道是不是因为没有shell, 反正它居然报错了, 修改成bash后显示正确, 这实在让我费解, 我仅仅是写一个博客而已, 你居然来个编译错误.不支持你就不管呗. jekyll用的液体模板并不能报出模板哪里错了, 总之出错了,不得不用排除法去猜, 让人郁闷.

牛逼的是我发现github貌似也使用的redcarpet,见github bloghttps://github.com/blog/832-rolling-out-the-redcarpet

github的GitHub Flavored Markdown,支持的高亮语言有这些:https://github.com/github/linguist/blob/master/lib/linguist/languages.yml

非常可惜的是我们没法用这么全的.

Resume

冯通 | 男 | 1992/6 | **科学技术大学本科
邮箱: [email protected] | 电话: 18914114612
应聘职位:

IT技能

前端

  • 熟悉HTML,CSS,Javascript
  • 深刻理解Web语义化,W3C标准
  • 了解_HTML5_新技术

项目经验

工作经验

其他信息

nginx模块开发之handler函数

http handler是http模块中最重要的函数, 直接托管http请求.

和前文set不同, set是在nginx启动的时候读取配置的过程中被触发的, 而handler函数是在真实请求到那个路径上时被触发的.

也就是浏览器请求多少次, handler就触发多少次.

返回值

handler的返回类型是ngx_int_t, 因为一般的http handler定义好body后就能交给http filter函数了, 比如我们hello模块的ngx_http_output_filter, 而filter函数都是返回整形数的.比如error就是-1.

参数

handler参数只有一个, ngx_http_request_t *req, 这个简单的出奇, 不过我用过不少http服务器框架, 也就只有nodejs分了request, response两个参数.

req是一个巨大的结构体.

不过这样的后果就是response的信息都写在request这个结构体上, 比如头部信息就是request.headers_out中.

请求的头部在headers_in中.

我们可以清楚的看到各种请求信息, 比如user_agent:

(gdb) p *req->headers_in.user_agent
p *req->headers_in.user_agent
$3 = {hash = 3194399592611459, key = {len = 10, data = 0x6eaa37 "User-Agent"}, v
   data = 0x6eaa43 "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHT
lowcase_key = 0x6ebc69 "user-agentaccept-encodingaccept-languagecookie"}

在printheaders_in的时候我们还发现nginx已经通过user_agent帮你分辨好了浏览器..还专门列出了msie6.. 当然还有很多重要的头部信息, 比如if_modified_since, chunked, cookie等.

在req中也有全部的我们需要的信息, 有method但不是保存字符串, 使用数字代表的, 比如get就是2. 还有http_version, 比如http1.1就是1001.

但是想uri, request_line, method_name, 都是显示一个request, line, 并没有做解析, 直接是生肉GET /test HTTP/1.1\r\nHost

但拥有全部http请求信息的我们也完全可以写出所有http相关的模块了.

源代码中是如何触发handler的

第一次使用gdb调试的时候发现调试不了, 马上就想起来nginx是分master和worker进程的, 在调试的时候, 我们不仅需要关掉daemmon后台运行, 以及master进程, 仅仅使用一个worker进程进行调试.

方法是修改nginx配置文件conf/nginx.conf,

daemon off;
master_process off;

这样就可以用gdb调试了.

handler函数在src/http/ngx_http_core_module.c中被调用.可以看到我们的handler函数被当成request结构体的content_handler属性.

返回ngx_http_output_filter(req, &out)的时候, 一次完整的请求+返回就完成了.

mysql折腾笔记

经常吹嘘自己玩过各种数据库. redis, mysql, sqlite, mongodb..常用数据库都不在话下,不料今天却在远程连接mysql上栽了跟头,于是记一下这个教训

mysql默认是不开启远程连接的,要让mysql支持远程连接需要两个步骤

第一步

首先是打开/etc/mysql/my.cnf, 如果发现自己的mysql配置文件不是这个路径,强烈建议重装mysql(我就是因为装的mysql和别人不一样才鼓捣很久), 重装失败可以后面详解

bind-address = 127.0.0.1那一行配置改为bind-address = 0.0.0.0

bind-address表示监听时绑定的IP地址,为什么要这么改呢?

因为网卡收到请求,会把每个socket的目标地址和端口解析出来,并对照进程监听的IP和端口,如果IP和端口吻合,就传给进程

端口我们都知道mysql是3306,错不了. 而IP默认是127.0.0.1,是一个环回地址,只有本机请求本机,目标地址才会是127.0.0.1,因此我们需要将绑定IP改为网卡地址, 也就是外网IP, 因为远程连接数据库目标IP肯定是公网IP. 不过还可以改成0.0.0.0,这样就表示监听所有IP, 只要端口一样, 网卡都会把请求传给进程

第二步

进入mysql

mysql -uroot -p, 输入密码

添加一个可以远程访问的用户

GRANT ALL PRIVILEGES ON *.* TO  'USERNAME'@'IP'  IDENTIFIED  BY  'PASSWORD';

FLUSH PRIVILEGES;

注意此处有三个变量, IP就是指远程client的ip地址,如果希望所有IP都可以访问, 就把IP改为%

重启mysql, service mysql restart

然后就可以通过navicat, 或者mysql --host ip地址 -uUSERNAME -p来访问了

如果你发现依然不能访问,首先确定是否打开了iptables(防火墙), (随便做个网页,用外网访问一下如果可以就不是iptables的问题)

这个时候就需要重装mysql了

千万不要以为精通mysql的安装(apt-get install mysql-server)就同样精通mysql的卸载了(apt-get remove mysql-server), (yum党退散

如果出现卸载不完全, 还出现了如下提示

Unable to set password for the MySQL “root” user An error occurred while setting the password for the MySQL administrative user. This may have happened because the account already has a password, or because of a communication problem with the MySQL server.

可以进行如下步骤:

dpkg --get-selections | grep mysql

libdbd-mysql-perl                               install
libmysqlclient18                                install
mysql-client-5.5                                install
mysql-client-core-5.5                           install
mysql-common                                    install
mysql-server                                    install
mysql-server-5.5                                install
mysql-server-core-5.5                           install

把这些都删了

apt-get remove libdbd-mysql-perl libmysqlclient18 mysql-client-5.5 ... --purge

收尾清理垃圾

apt-get autoremove
apt-get autoclean
rm /etc/mysql/ -fr
rm /var/lib/mysql/ -fr

然后再执行

apt-get install mysql-server

就成功啦

深入理解jQuery中的data(data_user, data_priv)

data函数在jQuery中看起来很不起眼, 就像沙滩上一颗平凡的沙子, 但仔细一瞅, 却惊讶的发现data是jQuery中无比重要的一环, 甚至jQuery中各种事件都基于此


data在jQuery中有两种

一个是用来存数据的, 对应的对象分别是

  • 存储对象: data_user
  • 获取与设置方法: $.data(el, key, value)

另一个是用来存事件的, 如click事件那种, 对应的对象分别是

  • 存储对象: data_priv
  • 获取与设置方法: $._data(el, key, value)

data_userdata_priv, 就如其名, 一个是用户用的, 一个是jQuery私有的, 他们都是一个叫Data的实例对象


存数据的 data_user

data的使用方法简单无比

$('body').data('key', 'value')
// 或者
$.data(document.body, 'key', 'value')

你可能会说, 这函数也太弱了吧, 自己分分钟就可以写一个

$.fn.data = function(k, v) {
  if (arguments.length === 2) {
    return this.each(function() {
      this[key] = v
    })
  }
  return this[0][k]
}

这样写看上去没啥问题, 其实却有很多隐患, 比如这样

// case1
$('body').data('tagName', 'somevalue')
// case2
$('body').data('key', $('body'))

直接套在node对象上碰到原生的属性只会失效. 又如果值存为自己, 谁保不准会引用来引用去出问题呢, 而且直接放在节点对象上, 容易暴露逻辑, 被轻易修改

我们来看看jQuery怎么做的

$(document.body).data('aaa', 'value-aaa')
console.dir(document.body)

document.body

我们看到多出来暗色的属性-->jQuery加一串随机数

首先是暗色, 我们就知道此属性不能被枚举(enumerable为false), 是用definePropert*定义的属性

其次可以看出这个属性和jQuery.expando很像

expando是一个jQuery的唯一标示, 这个多出来的属性就是jQuery.expando再加上一串随机数产生的

为啥费这么大劲, 直接叫jquery-data啥的不好嘛? 这是因为一个网页能共存多个jQuery, 用$.noConflict来实现不冲突共存

同时我们能看到这个属性的值为3

我们再试试修改body上的data值

$(document.body).data('aaa', 'value-bbb')
$(document.body).add(document).data('bbb', 'value-xxx')

再看看这个属性的值, 还是3, 根本没有变

聪明的同学一定知道了, jQuery存data是用一个数组一样的东西存的, dom上只存这个数据的位置

而这个数组(array-like), 就是本文一开始提到的data_user的一个属性

先打印一下data_user存的东西

data_user

可以看到这里面存了2个属性, 一个就是expando, 也就是节点元素上多出来的那个奇怪属性, 另一个是cache, 不像数组, 但存着刚才我们看到的3, 3属性的值就是之前存入的data

知道这些我们就可以写出一个最简单的类似jQuery的data函数

;(function() {
  $.fn.data = dataFn
  var data = {
    expando: jQuery.expando + Math.random(),
    cache: []
  }
  var eo = data.expando
  function dataFn(k, v) {
    if (arguments.length === 2) {
      return this.each(function() {
        if (this[eo] >= 0) {
          data.cache[this[eo]][k] = v
        } else {
          var len = data.cache.length
          this[eo] = len
          data.cache[len] = {}
          data.cache[len][k] = v
        }
      })
    }
    var o = data.cache[this[0][eo]] || {}
    if (arguments.length === 0) {
      return o
    }
    return o[k]
  }
})()

存事件的 data_priv

那为啥jQuery的事件也和它有关呢?

了解jQuery事件的同学一定知道, 不管怎么给dom加事件, 在chrome的调试板上只能看到dom被绑了一个很奇怪的匿名函数

因为所有事件都被jQuery托管了, 但是都绑同一个函数, jQuery是怎么知道哪个handler对应哪个dom呢?

我们不妨再打印一下dom元素

$('a').click(alert)
console.dir($('a')[0])

我去, 怎么看上去和存了data一模一样啊, 也多了一个暗色奇怪字符串, 值也是3

我们也给a存下data看下

$('a').data('aaa', 222)

这下有俩奇怪字符串了!

其实它们就是data_userdata_priv这俩货各自的expando

事实上data和events本来就是完全一样的东西, 只是一个存在data_user中, 一个存在data_priv

打印一下data_priv

data_priv

可以看到data_priv确实和data_user一样, 只是存放的数据比较复杂, 是一些jQuery的events标示

jQuery提供了$._data这个方法(以后会被删掉), 让我们可以像$.data那样轻松的获取绑定在某节点上的handlers

比如我们看一下github在document上绑定的事件

github-data-priv

知道jQuery的event handlers存放和data一样, 借助data, 那我们自己模仿jQuery实现一个最简单最简陋的$.fn.on函数就像玩一样

;(function() {
  $.fn.on = onFn
  function onFn(ev, sel, handler) {
    return this.each(function() {
      this.addEventListener(ev, _handler)
      var o = {}
      o[ev] = {
        sel: sel,
        handler: handler
      }
      $.data(this, o)
    })
  }
  function _handler(e) {
    var _event = $.data(this)[e.type]
    var sel = _event.sel
    var elements = $(this).find(sel)
    var handlerQueue = []
    elements.each(function() {
      var cur = e.target
      if (cur === this || $(this).has(cur).length) {
        _event.handler.call(this, e)
      }
    })
  }
})()

nginx学习1

最近看了下nginx源码。有点头大。首先是我没用过nginx,第二是我不会c语言。

因此想借着看nginx源码学习下c语言。之所以学习nginx是因为nginx非常实用,而且代码写得好,并且已经有不少的源码分析博客。不会的话至少有个参考。

nginx有模块这个概念,可以自定义模块,这是入口点。

nginx模块的基本原理我总结了一下,基本就是靠用函数当参数,在特定地方调用函数。

写过js的一定对这个非常熟悉。因为js的cps风格就是不断的用函数做参数进行回调的。

nginx也可以叫回调,反正也是基于epoll和kqueue的嘛。

学习了一下c,发现c实现回调函数是用函数指针的。整个nginx使用模块的方法大概如下

#include <stdio.h>

void init(char *str) {
  printf("init: %s\n\n", str);
}

void bye(char *str) {
  printf("bye: %s\n\n", str);
}

struct ngx_module_t {
  char *name;
  int type;
  void (*init)(char *);
  void (*handle)(char *);
  void (*bye)(char *);
} ngx = {
  "my_module",
  3,
  &init,
  NULL,
  &bye
};

int main() {
  if (ngx.init)
    ngx.init("welcome");
  printf("I'm handle the request\n");
  if (ngx.handle)
    ngx.handle("handle it!\n");
  if (ngx.bye)
    ngx.bye("have finished!");
  return 1;
}

基本就是说你要给nginx一个struct,里面有你定的type, name啥的,同时要定义你的事件触发函数。

因为nginx整个模块是一个数组, 每执行到一个可以触发事件的地方,都会遍历所有模块,也就是那个结构体,然后访问模块结构体中的指定函数,比如刚进来的时候触发init事件,那就遍历所有模块,调用其init的函数。原理很简单。

虽然还没看具体代码,不过应该充斥着

if (ngx.xxx) {
    ngx.xxx( ... )
}

这样的代码。

话说c的声明,调用实在是太罗嗦了。用c真的会局限人的思维,程序员需要花太多时间在c语言本身上。

明天继续,争取写一个hello world模块出来。

使用国外vps简单科学上网

注意:本文只为了看一些被墙的文献,18岁以下请主动关闭此页

这里将介绍3种使用国外vps科学上网的方法,分别是vpn, ssh, shadowsocks

首先是vpn翻墙,vpn一般是全局科学上网,不管是iPhone还是Android还是windows都不需要安装任何其他软件就可以翻*墙,对客户端来说是最方便的方案

服务端部署也不麻烦(有了下文的一键脚本后..),就3步

1 - 下载并运行脚本

wget http://soft.yzs.me/pptpd.sh;sh pptpd.sh

2 - 按脚本提示填写ip和网卡

Which IP is your server IP:
IP:(就是你服务器IP)

Please input the netdriver of your server:
Net Driver:(你用了哪张网卡,一般都是eth0,阿里云貌似是eth1)

再填上用户名和密码,比如都用vpn

运行lsof -i:1723

看到如下结果(有pptpd和1723)就算对了

COMMAND  PID USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
pptpd   1614 root    6u  IPv4   9988      0t0  TCP *:1723 (LISTEN)

为了感谢该脚本(主要是防止以后链接失效), 决定把脚本贴进来。。

#!/bin/bash

get_char()

        {

        SAVEDSTTY=`stty -g`

        stty -echo

        stty cbreak

        dd if=/dev/tty bs=1 count=1 2> /dev/null

        stty -raw

        stty echo

        stty $SAVEDSTTY

        }

clear

ip=$(ifconfig | grep 'inet addr:'| grep -v '127.0.0.1' | cut -d: -f2 | awk '{ print $1}')
echo "$ip"

        echo "==========================="

serverip=""
while [ "$serverip" = "" ]; do
        echo "Which IP is your server IP:"
        read -p"IP:"  serverip
done
        echo "==========================="
        echo "Server IP:$serverip"
        echo "==========================="

        echo "==========================="

ifconfig

netdriver=""
while [ "$netdriver" = "" ]; do
        echo "Please input the netdriver of your server:"
        read -p"Net Driver:"  netdriver
done
        echo "==========================="
        echo "Net Driver:$netdriver"
        echo "==========================="

username=""
while [ "$username" = "" ]; do
        echo "Please input the username of PPTP:"
        read -p"Username:"  username
done
        echo "==========================="
        echo "PPTP Username:$username"
        echo "==========================="

password=""
while [ "$password" = "" ]; do
        echo "Please input the password of PPTP:"
        read -p"Password:"  password
done
        echo "==========================="
        echo "PPTP Password:$password"
        echo "==========================="

        echo "Press any key to continue."
        char=`get_char`
apt-get -y update
apt-get -y install pptpd
sed -i "s#\#localip 192.168.0.1#localip 192.168.0.1#g" /etc/pptpd.conf
sed -i "s#\#remoteip 192.168.0.234-238,192.168.0.245#remoteip 192.168.0.234-238,192.168.0.245#g" /etc/pptpd.conf
wget http://soft.yzs.me/pptpd-options -O /etc/ppp/pptpd-options
touch /var/log/pptpd.log
sed -i 's/#net.ipv4.ip_forward=1/net.ipv4.ip_forward=1/g' /etc/sysctl.conf
sysctl -p
iptables -t nat -A POSTROUTING -s 192.168.0.0/24 -o $netdriver -j MASQUERADE
iptables -t nat -A POSTROUTING -s 192.168.0.0/24 -o $netdriver -j SNAT --to-source $serverip
echo "$username * $password *">>/etc/ppp/chap-secrets
sed -i "1i\iptables -t nat -A POSTROUTING -s 192.168.0.0/24 -o $netdriver -j MASQUERADE;iptables -t nat -A POSTROUTING -s 192.168.0.0/24 -o $netdriver -j SNAT --to-source $serverip" /etc/rc.local
service pptpd restart

3 - 在windows或android或iphone上添加vpn连接, 结束!


接下来介绍普通ssh科学上网方法

ssh的方法不需要在服务器上做任何设置,因为一般vps都会默认开启sshd,我们同样只需要3步即可科学上网

1 - 下载putty(下载链接)

2 - 编写一个一行命令的脚本kexueshangwang.bat, 放在putty同路径下

putty.exe -D 1080 root@你vps的IP

双击执行并登陆

3 - 安装proxy SwitchySharp

chrome插件地址

添加一个情景

科学上网

也就是在socks中填上ip: 127.0.0.1, 端口: 1080, 刚才-D后面的数字

启用这个情景,看看是不是能跑出去了


最后介绍shadowsocks科学上网的方法

shadowsocks也是目前流行所趋,他的优点,我也不大清楚,不过至少他不用建一个单独给人上网的linux账号,而且一般shadowsocks都会用上epoll的event库,性能应该也大大提升。也就是说shadowsocks是用来大批量科学上网的方法(卖¥)

方法同样简单,不过此处只介绍nodejs方法 (谁让nodejs是最牛x的平台呢,同样是3步

1 - 分别在客户端和vps端安装nodejs

window直接在nodejs官网下载安装即可

ubuntu更容易, apt-get install nodejs

2 - 在客户端和vps端分别安装shadowsocks的全局程序

npm install -g shadowsocks

此命令是增加两个全局命令(在命令行可以直接用的命令),一个是ssserver,另一个是sslocal

我们需要修改服务端和vps端的配置文件,其地址在全局安装的时候会显示出来

修改服务端的config.json, 就是把config.json中的ip从127.0.0.1改成0.0.0.0

在客户端我们也需要修改配置文件config.json, 把ip从127.0.0.1改成你的vps外网ip

3 - 启动

在服务端直接执行ssserver即可,看到如下即成功

1 Feb 03:51:23 - UDP server listening 0.0.0.0:8388
1 Feb 03:51:23 - server listening at 0.0.0.0:8388

在客户端执行sslocal

然后做端口代理转发即可,具体步骤同方法2普通ssh上网步骤3

据说shadowsocks使用udp通信,不必担心断线问题

论坛已然有了雏形

上文说到我做了个论坛,其实那时我还没做界面,仅仅把model层做了下,这次借助bootstrap3.0做了下界面,感觉真的有个样子了

相比nodeclub来说,主要是有了板块这个概念,同时它是响应式的,我更加关注论坛在手机浏览器上的显示效果

这次制作的原则是留白,一切都要最简单,方便以后的扩展,使用的颜色基本都是灰色和白色

由于我坚持完全使用bootstrap(主要是我不想放任何静态文件,全靠cdn),因此有不少布局我都有点茫然,一个劲的翻bootstrap官网

例如这个帖子列表,我一开始都感觉自己不会写了,后来动手设计了一下,试着实现,结果一下子就出来了

不过这完全依赖bootstrap的栅格,可以看下手机和电脑的效果,同一段html代码,在pc和手机上显示居然如此不同

新版的bootstrap确实在响应式上花了大工夫,每次实现一个效果,我都觉得bootstrap精妙绝伦,上次有人在微博上吐槽“什么时候bootstrap也是技能了?”。 在我看来bootstrap绝壁是个大技能,熟练使用的话完全可以快速建站。我下一个计划就是准备弥补一下自己CSS薄弱的问题,好好学习bootstrap,认真看一下源码

手机上效果

电脑上效果

另外想说的是我这次使用的模板引擎是jade,很早前我就知道jade,但觉得它语法怪异,上手有难度,于是自己实现了一个简单的模板引擎,现在看来和ejs几乎一样,非常的简单,一个文件

这次突然转向jade,完全是因为新版的ejs放弃了layout这个概念,理由是layout会触发复杂IO操作(大概这意思)。于是ejs推荐用include header.html这样的方法在引入子模板,显然,这样的问题是除了引入header.html,我们还需要foot.html,每个文件都这么写蛋都碎了。这个问题本质的原因是include是父模板引用子模板,而layout是子模板嵌入父模板,根本不是一个概念

然而,就算有layout,依然会有问题,比如我们希望子模板来定义title,但title标签显然是在layout中的

在真正建站的时候,使用jade模板将解决所有问题,没错,就是所有问题

我如此青睐jade主要是它提供了三个父子模板关系的标识block, include, extends

这三个标识看上去特别熟悉,它是django模板中的功能,我在使用django模板的时候就觉得真心碉堡了,一直想做一个有这样功能的nodejs的模板引擎。

看到jade后才知道自己想多了,jade早就已经完美,解决了我长久以来所有的问题

奇怪的是jade的官网并没有提到这方面的功能,这让人很不解,我甚至都忘了当时是怎么发现jade有这个功能的

来看下使用jade和使用ejs的一段html代码

没有尖三角,没有结束标签看上去简洁不少,jade的优点实在太多,光是注释用起来这么爽就足以完爆其他模板

可以这么说,制作论坛本意是搞mongodb的,但最大的收获不是mongodb也不是bootstrap,就是jade模板的使用

nginx学习2

今天尝试写一个模块

ngx_http_module_t

这里面初始化所有8个成员全部是函数指针,同样是可选的,用NULL放弃使用回调。

create_loc_conf

此函数接受的参数是ngx_conf_t *, 也就是nginx配置结构体的指针

返回结果会被放在ctx->loc_conf[mi]中,mi是module index的缩写

返回一个有字符串和整数的结构体,

作用是告诉总的配置文件, 我需要一个啥结构体, 保存哪些信息

这个函数貌似不重要..不管了

merge_loc_conf

可以不写,和上面一样,没啥用

ngx_command_t

struct ngx_command_s {
    ngx_str_t             name;
    ngx_uint_t            type;
    char               *(*set)(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
    ngx_uint_t            conf;
    ngx_uint_t            offset;
    void                 *post;
};

其中主要是set这一个回调函数

set函数给这个模块加上了handler.

ngx_http_handler_pt handler

handler函数

核心部分

昨天没写出个模块,因为有太多很杂的东西了.

其实和大部分web程序一样, handler才是核心内容.

ngx_http_get_module_loc_conf(r, ngx_http_hi_module)这个函数可以(应该是把配置文件中的信息给r)

假设返回结果放到cglcf中了.

然后就是设置content-type,这永远都是必须的,注意nginx设置字符串全部是用data和len两个属性.

比如

r->headers_out.content_type.len = sizeof("text/html");
r->headers_out.content_type.data = (u_char *) "text/html";

当然也要设置状态码.

r->headers_out.status = NGX_HTTP_OK; // 这当然就是200

设置content_length_n, 不得不说我在nodejs和django中并没有发现返回数据的时候要写content-length.我猜这个content_length_n可能并不是http首部content-length.设置长度很简单

r->headers_out.content_lenght_n = cglcf->ecdata.len;

设置buffer.

b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));

b里面是什么东西呢,我也看不懂,大概是一些起始和末尾位置,chain位置.

设置out, 就是output啦.

out.buf = b; 
out.next = NULL; // out可以是一个buffer chain,这里表示后面没有了

调整b的pos地址,指向需要返回字符串的初始位置.

b->pos = cglcf->ecdata.data; // ecdata是网上抄的例子中的,实际自己取名

设置结束地址

b->last = cglcf->ecdata.data + (cglcf->ecdata.len)

可以把buffer放入memory

b->memory = 1;
b->last_buf = 1; // 这是最后一个buffer,我不知道这跟上面的next = NULL有啥区别

跟上面的发送头部一样先把头部发出去, 头部都存在r->headers_out中

rc = ngx_http_send_header(r);

如果成功的话, 继续发送output

return ngx_http_output_filter(r, &out);

这个filter啥意思呢?

其实filter有两个,一个是header filter,在r传进来之前就filter了header.

另一个filter就是这个body filter,是传输body的开始函数.

filter函数是链, 可以把它传给比如gzip这样的filter

交给filter, handler的任务就完成了.

总结一下

  1. 先设置头部,必要的有content-type, status, content-length.
  2. 设置要返回的buffer, 是通过设置buf的pos来定位字符串的.
  3. 发送头部
  4. 交给filter

明天计划写一个静态文件模块.

I promise, I resolve

Promise是当今大热, 很多人选择Promise来控制自己的异步流程, Promise甚至还进了下一代标准, 在最新的浏览器(chrome 32, 最新的Firefox)中已经自带

要注意的是Promise目前属于DOM Object, 虽然是es6的标准, 但只要不进v8, nodejs就不可能内置, (还是yield好

Promise的API可以参照MDN中的文档, 以及html5rock中的这一篇

首先要知道Promise最基本的用法, 把原生的Promise替换掉, 效果还一样就说明大致成功了

最简单的promise用法

new Promise(function(resolve) {
  setTimeout(function() {
    resolve('hello')
  }, 1000)
}).then(function(d) {
  console.log(d) // hi
})

可以看出Promise的理念就如其名, 先把异步函数放在Promise对象中, 在then函数中执行

换句话说, 就是我给你这个承诺, 但不马上执行, 先存起来, then的时候就执行, 执行完后告诉你是resolve了还是reject了

then函数

可以说Promise对象中最重要的属性就是then, then是一个函数, 其参数为then(resolve, reject)

resolve和reject也是函数, resolve函数中的参数就是上一次执行带来的参数, 注意, 一般来说是没有第一个参数是error的习惯的, 因为promise有reject

Promise当然不会是先存起来再then执行这么简单, 这也太弱了, 它更大的特点是可以不断的then

链式then

new Promise(function() {
  ...
}).then(resolve).then(resolve).then(resolve)...

Promise的魅力就是它可以链式的then下去, 当然像上面这样then只有第一个有用, 后面都是空then

后面的then的执行内容取决于前一个then中resolve的返回值, 看这个例子

new Promise(function(resolve) {
  setTimeout(function() {
    resolve('haha')
  }, 1000)
}).then(function(d) {
  console.log(d) // haha
  return new Promise(function() {
    setTimeout(function() [
      resolve('hahaha')
    }, 1000)
  })
}).then(function(d) {
  console.log(d) // hahaha
})

上面例子中第一个then返回了一个新的Promise对象, 然后接下来的then就能获取上面resolve的参数了

可以想见, Promise使用如此广泛, 却很少看到, 因为Promise都用在库的内部, 比如jQuery的ajax中, 他能随意中断或者执行, 不过是因为它每一步都返回一个Promise对象

那Promise对象到底有何特征呢? then()是如何判断resolve的return值是Promise对象呢?

其实Promise对象特征就一个, 上文也说过了, 就是有then属性

不信来做个试验

new Promise(function(resolve) {
  setTimeout(function() {
    resolve('haha')
  }, 1000)
}).then(function(d) {
  console.log(d) // haha
  return {
    then: function(resolve) {
      setTimeout(function() {
        resolve('hahaha')
      }, 1000)
    }
  }
}).then(function(d) {
  console.log(d) // hahaha
})

上面例子的第一个resolve仅仅是返回了一个带then一个属性的对象, 也可以链式的then下去

可见不管Promise的库有多少, 不管他用catch还是fail来表示错误, 不管是怎样的接口, then是肯定要有的, 或者说, then就是promise的一切

知道了这个我们就可以动手实现一个自己的Promise了

(function(exports) {
  exports.Promise = Promise
  var stack = []
  var resolves = []
  function Promise(fn) {
    stack.push(fn)
  }
  Promise.prototype.then = function(resolve) {
    resolves.push(resolve)
    run()
    return this
  }
  function resolve(val) {
    var fn = resolves.shift()
    var res = fn(val)
    if (res && res.then) {
      stack.push(res.then)
      run()
    }
  }
  function run() {
    var fn = stack.shift()
    if (typeof fn === 'function') {
      fn(resolve)
    }
  }
})(window)

去年小米面试说用20行实现一个Promise, 想必我这个还有没精简的地方, 我那时随便写了个不知道啥的东西交上去, 至今想起还有点羞愧

这里面最关键的函数就是resolve, 但功能简单明确, 执行resolve函数, 判断其返回值有没有then属性, push进执行函数数组中, 等待下一个run

闭包中存放两个数组, 一个是stack, 即第一个Promise的参数加上所有的返回值的then. 第二个是resolves参数, 是所有的then后面的resolve函数的集合

Promise该如何用呢?

我们还是写一个delay函数

function delay(time) {
  return {
    then: function(resolve) {
      setTimeout(function() {
        resolve(time)
      }, time)
    }
  }
}

这个delay比我们之前看到的toThunk型delay还多套了一层, 光这一点就说明Promise不如thunk

实现一个简单的waterfall流程

new Promise(delay(600).then).then(function(d) {
  console.log(d) // 600
  return delay(d + 200)
}).then(function(d) {
  console.log(d) // 800
  return delay(d + 200)
}).then(function(d) {
  console.log(d) // 1000
  return delay(d + 200)
})

上述Promise都完全没有涉及reject, 就已经有点复杂了, Promise说到底, 真的是解决了缩进, 也仅仅是解决了缩进, 没有从根本上简化异步流程, 要想真爽, 还得靠yield

KOA框架为何这么屌

最近逢人就说KOA用着好爽啊,但每次都被泼冷水

首先是看不起Nodejs派:“能跟我大structs比?”。我不得不低头,这真比不起,express就因为太小没法和那些"大框架"比,KOA比express还小几倍,核心部分就100来行,说出来岂不是让java程序员笑死,自讨没趣

其次是express派:“express就够屌了,我看KOA也就和express一样嘛”,我马上回到,不是有generator么,多方便。express派说:那不过是用了点ES6的特性,KOA本身没啥进步的。我一时语塞

不管别人怎么看,KOA确实用着爽,忍不住扯KOA的两点好

1. yield数组

yield处理逐个执行的异步函数上次已经说过了

yield后面还可以跟数组,如下

var fs = require('fs')
var app = require('koa')()
var readFile = function(dir) {
  return function(fn) {
    fs.readFile(dir, fn)
  }
}
app.use(function* () {
  var arr = yield ['1.txt', '2.txt', '3.txt'].map(function(path) {
    return readFile(path)
  })
  this.body = arr.join(',')
})
app.listen(8000)

其实这不过是[上上篇]({{ page.previous.previous.url }})中提到的不太难的多个平行异步函数获取总的回调函数的yield写法,不过也已经简洁到令人赞叹的地步了

试想下如果我们同时要获取数据库中多个内容,代码也会非常简洁

可问题来了,如果这其中有异步函数出错怎么办?

2. KOA中的错误处理

KOA中的错误处理才是真正让人叹为观止的地方,牛逼的同学可以看看generator’s throw feature

直接上写法

var fs = require('fs')
var app = require('koa')()
var readFile = function(dir) {
  return function(fn) {
    fs.readFile(dir, fn)
  }
}
app.use(function* (next) {
  try {
    yield next
  } catch(e) {
    return this.body = e.message || "I'm dead"
  }
})
app.use(function* () {
  var arr = yield ['4.txt', '2.txt', '3.txt'].map(function(path) {
    // 4.txt不存在
    return readFile(path)
  })
  this.body = arr.join(',')
})
app.listen(8000)

可以看到访问网页的返回结果ENOENT, open '4.txt'

我们在app的匹配栈上直接给yield next套上try catch

当然KOA本身也是会处理这些错误的,比如还提供了app.onerror这些东西,甚至自动做了404或者500的判断,源码如下

// delegate
this.app.emit('error', err, this);

// force text/plain
this.type = 'text';

if ('ENOENT' == err.code) err.status = 404;

// default to 500
err.status = err.status || 500;

// respond
var code = http.STATUS_CODES[err.status];
var msg = err.expose ? err.message : code;
this.status = err.status;
this.res.end(msg);

但我们往往需要自己处理错误: 处理数据库错误,处理文件读取错误,读取解析错误... 如果用自带的错误处理肯定会有人不解为什么在app.onerror中就不能用酷炫的this.body=了呢?也会疑惑ctx.res用了之后怎么会二次出错

因此我觉得在最前面加上next函数的try catch包裹非常有必要,这样我们可以统一的处理这些错误,任何yield错误都会被抓到,我们只需分析e.message以及e.code即可,我们甚至可以玩出很多花样来,利用this.throw()new Error()来定义自己的错误,简化错误管理,还能对错误进行归类。

代码要写在try catch外面

虽然用try catch 捕捉next错误很爽,但我们知道不管是try块中的代码还是catch块中的代码,都是无法让V8引擎进行任何优化的(具体原因忘了),不能优化的函数比优化的函数会慢上好几倍,也就是说try和catch中的代码比外面的慢很多倍。但是如果是调用try catch块外面的函数就不会有这个问题了。因此那段头部代码应该这么写

function errHandler(e) {
  ...
}
app.use(function* (next) {
  try {
    yield next
  } catch(e) {
    // 所有处理错误的代码都放在外面
    errHandler(e)
  }
})

不要小看这个,不信的话可以做这个试验

console.time(1)
try {
  throw new Error()
} catch(e) {
  for (var i = 0; i < 1000000000; i++) {
    var p = i % 2
  }
}
console.timeEnd(1)

// 取出来放在外面
console.time(2)
try {
  throw new Error()
} catch(e) {
  run()
}
console.timeEnd(2)
function run() {
  for (var i = 0; i < 1000000000; i++) {
    var p = i % 2
  }
}

结果是

1: 8352ms
2: 1294ms

差距还是相当大的

有了错误管理yield这俩大杀器,Nodejs中被人诟病的缺陷就足以解决一大半!

Web语义化

Web语义化主要分两个

  1. HTTP语义化
  2. HTML语义化

HTTP语义化

HTTP语义化是针对HTTP协议来说的,最有代表性的是Rest API

path

首先是path,路径,对于有大量接口的服务器,我们需要仔细的设计自己的API

比如看新浪微博的APIhttp://open.weibo.com/wiki/%E5%BE%AE%E5%8D%9AAPI

一般的话path用/来表示嵌套关系, 用querystring来表示filter,和属性值

HTTP method

HTTP method也具有语义化

一般来说普通服务器只用GETPOST,但还有PUTDELETE来对应数据库操作.不过说实话在实践的过程中,我还是觉得只用GETPOST

无状态HTTP

HTTP必须要是无状态的,所谓无状态,说细点就是用户带着你给用户配的token,可以访问任意的带有自己私钥的服务器.

HTTP无状态,说白了就是为了负载均衡.

如果HTTP是有状态的,你在后台保存着用户的session,那用户就只能访问你这台服务器,除非你还做了session同步

另一种方法是在负载均衡中配置会话保持,一般的方法是使用cookie保存server的ID,也可以是保存一张IP HASH表,但很显然这会产生其他问题

可以看出牵连会话的HTTP对于性能会有巨大的损失,而且如果一个服务器坏了,那上面的用户都没法继续会话了

HTML语义化

这才是重头戏,上文说了,Web语义化是为了让机器读懂.我觉得这个说的太玄乎.

系分到HTML语义化,一句话概括就是

块元素不出现<DIV>, 行内元素不出现<SPAN>

有朋友要问了,"现在主流不都是div+css么,你看看xx网站, 各种div,全部div啊"

div确实很全能,但这都是css的功劳,这使得我们连表格也都可以用div来完成

现在谁再用<table>标签,可能会被同行认为过时吧

可是HTML语义化在显示二维数据的时候就推荐用table.因为table就是用来显示表格的啊!不然W3C设计这个标签干嘛的.因此我们所说的不用table,完全是指你不该在整个网站上套table.

理解HTML语义化,就是理解HTML标签.

kejunz大大曾说过,最好的html代码是这个

<div class="mod">
  <div class="hd"></div>
  <div class="bd"></div>
</div>

玉伯大大发ISSUE表示赞成

其中也说道为何不用<header>/<section>/<footer>,却没有明确说明

在我看来,仍然觉得<header><footer>这样的好,为什么呢?

因为越是外层的块元素,选择器权重应该越低,我们知道id,class,标签对应的选择器权重分别是00100, 00010, 00001

如果我们用id="hd"来写的话,显然会导致选择器灾难,因为ID应该用在嵌套最深处的标识元素

但使用class="hd"就会好很多

Section Content

Section标签有4个

  • article
  • aside
  • nav
  • section

section content是用来表示从头到尾的部分,也就是这样用

<header>
</header>
<section>
</section>
<footer>
</footer>

在section内部我们一般是放头信息和概要,比如

<section>
  <h1>title</h1>
  <p>outline</p>
</section>

Heading Content

就是我们常用的h1,h2,h3,h4,h5,h6,hgroup

Phrasing Content

其他大部分标签都是此类(语法标签?),如a, b, canvas, cite, code, time, textarea

Embedded content

嵌入标签,有audio,canvas,embed,iframe,img,math,object,svg,video

Interactive content

a, audio, button, input, select, video

一些语义化标签

  • <nav> 导航标签,用来放入超链接列表
  • <article> 最常用就是里面包含一个h1和p
  • <aside> 用来放置于正文无关的内容,比如广告,侧边栏
  • <header> | <footer> 只需要知道他们并不是section content
  • <address> 用来放置联系信息,如作者名字,电话等.

试想如果有了这些标签,那爬虫就能干的事就多了,比如可以轻易通过<article>找出正文,自动过滤<aside>广告,根据<nav>来继续爬,根据<address>来显示作者和联系方式.

因此与其说Web语义化是为了让机器明白,其实就是让爬虫更加轻松.

另一个明显的语义化是rel属性

rel用在链接标签中(<a>, <area>, ```)

我们知道链接标签最常用的属性是href

rel属性的值很多,比如常见的是css的stylesheet,还有RSS的alternate

哦,对了,rel的全称是什么?其实就是relationship

因此,rel是用来表示href所指向位置和当前位置的关系的

rel的还有license,help,author,tag

如果链接是作者个人页面,那我们就应该写上rel="author"

还有的属性有next, prev, nofollow

这个也非常实用,比如对于很多有阅读模式的浏览器,他们会自动找next和prev的链接,帮用户设置好上一页,下一页

浏览器甚至可以智能的帮用户预加载next页面.同样nofollow可以告诉爬虫不要接着爬了

可以这么说,只有<link>stylesheetrel值是对普通浏览器有用的(它会使浏览器自动下载href指向的css文件),其他rel值都是无用的

没用我们还要写,完全是为了让爬虫,让阅读器,特殊浏览器,浏览器插件更加方便的读取我们的信息

我想这大概就是语义化的一部分了

块标签

  • dl 全称是description list
  • dt 全称是description term
  • dd 全称是description definition
  • figcaption 用于代码,图片,表格的标题,一般
  • figure 用于包含带有标题的代码,图片
<figure>
 <img src="bubbles-work.jpeg"
      alt="Bubbles, sitting in his office chair, works on his
           latest project intently.">
 <figcaption>Bubbles at work</figcaption>
</figure>
  • div 万恶的无语义元素,W3C明确指出,只有你想不出其他标签的时候才应该用div

文本级标签

  • em emphasis强调,会被搞成斜体
  • strong 也是强调,比em还强,一般是粗体(话说我今天才知道粗体会使元素变长)
  • i 全称是italicized,
  • small 常用来免责声明
  • abbr abbreviation
<abbr title="Web Hypertext Application Technology Working Group">WHATWG</abbr>

title为text的全称

  • dfn defining instance of a term,表示一个术语
  • time 属性datetime放置时间格式
<span class="summary">Web 2.0 Conference</span>:
<time class="dtstart" datetime="2005-10-05">October 5</time>
  • span 行内元素的div,本身毫无语义,也就是实在没合适的行内元素才会选这个,一般放在<code>中显示代码高亮

如何让IE6-8支持语义新标签?

首先是JS

(function(){
    if(!/*@cc_on!@*/0) return
    var html5 = "abbr,article,aside,audio,bb,canvas,datagrid,datalist,details,dialog,eventsource,figure,footer,hgroup,header,mark,menu,meter,nav,output,progress,section,time,video".split(',');
    for(var i = 0, len = html5.length; i < len; i++ ) {
        document.createElement(html5[i])
    }
})();

代码很容易,唯一蛋疼的是/*@cc_on!@*/是啥玩意儿

这个还得看MSDN的手册http://msdn.microsoft.com/zh-cn/library/vstudio/eb0w91wa(v=vs.100).aspx

然后是CSS

article,footer,header... {
  display: block;
}

总结

Web语义化除了HTTP的语义化外,主要就是HTML语义化

HTML语义化只要是反对使用无语义化的<div><span>而使用HTML定义好的语义化标签

语义化的好处是方便爬虫,浏览器,插件,特殊浏览器这些软件可以方便的把HTML中的信息归类

Nginx模块开发之set函数

set函数就是上一篇提到的很重要的函数之一, 是command命令结构体中的第三个参数.在读取命令时候触发的回调函数.

set是一个函数指针, 原型是这样的.

char * (*set)(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);

返回值

char *, 返回值可以是NGX_CONF_OK, 或者NGX_CONF_ERROR. 也就是0或者-1的指针.

参数

ngx_conf_t *cf含义未知

ngx_command_t *cmd 表示自己所在的那个command结构体

void *conf 当然是空

如何在set函数中挂上handler

ngx_http_core_loc_conf_t *corecf;
corecf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
corecf->handler = handler;

corecf为何物?, 其实就是读到test命令时的配置状态, corecf是一个巨大的结构体, name属性是"/test", 也就是我们定义的那个路径.后面还有regex等属性, 应该也是用来匹配路径的.

应该可以猜测, 给corecf的handler属性赋值后, 如果请求的路径匹配corecf的name或者正则等, 就会触发我们挂载的handler函数.

挂完后返回NGX_CONF_OK即可.

源代码中如何调用set函数

set在ngx_conf_handler.c文件在ngx_int_t ngx_conf_handler(ngx_conf_t *cf, ngx_int_t last)函数中被调用.

显然set函数的第一个参数cf就是这个大函数中的第一个参数.

第二个参数cmd, 是每一个模块的cmd, 在gdb中print每个模块的cmd, 可以发现daemon, error_log都是command.

ngx_conf_handler函数的作用就是遍历所有模块, 然后带着cf和cmd参数触发cmd的set函数,仅此而已.

nginx基本数据类型

我的参考来自这里http://tengine.taobao.org/book/chapter_02.html#id3

ngx_str_t

typedef struct {
    size_t      len;
    u_char     *data;
} ngx_str_t;

声明文件: core/ngx_string.h

最小的数据结构, 就是一个字符串,跟传统以\000来判断字符串结尾不同,ngx_str_t通过长度来判断字符串结尾.这样做有什么好处呢?

  • 减少计算字符串长度的操作,不再需要老是用strlen来算长度了.
  • data是指针, 使用的时候指过去就行了, 不需要copy字符串.
  • 坏处是glibc提供原生字符串的api, 使用时需要先吧ngx_str_t变成原生字符串才能用,略显啰嗦.

ngx_str_t的api

ngx_string(str)

// 本质就是

#define ngx_string(str) {sizeof(str) - 1, (u_char *)str}

宏定义,构造函数, 通过一个标准字符串构造出一个nginx字符串.

只能用来初始化.

ngx_str_t str = ngx_string("hello");

// error
ngx_str_t str;
str = ngx_string("hello");

这是因为结构体初始化后不能直接赋值

我实在不理解为何要这么做, 直接写有多打几个字么?可能是c开发者都喜欢建立自己的世界观吧.

ngx_null_string

宏定义ngx_str_t空字符串,也就是{0, NULL}

ngx_str_set(&str, text);

// ---

define ngx_str_set(&str, text) str->len = sizeof(text) - 1; str->data = (u_char *)text

此为宏定义,str为ngx_str_t的指针, 用法是把标准字符串赋值给ngx_str_t这个str指针.

这个text是常量字符串,而不是指针,否则len永远是3.

然后ngx就提供了很多ngx前缀的字符串方法.比如ngx_atoi, ngx_strcmp等, 不知道这样重复造轮子有啥真正的好处..

数据库操作太多了

我发现才做了一点功能,数据库操作就多的不行了,比如访问一下功能正常的主页,要进行至少如下的数据库操作

  • token解析后找到登陆的人,1次
  • 显示最近发的帖子,如果一页30的话,1次
  • 每个帖子根据作者id,查询作者信息,1*30次
  • 如果有最近回复的id,那就是查询最近回复, 1*30次
  • 30个帖子中是否有收藏的,1*30次
  • 是否点赞,1*30次

也就是说大概要进行上百次的操作,这简直是可怕的,我查了下discus访问一下主页大概是要10-30次之间,这也很多了

很显然,肯定是我什么地方想错了,不该有这么多数据库查询

比如我一直在纠结一个帖子的评论使用mongoose的子文档还是就是放在正常的文档中呢

放在子文档会很爽,每次新增评论只需要向comments这个属性中push这个评论,只会有一次save操作

如果是单独的collection,则需要新增一个reply,并且更新last_reply_id,如果不更新last_reply_id,则会导致显示最近回复需要一次复杂的操作

数据库操作复杂起来,对于nodejs简直是致命的,因为每次回调加上错误判断,3次以上的异步操作,函数就斜的不能看了

所以是时候使用一下异步的库,以前确实不知道有专门解决回调过深的库,现在觉得真是丢死人,写js不用异步库简直智商拙计

我想了很久,认为model设计上出了问题

比如一个帖子,按照nodeclub的做法,在帖子的model中既有last_reply_id也有last_reply_at,其中last_reply_at似乎是必须的,因为大多数排序是按照最后回复时间来排序的

但这样也有问题

  • 如果还是要获取最后回复的用户的name,则还需加一层嵌套
  • 每次发布一个评论,都要在主题中加入last_reply_idlast_reply_at,有一个发生错误就会产生冲突
  • 删除评论,如果碰巧是最新的评论,还需要删除该主题的last_reply_idlast_reply_at并且重新找最新的回复,因此大部分都是直接写评论已删除。。

总之,光是这几点,就足以让人崩溃,我们理想中的方法就是添加一个评论,不牵扯其他,仅仅为了最新的回复排序要折腾这么多?

目前的想法就是在内存中保留一个hash,键为帖子的id,值为所有评论,按时间排序。同时保存一个数组,按时间排序hash的键

这样的话有如下好处

  • 按最新回复排序,只需从数组中取出值
  • 显示一个帖子的回复已经不需要查询
  • 新增一个帖子只需要加入数据库后,把返回值shift入该主题的评论数组,并重新排序时间数组
  • 删除一个评论只需要从数据库中删除,并将其从该主题的评论数组slice出来,并重新排序时间数组
  • 除了数据库操作外,其他都不是IO操作
  • 每次启动论坛程序,只需遍历全部主题,根据id查询评论,按时间输出

坏处

  • 把数据保存在变量中很容易出问题,比如一个帖子有1000个回复,有1w多个帖子了,但变量就至少是600M大,越到后面就越无法扩展

解决方法是获取全部主题的前100个,然后存在数组中

但这样实现想想就很蠢

我最终的决定是加上一个辅助的collections,专门用来统计,每次开启服务的时候取到回复数(注意浏览数不属于统计),收藏数,最近回复

这样,每添加一个回复,直接替换该帖子的最近回复即可,也就是说这个新增的collection是专门用于算出可以统计出来的信息,防止冲突

同时在api设计的时候,还在想如何把评论插件化,如果能顺利插件化,那以后做赞,收藏,踩这样的功能就会轻松许多

nginx学习1

最近看了下nginx源码。有点头大。首先是我没用过nginx,第二是我不会c语言。

因此想借着看nginx源码学习下c语言。之所以学习nginx是因为nginx非常实用,而且代码写得好,并且已经有不少的源码分析博客。不会的话至少有个参考。

nginx有模块这个概念,可以自定义模块,这是入口点。

nginx模块的基本原理我总结了一下,基本就是靠用函数当参数,在特定地方调用函数。

写过js的一定对这个非常熟悉。因为js的cps风格就是不断的用函数做参数进行回调的。

nginx也可以叫回调,反正也是基于epoll和kqueue的嘛。

学习了一下c,发现c实现回调函数是用函数指针的。整个nginx使用模块的方法大概如下

#include <stdio.h>

void init(char *str) {
  printf("init: %s\n\n", str);
}

void bye(char *str) {
  printf("bye: %s\n\n", str);
}

struct ngx_module_t {
  char *name;
  int type;
  void (*init)(char *);
  void (*handle)(char *);
  void (*bye)(char *);
} ngx = {
  "my_module",
  3,
  &init,
  NULL,
  &bye
};

int main() {
  if (ngx.init)
    ngx.init("welcome");
  printf("I'm handle the request\n");
  if (ngx.handle)
    ngx.handle("handle it!\n");
  if (ngx.bye)
    ngx.bye("have finished!");
  return 1;
}

基本就是说你要给nginx一个struct,里面有你定的type, name啥的,同时要定义你的事件触发函数。

因为nginx整个模块是一个数组, 每执行到一个可以触发事件的地方,都会遍历所有模块,也就是那个结构体,然后访问模块结构体中的指定函数,比如刚进来的时候触发init事件,那就遍历所有模块,调用其init的函数。原理很简单。

虽然还没看具体代码,不过应该充斥着

if (ngx.xxx) {
    ngx.xxx( ... )
}

这样的代码。

话说c的声明,调用实在是太罗嗦了。用c真的会局限人的思维,程序员需要花太多时间在c语言本身上。

明天继续,争取写一个hello world模块出来。

nginx学习1

最近看了下nginx源码。有点头大。首先是我没用过nginx,第二是我不会c语言。

因此想借着看nginx源码学习下c语言。之所以学习nginx是因为nginx非常实用,而且代码写得好,并且已经有不少的源码分析博客。不会的话至少有个参考。

nginx有模块这个概念,可以自定义模块,这是入口点。

nginx模块的基本原理我总结了一下,基本就是靠用函数当参数,在特定地方调用函数。

写过js的一定对这个非常熟悉。因为js的cps风格就是不断的用函数做参数进行回调的。

nginx也可以叫回调,反正也是基于epoll和kqueue的嘛。

学习了一下c,发现c实现回调函数是用函数指针的。整个nginx使用模块的方法大概如下

#include <stdio.h>

void init(char *str) {
  printf("init: %s\n\n", str);
}

void bye(char *str) {
  printf("bye: %s\n\n", str);
}

struct ngx_module_t {
  char *name;
  int type;
  void (*init)(char *);
  void (*handle)(char *);
  void (*bye)(char *);
} ngx = {
  "my_module",
  3,
  &init,
  NULL,
  &bye
};

int main() {
  if (ngx.init)
    ngx.init("welcome");
  printf("I'm handle the request\n");
  if (ngx.handle)
    ngx.handle("handle it!\n");
  if (ngx.bye)
    ngx.bye("have finished!");
  return 1;
}

基本就是说你要给nginx一个struct,里面有你定的type, name啥的,同时要定义你的事件触发函数。

因为nginx整个模块是一个数组, 每执行到一个可以触发事件的地方,都会遍历所有模块,也就是那个结构体,然后访问模块结构体中的指定函数,比如刚进来的时候触发init事件,那就遍历所有模块,调用其init的函数。原理很简单。

虽然还没看具体代码,不过应该充斥着

if (ngx.xxx) {
    ngx.xxx( ... )
}

这样的代码。

话说c的声明,调用实在是太罗嗦了。用c真的会局限人的思维,程序员需要花太多时间在c语言本身上。

明天继续,争取写一个hello world模块出来。

jekyll学习

jekyll真的是太棒了,特别是代码显示部分,我指的是jekyll官网的css效果,我直接复制粘贴了他的css文件.

一开始我觉得jekyll所有post要求统一命名,比如2013-08-01-hello-world.md感觉很蠢.现在觉得挺好的,因为对于一篇随笔,你可能只是要一个时间.

我已经在考虑是否要完全使用jekyll写博客了,因为github的pages就是用jekyll渲染的,不用白不用.

放弃自己写博客生成是因为已经开始工作,这些东西看起来有点幼稚,而且不再有大量的时间放在自己的兴趣上了.需要赶紧补一下C语言.

挑选更纯粹的工具,才能更加高效.下面是学习jekyll的心得, 好的话以后就不自己折腾博客了..

文件夹结构

.
├── _config.yml
├── _drafts
|   ├── begin-with-the-crazy-ideas.textile
|   └── on-simplicity-in-technology.markdown
├── _includes
|   ├── footer.html
|   └── header.html
├── _layouts
|   ├── default.html
|   └── post.html
├── _posts
|   ├── 2007-10-29-why-every-programmer-should-play-nethack.textile
|   └── 2009-04-26-barcamp-boston-4-roundup.textile
├── _site
└── index.html

_posts文件夹就是放markdown格式的博客的.

_includes用于放子模版, 一般不需要.

_layouts用于放父模板,默认有default.htmlpost.html

_sites是自动生成的,需要用.gitignore忽略

_drafts你可以建一个草稿文件夹放还没写完的文章.

可以新建其他文件夹,并且像静态服务器那样访问他们,
比如可以新建一个about文件夹,里面放一个index.html放置自己的简历或者自我介绍,
同样你也可以直接在跟目录下放一个about.html
我试了下放markdown文件,结果报错了,不知道哪里出了问题.

_config.yml

配置文件,配置文件完全可以不管,这是非常好的,因为它提供默认值.

其中默认的键值pygments是代码高亮,我特别喜欢.

不得不重提一下markdown解析器,markdown: redcarpet, 这是能像Github-Flavored-Markdown解析出的结果那样.更重要的是code也能用python那种注释来包含,当然也是github的风格.这让我觉得爽极了,我以前的博客代码高亮是靠marked这个插件自己猜的..非常不好.

jekyll的redcarpet配合highlight样式以及Monokai(sublime默认配色),我觉得已经完美.看起来非常舒服.
平时上网搜问题,看到别人排版拙鸡的博客和挤在一坨的代码,不满的同时有点小小的优越感..

要注意解析器不能乱写,redcarpet是github自带的.github支持的jekyll插件在https://help.github.com/articles/using-jekyll-with-pages列全了,而且有版本号, 本地测试的时候最好也要下一样的版本.

其他配置等了解后再补充

front-matter


---
layout: post
title: Blogging Like a Hacker

---

这种放在最前的博客信息我一看就明白,因为我已经也是这样做的,原因是因为单靠ctime,mtime啊来判断时间并不靠谱.

jekyll的头部信息强制要求是yaml格式的.我觉得这点很棒.规范了格式.可以这么写


---
layout: post
title: Jekyll is Cool
tags:
  - jekyll
  - github
  - blog
categories:
  - blog

---

要注意的是这个头部信息是可选的,可写也可以不写.

预设的博客格式属性有如下几个

  • layout: 这个不多说.就是父模板
  • pemalink: 访问该博客的连接, 默认是像这样子/2013/08/01/hello/world.html
  • published: boolean类型,默认是true
  • category: 分类
  • categories: 可以是列表,就是属于多个分类.
  • tags: 标签.(说实话我搞不清categories和tags的区别, 觉得他们功能重合了..)
  • date: 日期, 可以覆盖外面的文件名日期, 日期写法很丰富,反正notepad中F5出来的日期是可以用的.

文章模板

文章模板是用一个叫Liquid template language做的,初步看来这不是一个全功能模板.不过语法属于大众语法,
{{ page.title }}表示插入, {% some code %}表示执行.没有太多学习成本.

注意我第一次提交的时候发现github,没有更新文章,我还以为是github不自动更新呢, 后来才知道模板没有编译通过
原因是我在文章中加入了{% %}, 这即便包含在反义符中都是没用的,因为模板解析在markdown解析之前,我只所以能打出来,
是因为我输入的是{&#37; &#37;}, 用的html编码。html编码很简单,在chrome的调试里直接输入

var code = '%'.charCodeAt(0); // 就能获取%的ascii次序
// html编码就是'&#' + code + ';'

所以如果修改文章导致jekyll编译不过, 那原因基本就是模板编译错误

{{ }} 字符串插入不会导致模板编译错误,没有的话只会返回空字符串

{% %} 语句错了就一定会导致模板编译不过

jekyll提供的默认变量很多

大变量site.

  • site.posts: 所有博客的hash.
  • site.pages: 所有page, 元素的属性page和post应该没啥区别
  • site.related_posts: 显示相关联的10个博客,不知道这相关联是怎么取的, 目测是时间..
  • site.categories.CATEGORY: 显示这个分类下的博客列表
  • site.tags.TAG: 显示这个标签下的博客列表
  • post: site.posts单个值, 有url, title, excerpt等子属性, 注意excerpt就是预览文本,取前面一小段.
  • page: 这是针对单个文章的全局变量, 作用等同于post, 同样拥有url, title, excerpt等属性.

Paginator

貌似中文叫页码.其中提供了获取上一篇 和 下一篇的功能

  • paginator.per_page
  • paginator.posts
  • paginator.total_posts
  • paginator.total_pages
  • paginator.page
  • paginator.previous_page: 上一页
  • paginator.previous_page_path: 上一页路径
  • paginator.next_page: 下一页
  • paginator.next_page_path: 下一页路径

注意, 这个paginator只能在index文件中使用, 坑爹啊.

paginator原来是总的页数的意思, 可以在_config.yml中设置, 比如

paginate: 5

那就是5篇文章一页.

上一篇和下一篇的路径分别是用page.previous.urlpage.next.url获取的.

jekyll的文档中貌似并没有说这俩个page的自属性.

暂时就看到这些文档, 感觉够用了, 剩下的就是继续去了解一下那个模板了.

co中yield的n种重载

yield在generator中不过是中断函数, 并且返回一个return.value的值, 但是到了co中, yield简直是无所不能, 简直是专治各种异步

来看这几个例子

还是准备之前一直用的delay函数(用setTimeout模拟延迟就是为了浏览器上也可以实验)

function delay(time) {
  return function(cb) {
    setTimeout(function() {
      fn(null, time)
    }, time)
  }
}

重载1. 首先是普通的thunk函数,也就是用的最多的情况

co(function* () {
  yield delay(100)
  yield delay(200)
})(function(err, d) {
  console.log(err, d) // null, 200
})

重载2. thunk数组

co(function* () {
  yield [delay(300), delay(200), delay(250)]
})(function(err, d) {
  console.log(err, d) // null, [300, 200, 250]
})

重载3. thunk字典

co(function* () {
  var o = {
    first: delay(300),
    second: delay(200),
    third: delay(250)
  }
  yield o
})(function(err, d) {
  console.log(err, d) // null { second: 200, third: 250, first: 300 }
})

这里一直在提thunk这个词,那到底thunk是啥呢

thunk是think的过去时,表示计算机一直在think(计算或者等待),等到结果出来了,就表示think结束了,说白了就是事件机制

thunk函数又是啥呢? delay函数就是典型的thunk函数, 来看它的特点, thunk函数的参数不带回调, 但是返回值却是一个参数为回调函数的函数, 也就是说, 只要是thunk函数, 都可以这么用

thunk(args)(next)

next也是一个函数, 但co会托管所有的next, 它一般长这样

function next(err, data) {
  if (err) cb(err)
  else doSomething()
}

因此可以看出co其实也是一个thunk函数, 因为co都是这么用

co(function* () {
  ...
})(function() {})

显然, 接受thunk函数的yield也同样能接受GeneratorFunction这种参数

重载4. GeneratorFunction

var gen = function* (time) {
  yield delay(time)
  yield delay(time + 100)
}
console.time(1)
co(function* () {
  yield gen(140)
  yield delay(100)
})(function() {
  console.timeEnd(1) // 1: 685ms
})

接受他们的混合也是可以的

var gen = function* (time) {
  yield delay(time)
  yield delay(time + 100)
}
console.time(1)
co(function* () {
  yield {
    first: gen(200),
    second: [delay(200, 100, 150)]
  }
})(function(err, data) {
  console.log(err, data) // null { second: [ 200, 100, 150 ], first: { key2: 200, key1: 300 } }
  console.timeEnd(1) // 1: 508ms
})

我也不是有意搞这么奇怪的(逃..

有人也许会说async也能满足这些个需求

但这其实是不可能的, 因为yield能真正中断函数, 而普通函数不行

co(function* () {
  var a = delay(200)
  var b = delay(a + 100)
})

在普通函数中a + 100, 早就在delay没执行完前被计算了, 但是在generator中, var b = delay(a + 100)是真真切切的被中断了

因此yield真正实用之处就是中断表达式求值

那co是怎么实现这么多重载的呢?

co多种重载的实现

实现这些重载非常简单, 个人觉得比TJ菊苣的co要好懂太多(捂脸), 代码如下

co核心

function co(Gen) {
  var gen = Gen
  if (isGeneratorFunction(gen)) {
    gen = gen()
  }
  return function(cb) {
    next()
    function next(err, arg) {
      if (err) {
        cb(err)
      } else {
        if (gen.next) {
          var ret = gen.next(arg)
          if (ret.done) {
            cb(null, arg)
          } else {
            // 新增toThunk
            toThunk(ret.value)(next)
          }
        }
      }
    }
  }
}

其中toThunk函数是之前没有的

toThunk做的就是分辨类型, 按参数类型把return.value变成真正的thunk函数

function toThunk(o) {
  var fn
  if (o.constructor && o.constructor.name.indexOf('GeneratorFunction') === 0) fn = co
  else if (Array.isArray(o)) fn = arr2Thunk
  else if (Object.prototype.toString.call(o) === '[object Object]') fn = obj2Thunk
  if (fn) o = fn(o)
  return o
}

thunk数组转成thunk函数

function arr2Thunk(arr) {
  return function(cb) {
    var len = arr.length
    var result = []
    arr.forEach(function(a, i) {
      var fn = toThunk(arr[i])
      fn(function(err, d) {
        if (err) cb(err)
        else {
          len --
          result[i] = d
          if (len === 0) cb(null, result)
        }
      })
    })
  }
}

thunk字典转成thunk函数

function obj2Thunk(o) {
  return function(cb) {
    var arr = Object.keys(o)
    var len = arr.length
    var result = {}
    arr.forEach(function(a, i) {
      var fn = toThunk(o[arr[i]])
      fn(function(err, d) {
        if (err) cb(err)
        else {
          len --
          result[arr[i]] = d
          if (len === 0) cb(null, result)
        }
      })
    })
  }
}

co中的yield还可以重载promise对象等

不过只要能理解co中的yield的值永远是thunk对象就足够了

IE6兼容适配实践

今天刚到公司,主管就表示今天的任务之一是适配各浏览器,该来的总是要来,从接触前端开始我就没用过低版本IE了,因为网上以及前辈都说IE太恶心,在各种招聘中也充斥着不需要兼容IE6的诱人条件也表明IE兼容难度极大。不幸的是我负责模板制作(很多专题页都来自模板,因为专题由编辑负责,一个事情一出,一般两小时要搞定,这种时候完全靠模板生成专题页),因此模板必须要兼容IE6。

测试人员打开IE6,果然之前排版好的界面完全爆了,我虽不懂IE6,但也知道它不支持border-box, 因此写的是content-box的网格,但布局依然是完全混乱

fixed

首先是导航栏挂了,position: fixed;, IE6妥妥的是不兼容这个属性的,这个时候的唯一解决方案就是IE6上的导航栏不随页面滚动,其他方案都太复杂了,事实上我看了几个大网站页面,都是支持fixed就fix,不支持的就不管

居中

第二是导航栏和内容没有居中,这让我有点奇怪了,在我有限的css知识中,只要是width: ***px; margin: 0 auto;就可以居中,用起来很顺手,IE怎么不居中呢?我问了几个前辈和网友居然没人知道为什么,不过我给父元素添加text-align: center;后就成功居中了。

我说这个text-align的时候还被嘲笑了,他们说text-align是行内元素的对齐方式,怎么可能让整个块居中呢?但我确实是加上后可以居中的,并且用真机试验了,其实这个上网一搜就知道原因,CSS1规范中,text-align仅应用于块级元素,而在CSS2.1中则相反,text-align仅支持行内元素,而IE6则几乎不支持CSS2.1,因此加上text-align可以实现IE6居中是非常合理的(不过各大牛依然坚持不需要text-align, 可能还是我写错了。。)

img外不应添加div

做一个可点击的img是非常常见的需求,也就是img套一个a标签,同时不少时候我们也需要图片和它下面的一行信息一起都是可以点击的,这时候往往a中套一个img和居中的span,如果在a中先套一个div, 再在div中加上img span,就会出现img不可点的情况,目前仅在IE6上发现。可见IE6中a里面还是不要放块级元素为好

double margin

最大的一个错误就是我整个布局错了,这是绝对不能忍的,如果是什么border-radius, 或者shadow等出错,这都是无伤大雅的,但布局不能错。IE6的问题就是我明明按content-box计算好的宽度和margin, 怎么就掉下去了呢?

查bug的时候我注意到,浮动的元素,第一个总是保持比后排向右移两倍的差距,我猜是不是margin算了两次啊,上网搜了下double margin IE6这样关键字,果然这是一个bug

幸好解决方法很简单,在所有float: left这样的浮动布局后面再加一个-display: inline;的IE hack,就完美解决了

overflow hidden失效

在IE6和IE7下会碰到oveflow hidden失效的问题, 只需在overflow: hidden;后面紧接着加上position: relative;即可

总结

在修复IE6中兼容bug的时候感觉成就感很强,并不如网上所说的那样可怕,而且只要了解大致的原理,做出兼容IE6的网站并不太难,甚至只要加几行CSS就行,很少需要去改HTML结构,不过这可能也是我还没接触到复杂状况的原因,非常期待其他的bug

nginx学习2

今天尝试写一个模块

ngx_http_module_t

这里面初始化所有8个成员全部是函数指针,同样是可选的,用NULL放弃使用回调。

create_loc_conf

此函数接受的参数是ngx_conf_t *, 也就是nginx配置结构体的指针

返回结果会被放在ctx->loc_conf[mi]中,mi是module index的缩写

返回一个有字符串和整数的结构体,

作用是告诉总的配置文件, 我需要一个啥结构体, 保存哪些信息

这个函数貌似不重要..不管了

merge_loc_conf

可以不写,和上面一样,没啥用

ngx_command_t

struct ngx_command_s {
    ngx_str_t             name;
    ngx_uint_t            type;
    char               *(*set)(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
    ngx_uint_t            conf;
    ngx_uint_t            offset;
    void                 *post;
};

其中主要是set这一个回调函数

set函数给这个模块加上了handler.

ngx_http_handler_pt handler

handler函数

核心部分

昨天没写出个模块,因为有太多很杂的东西了.

其实和大部分web程序一样, handler才是核心内容.

ngx_http_get_module_loc_conf(r, ngx_http_hi_module)这个函数可以(应该是把配置文件中的信息给r)

假设返回结果放到cglcf中了.

然后就是设置content-type,这永远都是必须的,注意nginx设置字符串全部是用data和len两个属性.

比如

r->headers_out.content_type.len = sizeof("text/html");
r->headers_out.content_type.data = (u_char *) "text/html";

当然也要设置状态码.

r->headers_out.status = NGX_HTTP_OK; // 这当然就是200

设置content_length_n, 不得不说我在nodejs和django中并没有发现返回数据的时候要写content-length.我猜这个content_length_n可能并不是http首部content-length.设置长度很简单

r->headers_out.content_lenght_n = cglcf->ecdata.len;

设置buffer.

b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));

b里面是什么东西呢,我也看不懂,大概是一些起始和末尾位置,chain位置.

设置out, 就是output啦.

out.buf = b; 
out.next = NULL; // out可以是一个buffer chain,这里表示后面没有了

调整b的pos地址,指向需要返回字符串的初始位置.

b->pos = cglcf->ecdata.data; // ecdata是网上抄的例子中的,实际自己取名

设置结束地址

b->last = cglcf->ecdata.data + (cglcf->ecdata.len)

可以把buffer放入memory

b->memory = 1;
b->last_buf = 1; // 这是最后一个buffer,我不知道这跟上面的next = NULL有啥区别

跟上面的发送头部一样先把头部发出去, 头部都存在r->headers_out中

rc = ngx_http_send_header(r);

如果成功的话, 继续发送output

return ngx_http_output_filter(r, &out);

这个filter啥意思呢?

其实filter有两个,一个是header filter,在r传进来之前就filter了header.

另一个filter就是这个body filter,是传输body的开始函数.

filter函数是链, 可以把它传给比如gzip这样的filter

交给filter, handler的任务就完成了.

总结一下

  1. 先设置头部,必要的有content-type, status, content-length.
  2. 设置要返回的buffer, 是通过设置buf的pos来定位字符串的.
  3. 发送头部
  4. 交给filter

明天计划写一个静态文件模块.

Promise原理解析与实现

之前写过一篇I promise, I resolve, 但我连微博都没发, 因为我自己都觉得很扯淡, 现在看来果然是在扯淡

这里说的Promise是es6 harmony的Promise, 而非那个DOM Promise.
现在的Chrome两种Promise都支持, 但默认为DOM的Promise, 要想打开harmony模式, 还得要在chrome://flag中打开harmony (启用实验性 JavaScript)

分辨dom promise和harmony promise的方法就是在dev中输入Promise(function(){})

如果报错了说明是dom的promise, 不报错则为harmony的promise

因为dom的promise标准已经被删除, 而harmony的promise既可以在浏览器中用又可以将来在nodejs中用, 我们当然是选harmony的promise啦

现在我们来尝试用100行左右代码实现一下promise的大概功能

首先写出主要的Promise函数

function Promise(resolver) {
  resolver(resove, reject)
}

我们都知道Promise的参数是一个函数, 其参数是promise内部控制流程的resolve和reject

看到这里, 想必大家觉得很熟悉, 所有流控制的库貌似都是传一个表达 继续往下传 的内部函数, 说大白话就是 我这里搞定了, 你继续 的回调函数

比如express4之前用到的connect, 其中的app.*()中的function第三个参数就是next, 可以用来移至下一个路由栈继续匹配, 而promise则使用了两个内部函数, 一个表达流程正确的resolve(解决了), 另一个是流程失败的reject(拒绝了)

虽然外观略不同, 但不管是connect还是promise, 其内部都有一个stack或者queue的东西保存着全部的流, 在js中显然也就是一个数组

比如express中可以这么链式的写

app.use(function(req, res, next) {
  next()
}).get('/xxx', function(req, res, next) {
  next()
}).use(function() {
})

其整个路由栈都被存入一个数组, 在next的时候移到下一个

而Promise的链式用法则为

// 先封装一个返回promise的函数
function delay(time) {
  return new Promise(function(resolve) {
    setTimeout(resolve, time)
  })
}

delay(100).then(function() {
  return delay(200)
}).then(function() {
  return delay(300)
})

promise的链式由then中的resolve返回值加入, 而非一开始就全部塞入, 这就是promise和express中next的主要区别

继续试着实现promise

function Promise(resolver) {
  resolver(resolve, reject)
  var queue = [] // 保存链式调用的数组
  this.then = function(resolve, reject) {
    queue.push([resolve, reject]) // 把then中的resolve和reject都存起来
  }
}

我们还没有写resolve和reject这两个内部函数呢, 这俩函数作用完全一样, 只不过一个表示正确, 一个表示错误, 我们完全可以用一个类似connect中的next来表达这两个函数

function resolve(x) {
  next(0, x) // 用0来告诉next是resolve
}
function reject(resson) {
  next(1, reason) // 用1告诉next是reject
}

那这个控制流程的next到底该啥样呢, 我们都知道, next的作用不过是去调用queue中下一个函数而已

function next(i, val) {
  // i 仅仅是用来区分resolve还是reject, val是值
  while (queue.length) {
    var arr = queue.shift() // 移出一个resolve和reject对, 也就是[resolve, reject]
    arr[i](val) // 执行之, val是唯一参数
  }
}

为何要while呢? 因为promise可以不停的then下去, 只不过传下来的都是resolve中的值都是undefined罢了, 因此我们用while来用光全部的resolve

问题就来了, 如果我们这么写

var p = new Promise(function(resolve) {
  resolve('ok')
})
p.then(function(x) {
  console.log(x)
})

因为完全没有延迟, 显然resolve先走了, 而resolve执行的时候, queue中还没有函数去接它, 这个时候就then就不可能触发了

因此要么把resolve的值存起来, 要么就是让resolve肯定晚于后面的then执行

我这里偷一下懒, 用一下setTimeout

function(i, val) {
  setTimeout(function() {
    while (queue.length) {
      var arr = queue.shift()
      arr[i](val)
    }
  })
}

这样resolve的出栈动作就肯定比进栈晚了, 不过这样写虽然很简洁, 但肯定有隐患(只不过我还没发现)

那如何让Promise支持链式调用呢? 这也不难, 我们只需将其执行结果存起来, 帮它then下去即可

function next(i, val) {
  setTimeout(function() {
    while (queue.length) {
      var arr = queue.shift()
      if (typeof arr[i] === 'function') {
        try {
          var chain = arr[i](val)
        } catch (e) {
          return reject(e)
        }
        if (chain && typeof chain.then === 'function') {
          // 一般来说链式的话resolve返回值为一个promise对象
          // 所谓promise对象, 其实不过是 {then: function() {} }
          // 也就是一个含有then函数的对象
          return chain.then(resolve, reject)
        } else {
          // 注意, 此处resolve中同样可以返回一个普通值
          // 我们帮他包装成promise对象即可
          return Promise.resolved(chain).then(resolve, reject)
        }
      }
    }
  })
}

上面是一个加上错误处理的next函数, 错误处理在promise中, 就是转成reject即可


其它函数

Promise还有其它函数, 比如Promise.all, Promise.resolved

我至今都不知道是Promise.resolved还是Promise.resolve

不过我觉得resolved听上去更对一点 (一个已经解决的承诺) , 而且chrome中也是这样的

实现这些附属函数特别简单

Promise.resolved = Promise.cast = function(x) {
  return new Promise(function(resolve) {
    resolve(x)
  })
}

Promise.rejected = function(reason) {
  return new Promise(function(resolve, reject) {
    reject(reason)
  })
}

Promise.all = function(values) {
  var defer = Promise.deferred()
  var len = values.length
  var results = []
  values.forEach(function(p, i) {
    p.then(function(x) {
      results[i] = x
      len--
      if (len === 0) {
        defer.resolve(results)
      }
    }, function(r) {
      defer.reject(r)
    })
  })
  return defer.promise
}

这里的all用到了一个Promise.deferred的函数, 这个函数格外重要


Promise.deferred

deferred的实现同样不难, 但其使用概率则是大大的, 可能比直接用Promise的几率还大

Promise.deferred = function() {
  var result = {}
  result.promise = new Promise(function(resolve, reject) {
    result.resolve = resolve
    result.reject = reject
  })
  return result
}

deferred的使用方法非常顺手

var def = Promise.deferred()
setTimeout(function() {
  def.resolve(222)
}, 1000)
def.promise.then(function(x) {
  console.log(x)
})

看到def, 才能看到Promise的精髓, 甚至jQuery反而提供defer作为主对象, promise不过是附属对象

我的完整Promise在这里

虽然目前Promise还不到100行, 但真正实现起来, 要比co那样借助yield的异步框架混淆很多, 我已经改了很多次, 但仍有bug, 这当然也跟我最近老打飞机有关, 已经有点神志不清

但是yield估计几年后才能用, 因此趁早学会Promise还是有必要滴

使用国外vps简单科学上网

注意:本文只为了看一些被墙的文献,18岁以下请主动关闭此页

这里将介绍3种使用国外vps科学上网的方法,分别是vpn, ssh, shadowsocks

首先是vpn翻墙,vpn一般是全局科学上网,不管是iPhone还是Android还是windows都不需要安装任何其他软件就可以翻*墙,对客户端来说是最方便的方案

服务端部署也不麻烦(有了下文的一键脚本后..),就3步

1 - 下载并运行脚本

wget http://soft.yzs.me/pptpd.sh;sh pptpd.sh

2 - 按脚本提示填写ip和网卡

Which IP is your server IP:
IP:(就是你服务器IP)

Please input the netdriver of your server:
Net Driver:(你用了哪张网卡,一般都是eth0,阿里云貌似是eth1)

再填上用户名和密码,比如都用vpn

运行lsof -i:1723

看到如下结果(有pptpd和1723)就算对了

COMMAND  PID USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
pptpd   1614 root    6u  IPv4   9988      0t0  TCP *:1723 (LISTEN)

为了感谢该脚本(主要是防止以后链接失效), 决定把脚本贴进来。。

#!/bin/bash

get_char()

        {

        SAVEDSTTY=`stty -g`

        stty -echo

        stty cbreak

        dd if=/dev/tty bs=1 count=1 2> /dev/null

        stty -raw

        stty echo

        stty $SAVEDSTTY

        }

clear

ip=$(ifconfig | grep 'inet addr:'| grep -v '127.0.0.1' | cut -d: -f2 | awk '{ print $1}')
echo "$ip"

        echo "==========================="

serverip=""
while [ "$serverip" = "" ]; do
        echo "Which IP is your server IP:"
        read -p"IP:"  serverip
done
        echo "==========================="
        echo "Server IP:$serverip"
        echo "==========================="

        echo "==========================="

ifconfig

netdriver=""
while [ "$netdriver" = "" ]; do
        echo "Please input the netdriver of your server:"
        read -p"Net Driver:"  netdriver
done
        echo "==========================="
        echo "Net Driver:$netdriver"
        echo "==========================="

username=""
while [ "$username" = "" ]; do
        echo "Please input the username of PPTP:"
        read -p"Username:"  username
done
        echo "==========================="
        echo "PPTP Username:$username"
        echo "==========================="

password=""
while [ "$password" = "" ]; do
        echo "Please input the password of PPTP:"
        read -p"Password:"  password
done
        echo "==========================="
        echo "PPTP Password:$password"
        echo "==========================="

        echo "Press any key to continue."
        char=`get_char`
apt-get -y update
apt-get -y install pptpd
sed -i "s#\#localip 192.168.0.1#localip 192.168.0.1#g" /etc/pptpd.conf
sed -i "s#\#remoteip 192.168.0.234-238,192.168.0.245#remoteip 192.168.0.234-238,192.168.0.245#g" /etc/pptpd.conf
wget http://soft.yzs.me/pptpd-options -O /etc/ppp/pptpd-options
touch /var/log/pptpd.log
sed -i 's/#net.ipv4.ip_forward=1/net.ipv4.ip_forward=1/g' /etc/sysctl.conf
sysctl -p
iptables -t nat -A POSTROUTING -s 192.168.0.0/24 -o $netdriver -j MASQUERADE
iptables -t nat -A POSTROUTING -s 192.168.0.0/24 -o $netdriver -j SNAT --to-source $serverip
echo "$username * $password *">>/etc/ppp/chap-secrets
sed -i "1i\iptables -t nat -A POSTROUTING -s 192.168.0.0/24 -o $netdriver -j MASQUERADE;iptables -t nat -A POSTROUTING -s 192.168.0.0/24 -o $netdriver -j SNAT --to-source $serverip" /etc/rc.local
service pptpd restart

3 - 在windows或android或iphone上添加vpn连接, 结束!


接下来介绍普通ssh科学上网方法

ssh的方法不需要在服务器上做任何设置,因为一般vps都会默认开启sshd,我们同样只需要3步即可科学上网

1 - 下载putty(下载链接)

2 - 编写一个一行命令的脚本kexueshangwang.bat, 放在putty同路径下

putty.exe -D 1080 root@你vps的IP

双击执行并登陆

3 - 安装proxy SwitchySharp

chrome插件地址

添加一个情景

科学上网

也就是在socks中填上ip: 127.0.0.1, 端口: 1080, 刚才-D后面的数字

启用这个情景,看看是不是能跑出去了


最后介绍shadowsocks科学上网的方法

shadowsocks也是目前流行所趋,他的优点,我也不大清楚,不过至少他不用建一个单独给人上网的linux账号,而且一般shadowsocks都会用上epoll的event库,性能应该也大大提升。也就是说shadowsocks是用来大批量科学上网的方法(卖¥)

方法同样简单,不过此处只介绍nodejs方法 (谁让nodejs是最牛x的平台呢,同样是3步

1 - 分别在客户端和vps端安装nodejs

window直接在nodejs官网下载安装即可

ubuntu更容易, apt-get install nodejs

2 - 在客户端和vps端分别安装shadowsocks的全局程序

npm install -g shadowsocks

此命令是增加两个全局命令(在命令行可以直接用的命令),一个是ssserver,另一个是sslocal

我们需要修改服务端和vps端的配置文件,其地址在全局安装的时候会显示出来

修改服务端的config.json, 就是把config.json中的ip从127.0.0.1改成0.0.0.0

在客户端我们也需要修改配置文件config.json, 把ip从127.0.0.1改成你的vps外网ip

3 - 启动

在服务端直接执行ssserver即可,看到如下即成功

1 Feb 03:51:23 - UDP server listening 0.0.0.0:8388
1 Feb 03:51:23 - server listening at 0.0.0.0:8388

在客户端执行sslocal

然后做端口代理转发即可,具体步骤同方法2普通ssh上网步骤3

据说shadowsocks使用udp通信,不必担心断线问题

为什么图片下面会多出3-5像素

今天给图片加背景的时候, 突然发现底部总是多出来3px(chrome下)

觉得非常奇怪, 还专门把html拎出来, css全删了还是出现底边, 后来把<!doctype html>都删了, 那条底边才没. 大家告诉我是没有reset css, 但加上margin和padding的重置后依然没用

不只是chrome, firefox有5px的底边, IE也有4px左右的底边, 具体可以看问题的放大版http://codepen.io/ftft1885/pen/CpDwa

问题的原因就是img属于inline元素, inline元素一般都是文字, 而英文的小写字母yg底部比别人多出来一段, 因此不管inline元素中有什么, 都会默认为y和g留出底部一段距离, 这段距离和字体大小有关, 比如把字体放大到129px后, 它外层的容易高度已经到了156px

不过知道了img表现同文字一样, 我们就能有很多解决办法

img {
  display: block;
}

这样img就不是inline元素了, 不用像文字那样需要留底

.box {
  font-size: 0;
}

把img的父元素设置字体大小为0, 字体不占位了, 因此也不会留底, 同理还有line-height: 0;

img {
  vertical-align: middle;
}

这是最佳的解决方法, 因为这问题就是由verticle-align: baseline;引起的

北京见闻

这次又来北京了,感觉北京就是吸铁石,也隐隐感觉北京就是我的归宿

北京的氛围确实完爆上海,地铁拥挤程度也完爆上海。。

来北京当然是继续面试,我比较喜欢面试,只要不是算法,我已经不大能碰到那些让我彻底语塞的题目了,因为我自己觉得知道的挺多,这次小米又让我大开眼界,特别是他们像素级别的浏览器自动化测试工具简直是突破天际,据说还是一个实习生写的。

可惜他们的主管一眼就看出我基本功不行,把我拉到一个会议室,问了几个小算法,我都是勉强做了下,总之他不是很满意,因此感觉也没啥戏了,这不禁让人想起业内知名度很高的司徒正美大大,他吐槽说小米面试必面算法,看来是这样的了。算法不行别想进小米做工程师。

以前我对此嗤之以鼻,我很少看到数据结构和算法的实际应用,我又不是搞系统或者搞网络的需要去知道红黑树之类的,但是那个主管却说到,在性能有瓶颈的时候,他们得去看webkit源码,数据结构足够扎实才能一眼看出dom加载是用了什么B树

这不禁让我陷入深深思考,毕竟我还在js本身的问题上纠结不清

最让我感到不可思议的是在北京遇到另一个校友,他表示也对我的能力感到失望,并吐槽说nodejs不就是v8套个壳么,你喜欢它干嘛?当时我就郁闷了,nodejs最怕这么黑,毕竟nodejs确实是libuv+v8,不像其他语言有很多特性,nodejs自己也说他是一个platform嘛。

不过后来我不得不服,校友说他以前就经常给v8还有webkit以及JVM提交过代码,这下我只能肃然起敬,我被connect merge一次都兴奋半天,校友都已经给这么牛逼的东西提交过了。

校友还不断问我,我有什么计划能超过我认为的这些大牛(校友觉得我说的这些人弱爆了。。),我不得不惭愧的承认根本没有想过,毕竟我的思维还局限在前端上

我当然也看过v8的代码,上次小米面试的快排优化就是从那里抄来的,可惜其他大部分都没看或者没看懂

我不禁感叹和校友之间的差距太大了,思维高度也差太多

当然,打击才是给人最大的警示和动力,我一度觉得比自己的同学厉害很多,那只不过我接触的圈子的人实在太弱了,我正准备重买那本数据结构熟读,真是觉得神奇当年我这门课怎么就没挂。。

这次来北京除了各种打击外,唯一值得高兴的是两家公司的面试官都觉得我jQuery还不错,在一个月前,我几乎都没用过jQuery,一直以写原生js为荣,现在能熟练用jQuery完全是因为一个月前我在还没用过jQuery的情况下实现了大部分常用jQuery的接口,因此和dom有关的题目,我都可以比较熟练的写出来,我相信要是他们面express我也是完全ok的,因为我几乎写出了一个迷你express, 当然,这些在校友看来依旧是太弱了,本来我下一步可能是看angular,现在有可能是需要直接看webkit,可是我的c语言超弱啊,唉真是愁人

用一个大大的是话来结尾,“学编程还是先把打字练利索吧”

resume test

[应聘]前端工程师-冯通-中科大-18914114612

基本信息

  • 姓名: 冯通
  • 电话: 18914114612
  • 邮箱: [email protected]
  • 应聘职位: 前端工程师

经历

工作经历

2013-12 ~ 至今 爱奇艺

实习经历

2013 北京般固科技(负载均衡相关)

教育经历

**科学技术大学 本科 | 计算机系

项目经验

PC端与移动端网页广告

负责爱奇艺PC端与移动端网页广告的SDK, 广告展示, 数据统计等

具体包括爱奇艺Flash广告播放器, 爱奇艺H5广告播放器, JS页面广告

广告后端数据接口

提供广告数据聚合服务, 使用技术为 nginx 与 Lua, 维护多台服务器, 万 QPS 负载, 提供接口稳定, 响应迅速的服务

业务后台编辑页面

爱奇艺视频节目后台管理页面, 包括增删改查等

网页生成

使编辑可以拖拽的方式快速做一个专题页面

开源小项目

喜欢在空余时间做有趣的小项目, 都在 Github 上, @chunpu

账号信息

IT技能

前端开发

  • 精通 Javascript, 擅长项目构建, 模块化, 面向对象, 函数式编程等
  • 擅长 HTML, 深刻理解 HTML 语义化, 擅长使用 HTML 模板引擎
  • 擅长 CSS, 熟悉 Bootstrap, 擅长 CSS 预编译, 能独立实现 CSS 框架, 相关项目 lesses
  • 精通 jQuery, 可手写简单的 jQuery, 相关项目 min-jquery
  • 了解 HTML5, 能熟练使用 HTML5 新功能 API
  • 擅长 浏览器兼容, 从 IE6 到 Android 百度浏览器, 能够写出兼容的 JS, CSS, HTML
  • 熟悉 Angular, Vue, Underscore 等框架, 深刻理解表现与分离, 能独立实现 mvvm 框架, 相关项目 mvvm2
  • 熟悉网络编程, Ajax, Jsonp, CORS
  • 像素级切图仔
  • 了解前端网络安全, 能够防范 XSS, CSRF 等攻击

后端开发

  • 擅长 Node.js, 精通 Express, Koa 等流行网络框架, 熟悉 Promise, async, request 等流行库
  • 擅长 Nginx, 熟悉 Nginx 模块开发, 熟悉 nginx.conf, Lua+Nginx, 独立完成 Nginx 线上热升级
  • 熟悉 Perl CGI 编程
  • 熟悉 Python Django
  • 深刻理解 HTTP 协议

测试

Linux

  • 熟悉 Shell, Perl, Awk 脚本
  • 熟练使用 VIM, SSH 等工具
  • 熟悉 Linux 中 coreutil 基本命令
  • 熟悉 Git, SVN 等版本控制
  • 熟悉 Docker 等新技术

其他

  • 有代码洁癖
  • 擅长断点调试与日志调试
  • 擅长写文档, 精通 markdown, wiki 书写
  • 英语六级, 阅读文档无障碍
  • 有 Javascript, Actionscript3, Java, Python, Golang, Lua, Perl, Shell, C, Nodejs, Erlang 等语言开发经验
  • 熟悉 Hive, Mysql, Redis, MongoDB 等数据库操作
  • 熟悉 Chrome 插件开发
  • 精通科学上网

wx

前端工程师简历

上文提到具体应该怎么写技能匹配,为什么选前端呢,因为我关注的大大经常转发前端招聘信息,肯定是有就业机会的.

先看下当今的招聘都有哪些要求,拿出几个近期的招聘瞧瞧

招聘信息太长了放到文末

总结一下

  • 理解Web,W3C标准 (一淘,SAE,云适配,Zealer,小米,蘑菇街,DNSpod,百姓网)
  • jQuery (云适配,金蚕网络,小米)
  • 跨浏览器适配 (一淘,Zealer,蘑菇街,)
  • HTML5 (云适配,小米,金蚕网络,DNSPod,新浪手机微博)
  • Web语义化 (云适配)
  • 后端语言或经验 (一淘,云适配,小米,金蚕)
  • Backbone或Angular (云适配,小米,DNSPod)
  • Ajax (云适配)

熟悉Web|W3C标准

可以很明显的看到"熟悉web标准"是非常非常重要的.那到底什么是Web标准呢?
请看这里http://www.w3help.org/zh-cn/standards/

这列表看的我想哭,说实话这一条我就不合格,不过话说回来,我觉得招聘要求并不一定是看完W3C标准这么严格(看完的人应该很少吧TT)

HTML5还是兼容适配

这里我们惊喜的发现,HTML5要求的企业比跨浏览器兼容的要多了.首先我是早早的放弃要求IE6-8的,除了不会兼容写法之外,由于我喜欢尝新,系统都是win8, OS X,Linux.玩很多系统却没有可以装IE6的地方.更何况会IE6的会考虑用户体验么?

后端经验

后端经验,这个我很喜欢,因为我会各种后端开发,C, NGINX, PHP, Perl, Python, Erlang, Nodejs.哇咔咔,我最不担心的就是这个了.

语义化

Web语义化,这个在我列的这些招聘中只有云适配,但实际上经常能看到要求web语义化的,特别是一些很重视前端代码的都有这要求.那到底什么是Web语义化呢?

看看知乎上的大妞怎么说的http://www.zhihu.com/question/20455165

一个语义化PPThttp://justineo.github.io/slideshows/semantic-html/

可以看到大家都说"语义化就是让机器也懂".说这话我真想拍死它,机器不是只懂0和1么.(不过Web语义确实是这么被定义了)

具体语义化请看下篇博客!

jQuery

一般企业都会要求至少会一种流行框架,这个流行框架中肯定会有jQuery

因此,掌握jQuery,走遍天下都不怕,最近我正在手写jQuery,不过完全不考虑兼容,也很少考虑健壮,只是为了熟悉原理,掌握API,相信手写一遍后在简历中写上熟练使用jQuery还是没问题的

HTML5

由于html5用的并不多,我只有在intel面试中问到了html5的问题,不幸的是intel面试官直接问我worker怎么消息传递

当时我就擦了,我玩过这么多html5,唯独没玩过worker,真是哪壶不开提哪壶

不过html5如果仅仅是接口的层面上,都非常简单,api设计的很人性化,在说自己玩过html5,最好都要完全了解

Angular或Backbone

企业有时会要求这些MVW框架,我在之前学习过Backbone,尝试开发过一个小应用,也自己实现过简单的MVVM,但要是说面试,我还是很虚的.

我在学习的时候觉得Angular和Backbone特别复杂,不像jQuery用起来感觉自己也能实现一个那样.要想在简历上写上熟悉Backbone,恐怕是要自己去多次实践才行

Ajax

有些企业会要求ajax,我不知道ajax有什么要了解的.不就自己写个小函数么,用jQuery的话更简单.不过我看了下jQuery的ajax模块,直接把我吓尿,复杂到爆表,后来导致我完全不想看jQuery源码,我最近在手写的时候也很少去借鉴源码,因为根本看不懂啊

HTTP

上面没有一条要求熟悉HTTP协议,但我觉得这也是很重要的,了解HTTP协议对于网站的涉及有着至关重要的作用

至少应该知道为什么Cache-controlmax-age大于Expires(上次朴大问的TT)

了解HTTP,看NGINX源码最好不过了,调试NGINX可以对各种错误信息状态码,以及各种头部有很深的理解,当然,如果你已经牛到可以手写NGINX,那你可以去应聘掏宝tengine组了..(话说我觉得不是太难唉.)

切图

切图属于UI设计了,但前端应该也懂,以前我甚至不知道什么切图,现在看来切图是快速建站的最佳方案,一切都用截下的img,大量使用relative,absolute布局

bootstrap

切图的反义词就是bootstrap了,一切用img实现的css效果都是耍流氓

站在css巅峰的bootstrap到了3.0版本,直面响应式设计

响应式设计就是切图建站的软肋,图片可以有很好的IE兼容,但响应式做不了

不过几乎没有企业要求掌握响应式布局和CSS预编译技术

一些企业的前端招聘事例

一淘网招聘

http://apps.weibo.com/djzhaopin/sign

资深前端开发工程师 岗位要求: 
1. 熟练掌握各种Web前端技术(HTML/CSS/Javascript等)和跨浏览器、跨终端的开发; 
2. 深刻理解Web标准,对前端性能、可访问性、可维护性等相关知识有实际的了解和实践经验; 
3. 至少熟练使用一门服务端语言(如:Java/Python/PHP/NodeJS等),并有项目经验; 
4. 对前端技术有持续的热情,个性乐观开朗,逻辑性强,善于和各种背景的人合作; 
5. 在博客或Github上有技术沉淀者优先。 

新浪SAE

SAE

云适配

云适配

ZEALER**

ZEALER**

小米

http://www.neitui.me/j/4465

* 最好有2年以上前端开发经验
* 懂javascript,了解其最基本语言特性,比如prototype chain,function is first class member这些知识,并在项目中经常使用他们
* 理解CSS/HTML规范。懂HTML5, CCS3更好。有过流行库,比如jquery, dojo, prototype的使用经验
* 对流行的框架,比如Angular JS, backbone, ember等有过研究是加分项
* 了解基本算法,有过后台开发经验,知道一个request从前端用户click一直到后台数据库中的lifecycle是加分项

蘑菇街

http://www.neitui.me/j/4501

职位要求:
1.细心,热爱技术,喜欢钻研;
2.成就感来自用户的认可.来自亲手实现的产品,来自亲手解决的疑难杂症;
3.良好的表达和理解能力,良好的学习能力,良好的解决问题能力;
4.精通主流Web前端技术,包括XHTML/XML/CSS/Javascript等;
5.深刻理解Web标准,对浏览器兼容性问题有丰富经验;

金蚕网络

http://www.uzwan.cn/job.html

html岗位要求
1. 熟练使用html,css,js等前端开发技术
2. 熟悉html5,css3,等移动前端开发技术
3. 熟练使用yui,jQuery之类的流行js框架之一
4. 熟练使用js面向对象编程,有大型js项目经验者优先
5. 熟悉canvas,webgl编程优先
6. 熟悉python优先
7. 有server编程经验者优先
8. 有游戏开发经验者优先

DNSPod

http://www.v2ex.com/t/76307#reply0

WEB前端工程师职位要求
能手写 HTML/JavaScript/CSS, 熟悉 jQuery 有丰富的关于 Web 标准、易用性、浏览器端原理的经验 有一定的英文基础,能阅读英文文档和邮件 有团队协作精神。善于学习,乐于探索,不墨守成规
加分:
热爱了解或分享互联网前沿技术如: HTML5/CSS3/Backbone.js/sea.js/CoffeeScript

新浪手机微博

http://www.neitui.me/j/4364

职位要求:
1.本科以上学历,计算机相关专业; 
2.两年以上前端开发经验,有移动前端开发经验 ;
3.精通 HTML5、JavaScript 等前端技术;
4.具有较强的学习能力和洞察力; 
5.具有良好的沟通能力和团队合作精神。

百姓网

http://jobs.baixing.com/

1-2年Web前端开发经验;
熟悉HTML、CSS、JavaScript等前台相关技术;
熟悉W3C网页标准;
熟悉PHP,或者熟悉任何一门其他编程语言,我们欢迎任何一门语言的加入;
充满好奇心,对新鲜事物有浓厚的兴趣;
很强的学习能力,新的东西可以很快地学会;
有责任感和服务意识,尽量想让周围的人更快乐;
希望你是一个好玩的人,可以给别人带来欢乐的人。

超简易AMD模块加载实现

最近深受打击,认识的几个菊苣工资都高的吓人,简直膝盖都跪烂了, 何时屌丝才能够钱娶到心爱的妹子啊

之前写网页,都是直接一个inline的style和一个inline的script搞定的

不开玩笑了..模块加载的好处是显而易见的,解决模块依赖的关系,线上联调(加url代理)方便

AMD就是一个模块加载的方案

AMD全称(Asynchronous Module Definition), 三个单词每个都是关键字,一个是异步,一个是模块,最后是定义

AMD暴露在外的函数只有一个: define

用requirejs的可能会说不是require([], funciton() {xxx})的么,但我们发现require完全可以改成define,虽然在语义上确实说不大通,我明明是要用这些个库,凭什么让我们用define呢,require才对嘛

但AMD标准就是如此,因此我们在真实环境中也应该用define而不是require

define的重载非常多

比如可以直接用来纯依赖

mod1.js

define(['./sub-mod1', './sub-mod2', './sub-mod3'])

可以完全不写factory

说到factory可能有人不明白了

这里要说到define函数的三个参数

  • id 给一个标志,这样别人通过id获取这个模块,而不是通过路径关系获取
  • dependencies 依赖,这是一个数组,包含自己需要的模块
  • factory 一个函数,这个函数的参数就是前面依赖的返回值

id这个值是可选的,甚至大部分时候都是不用的,也许我们可以在获取jquery的时候define它为jquery,大多数时候子模块都是不需要id的, 一般顶级模块会用id

dependencies需要注意的就是, 首先它没有.js这个后缀。其次是dependencies如果是相对路径,必然以.开头,我看了下AMD上面貌似没有定义如果不写.是表示绝对还是相对路径还是id,总之,如果是相对路径,那第一个字符必然是.,这点可以参照jQuery的文件

关于路径处理,AMD的WIKI举了俩例子

a/b/c + ../d -> a/d
a/b/c + ./e -> a/b/e

当然dependencies也是可选的,因为可以直接这样

define(function() {
  return '0.1.2'
})

但这有点说不过去,因AMD的wiki上写了CommonJS wrapping的方式 (就是nodejs那个方式), 它也是一个函数参数

define(function(require, exports, module) {
  var a = require('a')
  exports.action = function() {}
})

不过反正function有length这个属性,估摸也是可以重载的。。

一个js文件兼容define的写法, 假设我们写一个log函数

!function(f) {
  if (typeof define === 'function' && define.amd) {
    define(f)
  } else {
    window.log = f()
  }
}(function() {
  function log(str) {
    if (window.console && typeof console.log === 'function') {
      console.log(str)
    }
  }
  return log
})

看着貌似长了一大截(加上commonJS还会更长。。),但说不定加上这个别人就愿意用你的库惹

我的超简易AmdJS在这里https://github.com/chunpu/AmdJS, 可以成功加载jQuery,加载完我就对继续实现AMD失去兴趣了,唉我又是五分钟热度,实现基本功能就跑的那种

原本本文是写其原理的,但其原理实在是简单,就是动态的创建script元素插进去,onload后执行,最后删掉(看起来干净)。反倒是路径处理有点麻烦

因此本文写到这里已经兴趣索然,烂尾结束!

nginx学习2

今天尝试写一个模块

ngx_http_module_t

这里面初始化所有8个成员全部是函数指针,同样是可选的,用NULL放弃使用回调。

create_loc_conf

此函数接受的参数是ngx_conf_t *, 也就是nginx配置结构体的指针

返回结果会被放在ctx->loc_conf[mi]中,mi是module index的缩写

返回一个有字符串和整数的结构体,

作用是告诉总的配置文件, 我需要一个啥结构体, 保存哪些信息

这个函数貌似不重要..不管了

merge_loc_conf

可以不写,和上面一样,没啥用

ngx_command_t

struct ngx_command_s {
    ngx_str_t             name;
    ngx_uint_t            type;
    char               *(*set)(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
    ngx_uint_t            conf;
    ngx_uint_t            offset;
    void                 *post;
};

其中主要是set这一个回调函数

set函数给这个模块加上了handler.

ngx_http_handler_pt handler

handler函数

核心部分

昨天没写出个模块,因为有太多很杂的东西了.

其实和大部分web程序一样, handler才是核心内容.

ngx_http_get_module_loc_conf(r, ngx_http_hi_module)这个函数可以(应该是把配置文件中的信息给r)

假设返回结果放到cglcf中了.

然后就是设置content-type,这永远都是必须的,注意nginx设置字符串全部是用data和len两个属性.

比如

r->headers_out.content_type.len = sizeof("text/html");
r->headers_out.content_type.data = (u_char *) "text/html";

当然也要设置状态码.

r->headers_out.status = NGX_HTTP_OK; // 这当然就是200

设置content_length_n, 不得不说我在nodejs和django中并没有发现返回数据的时候要写content-length.我猜这个content_length_n可能并不是http首部content-length.设置长度很简单

r->headers_out.content_lenght_n = cglcf->ecdata.len;

设置buffer.

b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));

b里面是什么东西呢,我也看不懂,大概是一些起始和末尾位置,chain位置.

设置out, 就是output啦.

out.buf = b; 
out.next = NULL; // out可以是一个buffer chain,这里表示后面没有了

调整b的pos地址,指向需要返回字符串的初始位置.

b->pos = cglcf->ecdata.data; // ecdata是网上抄的例子中的,实际自己取名

设置结束地址

b->last = cglcf->ecdata.data + (cglcf->ecdata.len)

可以把buffer放入memory

b->memory = 1;
b->last_buf = 1; // 这是最后一个buffer,我不知道这跟上面的next = NULL有啥区别

跟上面的发送头部一样先把头部发出去, 头部都存在r->headers_out中

rc = ngx_http_send_header(r);

如果成功的话, 继续发送output

return ngx_http_output_filter(r, &out);

这个filter啥意思呢?

其实filter有两个,一个是header filter,在r传进来之前就filter了header.

另一个filter就是这个body filter,是传输body的开始函数.

filter函数是链, 可以把它传给比如gzip这样的filter

交给filter, handler的任务就完成了.

总结一下

  1. 先设置头部,必要的有content-type, status, content-length.
  2. 设置要返回的buffer, 是通过设置buf的pos来定位字符串的.
  3. 发送头部
  4. 交给filter

明天计划写一个静态文件模块.

nginx学习1

最近看了下nginx源码。有点头大。首先是我没用过nginx,第二是我不会c语言。

因此想借着看nginx源码学习下c语言。之所以学习nginx是因为nginx非常实用,而且代码写得好,并且已经有不少的源码分析博客。不会的话至少有个参考。

nginx有模块这个概念,可以自定义模块,这是入口点。

nginx模块的基本原理我总结了一下,基本就是靠用函数当参数,在特定地方调用函数。

写过js的一定对这个非常熟悉。因为js的cps风格就是不断的用函数做参数进行回调的。

nginx也可以叫回调,反正也是基于epoll和kqueue的嘛。

学习了一下c,发现c实现回调函数是用函数指针的。整个nginx使用模块的方法大概如下

#include <stdio.h>

void init(char *str) {
  printf("init: %s\n\n", str);
}

void bye(char *str) {
  printf("bye: %s\n\n", str);
}

struct ngx_module_t {
  char *name;
  int type;
  void (*init)(char *);
  void (*handle)(char *);
  void (*bye)(char *);
} ngx = {
  "my_module",
  3,
  &init,
  NULL,
  &bye
};

int main() {
  if (ngx.init)
    ngx.init("welcome");
  printf("I'm handle the request\n");
  if (ngx.handle)
    ngx.handle("handle it!\n");
  if (ngx.bye)
    ngx.bye("have finished!");
  return 1;
}

基本就是说你要给nginx一个struct,里面有你定的type, name啥的,同时要定义你的事件触发函数。

因为nginx整个模块是一个数组, 每执行到一个可以触发事件的地方,都会遍历所有模块,也就是那个结构体,然后访问模块结构体中的指定函数,比如刚进来的时候触发init事件,那就遍历所有模块,调用其init的函数。原理很简单。

虽然还没看具体代码,不过应该充斥着

if (ngx.xxx) {
    ngx.xxx( ... )
}

这样的代码。

话说c的声明,调用实在是太罗嗦了。用c真的会局限人的思维,程序员需要花太多时间在c语言本身上。

明天继续,争取写一个hello world模块出来。

CSS grid without border-box

这篇还是不删了,作为我愚蠢的教训...本文全是错的TT

在新公司呆了几天,任务还很轻松(刚来当然轻松,逃->

bootstrap中新版的网格系统(grid system)是及其好用的,http://getbootstrap.com/css/#grid

用非常简洁明了的class命名,如col-md-4, col-md-6这种来表示4 / 12, 6 / 12的宽度占比

同时用xs(extre small), sm(small), md(medium), lg(large)来解决响应式的问题,比如给一个元素同时加上class='col-md-4 col-xs-12'就表示正常下是占了三分之一,而手机上是独占一行

同时bootstrap还提供了col-md-offset-4这样的偏移定位,本质上不过是把width属性换成了margin-left属性罢了,bootstrap的网格通俗易懂,每个人都可以轻松手写之

可惜的是bootstrap的网格是彻底依赖* {box-sizing: border-box}

咱上http://caniuse.com/#search=border-box一查就可以发现IE6和IE7是不可能支持这种属性的

要知道,在border-box之前,所有的布局都是算出来的,我们必须把所有元素的border, padding, margin加起来,正好等于容器宽度才行。那时候的网页开发者,只能拿着计算器慢慢的计算,写个border都痛苦万分。border-box非常完美的解决了这个问题,把border和padding都归为自身长度

可惜我的任务是写出一个像bootstrap一样的网格,而且不能用border-box(我去年喵了个咪->

我从一开始就表示这是不可能的,如果也用百分比,那row中的每个col只能是等分,不然根本无法处理好中间的margin(算不出)

但需求就在那里,主管表示可以让一步,但必须要支持几种基本的布局,如1:1:1, 1:2, 1:1

其实这也已经很复杂了,因为margin不同, 直接写可能还很简单,但是要统一成一个css框架还是要开动脑筋的

其实只要是不用border-box,那外面的layout必然是固定宽度的,也就是必须写明是970px还是980px之类的,这样才能根据固定的margin算出宽度

至于为何是margin而不是padding,那是因为我们并不知道是border-box还是content-box,用margin两种都可以兼容

因此我们要写出这样一个css,拿960举例

.layout960 {
  width: 960px;
}
.layout960 .col {
  margin: xxxx;
  float: left;
}
.layout960 .cols-1 {
  width: xxxpx;
}
.layout960 .cols-2 {
  width: xxxpx;
}
...
.layout960 .cols-5 {
  width: xxxpx;
}

事实上,我们为了便于计算,会选择把.col左右的margin都放到margin-left中.

重点就是这些数值,它并不像border-box那样可以简单写一个百分比了事,他需要确切的数字,可以完成1:1, 1:2, 1:1:1等排列的数字

这个时候,我们终于发现,不带border-box的css果然还是数学问题,我们需要列出一个方程,这也回答了为啥我只用了6栏布局(12栏怎么算得出来嘛->

我们可以很快写出一个小程序

var s = []
for (var m = 10; m <= 30; m++) {
  for (var w = 940; w <= 1020; w++) {
    var x1 = (w - 5 * m) / 6
    var x5 = w - m - x1
    var x2 = (w - 2 * m) / 3
    var x4 = w - m - x2
    var x3 = (w - m) / 2
    var _x4 = w - 2 * m - 2 * x1
    if (x4 === _x4) {
      s.push([x1, x2, x3, x4, x5, m, w])
    }
  }
}

这段小程序就是遍历margin-left从10-30,layout的width从940-1020的全部匹配的值,发现会有超多。

我自己选了几组都是整数,而且看着顺眼的

cols-1, cols-2, cols-3, cols-4, cols-5, margin-left, width
[145, 308, 471, 634, 797, 18, 960]
[145, 310, 475, 640, 805, 20, 970]
[145, 312, 479, 646, 813, 22, 980]
[150, 320, 490, 660, 830, 20, 1000]
[150, 324, 498, 672, 846, 24, 1020]

也就是说,我们无法指定layout width的同时指定col的margin.只能靠计算撞到一组解,然后使用之,因此,大多不带border-box的css grid,都看起来用了些magic number

可惜这还是不对啊,我第一个col不要margin-left呀!

嗯,这个很容易,只需要加上

.layout960 .row {
  margin-left: -20px; /* 加个负边距就行了 */
}

虽然看上去解决了需求,但要注意的是它只支持如下布局

  • 1:1:1:1:1:1
  • 2:4
  • 3:3
  • 1:5
  • 2:2:2

不支持1:2:3, 1:1:1:3.方程无解咱也没办法啊..

超简易AMD模块加载实现

最近深受打击,认识的几个菊苣工资都高的吓人,简直膝盖都跪烂了, 何时屌丝才能够钱娶到心爱的妹子啊

之前写网页,都是直接一个inline的style和一个inline的script搞定的

不开玩笑了..模块加载的好处是显而易见的,解决模块依赖的关系,线上联调(加url代理)方便

AMD就是一个模块加载的方案

AMD全称(Asynchronous Module Definition), 三个单词每个都是关键字,一个是异步,一个是模块,最后是定义

AMD暴露在外的函数只有一个: define

用requirejs的可能会说不是require([], funciton() {xxx})的么,但我们发现require完全可以改成define,虽然在语义上确实说不大通,我明明是要用这些个库,凭什么让我们用define呢,require才对嘛

但AMD标准就是如此,因此我们在真实环境中也应该用define而不是require

define的重载非常多

比如可以直接用来纯依赖

mod1.js

define(['./sub-mod1', './sub-mod2', './sub-mod3'])

可以完全不写factory

说到factory可能有人不明白了

这里要说到define函数的三个参数

  • id 给一个标志,这样别人通过id获取这个模块,而不是通过路径关系获取
  • dependencies 依赖,这是一个数组,包含自己需要的模块
  • factory 一个函数,这个函数的参数就是前面依赖的返回值

id这个值是可选的,甚至大部分时候都是不用的,也许我们可以在获取jquery的时候define它为jquery,大多数时候子模块都是不需要id的, 一般顶级模块会用id

dependencies需要注意的就是, 首先它没有.js这个后缀。其次是dependencies如果是相对路径,必然以.开头,我看了下AMD上面貌似没有定义如果不写.是表示绝对还是相对路径还是id,总之,如果是相对路径,那第一个字符必然是.,这点可以参照jQuery的文件

关于路径处理,AMD的WIKI举了俩例子

a/b/c + ../d -> a/d
a/b/c + ./e -> a/b/e

当然dependencies也是可选的,因为可以直接这样

define(function() {
  return '0.1.2'
})

但这有点说不过去,因AMD的wiki上写了CommonJS wrapping的方式 (就是nodejs那个方式), 它也是一个函数参数

define(function(require, exports, module) {
  var a = require('a')
  exports.action = function() {}
})

不过反正function有length这个属性,估摸也是可以重载的。。

一个js文件兼容define的写法, 假设我们写一个log函数

!function(f) {
  if (typeof define === 'function' && define.amd) {
    define(f)
  } else {
    window.log = f()
  }
}(function() {
  function log(str) {
    if (window.console && typeof console.log === 'function') {
      console.log(str)
    }
  }
  return log
})

看着貌似长了一大截(加上commonJS还会更长。。),但说不定加上这个别人就愿意用你的库惹

我的超简易AmdJS在这里https://github.com/chunpu/AmdJS, 可以成功加载jQuery,加载完我就对继续实现AMD失去兴趣了,唉我又是五分钟热度,实现基本功能就跑的那种

原本本文是写其原理的,但其原理实在是简单,就是动态的创建script元素插进去,onload后执行,最后删掉(看起来干净)。反倒是路径处理有点麻烦

因此本文写到这里已经兴趣索然,烂尾结束!

Linux学习-1

一直不会Linux,有些遗憾,不过我觉得现在最可以胜任的工作可能是运维了。

首先是因为我目前的工作虽叫研发但每天代码量不到50.大量的修复前人的烂摊子。

其次我们经常看集群,网络等信息,反而很接近运维。再加上我本人对各种服务器很熟悉,也算了解各种协议,而且又会shell,perl。活脱脱一个运维嘛。。

运维怎么可能不懂Linux,你可能要问了。原因是我根本不用Linux系统,我是windows的粉丝,一直觉得Linux非常反人类。

我使用Linux的方式全部为在windows中用putty远程登录,因此Linux的图形界面很少接触。

Linux之所以实用性远不如windows,归根到底是Linux的cli太强大了。这让大多数人沉迷在命令行界面无法自拔,不愿去接触图形界面。

这次又入门了一下Linux,我发现不少之前的误区

shell

首先是shell,什么是shell,shell就是系统的壳,用来和系统交互的。我以前的误区在于认为命令行就是shell。现在看来大错特错了,GNOME同样有shell,GNOME shell就是我们看到的图形界面,这个shel用openGL渲染,它也可以编辑文件,修改配置等。当然bash也是一个shell。

因此shell分两类,一种是GUI(Graphical User Interface)的shell,图形shell,比如GNOME,KDE.

另一种是CLI(Command Line Interafce),命令行shell,有bash,zsh等。

之所以会有这个误区,是因为我们把shell当成了终端.

在很早以前,终端是一个类似打字机的东西,而现在我们在OS X或者Ubuntu上打开命令行仍然叫terminal,是因为现在的terminal是虚拟terminal.

其实终端是包括这两种shell的.终端可以理解为一个设备,/dev/tty

echo "hello" > /dev/tty

直接就打印到屏幕了.tty同样也是个命令

man一下可以看到这个:tty - print the file name of the terminal connected to standard input

tty会输出目前连接标准输入(键盘)的终端文件.(Linux一切都是文件..)

在一般的Linux发行版中,可以通过ctrl+alt+F1----F7来切换终端.对应tty1-tty6命令模式,tty7对应图形界面.

普通用户和root用户每次启动终端都会自动启动一个shell.这个在/etc/passwd中可以看到.

比如root一般就是对应/bin/bash这个shell.如果是zsh粉丝的话可能就是/bin/zsh

而其他系统用户的shell都是/bin/false,也就是没有对应shell.至于为什么没有GNOME这种,可能是因为图形界面也是先进入bash,再启动start X的吧.

分区

第二个误区在于分区,在较早接触Linux的时候,碰到分区问题,前辈会告诉你一堆英文和数字,什么/home,swap,根目录,等等.当时就吓坏了,想想还是跑回去玩玩windows吧,毕竟只要分个C盘D盘就可以了.

知道一个月前我装centos,依然是分四个区才敢装系统.现在看来,唯一需要分区的是根目录,根目录就好比C盘,系统塞里面呢.

不过在分根目录/之前,我们一般会先分swap分区,也就是内存不够用硬盘来替代的分区,虚拟内存分区.

有种说法是swap分区是实际内存的两倍,这在以前内存只有几M的时候是对的.但是现在内存最少4g,动辄几十G的环境下,swap早就已经没用了.

我的内存是8g,难道我要分16g给swap嘛?我的固态硬盘才128g.可惜的是我并不知道swap分区不分会怎样.按我看来swap分区应该分1g.

接下来所有分区都给/根分区.

那些/home,/boot怎么办呀!

这问题就跟windows分区分5个盘一样笨,有些朋友硬盘才300多g,却分了5,6个区.连一个30g的dota2或者30g的wow都不知道放哪里.

windows当然就一个C盘就行了,Linux更加是,/home/boot不就是在根目录下嘛,干嘛要分开来.我只能说纯属蛋疼.毫无必要.

我们可以通过mount命令来查看挂在的分区.

如果只分一个根目录区的话,就会有一行/dev/sda1 on /.

硬盘和分区

上面的sda1是什么?看完本段就没有这个疑问了.

在Linux中,设备都被显示在/dev目录下,硬盘也不例外.

硬盘的名字分两种,一种是sd+字母,如sda, sdb.

另一种是hd+字母,如hda,hdb.

hd是n年前IDE接口的硬盘,口很大针很多.现在几乎看不到了.

sd是现在的硬盘,比如sata串口硬盘.sd可以理解为small disk.

因此现在都是sda,sdb.非常好理解,sdb就是双硬盘的时候第二块硬盘

这些文件在硬盘插上去就会显示,即便他们还没被格式化,还没被分区.

分区是使用fdisk命令(只能MBR),我们分出来的区就成为sda1, sda2这样的.

一般的话我们只有一块硬盘, 一个根分区,因此就只显示/dev/sda1 on /

文件权限

Linux有着完善的文件权限管理.

一般使用chmod来进行权限管理

文件权限可以分别对user grounp other三个分类进行设置

习惯上user和group一样,因为group就是user同名的.暂时看来很少会用到group.

权限有三个,read, write, 和execute.

ls -l中我们可以看到文件的权限信息drwxr-xr-x.

分开看就是d rwx r-x r-x.第一个d是指目录,如果是l就是链接,如果是-就是普通文件.

同样第一个rwx表示user对文件有读,写,执行,全部三个权限.

第二个r-x表示表示group对文件有读和执行两个权限,第三个同理是对other说的.

chmod a+x # 所有用户加上可执行权限
chmod u+wx # user用户加上写和执行权限

我们可以这样给文件加上权限, 一般我们写脚本,如果想直接运行,都会用chmod +x xxx来使其变成可执行文件.

注意这儿的a表示all user.即包含了user+group+other.如果不写就是默认为a.

数字模式

我们还看到别人输入chmod 777 xxx,那这个数字代表什么呢.

其实这个数字是8进制表示法.

r = 4 (2^2)
w = 2 (2^1)
x = 1 (2^0)

rw = 4+2 = 6 = 110
rwx = 4+2+1 = 7 = 111
r-x = 4+1 = 5 = 101

常用mod的有可执行文件775 普通文件660 文件最高也是666

我们可以通过umask命令查看默认权限配置

umask root 0022 普通是0002

root限制了group权限,新建的时候是减法计算.

还有另一个模式叫s, g, t,(suid guid sticky)

比如sticky,

root@ft:~# ll /usr/bin/passwd
-rwsr-xr-x 1 root root 42824 Apr  9  2012 /usr/bin/passwd*

首先要知道用户执行的进程拥有和用户一样的权限.

passwd命令所有用户都可执行,但我们知道passwd本质是修改/etc/shadow文件,这个是一般用户无法查看的.那怎么办呢?

这时候就需要suid了,passwd不管是什么用户执行,都会以所属用户即root执行,这样该进程就拥有查看并修改/etc/shadow的权限了.

sticky是别人无法删除不是自己的东西.guid当然和suid一样啦.

Linux根目录下的目录

Linux下的目录都有些是成对的.

比如mnt media

他们都是空的,用来挂在新的设备,一般是存储设备.

media用来挂在自动播放的存储设备.

还有bin和sbin

他们都是可执行文件的目录,sbin表示super binary.里面放的都是危险的可执行程序,都需要root权限,比如fdisk.

最后是usr和opt

usr是放用户自己安装的程序的,而opt也是放自己的程序的,但opt一般习惯放大文件.

/proc

/proc是虚拟文件系统,是一部分内存的映射,这个是Linux提供的接口,在UNIX比如苹果OS X上并没有这个.

/proc的接口是Linux比其他系统简单的原因,我们可以轻松获取硬件信息,比如常用的/proc/cpuinfo, /proc/meminfo

还有电池信息, 鼠标信息, 我们仅仅修改文件就可以控制鼠标和键盘,这是多么爽啊.

因此,在Linux中查看实时的硬件信息,比如网卡状况啊,尽量使用/proc查看,显得更加专业.

/etc

和查看信息用/proc一样,在Linux中修改配置,我们尽量的使用/etc直接修改文件,这样更加方便我们了解本质,比如新增一个用户,你需要纠结到底是useradd还是adduser,但实际上新增一个用户就是在/etc/passwd中加一行而已.

同样,修改ip,我们需要纠结很多网络命令,但其本质也是修改ifcg-ethx这种文件.多用/etc/修改配置,我只能说分分钟涨姿势.

乱七八糟的小知识

cal

cal命令可以显示可爱的日历

    August 2013
Su Mo Tu We Th Fr Sa
             1  2  3
 4  5  6  7  8  9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31

Linux很多rc文件的rc啥意思

rc = run command

跟踪日志信息

tail --follow 用来实时跟踪查看日志信息

快速搜索文件

locate命令,通过文件数据库搜索,文件数据库一天更新一次,可以通过updatedb命令手动更新

其他小知识

分区跟硬件无关,是软件实现, 主流有MBR和GPT

小技巧

esc键 . 可以复制上一个参数

find . -name "a*" -exec ls -l {} \;管道参数.

网络对应配置文件

网卡配置 /etc/sysconfig/network-scripts/ipcfg-eth0

dns /etc/resolv.conf

主机名 /etc/sysconfig/network

静态主机名 /etc/hosts

可以用mtr来测试网络质量

mtr = my traceroute. 可以看到丢包率

使用文本工具

cut

cut -d: -f1 /etc/passwd

sed 正则替换

Linux 启动顺序

BIOS 可启用设备

扇区前512字节最后两个字符是不是55aa BIOS移交控制权给MBR前446字节

446字节一般用来跳转一个复杂的引导程序, 比如grub

启动顺序 grub中的stage1也就是MBR,然后stage2 最后是内核

加载完linux内核后运行第一个进程 init

我们可以看到init是pid为1的进程, init是所有进程的父进程

他的作用是启动/etc/rc.d/rc.sysinit

init 有0-6 7个运行级别,在/etc/tabinit中可以看到注释,

同时这个运行级别就配置在这个文件中

shell中的$#

$表示普通用户user

#表示超级管理员root

shell前面每次那一段叫提示符,可以在shell的配置文件中修改.

root忘记密码

grub是给kernel加上1,可以单用户模式,直接进root不要密码

可以用grub-md5-crypt给grub加密码.

yum

yum是rpm的前端程序 全程叫yellowdog updater modified 可以解决依赖关系

查询一个文件是哪个程序的

yum whatprovides /etc/tabinit

centos中所有几乎所有软件都是rpm包装的

待了解

Linux加密算法/etc/shadow

语义化

这是我学习Linux最大的感悟,如果有人问我怎么学Linux,我会告诉他三个字语义化.

曾见过前辈教新人Linux, 嘴里飞快的吐着各种命令,比如gdb, 一会儿s,一会儿f,一会儿b.新人呆呆的照着做.

前辈说完,问道会了吧,新人点点头,心里不住咒骂,这sb Linux,调试都这么烦, 还来个vim,能跟我大eclipse比?

其实这就是因为学习Linux没有语义化学习的原因.

比如一个简单的tar zxvf xxx.tar.gz不少人敲了十遍还是记不住,就算记住了解压缩释放, 压缩的时候还是得去百度一下.

我是非常反对简写的.

tar --gzip --extract --verbose --file  nginx-1.5.2.tar.gz

一般cli程序都会用--来作为参数的开头,并且加入-开头的alias,也就是简便的别称

比如版本是--version,帮助是--help.一般的别称是-v-h

虽然有简写,比如-h,但版本就不一定了,因为-v可能是--version也可能是--verbose.所以有些--version的缩写是-V.

-h也未必是--help,也可能是--human-readable

因此使用全称更加方便我们语义化的记住用法.

比如gdb,如果我们每次都输入step, refresh, finish这些全称的话,估计10分钟就用熟了.

同样vim, 非常不推荐在配置中写上set ts=2,谁能想到ts是tabstop呢.没准自己也有一天忘了呢.

之所以提语义化是因为我用这么久命令行,依然不记得如何ls用时间或者大小排序.

其问题就在于ls -s根本不是按大小排序,而是另一个功能.

如果我早点知道ls --sort=timels --sort=size,我面试还会煞笔到连ls都说错! 哎,一提到曾经的面试我只能说都是泪.

语义化实在太重要了.

现在的前端招聘,对html的要求一般就一条:

熟悉语义化html

这是基本的一条,同时也是要求很高一条.

直接涉及的html标签的使用,嵌套,控件的设计, 以及CSS class命名, 行为和显示分离.一大堆知识.

最后,我只能说,语义化学习Linux,轻松加愉快

Unix 文件描述符

Linux通过文件来提供底层接口, 有句话说的好, Linux中一切都是文件.

不管是系统信息(/proc)还是目录, 甚至是socket套接字, 都是文件.

程序使用文件是通过文件描述符, 英文叫file description. 通常简写为fd.

文件描述符是一个非负整形.

程序进程运行的时候, 会默认有三个预置的文件描述符,分别是:

    1. 标准输入, stdin
    1. 标准输出, stdout
    1. 标准错误输出, stderr

在shell中, 我们用小于号<给程序提供标准输入

nc -n 192.168.1.2 1111 < somefile

用大于号>提供标准输出

svn log > /tmp/svnlog

2>提供错误输出

ping 192.168.1.100 2> /tmp/pingerr

Linux提供标准的POSIX接口, 什么是POSIX?

POSIX 表示可移植操作系统接口(Portable Operating System Interface ,缩写为 POSIX 是为了读音更像 UNIX)

其中主要的有4个函数, read, write, open, close

他们的接口果然很标准, 很好记

read(fd, buf, len);

write(fd, pos, last - pos);

open(filename, flags); // return fd

close(fd);

他们打开的时候如果错误, 都是返回负数, 所以习惯上把他们写在if语句中.

使用这些POSIX接口的时候, 必须要引入一个库unistd.h

因为标准输出是1, 所以我们可以写出一个最简单的hello world.

#include <unistd.h>

int main() {
  char str[] = "hello world!";
  write(1, str, sizeof(str));
  return 1;
}

这里不得不提一下, 我刚开始学c的时候, hello world是用printf写的.

我一直很好奇printf是啥, 有学长跟同学跟我说是print file. 我还是很疑惑, print file是啥玩意儿, 然后就得知Linux中一切都是文件的说法, 屏幕也是文件.

不过man一下printf就知道就是print format的意思, 联想一下erlang的io:format即可.

jQuery原理窥探

jQuery是一个伟大的库,但我一直没有去专门学习它.不过jQuery非常的人性化,导致于我们不需要像backbone那样去看说明书.

不过不看说明书的后果就是我对jQuery产生很多误区.

这些误区直接暴露在我几个月前的面试中,比如

我会原生Javascript,同时也会jQuery

@朴灵大大听后立马纠正我,jQuery也是原生Javascript.

这点我在此次学习中算是了解了,jQuery确实是完全的Javascript,除了两个全局变量$jQuery外,做到了完全没有污染其他空间.

也就是说如果引进了jQuery,照样可以写原生的js,完全不妨碍.

jQuery自已定义了dom,用起来不舒服

我现在想起自己的回答都想找个缝钻进去,当时的我根本没意识到所有的jQuery DOM对象都是一个类数组(Arraylike)

选择器

jQuery从名字上讲,是一个js的dom选择器.我们熟知的dom选择器是document.getElementByIddocument.getElementsByTagName

但我们知道CSS的选择器非常实用,比如div h1.title,他一下子表达了很多意思,想一下子选出这些dom在css中很快,但是在JS中却非常难办.

jQuery本身主要是为了解决这个问题.要注意的是现代浏览器都提供了document.querySelectorAll让我们开发者也能用css选择器的接口

让人大跌眼镜的是很多同学,甚至是一些jQuery讲师,居然不知道这个接口.jQuery和css选择器接口的返回值都是一个NodeList,他们都是类数组

类数组是指键是从0,1,2,...这样像数组那样排列,可能有length属性,但没有数组中prototype的比如push,pop等方法的对象

jQuery和css选择器返回的NodeList都不是动态的(getElementsByTagName是动态的),jQuery返回的NodeList有大量的proto方法,比如bind,click等等,正是这些proto方法让jQuery变得如此简单易用.

jQuery的nodeList

正是这个nodeList让我刚学js的时候困惑不解.我天真的以为在选id的时候就是调用getElementById而其他的时候是调用其他高级选择器.

就是说我根本无法理解我明明是选定一个id,它怎么可能返回一个类数组.

因此我才会在面试的时候说jquery返回的不是原生的dom对象,比如不能用myDiv.innerHTML来获取innerHTML.

我们知道jQuery获取innerHTML的方法是$('xxx').html().

看到上面这个api,我当然是以为$('xxx')是返回一个自己封装的dom对象,我当时抱怨为何把innerHTML的属性给删了.真是怎么也想不到我们这样是对一个列表调用.html()

$('xxx').html()返回的当然是字符串,不是数组,因此实际上$('xxx').html()的返回值就是$('xxx')[0].innerHTML

但我敢肯定,新人在碰到这个api的时候都会想一下:"为什么jQuery不直接提供$('xxx').html"的值啊,非要做成一个函数.jQuery只能哭着说"臣妾做不到啊."

而相反的,$('for').html('bar')又是对整个nodeList进行innerHTML赋值

jQuery的链式编程

jQuery的链式(chainable)编程一直是被人称道的,他可以这么写

$('div').find('h3').eq(2).html('Hello')
          .end().eq(4).css(xxx)

但原理说穿了不值一提,就是每个proto方法返回return this罢了.

$('div1').insertAfter($('div2')) // return div1
$('div2').after($('div1')) // return div2

虽然很简单,但我们依然要注意每条链主语,上面两句作用一样,但主语不同,同理还有appendToappend

上面还有一个.end()这个函数可以返回上一个主语.

jQuery事件触发

jQuery事件触发也和我们常用的不同,平常的我在注册click事件的时候都是

myBtn.onclick = function() {
  doSomething()
}

这样的问题是如果有别人也想用到这个onclick事件,就会把之前注册的函数覆盖了.

jQuery的做法是

$('myDiv').click(function() {
  doSomething()
})

这样的好处是不会覆盖之前的click事件函数,我们可以猜测它内部应该是使用了addEventListener

这样只是说对了一半,如果单单使用addEventListener,那我们在webkit inspector的event listener中应该能看到绑定的所有函数.

但事实上我们用jQuery绑定事件永远只看到一个奇怪的函数.

据说这就是jQuery处理多事件高效的原因..(一个函数托管所有事件)

那他是怎么实现的呢,我看了一下源码,发现根本看不懂..一个大大说过,源码看不懂,就自己实现,在对比源码,如果不一样,那不是你**了,就是作者**了.

var fn = {}
$.fn = fn
var eventList = []
function handler(e) {
  for (var i = 0; ev = eventList[i]; i++) {
    if (ev.target === e.target && ev.type === e.type) {
      // event match
      if (ev.handler(e) === false) {
        e.preventDefault()
        e.stopPropagation()
      }
    }
  }
  return this
}

fn.__proto__.bind = function(evType, fn) {
  for (var i = 0; i < this.length; i++) {
    this[i].addEventListener(evType, handler)
    eventList.push({
      handler: fn,
      type: evType,
      target: this[i]
    })
  }
  return this
}

;['click', 'dbclick', 'mousedown', 'mousemove', 'mouseover', 'mouseout', 'mouseup'].forEach(function(evType) {
  fn.__proto__[evType] = function(fn) {
    return this.bind(evType, fn)
  }
})

随手写了一下,简单的说就是注册一个事件的时候,不管是什么事件,直接dom绑定handler函数,然后把事件的类型和dom对象以及其函数加到一个内部的数组eventList中. 这样所有的事件函数都在这个数组中了.

当事件触发的时候,从头遍历eventList,如果e.typee.target都相同了,那就调用此函数.

可能有同学会问,你每次bind都去addEventListener会注册多个handler啊,其实不会的

function handler(){}
myDiv.addEventListener('click', handler)
myDiv.addEventListener('click', handler) // 最终还是只有一个事件

提一下jQuery中如果事件函数返回值是return false就是取消冒泡事件和默认事件,也就是上面代码中这一段

if (ev.handler(e) === false) {
  e.preventDefault()
  e.stopPropagation()
}

实现的干净利索,方便实用,居家旅行必备

ps: 上文提到的原理均为自己猜测,jQuery具体实现比我瞎想的要复杂且健壮10倍多.

nginx学习日记

哎,说起这两天nginx的学习, 还是毫无进展, 上次我还吹牛说要写个静态服务器模块, 现在看来纯属扯淡.

nginx实在是复杂, 一个最简单的模块, 比如就是接管路径访问一个hello吧, 就要至少70多行.相比起express, 不过3行, 实在差距太大.

而且不少人说nginx代码写的好,我实在是看不出来,一个函数长的不行,却还是看不出啥意思.相比express的源码, 每次看都觉得酷毙了, 就应该这么写.

不过我对nginx有心理障碍也难怪, 几天前我根本不会c, 现在也等于不会, 看起来吃力是必然的.

写的太少是最大的问题, nodejs够简单了吧, 那个简单http服务器的例子, 我写了十几遍才能独立一边写对.

nginx最简单的模块我已经写了两遍了, 离独立写出来还有一段距离, 估计要写个20遍才能独立写出来, 那时候在去看nginx源码吧.

总结这次教训, 一口吃不成大胖子[ku]

nginx学习1

最近看了下nginx源码。有点头大。首先是我没用过nginx,第二是我不会c语言。

因此想借着看nginx源码学习下c语言。之所以学习nginx是因为nginx非常实用,而且代码写得好,并且已经有不少的源码分析博客。不会的话至少有个参考。

nginx有模块这个概念,可以自定义模块,这是入口点。

nginx模块的基本原理我总结了一下,基本就是靠用函数当参数,在特定地方调用函数。

写过js的一定对这个非常熟悉。因为js的cps风格就是不断的用函数做参数进行回调的。

nginx也可以叫回调,反正也是基于epoll和kqueue的嘛。

学习了一下c,发现c实现回调函数是用函数指针的。整个nginx使用模块的方法大概如下

#include <stdio.h>

void init(char *str) {
  printf("init: %s\n\n", str);
}

void bye(char *str) {
  printf("bye: %s\n\n", str);
}

struct ngx_module_t {
  char *name;
  int type;
  void (*init)(char *);
  void (*handle)(char *);
  void (*bye)(char *);
} ngx = {
  "my_module",
  3,
  &init,
  NULL,
  &bye
};

int main() {
  if (ngx.init)
    ngx.init("welcome");
  printf("I'm handle the request\n");
  if (ngx.handle)
    ngx.handle("handle it!\n");
  if (ngx.bye)
    ngx.bye("have finished!");
  return 1;
}

基本就是说你要给nginx一个struct,里面有你定的type, name啥的,同时要定义你的事件触发函数。

因为nginx整个模块是一个数组, 每执行到一个可以触发事件的地方,都会遍历所有模块,也就是那个结构体,然后访问模块结构体中的指定函数,比如刚进来的时候触发init事件,那就遍历所有模块,调用其init的函数。原理很简单。

虽然还没看具体代码,不过应该充斥着

if (ngx.xxx) {
    ngx.xxx( ... )
}

这样的代码。

话说c的声明,调用实在是太罗嗦了。用c真的会局限人的思维,程序员需要花太多时间在c语言本身上。

明天继续,争取写一个hello world模块出来。

如何写一份简历

以前写简历一直很认真,但还是被同学嘲笑.这次专门学习了一下怎么写简历

首先看简历是如何被筛选的

  1. HR收邮件,把简历下载下来,以大约20秒一封的速度看简历,HR筛选大约筛掉80%
  2. HR将技能匹配的,条件较为优秀的简历交给此职位专业人员(可能是面试官),专业人员以专业的眼光筛掉自己认为不行的简历,比例不定,可能也高达80%.
  3. 然后才有可能收到面试通知

看上去非常吓人,一封简历寄过去,十有八九是被扔到垃圾桶里.

但事实我们不用太害怕,我以前的简历蠢爆了,也可以有一面二面.

原因是因为目前的形式都是: 普通简历泛滥,好简历稀缺

因为不少使用大街网,51Job这样的童鞋都是海投的,这些简历并不能大比例匹配技能,经常会被刷掉,因此写简历是不能一招鲜吃遍天的,需要"定制"相关简历.

个人资料

个人资料只需两行或三行

姓名 | 男 | 出生日期 | **XXXX大学
邮箱: mail | 电话: 1000000000
应聘职位: 软件工程师

第一行资料,第二行联系方式

这里唯一的难点在于哪些要写那些不要写

首先地域,名族,政治面貌都不用写.除非是党员,党员是个加分项,不是就不用写

年龄也是个问题,有人说不写,但我觉得年轻更加可塑有活力,也能算是加分项

教育信息直接写上最近的教育信息就可以了,不需要中学小学都写上

第一块:职位匹配

职位匹配就是自己与招聘信息匹配的东西,比如公司招的是数据库,你就该写上熟悉mysql,Sqlserver,mongodb等.公司需要熟练office,你就该明确写上熟练word,excel.

职位匹配是最最关键的,从高中开始写作文老师就告诉我们一定要点题,离题属于不及格作文.因此职位匹配是要放在第一块,HR不用碰滚动条就能看到的信息.具体怎么写请看下一篇具体分析,前端工程师

第二块:项目经验

这也是比较重要的部分,能具体看出你做过什么

项目经验非常重要,面试官可以看到你做过什么,从字里行间能窥探你的实际水平,因此在表述的时候不要吝啬使用专业词汇

但注意不要把项目细节讲出来,毕竟不是讲故事,只需要说用什么技能做出了什么,比如

使用Canvas画布独立开发2D塔防游戏

这里简介的说明了使用的技术,以及完成的成果,并且用独立两字表示这并不是团队项目.

对于大的项目来说,还需要多两行描述具体信息.要是又能看到效果的项目,应尽量加上地址

要注意的是项目经验并不一定是一个作品,也可能就是项目经验,比如运维和管理,运维不是很高级的话不会去做开发的事情,因此也不会有作品产生,但我们依然可以写到,熟悉网络配置,熟悉使用数据库等等作为我们的项目经验

面试官很有可能一半时间都是围绕你做的项目进行提问,比如问你做的时候有什么难题,是怎么解决的,等等

第三块: 工作经验

这并不是重要的,但大多招聘要求1-3年工作经验,最好是写上之前的工作时间和公司名称.

第四块: 其他相关

最后一块是用来阐述自己的加分项,你认为能加分的都可以写上,但不要写无关的兴趣爱好,比如"我喜欢养狗", "喜欢打篮球".根本没有人管你喜欢什么.面试官要看的是你能为公司做什么.也不用写外向内向.

加分项我认为有用的是

看过的书单

书单可以体现你的知识量,面试官从你看过的书名大概就能猜测你的水平.

同样书单也提供面试官问你的机会,同行的话肯定是愿意交流看书心得的.

博客地址

现在招聘有时直接会说最好能写上你的微博和博客地址.

因为微博直接暴露了一个人的大概,从他的每条微博,关注的人都能详尽的了解一个人

不幸的是我的微博都是卧槽救命233被面试官看到直接扔垃圾桶的货.因此一般是附上自己的博客地址.因为博客地址都是用脑子写的,不是手滑转发的

对于技术博客,从我的面试经验来看,是一个很大的加分项

专业网站ID

对于设计,绝对要贴上自己dribble ID,方便面试官看你的作品

同样程序员应该放上你的Github地址

不过Github分量太重了,你的代码水平,规范程度,**高度全部暴露

如果你觉得代码写的太挫了,那还是不要贴Github地址了

值得一提的是,就算招聘要求里说明了贴上Github地址加分,敢于在简历中加Github地址的人也是几乎没有的.

因此,不少公司说"招不到人","连份像样的简历都没有",这些可能都是真的.

同样有作用的网站ID有

  • 知乎
  • StackOverFlow
  • Quora
  • CSDN

发邮件

标题

标题的大忌就是直接写简历两字,不过还真是很多人这么做.

标题如果有要求的话最简单,直接按要求

如果没有,格式可以写

应聘软件工程师_冯通_中科大_189000000

首先加上应聘,是为了让HR方便区分求职邮件和其他邮件

其次你必须清楚的写明应聘职位,因为一个公司经常都是同时招几个职位.不同部门分管不同职位,便于HR转发邮件.

第三是写上姓名,方便HR搜索到你的邮件

第四是写上你的加分项,比如我觉得我的学校是全国前十,那我写上学校名字就能比那些其他学校的厉害了.HR会更有兴趣点开看.如果你有"xx冠军", "xxApp作者",那就更要写上了,没准就直接进了.

最后是写上联系方式,因为我们打心底想接到面试通知,当然要方便HR找号码咯

邮件内容

千万不要在邮件中写你的简历,因为你不能保证HR的邮箱显示正确.

你需要的只是写一段简单的陈述

您好,我是xxx,是xxx大学xxx专业的毕业生,认为具有(符合要求),申请贵公司xxx职位
附件中是我的简历,如果您对我的条件感兴趣,请联系189xxxx.谢谢关注

然后在附件中放上pdf简历.

哦,为什么是pdf?

肯定有朋友会这么问.主要是因为word是一个排版工具,打开看肯定是不够整洁,而且office和wps软件本身占很大的屏幕,会让人看起来很不爽.

而pdf就不同了,他的优点很多

  • pdf无法编辑,HR可以在上面随意点击
  • pdf的格式完全兼容,不会因为打开软件不同而格式混乱
  • pdf提供更纯碎的阅读(很多pdf软件几乎是全屏的)

千万不要使用ppt, html, 等特殊格式,这并不能体现你的创意,反而会让HR觉得很麻烦,都懒的打开了

排版

强烈建议排版全部是左对齐,居中是没有任何必要的

一定要充分利用缩进列表,看起来会很清晰

同样,如果能把关键字加粗就更好了

最后来句有哲理的话结尾

简历不是写出来的,而是用脚走出来的.

近期总结

好久没扯淡了,先是辞职不算顺利(老是交接啊我晕)

然后是面试被拒了俩,没有满意的offer,可喜的是所有发出去的简历都有面试通知

起初,被淘宝第3次拒的时候我着急的不行,害怕裸辞会进入“找工作没经验,没工作没经验”的死循环

现在却反而慢慢平静下来,我一直担心的是不在大公司会不会跟不上大家的节奏,现在看来错了,如果能熟练使用github,会看英文文档,能知道在哪里能快速学习知识的话,在家反而是进步最快的,因为在公司,你不得不按着流程一点一点的执行,修bug,code review,报告bug,单元测试, 总结报告。甚至可能会有无止境的奇怪需求变更

说到github,小屌丝第一次被merge了一次pull request,还是大名鼎鼎的connect,哟呵呵呵

不去海投简历会不会工作机会丢失?这还真不用担心,只能庆幸当年选了个好专业,计算机根本不用担心岗位不够,每天都能看到招聘,而且随着知识面变广,很多职位都很对口

回到找工作经历,愈发觉得自己项目经验缺乏了,在简历上写的项目我自己看了都羞愧,实在是太小太过简单(不少是刚学的时候写着玩的),但是公司的项目显然又不能贴出来(毫无亮点又涉及机密),因此我又动手写了些,不仅是为了增加点项目内容,也是增加自己经验

比如redis,我之前用过,但不熟练,不好意思在简历中写,但近来发现要求会nosql的招聘越来越多,于是这次做了个聊天室应用,主要是手机上用。用socket.io做聊天室只需要10分钟,但配合redis,可以做出一个随意新建房间,显示之前timeline,且时刻显示最火房间及人数的小应用。说白了还是为了练redis。。

然后又做了个论坛,这个大量借鉴了cnodejs的nodeclub,除了目录结构等,有很多我之前不解的问题也看着nodeclub源码解决了,比如token到底是怎么生成和解析,要不要session等

不过比cnodejs好的是用了express3+,并加入板块的概念,这个论坛本来是为了练习mongodb,不过现在我觉得做论坛太有意思了,我决定把它做成一个像觅链一样的东西,这样就算没人,我也可以自己一个人玩的开心,哈哈

现在愈发不想搞界面了,主要是有几次面试的CSS打击到了我,每次到实际的布局场景,我就暴露经验少的问题

其实我觉得用bootstrap真的太棒了,响应式布局,内置大量样式,还有网格,用bootstrap的话手写CSS的机会少太多了,我只能说,在不响应式就软了!

最后,我还想说,看源码真的是开挂的学习方式。看过源码,再用这个库,绝对是驾轻熟路,比如jQuery,自己尝试实现一下,再参考一下源码,不要比买一本jQuery书厉害太多

尝试一下jekyll!

我觉得jekyll真棒啊,这是一个测试.

我是用了 redcarpet 这个markdown解析器, 据说是Github flavored markdown

现在我们来看下效果.注意,在_config.yml中的markdown值为redcarpet

function() {
  var love = true;
  if (love == true) {
    console.log("my love is " + love);
  }
}

It works!

Linux下的socket编程

我发现c语言学习环境极为恶劣, 有问题上网找, 首先c关键词太弱..而且c语言的博客根本没法看, 一段代码基本是不能跑的, 如果有缩进我都已经感激涕零了. 不得不感叹还是js好啊, 毕竟会js的文章不会太丑.

所以我写c有关的学习博客, 都只求代码美观, 能直接运行.

socket

socket方法是用来获取一个文件描述符的。

int fd = socket(PF_INET, SOCK_STREAM, 0);

SOCK_STREAM表示流式数据,也就是tcp协议,udp的参数是SOCK_DGRAM.全程叫datagram,数据豌豆,也就是我们常说的数据报啦。

这下我终于明白为啥nodejs中使用udp协议是require("dgram");

AF_INETPF_INET

在网上找例子的时候, 会看到有的写AF_INET, 有的写PF_INET, AF是address family的缩写, PF是protocol family的缩写

这两个完全相同

AF_INET = 2
PF_INET = 2
AF_INET6 = 10
PF_INET6 = 10

他们的区别在于, AF是BSD的, 而PF是POSIX的.在windows上完全相同, 在我看来直接用2没什么问题.

bind

int bind(int fd, struct sockaddr *addr, socklen_t addrlen);

但我们一般不适用sockaddr, 因为sockaddr只有两个属性, 不能语义化的体现一个套接字地址.(ip, 协议, 端口)

因此我们使用sockaddr_in这个结构体代替, 这两个结构体长度相同, 可以直接强制转换(其实ipv6的sockaddr_in6长度不同依然是强制转换).

真正使用的时候一般是这样

int bind(fd, (struct sockaddr *)&addr, sizeof(addr));

sockaddr_in

一般使用sockaddr_in这个更加语义化的结构体来表示ipv4的套接字地址, ipv6为sockaddr_in6,需要引用netinet/in.h.

struct sockaddr_in {
    sa_family_t sin_family;
    in_port_t sin_port;
    struct in_addr sin_addr;
    unsigned char sin_zero[8];
}

这么多sin是啥意思, 我猜可能是socket inet的缩写.

sin_family_t就是前面的unsigned short类型的PF_INET, 也就是2.

sin_port当然就是端口, 但不能直接写, 需要用例如honts(80);转化.

sin_addr.s_addr同样需要转换,addr.sin_addr.s_addr = inet_addr("0.0.0.0");

sin_zero仅仅是用来对齐sockaddr大小的。

accept

accept函数是接到请求的时触发的,一般网络出错调试,都可以在accept上设置断点,然后逐步调试,但要注意的是accept并不是进行3步握手, 3步握手是更加底层的系统完成的,accept只是把完成握手的请求从系统中拿出来。

accept是放在while 1中的,这样它永远尝试着去获取新的socket文件描述符。

int len = sizeof(addr);
while (1) {
  req_fd = accept(fd, (struct sockaddr *)&addr, &len);
  ...
}

第二个参数类型是sockaddr, 存在sockaddr_in中,这样方便我们读取其ip和端口信息

第三个参数居然是地址,以我的水平是无法理解的。

accept的任务是获取一个新的请求fd。于是我们就可以通过这个新的fd进行接收数据和发送数据了。

接收数据 recv

char buf[1000];
recv(req_fd, buff, sizeof(buf), 0);

这函数api很清楚,从req_fd中读数据,放入buf中,buf长度为参数3.

那如果请求头太长怎么办呢?比如一个GET的路径就1k大了。这时候如果长度超出会怎样呢?我们可以做一个小实验。

int req_fd;
int size, size2;
char[300] buf;
char[300] buf2;

while (1) {
  if ((req_fd = accept(fd, addr, &len) == -1) {
    exit(1);
  }
  size = recv(req_fd, buf, 300, 0);
  size2 = recv(req_fd, buf, 300, 0);
}

这时候我们发现size等于300, size2等于133.(这是使用中文chrome请求时的头部),可以看出,size2还没装满300,说明缓冲区已经被我们拿完了,那我们继续recv会怎样呢?我尝试了一下,程序直接没反应了,也没有报错。

发送数据 send

char res_str[] = "HTTP/1.1 200 OK\nContent-Type: text/html ...";
send(req_fd, res_str, sizeof(res_str), 0);

可以看到send和recv确实是一对api,参数基本一样,第四个参数都可以不管直接写0

我真是弱爆了

起初的时候,我还觉得自己挺厉害,产生这种感觉不过是因为我可以分分钟写一个微博,写个博客,写个小网站。。

我甚至觉得自己的前端水平是不是再过个一两年就可以碰天花板了。。实在愚蠢之极

今天打完dota,看了下关于京js的一些分享,其中一个@belleveinvisppt,我看了直接想找个缝钻进去,ppt做的漂亮极了,讲的是ecma6的东西,我除了最简单的let之外什么都没听过,点开微博一看还是我科ustc校友,目前大四. 回想我大四的时候还在为编译原理而苦恼,但人家已经做出一个自己的语言。不禁内牛满面,这是他的博客

最近有收到猎头电话,以及两个大公司的面试通知,一开始我妈直夸我厉害,现在我只觉得弱爆了

不得不提一下n久前一道小米的面试题

简化以下大概是这样的

完成以下需求
写一个函数repeat
function repeat (callback, interval) {
  ...
}
使得这个repeat可以这样用
var repeatedFunc = repeat(console.log, 5000);
repeatedFun("helloworld")

初看此题感觉很简单,不过是写一个返回函数的函数(抢票用的?抢票是6秒)

var repeatFunc = repeat(console.log, 5000)

repeatFunc('hi')

function repeat(fn, interval) {
  return function (str) {
    setInterval(function() {
      fn(str)
    }, interval)
  }
}

非常不妙的是chrome直接报typeError了,我当时真的急哭,又套了几层function,感觉自己是不是哪里弄错了。大约耗了几十分钟,基本都想放弃了,后来试了下加了一句

function print(obj) {
  console.log(obj)
}
var repeatFunc = repeat(console.log, 5000)

repeatFunc('hi')

function repeat(fn, interval) {
  return function (str) {
    setInterval(function() {
      fn(str)
    }, interval)
  }
}

就可以了,当然这显然无法让面试官满意,面试官还问我为什么,我说可能是因为console是native函数吧(我当然自己也不知道我说的是啥),请原谅我的愚昧

今天手贱在node中试了一下,node出乎意料乖巧的一个一个把hi输出来了,我懵了,尼玛nodejs和chrome难道不都是v8?

这里面包含了不少问题:

  • 为什么nodejs可以,chrome不可以
  • 为什么把console.log放入print就可以了

当然这其中的原因还是要看上面提到那个大神的博客http://typeof.net/s/jsmech/01.html

首先说我对scope理解不对的地方

前面错误的代码在chrome中报的错是Uncaught TypeError: Illegal invocation

也就是说类型不对,错误的调用,其原因就是console.log中使用了this,虽然fn确实是console.log的引用,但this已经不是console了,解决方法是这样写repeat

var repeatFunc = repeat(console.log, 5000)

repeatFunc('hi')

function repeat(fn, interval) {
  return function (str) {
    setInterval(function() {
      fn.call(console, str) // 加上console
    }, interval)
  }
}

当然这也是权宜之计,我们是知道函数叫console.log因此写上console的

用一个最简单的例子说明这种现象

var obj = {
  func: function() {
    console.log(this)
  }
}

var fn = obj.func
obj.func() // obj
fn() // window

现在我们至少能猜测第一个问题

为什么nodejs可以,chrome不可以

因为宿主环境不同,nodejs输出并不需要用到this,而chrome用到了

改成print为啥可以呢?因为print中是直接调用console.log,它的this就是console

可以推断出如果是var print = console.log,肯定也会报相同的错误

至于为什么浏览器中的console.log会用到this,我还是多看看源码和标准再来说吧,现在一多说就暴露学历,哎

nginx学习2

今天尝试写一个模块

ngx_http_module_t

这里面初始化所有8个成员全部是函数指针,同样是可选的,用NULL放弃使用回调。

create_loc_conf

此函数接受的参数是ngx_conf_t *, 也就是nginx配置结构体的指针

返回结果会被放在ctx->loc_conf[mi]中,mi是module index的缩写

返回一个有字符串和整数的结构体,

作用是告诉总的配置文件, 我需要一个啥结构体, 保存哪些信息

这个函数貌似不重要..不管了

merge_loc_conf

可以不写,和上面一样,没啥用

ngx_command_t

struct ngx_command_s {
    ngx_str_t             name;
    ngx_uint_t            type;
    char               *(*set)(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
    ngx_uint_t            conf;
    ngx_uint_t            offset;
    void                 *post;
};

其中主要是set这一个回调函数

set函数给这个模块加上了handler.

ngx_http_handler_pt handler

handler函数

核心部分

昨天没写出个模块,因为有太多很杂的东西了.

其实和大部分web程序一样, handler才是核心内容.

ngx_http_get_module_loc_conf(r, ngx_http_hi_module)这个函数可以(应该是把配置文件中的信息给r)

假设返回结果放到cglcf中了.

然后就是设置content-type,这永远都是必须的,注意nginx设置字符串全部是用data和len两个属性.

比如

r->headers_out.content_type.len = sizeof("text/html");
r->headers_out.content_type.data = (u_char *) "text/html";

当然也要设置状态码.

r->headers_out.status = NGX_HTTP_OK; // 这当然就是200

设置content_length_n, 不得不说我在nodejs和django中并没有发现返回数据的时候要写content-length.我猜这个content_length_n可能并不是http首部content-length.设置长度很简单

r->headers_out.content_lenght_n = cglcf->ecdata.len;

设置buffer.

b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));

b里面是什么东西呢,我也看不懂,大概是一些起始和末尾位置,chain位置.

设置out, 就是output啦.

out.buf = b; 
out.next = NULL; // out可以是一个buffer chain,这里表示后面没有了

调整b的pos地址,指向需要返回字符串的初始位置.

b->pos = cglcf->ecdata.data; // ecdata是网上抄的例子中的,实际自己取名

设置结束地址

b->last = cglcf->ecdata.data + (cglcf->ecdata.len)

可以把buffer放入memory

b->memory = 1;
b->last_buf = 1; // 这是最后一个buffer,我不知道这跟上面的next = NULL有啥区别

跟上面的发送头部一样先把头部发出去, 头部都存在r->headers_out中

rc = ngx_http_send_header(r);

如果成功的话, 继续发送output

return ngx_http_output_filter(r, &out);

这个filter啥意思呢?

其实filter有两个,一个是header filter,在r传进来之前就filter了header.

另一个filter就是这个body filter,是传输body的开始函数.

filter函数是链, 可以把它传给比如gzip这样的filter

交给filter, handler的任务就完成了.

总结一下

  1. 先设置头部,必要的有content-type, status, content-length.
  2. 设置要返回的buffer, 是通过设置buf的pos来定位字符串的.
  3. 发送头部
  4. 交给filter

明天计划写一个静态文件模块.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.