是否可以在javascript中创建隐藏属性

时间:2010-04-14 10:07:05

标签: javascript

我想创建一个具有隐藏属性的对象(一个未在for (var x in obj循环中显示的属性)。有可能这样做吗?

6 个答案:

答案 0 :(得分:44)

在ECMAScript 3中是不可能的(这是主要的浏览器在2010年提出这个问题时实现的)。但是,在ECMAScript 5中,所有主流浏览器的当前版本都可以实现,可以将属性设置为不可枚举:

var obj = {
   name: "Fred"
};

Object.defineProperty(obj, "age", {
    enumerable: false,
    writable: true
});

obj.age = 75;

/* The following will only log "name=>Fred" */
for (var i in obj) {
   console.log(i + "=>" + obj[i]);
}

这适用于当前浏览器:有关旧浏览器兼容性的详细信息,请参阅http://kangax.github.com/es5-compat-table/

请注意,在调用Object.defineProperty时,该属性也必须设置为可写,以允许正常分配(默认情况下为false)。

答案 1 :(得分:9)

这有点棘手!

function secret() {
  var cache = {};
  return function(){
    if (arguments.length == 1) { return cache[arguments[0]];}
    if (arguments.length == 2) { cache[arguments[0]] = arguments[1]; }
  };
}
var a = secret();

a.hello = 'world';
a('hidden', 'from the world');

如果你是真正的专业人士,你可以这样做!

var a = new (secret())();

a.hello = 'world';
a.constructor('hidden', 'from the world');

现在如果你看一个萤火虫它会成为一个对象......但你知道的更好! ;-)

答案 2 :(得分:6)

为了保持最新状态,这是ES6 +中的状态。我有点超出了问题的范围,并讨论了如何隐藏属性,而不仅仅是for ... in循环。

有几种方法可以创建可能被称为"隐藏的属性",而不会查看由闭包关闭的变量之类的东西,这些变量受到范围规则的限制。

现在经典,非可枚举属性

与以前版本的ECMAScript一样,您可以使用Object.defineProperty创建未标记为enumerable的属性。当您使用某些方法枚举对象的属性时,这会使属性不显示,例如for ... in循环和Object.keys函数。

Object.defineProperty(myObject, "meaning of life", {
    enumerable : false,
    value : 42
});

但是,您仍然可以使用Object.getOwnPropertyNames函数找到它,该函数甚至返回不可枚举的属性。当然,你仍然可以通过它的密钥访问该属性,这只是一个任何人都可以构建的字符串。

A(不可枚举)symbol属性

在ES6中,可以使用新原始类型的键 - symbol创建属性。 Javascript本身使用此类型来使用for ... of循环和库编写器枚举对象来执行各种其他操作。

Symbols有一个描述性文字,但它们是具有唯一标识的引用类型。它们不像字符串,如果它们具有相同的值则相等。要使两个符号相等,它们必须是两个完全相同的参考。

您使用symbol函数创建Symbol

let symb = Symbol("descriptive text");

您可以使用Object.defineProperty函数定义符号作为键的属性。

let theSecretKey = Symbol("meaning of life");
Object.defineProperty(myObject, theSecretKey, {
    enumerable : false,
    value : 42
});

除非有人获得对该确切符号对象的引用,否则他们无法按键查找该属性的值。

但您也可以使用常规语法:

let theSecretKey = Symbol("meaning of life");
myObject[theSecretKey] = 42;

具有此键类型的属性永远不会出现在for ... in循环等中,但仍然可以枚举且不可枚举,因为像Object.assign这样的函数对于非可枚举属性的工作方式不同。 / p>

Object.getOwnPropertyNames无法获取对象的symbol个键,但名称相同的Object.getOwnPropertySymbols将会解决问题。

弱地图

隐藏对象上的属性的最强方法是不将它存储在对象上。在ES6之前,这有点棘手,但现在我们的地图很弱。

弱映射基本上是Map,即键值存储,它不会保留(强)对键的引用,因此可以对它们进行垃圾回收。弱映射非常有限,并且不允许您枚举其键(这是设计)。但是,如果您获得了某个地图键的引用,则可以获得与其相关的值。

它们主要用于扩展对象而不实际修改它们。

基本思路是创建一个弱映射:

let weakMap = new WeakMap();

并使用您想要扩展为对象的对象。然后,值将是属性集,可以是{}个对象的形式,也可以是Map数据结构的形式。

weakMap.set(myObject, {
    "meaning of life" : 42
});

这种方法的优势在于,某人需要获取对您的weakMap实例密钥的引用,以便获取值,或者甚至知道它们存在于那里&#39我没办法解决这个问题。所以它是100%,保证是安全的。以这种方式隐藏属性可确保用户不会发现它们,并且您的Web应用程序永远不会被黑客攻击*

当然,所有这一切中最大的缺陷是,这并没有创造出实际的财产。所以它没有参与原型链等。

(*)这是谎言。

答案 3 :(得分:0)

var Foo=function(s){
    var hidden
    this.setName=function(name){theName=name}
    this.toString=function(){return theName}
    this.public=s
}
var X=new Foo('The X')
X.setName('This is X')
X // returns 'This is X'
X.public // returns 'The X'
X.hidden // returns 'undefined'

答案 4 :(得分:0)

尝试一下:

Object.defineProperty(
    objectName,
    'propertiesName', {
        enumerable: false
    }
)

答案 5 :(得分:0)

这是使用Proxy对象的解决方案。

事件发射器示例:

class Event {
  constructor(opts = {}) {
    this.events = new Map
    this.proxy = new class {}
    Object.defineProperty(this.proxy, 'on', { value: this.on.bind(this) })
    Object.defineProperty(this.proxy, 'emit', { value: this.emit.bind(this) })
    Object.defineProperty(this.proxy, 'length', { get: () => this.length })
    Object.defineProperty(this.proxy.constructor, 'name', {
      value: this.constructor.name
    })
    return new Proxy(this.proxy, {})
  }
  on(topic, handler) {
    if (!this.events.has(topic))
      this.events.set(topic, new Set)
    this.events.get(topic).add(handler)
    return this.remove.bind(this, topic, handler)
  }
  emit(topic, ...payload) {
    if (!this.events.has(topic))
      return
    const set = this.events.get(topic)
    for (const fn of set)
      fn(...payload)
  }
  remove(topic, handler) {
    if (!this.events.has(topic))
      return
    const set = this.events.get(topic)
    if (set.has(handler))
      set.delete(handler)
  }
  get length() {
    return this.events.size
  }
}

注意,在构造函数中,该构造函数将返回带有对proxy属性的引用的新Proxy。我“装饰”了代理对象,使其看起来像原始类。您可以得到长度,因为我公开了该吸气剂,但是(据我所知)无法直接访问事件Map并遍历键。我猜这有点像反向关闭吗?我不确定这在垃圾回收方面如何工作。但这确实可以将功能封装在远离用户的位置,这样他们就无法将事情搞糟。

更新: 因此,该方法正在干扰原型继承。在这里,我通过创建类时使用了构造方法并提升了“隐藏”变量events,从而找到了一种类似但更好的技术。

let Event = 
class Event {
  on(topic, handler) {
    if (!events.has(topic))
      events.set(topic, new Set)
    events.get(topic).add(handler)
    return this.remove.bind(this, topic, handler)
  }
  emit(topic, ...payload) {
    if (!events.has(topic))
      return
    const set = events.get(topic)
    for (const fn of set)
      fn(...payload)
  }
  remove(topic, handler) {
    if (!events.has(topic))
      return
    const set = events.get(topic)
    if (typeof handler === 'undefined')
      return events.delete(topic)
    if (set.has(handler))
      set.delete(handler)
  }
  get length() {
    return events.size
  }
}
let events
Event = new Proxy(Event, {
  construct (target, args, self) {
    events = new Map
    return Reflect.construct(target, args, self)
  }
})

以下是使用此概念的功能更全的事件发射器的要点: https://gist.github.com/aronanda/18b6397c355da88ca170d01e0cc02628