如何“;覆盖“;原型上已定义的(get-)属性

How to "override" a defined (get-)property on a prototype?

本文关键字:get- 属性 覆盖 原型 如何 定义      更新时间:2023-09-26

我有一些代码在原型上定义了一个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上定义一个新的属性(value或get/set),但我正在寻找是否有办法停止[原型]中属性的行为,并将"bar"变回特定实例的正常/特殊属性。

通过在x:上使用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
(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>");
}

我正在寻找是否有一种方法可以停止[原型]中属性的行为,并将"bar"恢复为正常/特殊属性。

好吧,这略有不同,但仍然使用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>");
}

它并不像Object.defineProperty那么简单。
您需要首先获取属性描述符(使用Object.getOwnPropertyDescriptor),然后检查该属性是否是可配置的
如果NOT可配置,则无法覆盖它。
此外,您还需要考虑一个属性可以有一个getter/setterXOR一个VALUE。

所以不能设置desc.value&desc.writable,如果您有getter/setter,反之亦然

let desc = Object.getOwnPropertyDescriptor(self.constructor.prototype, key);
if (desc != null)
{
    // We can only redefine configurable properties !
    if (!desc.configurable)
    {
        console.log("AUTOTRACE-WARNING: Property '"" + key + "'" not configurable ! (" + self.constructor.name + ")");
        continue;
    }
    let g = desc.get != null;
    let s = desc.set != null;
    if (g || s)
    {
        let newDescriptor: PropertyDescriptor = {};
        newDescriptor.enumerable = desc.enumerable;
        newDescriptor.configurable = desc.configurable; 
        // Argh !
        // newDescriptor.value= desc.value; 
        // newDescriptor.writable = desc.writable; 
        if (g)
            newDescriptor.get = getLoggableFunction(desc.get.bind(self), "Property", "get_" + key)
        if (s)
            newDescriptor.set = getLoggableFunction(desc.set.bind(self), "Property", "set_" + key)
        Object.defineProperty(self, key, newDescriptor);
        continue; // if it's a property, it can't be a function 
    } // End if (g || s) 
} // End if (desc != null) 

以下是我的操作方法(注意"TestClass"构造函数中的autobind&autotrace调用):

import { autoBind, autoTrace } from "./autobind_autotrace.js";
class TestClass
{
    constructor()
    {
        autoBind(this);
        autoTrace(this);
    }

    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);
    }
}

自动绑定/自动跟踪(TypeScript):

export function 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)
            {
                // We can only redefine configurable properties !
                if (!desc.configurable)
                {
                    console.log("AUTOBIND-WARNING: Property '"" + key + "'" not configurable ! (" + self.constructor.name + ")");
                    continue;
                }
                let g = desc.get != null;
                let s = desc.set != null;
                if (g || s)
                {
                    let newGetter = null;
                    let newSetter = null;
                    if (g)
                        //desc.get = desc.get.bind(self);
                        newGetter = desc.get.bind(self);
                    if (s)
                        // desc.set = desc.set.bind(self);
                        newSetter = desc.set.bind(self);
                    if (newGetter != null && newSetter == null)
                    {
                        Object.defineProperty(self, key, {
                              get: newGetter 
                            , enumerable: desc.enumerable
                            , configurable: desc.configurable
                            // , value: desc.value
                            // , writable: desc.writable
                        });
                    }
                    else if (newSetter != null && newGetter == null)
                    {
                        Object.defineProperty(self, key, {
                              set: newSetter
                            , enumerable: desc.enumerable
                            , configurable: desc.configurable
                            // , value: desc.value
                            // , writable: desc.writable
                        });
                    }
                    else // at least one is set, but none of the above cases, so two are set 
                    {
                        Object.defineProperty(self, key, {
                              get: newGetter
                            , set: newSetter
                            , enumerable: desc.enumerable
                            , configurable: desc.configurable
                            // , value: desc.value
                            // , writable: desc.writable
                        });
                    }
                    // Object.defineProperty(self.constructor.prototype, key, desc);
                    // 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

export function autoTrace(self: any): any
{

    function getLoggableFunction_old(func: any, type: any, name: any)
    {
        return function (...args:any[])
        {
            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: any, type: any, name: any)
    {
        return function (...args: any[])
        {
            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)
            {
                // We can only redefine configurable properties !
                if (!desc.configurable)
                {
                    console.log("AUTOTRACE-WARNING: Property '"" + key + "'" not configurable ! (" + self.constructor.name + ")");
                    continue;
                }
                let g = desc.get != null;
                let s = desc.set != null;
                if (g || s)
                {
                    let newGetter = null;
                    let newSetter = null;
                    if (g)
                        //desc.get = getLoggableFunction(desc.get.bind(self), "Property", "get_" + key)
                        newGetter = getLoggableFunction(desc.get.bind(self), "Property", "get_" + key)
                    if (s)
                        // desc.set = getLoggableFunction(desc.set.bind(self), "Property", "set_" + key)
                        newSetter = getLoggableFunction(desc.set.bind(self), "Property", "set_" + key)
                    if (newGetter != null && newSetter == null)
                    {
                        Object.defineProperty(self, key, {
                              get: newGetter
                            , enumerable: desc.enumerable
                            , configurable: desc.configurable
                            // , value: desc.value
                            // , writable: desc.writable
                        });
                    }
                    else if (newSetter != null && newGetter == null)
                    {
                        Object.defineProperty(self, key, {
                              set: newSetter
                            , enumerable: desc.enumerable
                            , configurable: desc.configurable
                            // , value: desc.value
                            // , writable: desc.writable
                        });
                    }
                    else // at least one is set, but none of the above cases, so two are set 
                    {
                        Object.defineProperty(self, key, {
                              get: newGetter
                            , set: newSetter
                            , enumerable: desc.enumerable
                            , configurable: desc.configurable
                            // , value: desc.value
                            // , writable: desc.writable
                        });
                    }
                    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

这可以转换为纯JavaScript:

"use strict";
function autoBind(self)
{
    for (var _i = 0, _a = Object.getOwnPropertyNames(self.constructor.prototype); _i < _a.length; _i++)
    {
        var key = _a[_i];
        if (key !== 'constructor')
        {
            var desc = Object.getOwnPropertyDescriptor(self.constructor.prototype, key);
            
            if (desc != null)
            {
                if (!desc.configurable)
                {
                    console.log("AUTOBIND-WARNING: Property '"" + key + "'" not configurable ! (" + self.constructor.name + ")");
                    continue;
                }
                var g = desc.get != null;
                var s = desc.set != null;
                if (g || s)
                {
                    var newGetter = null;
                    var newSetter = null;
                    if (g)
                        newGetter = desc.get.bind(self);
                    if (s)
                        newSetter = desc.set.bind(self);
                    if (newGetter != null && newSetter == null)
                    {
                        Object.defineProperty(self, key, {
                              get: newGetter 
                            , enumerable: desc.enumerable 
                            , configurable: desc.configurable 
                            // , writable: desc.writable 
                            // , value: desc.value 
                        });
                    }
                    else if (newSetter != null && newGetter == null)
                    {
                        Object.defineProperty(self, key, {
                              set: newSetter 
                            , enumerable: desc.enumerable 
                            , configurable: desc.configurable 
                            // , writable: desc.writable 
                            // , value: desc.value 
                        });
                    }
                    else
                    {
                        Object.defineProperty(self, key, {
                              get: newGetter 
                            , set: newSetter
                            , enumerable: desc.enumerable
                            , configurable: desc.configurable
                            // , writable: desc.writable
                            // , value: desc.value
                        });
                    }
                    continue;
                }
            }
            if (typeof (self[key]) === 'function')
            {
                var val = self[key];
                self[key] = val.bind(self);
            }
        }
    }
    return self;
}
function autoTrace(self)
{
    function getLoggableFunction_old(func, type, name)
    {
        return function ()
        {
            var args = [];
            for (var _i = 0; _i < arguments.length; _i++)
            {
                args[_i] = arguments[_i];
            }
            var 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 ()
        {
            var args = [];
            for (var _i = 0; _i < arguments.length; _i++)
            {
                args[_i] = arguments[_i];
            }
            var logText = name + '(';
            for (var i = 0; i < args.length; i++)
            {
                if (i > 0)
                {
                    logText += ', ';
                }
                logText += args[i];
            }
            logText += ')';
            console.log("Pre " + type + " " + logText + "; ");
            var res = func.apply(self, args);
            console.log("Post " + type + " " + logText + ":", res);
            return res;
        };
    }
    for (var _i = 0, _a = Object.getOwnPropertyNames(self.constructor.prototype); _i < _a.length; _i++)
    {
        var key = _a[_i];
        if (key !== 'constructor')
        {
            var desc = Object.getOwnPropertyDescriptor(self.constructor.prototype, key);
            if (desc != null)
            {
                if (!desc.configurable)
                {
                    console.log("AUTOTRACE-WARNING: Property '"" + key + "'" not configurable ! (" + self.constructor.name + ")");
                    continue;
                }
                var g = desc.get != null;
                var s = desc.set != null;
                if (g || s)
                {
                    var newGetter = null;
                    var newSetter = null;
                    if (g)
                        newGetter = getLoggableFunction(desc.get.bind(self), "Property", "get_" + key);
                    if (s)
                        newSetter = getLoggableFunction(desc.set.bind(self), "Property", "set_" + key);
                    if (newGetter != null && newSetter == null)
                    {
                        Object.defineProperty(self, key, {
                              get: newGetter 
                            , enumerable: desc.enumerable
                            , configurable: desc.configurable
                            // , writable: desc.writable
                            // , value: desc.value
                        });
                    }
                    else if (newSetter != null && newGetter == null)
                    {
                        Object.defineProperty(self, key, {
                              set: newSetter 
                            , enumerable: desc.enumerable
                            , configurable: desc.configurable
                            // , writable: desc.writable
                            // , value: desc.value
                        });
                    }
                    else
                    {
                        Object.defineProperty(self, key, {
                              get: newGetter 
                            , set: newSetter 
                            , enumerable: desc.enumerable
                            , configurable: desc.configurable
                            // , writable: desc.writable
                            // , value: desc.value
                        });
                    }
                    continue;
                }
            }
            if (typeof (self[key]) === 'function')
            {
                var val = self[key];
                self[key] = getLoggableFunction(val.bind(self), "Function", key);
            }
        }
    }
    return self;
}
// exports and module are globals of "require" - delete if usage without require 
exports = exports || {}; // prevent error if no module 
exports.autoBind = autoBind;
exports.autoTrace = autoTrace;

正如T.J.Crowder所说,再次使用defineProperty可以达到目的。您可以考虑以下变体,其中setter本身覆盖属性:

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

您可以通过定义Setter来更改Getter行为。

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

  • Setters/Getter(访问器属性)将被复制到其原型的新创建的对象中。

    与访问器属性不同,值属性总是在对象本身上设置,而不是在原型上设置。

  • bar的值"隐藏"在Symbol属性后面。这样可以防止直接操纵。

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

  • 选项A:

    ➜创建新的foo

    ➜使用set bar()更改默认值。

  • 选项B:

    ➜使用重新定义的set bar()创建新的foo

    访问器属性set bar()值属性bar覆盖。

  • 选项C:

    ➜创建新的fooObject.assign()作为新的默认值。

    ➜这涉及到set bar()

    它在源上使用[[Get]],在目标上使用[[Set]],因此它将调用getter和setter。

// Freeze foo. (Prevent accidental manipulation of the prototype)
const foo = Object.freeze(
  // Create new objecct with NO prototype: null 
  // Change null ➜ {} for inheritance of standard object methods
  Object.create(null, {
    // 'bar' set/get
    bar: {
      get() {
        // Check: Symbol existence || random number
        return this[Symbol.for('bar')] ?? (Math.random() * 6) | 1;
      },
      set(val) {
        console.log(`setter 'bar' = ${val}`);
        // Set 'bar' val and hide 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