转载

拒绝外部修改的对象(Node.js)

document 是 mongoose 的数据实例,它拥有一些比较有意思的特性

  1. 实例拥有一些方法,能被正常调用
  2. console.log 显示的是纯数据,不包含方法
  3. 实例属性可以内部增加,但不能在外部增加
  4. 实例属性在外部不可被覆写,也不能被删除
  5. 实例在执行 update 方法后,属性也能被更新

看起来,1 和 2 比较冲突,3、4 和 5 也比较冲突。所以要怎么实现呢?

1、2 实现看起来比较简单,最简单的是把方法都添加到实例创建的原型上,这样创建的 document 实例的时候会自动继承原型上的方法,而在 console.log 实例的时候不会显示继承的属性,所以显示的结果会看起来非常干净。

后面的 3、4、5 统称起来就是 实例可内部修改但不能被外部修改 ,这个实现起来就麻烦了。

创建一个 不可被修改也不能被删除 的对象非常地容易,将 Object.defineProperty 中的 configurablewritable 都设为 false 就可以了。但是这样创建的对象是完全不能被修改的,更新属性的话没法搞。

要支持更新属性也行啊,把 configurablewritable 都设为 true ... orz,这样就能外部修改了。

还有个办法就是用 getter ,听起来不错,但是实际使用的时候发现 configurable 不能设为 false ,不然的话不能更新 getter 里面的数据,设为 true 以后数据虽然不能被更新但又能被删除。

而且,设置了 getter 以后,用 console.log 打印实例的时候属性会显示成 [Getter] ,而不是实际值,这样跟 2 又冲突了。

还有个办法 Object.seal 可以密封对象。 被密封后的对象不能添加新的属性,不能删除已有属性,以及不能修改已有属性的可枚举性、可配置性、可写性,但可以修改已有属性的值的对象 。orz ... 这里的修改并不能限制外部的。

似乎可以综合一下, Object.seal 允许修改, getter 可以限制修改来源。

而实际上 getter 的数据更新可以指定到一个内部变量,这样只用更改这个变量就可以更改这个属性了,而不用去直接改 getter ,类似于做了一个映射。

class Model   constructor: (record) ->     _.extend @, record: record      _.keys(record).map (key) =>       Object.defineProperty @, key,         enumerable: true         configurable: false         get: ->           @record[key]    update: (record) ->     _.extend @, record: record 

这样处理的话已经成功了一部分了,再还要不允许往实例上添加属性,在构造函数里面加上 Object.seal @ 就好了。

后面的问题处理完了,但是前面的 1、2 又冲突了。添加的 record 属性会在 console.log 的时候显示出来,而添加的 getter 也会显示成了 [Getter]

去研究了下 mongoose 的实现,发现它在 document 的原型里面添加了一个 inspect 方法。

我和逸川一起追溯了一下 console.log 的在 node.js 中的实现,整个的流程就是

  • console.log 调用了 util.format
  • 然后进而调用了 util.inspect
  • util.inspect 又调用了 formatValue
  • 然后是 formatValue 调用了传入 console.log 的参数的 inspect 方法

所以,console.log 的传入参数的 inspect 方法会直接影响 console.log 显示的内容。

class Display   constructor: ->    inspect: ->     return 'hello'  console.log new Display()  # 会显示 hello 

那我们再给我们的 Model 加上 inspect 方法,里面返回 record 的数据即可。

这样我们就实现了一个 可内部修改但不能被外部修改 的对象了。

完整的示例参见 cado 。

正文到此结束
Loading...