跳到主要内容

简要分析socketio-auth

· 阅读需 7 分钟

看到不少noder在问socket.io如何控制权限这类问题,想到去年在echojs还是HackerNews上看到的socketio-auth模块,还是一定程度上解决这类问题的,尽管它的实现相当简单,但至少提供了一个思路。

socketio-auth是啥

也不详细介绍了,原文在这里《socket.io认证模块socketio-auth》

大致验证过程

1.为socket.io挂上模块

var io = require('socket.io').listen(app);

require('socketio-auth')(io, {
authenticate: authenticate,
postAuthenticate: postAuthenticate,
timeout: 1000
});

参数

  • authenticate 验证用户身份,比如在这里查询数据库,对比用户信息,成功后标记,然后调用下面的postAuthenticate函数
  • postAuthenticate 在验证成功后的处理,你可以做一些你爱做的事,比如买衣服什么的:)
  • timeout 超时时间,等待用户验证的时间毫秒数,超过这个时间就会强制断开了

在这个过程干了这么几件事:

这里主要是注册事件做准备工作。

2.客户端连接并进行验证

客户端在连接后,emit一个authentication事件并附带验证信息。 这时就会进入到验证流程里了,首先将信息传入authenticate方法里进行诸如查DB的验证过程。

// 验证
config.authenticate(data, function(err, success) {
if (success) {
debug('Authenticated socket %s', socket.id);
socket.auth = true;

...
} else if (err) {
...
} else {
...
}

});

// 验证的实现
function authenticate(data, callback) {
var username = data.username;
var password = data.password;

db.findUser('User', {username:username}, function(err, user) {
if (err || !user) return callback(new Error("User not found"));
return callback(null, user.password == password);
}
}

回调时看是否成功,成功就可以对连接进程标记了(稍后就不会被强制断开)。

成功时会调用postAuthenticate函数,比如将user信息绑定到连接或者其他的什么,总之,这个不是必须的。

3.返回状态给客户端

成功

socket.emit('authenticated', success);

失败

//error
socket.emit('unauthorized', {message: err.message}, function() {
socket.disconnect();
});

//fail
socket.emit('unauthorized', {message: 'Authentication failure'}, function() {
socket.disconnect();
});

后续任由客户端发挥了。

4.服务端还有一件小事要完成

那就是在timeout时间过后判断是否已验证,否则强制断开连接。

setTimeout(function() {
// If the socket didn't authenticate after connection, disconnect it
if (!socket.auth) {
debug('Disconnecting socket %s', socket.id);
socket.disconnect('unauthorized');
}
}, timeout);

完....

https://github.com/invisiblejs/socketio-auth

追加

@Pana 分享了另外两个也一并列出,有待研究 socketio-jwt session.socket.io

Node.js 模块加载过程 module.js 源码阅读

· 阅读需 9 分钟

引言

nodejs 中以模块来组织代码,加载模块是怎样的一个过程呢?require 又是怎么在运作?

模块

nodejs 中以模块为最小单位来组织代码,类似于 Java 中的 package 概念,而在 nodejs 中要搞清楚模块,就要搞清楚模块是怎么样加载的。

源码分析

我们都知道加载一个模块是使用 require 函数来实现的,那么先从 require 函数下手。

1.Module.prototype.require

我们从 module.js 里找,发现有个 require 方法,Module.prototype.require

Module.prototype.require = function(path) {
assert(path, 'missing path');
assert(util.isString(path), 'path must be a string');
return Module._load(path, this);
};

2.Module._load

通过路径加载一个模块。 方法注释上给出如下解释:

从缓存中查找所要加载的模块

  1. 如果一个模块已经存在于缓存中:直接返回它的 exports 对象
  2. 如果模块是一个本地模块,调用 'NativeModule.require()' 方法,filename 作为参数,并返回结果
  3. 否则,使用这个文件创建一个新模块并把它加入缓存中。在加载它只会返回 exports 对象。

3.Module._resolveFilename

而通常我们在某个模块中给出的路径都是一个模块的相对路径,那么会先调用 Module._resolveFilename 这个方法来查找下这个文件的真实路径的全路径文件名。

而在 Module._resolveFilename 这个方法中,首先会去检查,本地模块是否有这个模块,如果有,直接返回,如果没有,继续往下查找。

4.Module._resolveLookupPaths

接着就会碰到 Module._resolveLookupPaths 方法了,从代码来看,他返回了一个数组,数组的第一个元素是模块的 id,而第二个元素是模块的 paths。

这些 paths 接下来会用来查找是否存在需要 require 的这个模块了,存在就会返回一个文件名。

而接下来通过这个 filename 来到 Module._cache 中查找是否,有则返回 module.exports 对象,没有缓存则又会查找一次本地模块,不存在这个本地模块,就新创建一个模块,并在 cache 中缓存它。这就是我们加载了一个模块之后,第二次在别处加载时也不会重新加载的原因。

5.Module.load

而在创建模块之后,还有个装载的过程 Module.load, 装载的过程中会将几种扩展名的文件执行不同的操作:

  • .js Module._complie,运行这个 js 并包裹在适当的作用域中,并传入参数 require, module, exports
  • .json 读文件之后,使用 JSON.parse 转成对象
  • .node 使用 process.dlopen 加载扩展

如果 js 文件中包含模块引用,那么还会继续重复以上操作的。

这里会 try catch 一下,如果装载失败,就会从 cache 中将这个模块删除。

try {
module.load(filename);
hadException = false;
} finally {
if (hadException) {
delete Module._cache[filename];
}
}

热部署

这里的细节给了我们一个思路,即当我们程序运行时,加载了某个模块,而我们可能在运行过程中修改了代码,或者是重新部署了代码,那么我们可以通过先删除 cache 中的内容,再加载一次,来实现动态加载,也可以说是热部署。 具体的做法可以参考这个文章 http://fex.baidu.com/blog/2015/05/nodejs-hot-swapping/

到这里一个模块基本上已经加载完成了。

js 判断一个变量是否为对象

· 阅读需 2 分钟

问题

js 中判断一个变量是否为对象,通常都是使用 typeof 来判断,那么还有其他办法么?

答案是当然的!

我们利用 Object 对象来实现这个功能。

原理

如果 Object 函数的参数是一个对象,它总是返回原对象。利用这一点,可以写一个判断变量是否为对象的函数。

方案

function isObject(value) {
return value === Object(value);
}

javascript 中怎样以可靠的方式判断 NaN

· 阅读需 2 分钟

定义

NaN 是 JavaScript 的特殊值,表示“非数字”(Not a Number),主要出现在将字符串解析成数字出错的场合。

注意

NaN 不是一种独立的数据类型,而是一种特殊数值,它的数据类型依然属于 Number,使用 typeof 运算符可以看得很清楚。

typeof NaN // 'number'

运算规则

NaN 不等于任何值,包括他本身

NaN === NaN // false

[NaN].indexOf(NaN) // -1

Boolean(NaN) // false

isNaN(NaN) // true
isNaN(123) // false

可靠的方式判断

利用它的特点,NaN 是 JavaScript 之中唯一不等于自身的值

function myIsNaN(value) {
return value !== value;
}

js 对象中什么是可枚举性 (enumerable)?

· 阅读需 3 分钟

说到枚举,可能很多人都会想到枚举类型,但在 javascript 对象中有一个属性为可枚举性,他是什么呢?

概念

可枚举性(enumerable)用来控制所描述的属性,是否将被包括在 for...in 循环之中。具体来说,如果一个属性的 enumerable 为 false,下面三个操作不会取到该属性。

  • for..in 循环
  • Object.keys 方法
  • JSON.stringify 方法

enumerable “隐身术”

var o = {a:1, b:2};

o.c = 3;
Object.defineProperty(o, 'd', {
value: 4,
enumerable: false
});

o.d
// 4

for( var key in o ) console.log( o[key] );
// 1
// 2
// 3

Object.keys(o) // ["a", "b", "c"]

JSON.stringify(o // => "{a:1,b:2,c:3}"

上面代码中,d 属性的 enumerablefalse,所以一般的遍历操作都无法获取该属性,使得它有点像“秘密”属性,但还是可以直接获取它的值。

至于 for...in 循环和 Object.keys 方法的区别,在于前者包括对象继承自原型对象的属性,而后者只包括对象本身的属性。如果需要获取对象自身的所有属性,不管 enumerable 的值,可以使用 Object.getOwnPropertyNames 方法

前端优化之观察新浪微博

· 阅读需 4 分钟

dns-prefetch

什么是 DNS Prefetch ?

DNS Prefetch 是一种 DNS 预解析技术。当你浏览网页时,浏览器会在加载网页时对网页中的域名进行解析缓存,这样在你单击当前网页中的连接时就无需进行 DNS 的解析,减少用户等待时间,提高用户体验。

font 12px/1.3

代表什么意思呢,那就是字体大小是 12px,后面的是行高 1.3 倍

针对 IE7 以下的背景图片缓存

try{
document.execCommand("BackgroundImageCache", false, true);
}catch(e){

}

页面样式分块分层渲染

我们通常都会想,尽量的把 css,js 等静态文件压缩合并,是的,这种思路是没有错的,但是,这只能说针对于在 css、js 文件的内容不多,压缩之后不是很大的情况下才算是较优的策略,然而当合并起来也较大时,其实应该分层次的将 css 拆开成不多的几个文件,让用户的首屏不会出现很长时间的空白。 其实这也是看了扑灵大大的博客《谈新浪微博的前端重构中的 CSS 请求数问题及优化策略 》之后才明白的一个问题。 css

我产生了一个问题

这个 link 标签中的 includes 属性哪来的?从后面的内容来看像是把各个区域分块加载出来的。

参考地址: http://skyhome.cn/div_css/301.html http://www.jb51.net/article/26422.htm

没图说个锤子之 js bind 方法

· 阅读需 9 分钟

bind 方法,写 javascript 的肯定都见过,我也是,但是,不是经常用的话,基本上过一段时间就会模糊了,所以,决定把它转化成图像,估计比较容易记忆。

我们来看看 MSDN 上关于 javascript bind 函数的解释:

对于给定函数,创建具有与原始函数相同的主体的绑定函数。 在绑定函数中,this 对象将解析为传入的对象。 绑定函数具有指定的初始参数。

哈哈哈哈,是不是懵逼了?

莫慌,我们慢慢来看,看看用法先:

用法

function.bind(thisArg[,arg1[,arg2[,argN]]])

参数

  • function 必选。 一个函数对象。
  • thisArg 必选。 this 关键字可在新函数中引用的对象。
  • arg1[,arg2[,argN]]] 可选。 要传递到新函数的参数的列表。

返回值

与 function 函数相同的新函数(注意是新函数!),thisArg 对象初始参数除外。

异常

如果指定的 function 不是函数,则将引发 TypeError 异常。

看到这里我们基本对 bind 方法的使用有个初步认识了,那先来看看具体示例再分析分析。

示例

this 绑定

/**
* 定义初始的函数
* 这个函数的功能很简单,就是判断数字是否在某个范围
*/
var checkNumericRange = function (value) {
if (typeof value !== 'number')
return false;
else
return value >= this.minimum && value <= this.maximum;
}

// 这里的范围将会被绑定到函数中的this值去
var range = { minimum: 10, maximum: 20 };

// 开始绑定!
var boundCheckNumericRange = checkNumericRange.bind(range);

// 使用一个数字来验证下这个函数
var result = boundCheckNumericRange (12);
document.write(result);

// 输出: true

好了,这个简单的示例看完了,我们知道了,使用 bind 将一个对象绑定到某个函数中,这个函数中所使用的 this 就会指向绑上去的函数了,不罗嗦了,画个图理解。 function 调用 bind 方法

再看个稍微有点不同的例子,其实也差不多:

// 创建一个带有刚才那个方法的对象,
// 并且方法调用当前这个对象中的最大值和最小值
var originalObject = {
minimum: 50,
maximum: 100,
checkNumericRange: function (value) {
if (typeof value !== 'number')
return false;
else
return value >= this.minimum && value <= this.maximum;
}
}

// 检查10是否在范围内
var result = originalObject.checkNumericRange(10);
document.write(result + " ");
// 输出: false

// 还是同样的配方,还是熟悉的味道
var range = { minimum: 10, maximum: 20 };

// bind技能要正在引导...
var boundObjectWithRange = originalObject.checkNumericRange.bind(range);

// 看看这次的效果
var result = boundObjectWithRange(10);
document.write(result);
// 输出: true, 有效了!

参数绑定

在参数中还可以有几个参数带进来 arg1[,arg2[,argN]]]

// 又是定义一个函数,这次是4个参数
var displayArgs = function (val1, val2, val3, val4) {
document.write(val1 + " " + val2 + " " + val3 + " " + val4);
}

var emptyObject = {};

// 使用bind,产生一个新函数
// 这个新函数的第一第二个参数已经定死了为这两个,再有参数往后排
var displayArgs2 = displayArgs.bind(emptyObject, 12, "a");

// 这里就是两个排队的参数了
displayArgs2("b", "c");
// 输出: 12 a b c

恩,知道,上图再说对吧 bind 函数绑定参数

上图可以看出 bind 时传入的参数,在新函数中作为最先使用的参数,但是它并没有改变原函数参数的个数

不知道看了两幅图,记住了 bind 方法没?

javascirpt 中的 new 到底干了什么

· 阅读需 4 分钟

new 操作符

加上 new 操作符,我们就能完成传统面向对象的 class + new 的方式创建对象,在 Javascript 中,我们将这类方式成为 Pseudoclassical。

我们执行如下代码:

var obj = new Base();

这样执行的结果是什么,javascript 引擎中看到的模型对象是: javascript 引擎中模型对象

new 操作符干了什么

new 操作符具体干了什么呢 ? 其实很简单,就干了三件事情。

var obj  = {};
obj.__proto__ = Base.prototype;
Base.call(obj);

第一行,我们创建了一个空对象 obj 第二行,我们将这个空对象的 _proto_ 成员指向了 Base 函数对象 prototype 成员对象 第三行,我们将 Base 函数对象的 this 指针替换成 obj,然后再调用 Base 函数,于是我们就给 obj 对象赋值了一个 id 成员变量,这个成员变量的值是”base”


2019-09-24 更新

最近看到一个专栏介绍 this 时,讲到为什么全局调用一个函数时,this 指向的是 window 对象呢? 因为全局调用时,并没有使用 call 方法绑定上下文,或是在一个对象中的方法调用 this ,而根据作用域链的规则,会从当前作用域范围向外找,找到全局作用域中的 window 对象。

而 new 操作时,实际上是调用了 call 方法将这个空对象设置为上下文指向的对象。

可以见 MDN 的解释 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/new

不过要注意是不是在严格模式下噢,行为不同

new Date 在浏览器中兼容的问题

· 阅读需 3 分钟

在开发时,只在 chrome 下开发,所以,在其他浏览器上的效果并未发现。 结果在 ff 和 IE 下的 new Date(str) 时,出现了 NaN。

比如如下代码:

var timestart = '2010-05-04';
var timeend = '2015-06-23';
var time1 = (timestart+' 00:00:00').toString();
var time2 = (timeend+' 23:59:59').toString();
timestart = new Date(time1);
timeend = new Date(time2);

尝试

  • IE 下的执行情况:

    结果:Invalid Date

  • firefox 下的执行情况:

    结果:Invalid Date

  • chrome 下的执行情况:

    结果:正常

正确的姿势

主要的变化是对默认的日期格式进行了转换, 基于 '/' 格式的日期字符串,才是被各个浏览器所广泛支持的,‘-’连接的日期字符串,则是只在 chrome 下可以正常工作。

var time1 = (timestart+' 00:00:00').toString();
var time2 = (timeend+' 23:59:59').toString();
timestart = new Date(Date.parse(str.replace(/-/g,"/"))).getTime();
timeend = new Date(Date.parse(str.replace(/-/g,"/"))).getTime();

结论

2015-06-23 是无法被各个浏览器中,使用 new Date(str) 来正确生成日期对象的。 正确的用法是 2015/06/23.

Promise/Deffered 模式

· 阅读需 3 分钟

包含两个部分:

  • Promise
  • Deffered

Promise 抽象定义

Promise/A 对单个异步作出了这样的抽象定义:

  • Promise 操作只会处在 3 中状态:未完成完成失败
  • Promise 的状态只会出现从未完成状态转化为完成态或失败态,不能逆反。完成态和失败态不能互相转化。
  • Promise 的状态一旦转化,将不能被更改。

Promise 状态转化示意图

API 定义

一个 Promise 对象只要具备 then 方法即可。 但是 then 方法也有要求:

  • 接受完成态错误态的回调函数。在操作完成或出错时,将会调用对应方法。
  • 可选的支持 progress 事件回调作为第三个方法。
  • then() 方法只接受 function 对象,其余对象将被忽略。(可使用 typeof func === 'function' 来判断)
  • then() 方法继续返回 Promise 对象,以实现链式调用。

代码可参考这里的例子 https://github.com/luoyjx/promise-impl