[Node.js] 模块化编程中的对象问题

按照习惯的面向对象的写法,我们很容易写出来这样的模块:

var value = -1;

function set(num) {
    value = num;
}

function get() {
    return value;
}

exports.value = value;
exports.set = set;
exports.get = get;

名字就定为Number模块吧。

这里我们把value作为一个公开的变量值,并把get和set方法也公开。

对模块进行一些操作,却发现:

> var n = require('./Number');
undefined
> n
{ value: -1,
  set: [Function: set],
  get: [Function: get] }
> n.set(10);
undefined
> n.get();
10
> n.value
-1

用get和set方法对值进行修改和读取是正确的,但直接读取value却发现并没有改变,值一直是初始值。

在node的js源代码中查找无果后到node的lib下查找关联的c++代码,发现在require进行上下文切换的是这段代码:

Local<Object> sandbox;
  if (context_flag == newContext) {
    sandbox = args[sandbox_index]->IsObject() ? args[sandbox_index]->ToObject()
                                              : Object::New();
  } else if (context_flag == userContext) {
    sandbox = args[sandbox_index]->ToObject();
  }

这段代码将传进来的之前解释模块文件得到的exports出的接口转换成c++的对象一个个放入sandbox中,然后进行上下文切换。

如果这个接口在c++中判断是个js对象,则直接转换成c++对象,否则则创建一个新的c++对象存放接口。

于是,现在的问题就是,前面我们的value究竟是不是一个js对象?

> typeof n.value
'number'

用typeof查看,在js里是一个number对象,那在C++里这个是不是对象呢?IsObject()判断的是什么样的Object呢?

后来终于找到这张图和IsObject的解释:

V8EXPORT bool v8::Value::IsObject    (         )      const
Returns true if this value is an object.

可以看出来在V8里面,Boolean,Number和String都分别有两种不同的对象类型,分别属于V8::Object和V8::Primitive下,IsObject判断的是是否是V8::Object。

再回到前面的问题,可以推测js中的number在c++中对应的类型应该是V8::Number,而js中的Number对象在c++中对应的才是V8::NumberObject。

但是,IsObject判断的范围究竟是什么?JS的对象还是V8::Object?为了验证这个想法,再修改Number模块:

var value = new Number(-1);

function set(num) {
    value = new Number(num);
}

function get() {
    return value.toString();
}

exports.value = value;
exports.set = set;
exports.get = get;

把value改成Number对象,重新运行:

> var n = require('./Number');
undefined
> n
{ value: {},
  set: [Function: set],
  get: [Function: get] }
> n.set(10);
undefined
> n.get();
'10'
> n.value
{}
> n.value.toString();
'-1'

看来判断的是js中的对象,而js中number,string,boolean都算是对象,V8::Value::IsObject()判断都为true。

那究竟还有什么原因会导致文章开头这样错误的结果呢?get和set中的value会不会指向的是另一个变量,而不是模块中的局部参数value?

 再次回到问题的起点,会不会是我的代码出了问题?

经过一番研究,发现的确是作为新手的我太不了解JS了。这里有两个关键的知识点:

从JavaScript内部工作原理去看,JavaScript处理上下文分为两个阶段:

1. 进入执行上下文
2. 执行代码
可以理解为,第一个阶段是静态处理阶段,第二个阶段为动态处理阶段。

而在静态处理阶段,就会创建 变量对象(variable object),并且将变量申明作为属性进行填充。
到了执行阶段,才会根据执行情况,来对变量对象中属性(就是申明的变量)的值进行更新。
JavaScript是弱类型语言,所有的对象赋值都是引用赋值。

回过头来看自己的代码,问题就出在了两个value引用的赋值上。

var value = -1; //Local变量value,值为-1

function set(num) {
    value = num; //Local变量重新赋值,指向新的变量对象num
}

function get() {
    return value;
}

exports.value = value; //引用赋值,exports.value指向一开始初始化的变量对象
exports.set = set;
exports.get = get;

于是,在使用set函数时,将Local的value引用指向了新的对象,而exports.value则还是指向原来的对象。

问题找到了,那怎么解决呢?一种方法是用this关键字:

var value = -1;

function set(num) {
    this.value = num;
}

function get() {
    return this.value;
}

exports.value = value;
exports.set = set;
exports.get = get;

确实,it works! 但是,使用this需要格外小心。

js中this是指向运行时的上下文对象,它的值取决于调用这段代码所在函数的对象,在运行时是随着闭包不断变化的。

 

> var n = require('./Number');
undefined
> var set = n.set;
undefined
> n
{ value: -1,
  set: [Function: set],
  get: [Function: get] }
> n.set(0);
undefined
> n
{ value: 0,
  set: [Function: set],
  get: [Function: get] }
> set(1);
undefined
> n
{ value: 0,
  set: [Function: set],
  get: [Function: get] }
> value
1

运行下可以发现,如果是直接使用set函数,this指向当前的上下文,即node的全局对象,于是this.value就变成了全局的一个value值。

使用this时,要么在外部调用时使用call或者apply指定函数运行的上下文,要么使用一个固定的上下文对象。

要更深入理解this,需要理解js中的闭包,可以参考:

http://blog.goddyzhao.me/post/11311499651/closures

 

另外一种解决方案就是直接对exports.value赋值,我想这应该是最终的解决方案。

var value = -1;

function set(num) {
    exports.value = num;
}

function get() {
    return exports.value;
}

exports.value = value;
exports.set = set;
exports.get = get;
> var n = require('./Number');
undefined
> n
{ value: -1,
  set: [Function: set],
  get: [Function: get] }
> n.set(0);
undefined
> n
{ value: 0,
  set: [Function: set],
  get: [Function: get] }
> var set = n.set;
undefined
> set(1);
undefined
> n
{ value: 1,
  set: [Function: set],
  get: [Function: get] }

 

参考资料:

http://bespin.cz/~ondras/html/classv8_1_1Value.html

http://cnodejs.org/topic/4f16442ccae1f4aa270010c1

http://howtonode.org/what-is-this

https://github.com/joyent/node/blob/master/src/node_script.cc

posted @ 2012-05-31 23:53  MySirius  阅读(668)  评论(0编辑  收藏  举报