如何"覆盖"原型上定义的(get-)属性?

时间:2014-10-10 09:46:23

标签: javascript properties override getter-setter defineproperty

我有一些代码可以在原型上定义一个getter(但是没有setter,如果相关的话)。返回的值在99.99%的情况下是正确的;但是,目标是将属性设置为评估特定对象的不同值。

foo = {}
Object.defineProperty(foo, "bar", {
    // only returns odd die sides
    get: function () { return (Math.random() * 6) | 1; }
});

x = Object.create(foo);
x.bar       // => eg. 5
x.bar = 4   // by fair dice roll
x.bar       // nope => eg. 3

如何为x,现有对象覆盖属性,使其可分配(例如,具有默认属性行为)?

附录:虽然可以在x上定义新属性(值或获取/设置),但我正在寻找是否有办法停止 [原型]中属性的行为将“bar”变回特定实例的正常/ ad-hoc属性。

4 个答案:

答案 0 :(得分:8)

Object.defineProperty上使用x

var foo = {}
Object.defineProperty(foo, "bar", {
    // only returns odd die sides
    get: function () { return (Math.random() * 6) | 1; }
});

var x = Object.create(foo);
display(x.bar);      // E.g. 5

(function() {
  var bar;
  var proto = Object.getPrototypeOf(x); // Or just use foo

  Object.defineProperty(x, "bar", {
    get: function () { return typeof bar !== "undefined" ? bar : proto.bar; },
    set: function(value) { bar = value; }
  });
})();

display(x.bar);  // Still odd
x.bar = 4;       // By fair dice roll
display(x.bar);  // Shows 4

function display(msg) {
  document.body.insertAdjacentHTML("beforeend", "<p>" + msg + "</p>");
}

  

我正在寻找是否有办法停止[prototype]中属性的行为并将“bar”变回普通/ ad-hoc属性。

好的,这略有不同,但仍使用Object.defineProperty

var foo = {}
Object.defineProperty(foo, "bar", {
    // only returns odd die sides
    get: function () { return (Math.random() * 6) | 1; }
});

var x = Object.create(foo);
display(x.bar);      // E.g. 5

Object.defineProperty(x, "bar", {
  value: undefined,
  writable: true,
  enumerable: true // Or leave off if you want it non-enumerable
});

display(x.bar);  // undefined
x.bar = 4;       // By fair dice roll
display(x.bar);  // Shows 4

function display(msg) {
  document.body.insertAdjacentHTML("beforeend", "<p>" + msg + "</p>");
}

答案 1 :(得分:1)

T.J。克劳德说,再次使用defineProperty可以达到目的。您可能会考虑以下变体,其中设置器本身会覆盖属性:

Foo = function () {}
Foo.prototype = {
  // computes, but only knows odd die sides
  get bar() { 
    console.log("getter invoked")
    return (Math.random() * 6) | 1 
  },
  
  // fix it
  set bar(value) {
    console.log("setter invoked")
    Object.defineProperty(
      this, 'bar', 
      {writable: true, enumerable: true, configurable: true}
    )
    this.bar = value 
  }
}

var x = new Foo
console.log(x.bar)       // => eg. 5
x.bar = 4   // by fair dice roll
console.log(x.bar)       // => 4
x.bar = 2   // no setter, no getter
console.log(x.bar)

我希望您能以稍微不同的语法重写我。它不会改变任何窍门。当我写这篇文章时,我实际上只是在寻找一种方法来覆盖继承的getter

答案 2 :(得分:1)

在TypeScript中,我使用Object.getOwnPropertyDescriptorObject.defineProperty

class TestClass
{

    constructor()
    {
        this.autoBind(this);
        this.autoTrace(this);
    }



    private autoBind(self: any): any
    {
        for (const key of Object.getOwnPropertyNames(self.constructor.prototype))
        {

            if (key !== 'constructor')
            {
                // console.log(key);

                let desc = Object.getOwnPropertyDescriptor(self.constructor.prototype, key);

                if (desc != null)
                {
                    let g = desc.get != null;
                    let s = desc.set != null;

                    if (g || s)
                    {
                        if (g)
                            desc.get = desc.get.bind(self);

                        if (s)
                            desc.set = desc.set.bind(self);

                        Object.defineProperty(self.constructor.prototype, key, desc);
                        continue; // if it's a property, it can't be a function 
                    } // End if (g || s) 

                } // End if (desc != null) 

                if (typeof (self[key]) === 'function')
                {
                    let val = self[key];
                    self[key] = val.bind(self);
                }

            } // End if (key !== 'constructor' && typeof val === 'function') 

        } // Next key 

        return self;
    } // End Function autoBind



    private autoTrace(self: any): any
    {


        function getLoggableFunction_old(func, type, name)
        {
            return function (...args)
            {
                let logText = name + '(';

                for (var i = 0; i < args.length; i++)
                {
                    if (i > 0)
                    {
                        logText += ', ';
                    }
                    logText += args[i];
                }
                logText += ');';

                console.log(type + " " + logText);
                return func.apply(self, args);
            };
        }


        function getLoggableFunction(func, type, name)
        {
            return function (...args)
            {
                let logText = name + '(';

                for (var i = 0; i < args.length; i++)
                {
                    if (i > 0)
                    {
                        logText += ', ';
                    }
                    logText += args[i];
                }
                logText += ')';

                console.log("Pre " + type + " " + logText + "; ");
                let res = func.apply(self, args);
                console.log("Post " + type + " " + logText + ":", res);
                return res;
            };
        }


        for (const key of Object.getOwnPropertyNames(self.constructor.prototype))
        {

            if (key !== 'constructor')
            {
                // console.log(key);
                let desc = Object.getOwnPropertyDescriptor(self.constructor.prototype, key);

                if (desc != null)
                {
                    let g = desc.get != null;
                    let s = desc.set != null;

                    if (g || s)
                    {
                        if (g)
                            desc.get = getLoggableFunction(desc.get.bind(self), "Property", "get_" + key)

                        if (s)
                            desc.set = getLoggableFunction(desc.set.bind(self), "Property", "set_" + key)

                        Object.defineProperty(self.constructor.prototype, key, desc);
                        continue; // if it's a property, it can't be a function 
                    } // End if (g || s) 

                } // End if (desc != null) 

                // if it's not a property, it can only be a function or not a function 
                if (typeof (self[key]) === 'function')
                {
                    let val = self[key];
                    self[key] = getLoggableFunction(val.bind(self), "Function", key);
                } // End if (typeof (self[key]) === 'function') 

            } // End if (key !== 'constructor' && typeof val === 'function') 

        } // Next key 

        return self;
    } // End Function autoTrace



get bar(): boolean
{
    return this._bar;
}
set bar(value: boolean)
{
    this._bar = value;
}


public hello()
{
    console.log("hello", "this", this);
}


public world(x, y)
{
    console.log("world", "this", this);

}



}

答案 3 :(得分:1)

您可以通过定义一个 Setter 来改变 Getter 的行为。

1.将 foo 定义为新实例对象的原型

2.创建 foo 的实例并覆盖 set bar() :

// Freeze foo. Makes sure none of the Options manipulates the prototype
const foo = Object.freeze(
  // Create new Obj with NO prototype 
  // ➜ Use {} instead of null if you need std Object methods, too
  // ➜ Add setter/getter for 'bar'
  Object.create(null, {
    bar: {
      get() {
        // Return if set by setter or random
        return this[Symbol.for('bar')] ?? (Math.random() * 6) | 1;
      },
      set(val) {
        console.log(`setter 'bar' = ${val}`);
        // Hide 'bar' val behind a Symbol key (non enumerable)
        this[Symbol.for('bar')] = val;
      }
    }
  })
);


console.log('Option A: Call setter (post create) =================');
const x = Object.create(foo);
console.log(x.bar);           // rnd
x.bar = 4;                    // setter 'bar' = 4
console.log(x.bar);           // 4
x.bar = null;                 // setter 'bar' = null
console.log(x.bar);           // rnd

console.log('Option B: Overwrite Setter (on create) ==============');
// Create new foo obj, overwrite 'bar' Setter/Getter
const create = obj => Object.create(foo, Object.getOwnPropertyDescriptors(obj));

const y = create({bar: 4});  
console.log(y.bar);           // 4
y.bar = 5;                    // overwrite prop 'bar' = 4 
console.log(y.bar);           // 5
y.bar = null;                 // overwrite prop 'bar' = null 
console.log(y.bar);           // null (no fallback)

console.log('Option C: Obj.assign invoces Setter (on create) ====');
// create new foo Obj, assign default
// ➜ Setter gets called on new target
const createFrom = obj => Object.assign(Object.create(foo), obj);

const z = createFrom({bar: 4});
console.log(z.bar);           // 4
z.bar = 6;                    // setter 'bar' = 6
console.log(z.bar);           // 6
z.bar = null;                 // setter 'bar' = null
console.log(z.bar);           // rnd