跳到主要内容

· 阅读需 2 分钟

一直都不知道如何获取javascript中,函数的形参个数。

javascript作为一个函数式编程的语言,hooker的使用多不胜数。我遇到一个很灵活的一个hooker接收器

钩子函数异步 resolve 规则
我们经常需要在钩子函数中进行异步操作。在一个异步的钩子被 resolve 之前,切换会处于暂停状态。钩子的 resolve 遵循以下规则:

  • 如果钩子返回一个 Promise,则钩子何时 resolve 取决于该 Promise 何时 resolve。
  • 如果钩子既不返回 Promise,也没有任何参数,则该钩子将被同步 resolve。
  • 如果钩子不返回 Promise,但是有一个参数 (transition),则钩子会等到 transition.next(), transition.abort() 或是 transition.redirect() 之一被调用才 resolve。

我估计他是对返回值做判断,看看是不是object有没有.then属性以及.then是不是一个函数。这样就知道是不是promise了。 但是我真心绞尽脑汁都没弄明白怎么他怎么实现用了一个参数transition.next()的情况的。

答案在这里

真心的,我看了好多好多本javascript的书籍,但是一直都没有注意到这个地方!!!! 大概是没说吧?!!

var fn = function(x, y){return x+y;};
fn.length // 2

· 阅读需 6 分钟

nodejs很适合做http数据服务器,用上express之类的框架很容易就搭出一套http service来。然后数据库感觉就跟mongodb很搭,这样子一套下来全都是javascript技术栈。但是nodejs跟mongodb之间的接口应该选择哪一个呢?

我关注到的库有两个:node-mongodb-nativemongoose

node-mongodb-native是mongodb的官方nodejs封装。其实应该说是简单封装。使用它的感觉就像是直接在shell里用mongo操作,特别的灵活以及效率。甚爱之。
mongoose是一个深封装的ORM库。需要像mysql设计表结构那样子设计Schema,然后把Schema具象为Model,然后可以面向对象的使用数据库了。


我是更爱高效灵活地node-mongodb-native的,然而使用之有一点不好,就是:感觉这个项目的team并没有花多少心思和时间在这上面,更多地是应付的做了一个专业的封装。
我这是什么意思呢?
node-mongodb-native库的封装很专业,但是文档是比较应付式的,阅读和资讯获取都挺难,至少我做了近一个月还是不知道web service应该怎样标准地去连接数据库(应该说怎么用数据库,连接一次然后一直使用这个db呢;还是每次用db.opendb.open是不是表示从连接池里拿一个连接?)。其开发人员是从对接口的封装出发来做这个项目,而不是从用户使用的角度来开发和写文档。
我在网上看人家对这两者的比较时看到他们说,大概意思是使用node-mongodb-native会比较折腾。
我现在认为这里的折腾不是指写起来麻烦,而是没有响应的指导和合适的文档,许多事情需要去读源码,去做测试(这种测试还不好做,也许某种用法低并发的时候适用,多了就不行了)。

这个项目的开发人员总该知道怎样使用吧!你倒是做个demo或者在doc中写出来啊!你倒是告诉我是不是针对每一个http请求都应该发起一个db的connection啊!你告诉我是不是对每一个http请求不用发起新connection但是需要重新db.open啊!你不说谁知道在web service中应该怎么用这个库啊!!!文档里每一个demo都是连接数据库-打开db-写入-关闭连接。也是醉了。

最后的结果是遇到不知名原因的bug,一个collection.find返回的promisepending,然后找不到原因和答案,放弃之。


mongoose这种把动态类型当作静态类型的思路其实我是不喜欢的(其实不是静态类型,只是做类型转换啦,我是说我不喜欢这样的思路而已)。就像我不喜欢微软的TypeSrcipt(包括ES6)给javascript弄上静态类型和class一样,人家javascript明明本身是基于prototype设计的,硬是弄上class搞毛!
但是mongoose的开发看来是熟知node-mongodb-native的源码的,这二次封装(在node-mongodb-native的基础上进行封装)然后提供了较为完善的文档,也算是一个合适的选择。

这里有两个点需要注意一下:

  1. 我现在使用的mongoose版本是4.2.5,其依赖"mpromise": "0.5.4"。也就是说mongoose返回的Promisempromise提供的promise实现,而这个promise实现实测不支持promise.reject方法。
  2. 同样是这个版本。我使用文档里说的方法mongoose.model('User', userSchema);注册Model。这个时候model是被注册到mongoose.models的。于是在mocha -w的时候,会出现OverwriteModelError,估计是mocha -w的时候里面的机制并不会清空mongoose.models,然后重载model注册模块,于是报错。根据issue#1251,网友提供的mongoose.models = {};mongoose.modelSchemas = {};似乎可以解决这个问题。比较逗的是aheckmann拼死不认为这是一个bug,一直说我们用错了。没太注意看,不过也许用connection.model('User', userSchema);来注册也能解决这个问题,这也许是aheckmann的意思。

· 阅读需 2 分钟

在中国桌面操作系统,windows还是占主流,windows中文版使用的是GBK编码,然后我神奇的发现我所爬的那个网站,即使在linux下,返回的html竟然也是GBK编码的!!

javascript原生就支持几种编码转换,但是好像就两三种,种类特别少。一般情况下我们写javascript都是出于开发者环境,少遇到非Unicode的编码,所以考虑编码的情况并不多。但是万一遇到了咧?
那个时候,就该使用iconv-lite库了!

npm install iconv-lite

具体使用看教程就好,这里只提醒一下:使用iconv.decode(buffer, encoding)的时候,第一个参数传的是Buffer。做爬虫的时候,应该用http或者request直接拿出Buffer来,然后用iconv-lite转换。直接拿body估计是不行的。

var iconv = require('iconv-lite');
var getBufferWithHeader = co.wrap(function *(url) {
return new Promise(function (resolve) {
var buffer = [];
request({url, jar: jarWithCookie}, function (error, response, body) {})
.on('data', function (chunk) {
buffer.push(chunk);
})
.on('end', function () {
buffer = Buffer.concat(buffer);
resolve(buffer);
});
})
});
var getPageWithHeader = co.wrap(function *(url) {
var out = yield getBufferWithHeader(url);
return iconv.decode(out, 'GBK');
})

· 阅读需 1 分钟

我做爬虫的时候,遇到一个问题:我想把页面的标题作为文件夹名字,结果出错了,仔细一看,发现标题里含有字符/

使用windows的人应该都遇到过,给一个文件命名带有?的名字时,他就会告诉你有七八个字符是不能作为文件名的。
所以写一个函数把这些字符滤掉。

var makeFilenameValid = function (rawFilename) {
var InvalidCharRegex = /[\/:\*\?\"< >\|]/;
var okFilename = '';
_.each(rawFilename, function (char) {
var match = InvalidCharRegex.test(char);
if (!match){
okFilename += char;
}
});
return okFilename;
}

· 阅读需 2 分钟

hexo中的scaffolds文件里存了几个模板文件,其中最重要的是post.md,当我使用hexo new title的时候,hexo会自动套用这个模版产生一个待编辑的post。hexo兼容yaml格式和json格式,不过现在用yaml的格式的地方不多了吧?也懒得去熟悉这种格式了,于是来改成试试json格式吧!

"title": {{ title }},
"date": "{{ date }}",
"categories": ["uncategorized"],
"tags": []
;;;

这样子就对了。

--

值得专门记载下来的原因是,注意{{ title }}那里不要使用引号,不要弄成了"{{ title }}",原因是hexo生成的时候会把{{ title }}替换为"title"
至于data那里不用引号行不行我就懒得去试了,估计是不太行的,可阅读源码:

node_moduels/hexo/lib/hexo/post.js:77

JSON.parse('{' + renderedData + '}');

这里renderedData就是渲染之后的post.md(不包括那个分隔符;;;)。

· 阅读需 1 分钟

由于浏览器里存在跨域问题,之前一段时间各种各样的跨域手法都出来了。之后却突然都销声匿迹了,估计是因为现在的浏览器都支持cors模式来解决跨域问题,而许多新的服务器都开启了这样子的服务。看了一眼发现开这样的服务原来这么简单!

cors的原理挺简单的,弄一个Access-Control-Allow-Origin字段在header里就差不多了。但是在express下使用还有更简单的

npm install cors

然后再express的app那里:

var cors = require('cors');
app.use(cors());

就好了。

· 阅读需 1 分钟

正则匹配内容的多处地方的注意事项。

一般情况下,javascript的正则在匹配到第一个位置之后,就不在匹配了。然而在做爬虫的时候常常需要得到所有匹配的内容,这时可以用g修饰符来开启全局匹配。

var regex = /b/g;

var str = 'abba';

regex.test(str); // true
regex.test(str); // true
regex.test(str); // false

于是想获得所有符合的内容,可以通过:

var aFilePath = 'path to file';
var regex = /src=\"(pic\?id=\d{2,})\"/g;
var data = yield fs.readFileAsync(aFilePath);
var name = regex.exec(data)[1];
var urls = [];
var match;
picUrlRegex.lastIndex = 0;
do {
match = picUrlRegex.exec(data);
if(match)
urls.push(match[0]);
} while (match);

实现。
这里有一个特别需要主要的是,你需要手动的使用picUrlRegex.lastIndex = 0;来复位索引。

· 阅读需 3 分钟

这是关于如何在nodejs环境下,发起http请求时带上cookie的教程。

其实我用python写爬虫/发起http请求会熟手一些,不过nodejs作为一个能开发命令行程序的引擎,我又想多熟练js的编程,于是就试着用nodejs来做一个爬虫。
也是我对cookie的原理不熟悉吧,爬虫带cookie的时候,我试着在header上通过setHeader设置Cookie属性a=123; b=456这样子,但是服务器不认,也不知道是为什么。但是看到好像是说服务器可以设置不让本地读和写cookie,不知道是不是相关。

我设置header不行,但是人家可以啊!我使用的是:

npm install request

文档里有cookie的使用说明

request.cookie
Function that creates a new cookie.
request.cookie('key1=value1')

以及

var j = request.jar();
var cookie = request.cookie('key1=value1');
var url = 'http://www.google.com';
j.setCookie(cookie, url);
request({url: url, jar: j}, function () {
request('http://images.google.com')
})

看起来知道应该使用jar来组一个cookie然后扔给http请求使用就好了。
但在这里有一个要注意的坑在,就是 var cookie = request.cookie('key1=value1'); 一句,谁家的cookie只有一个property的啊!于是我就 var cookie = request.cookie('key1=value1; key2=value2'); ,然后服务器不认。
苦心孤诣地尝试很多遍之后,才挖掘出来应该是:

var cookie;
cookie = request.cookie('key1=value1');
j.setCookie(cookie, url);
cookie = request.cookie('key2=value2');
j.setCookie(cookie, url);

文档不能写清楚一点吗!

扔上代码,url是比较敏感的所以改成example了。

var collectionHost = 'www.example.com';
var collectionDomain = 'http://' + collectionHost;
var jarWithCookie = request.jar();
var cookieString = 'key1=value1; key2=value2; key3=value3; key4=value4; key5=value5';
for (kv of cookieString.split('; ')) {
jarWithCookie.setCookie(request.cookie(kv), collectionDomain);
}

var getBufferWithHeader = co.wrap(function *(url) {
return new Promise(function (resolve) {
var buffer = [];
request({url, jar: jarWithCookie}, function (error, response, body) {})
.on('data', function (chunk) {
buffer.push(chunk);
})
.on('end', function () {
buffer = Buffer.concat(buffer);
resolve(buffer);
});
})
});

其中,cookieString可以轻松从chrome的dev工具中搞出来。

· 阅读需 2 分钟

手上有一个大的项目,分别是好几个hg库。
我想合并成一个git库。

分几个步骤吧:

  1. 转换hg库为git库
  2. 合并两个git库
  3. 重复1、2

转hg库为git库

参考git-scm的文档

合并两个git库

不知道是参考哪里的方案了,不过思路很简单。

  1. 首先有一个git库:/tmp/git1,以及另外一个git库/tmp/git2 $ cd /tmp/git1

  2. 其次设置git2为git1的一个源
    $ git remote add git2 /tmp/git2

  3. fetch之。(似乎pull会帮你合并,还是fetch安全,参考$ git fetch git2 master:branch2

  4. merge之。(我用的是图形操作,其实fetch也是用图形操作的,到这里就差不多了)

排序?

merge成功之后,git库在sourceTree中的表示方式是最下面是git2的,上面是git1的。我觉得应该按时间排那样子才酷炫,但是还没有花时间去研究怎么搞。待更新。

· 阅读需 4 分钟

一直觉得react太折腾,ng太庞大,knockout太老土,像我需要开发大型SPA的,总是找不到路可走。如果你有跟我一样的感受,那来试试vuejs吧。

vuejs

vuejs是一个在美国的中国小伙子开发的一个数据绑定库,有着简洁的代码风格。
作者尤雨溪开发并维护着这个项目,项目的两个核心:

本质上 Vue.js 做的是两件事情,数据绑定和视图层组件化。

都非常吸引我,因为我认为大型SPA必须要采取组件式开发,另外我需要一个纯粹(不要像angular这么繁杂)的数据绑定库。

vuejs在实现上很吸引人的一点是,它跟knockoutjs一样,采取依赖链而不是脏数据 检查的方式来实现绑定,这样其运算量不会因为监听器的数量增加而增加,在一定程度上保证了大型SPA的性能。

components

见vuejs文档的components一章。
另见vuejs文档创建大型应用一章。这章提供了一种.vue后缀的开发方法。由于组件应该是包括了模版、逻辑代码、样式的,于是怎么布置这些个文件的位置就是一种学问了,通常我们会给每一个component一个文件夹,index.js作为入口。而.vue可以把三样内容放在一个.vue文件里。这样文件树突然就变得超级的干净简洁了。(你可以从他的demo看到他使用stylus、coffeescript、jade全都是最简洁的语言)
但是我放弃了使用.vue,虽然尤雨溪甚至提供了sublime显色插件,但是毕竟多了一层编译过程,有时候显色不正常还算小事,出了错不好定位那就比较耽误事儿了,所以我采取的是webpack+style-loader的解决方案,其实这也是vueify的工作方式(它使用的是insert-css),只是我使用更传统的布局,手动地加载罢了。

var vue = require('vue');
var genData = function(){
require('style!./style.less');
return {};
};
var component = vue.extend({
template: require('./template.jade')(),
data: genData
})
module.exports = component;

当然了,webpack.config得相应配置上

module: {
loaders: [
{ test: /\.jade$/, loader: "jade-loader"},
{ test: /\.html$/, loaders: ["raw-loader"]},
{ test: /\.less$/, loaders: ["raw-loader","less-loader"]},
{ test: /\.js$/, exclude: /node_modules/, loader: "babel-loader"}
]}

updated:2016-03-14 还是用上了webpack + .vue的架构,没办法不用不行啊。 特别是引入了babel之后就更难找到出错的地方了,不过接受这一设定的话,整个项目突然变简洁了。 当然还是有难以定位出错点的问题,想其他办法解决吧。

vue-router

vue-router提供了开发SPA所需的路由和历史功能,与vuejs配合那自然是真真好的。

vuex

vuex提供了开发大型SPA的状态管理。

后记

vuejs正式版1.0.0于2015年10月27日终于面世了。