使用 getter/setter 覆盖 javascript 属性,同时仍访问底层属性

Override javascript property with getter/setter while still accessing underlying property

本文关键字:属性 访问 setter 覆盖 javascript 使用 getter      更新时间:2023-09-26

为了构建 API polyfill,我想用 getter 和 setter 覆盖元素的属性(在本例中为宽度和高度),以捕获对值的更改并在将其传递给底层元素之前对其进行修改。理想情况下,此过程也是可逆的。类似于此代码片段的内容:

var realWidth = null;
function patch(targetObject) {
 realWidth = targetObject.magicalPropertyAccessor("width");
 Object.defineProperty(targetObject, 'width', {
    get: function() {
      return realWidth / 2;
    },
    set: function(value) {
      realWidth = value * 2;
    }
  });
}
function unpatch(targetObject) {
  if (realWidth)
    targetObject.magicalPropertySetter('width', realWidth);
}

该示例的目的是,在修补元素时,它将静默地对其维度进行更改,同时报告原始的、未更改的值。如果这是一个函数,那将非常简单,但作为一个属性,不清楚如何缓存对原始访问器的引用。

多亏

了Bergi,我发现Object.getOwnPropertyDescriptor正是我想要的。我以前试过,但错过了我必须去对象的__proto__找到我正在寻找的属性的属性。(您的里程可能会因您要更换的属性而异。这是对我有用的代码:

function WidthPatch(canvas) {
  var self = this;
  var fakeWidth = canvas.width;
  this.canvas = canvas;
  // Cache the real property
  this.realWidthProp = Object.getOwnPropertyDescriptor(canvas.__proto__, 'width');
  // Replace the property with a custom one
  Object.defineProperty(canvas, 'width', {
    configurable: true,
    enumerable: true,
    get: function() {
      return fakeWidth;
    },
    set: function(value) {
      fakeWidth = value;
      // This updates the real canvas property, silently doubling it.
      self.realWidthProp.set.call(canvas, fakeWidth * 2);
    }
  });
}
WidthPatch.prototype.unpatch = function() {
  // Replace the custom property with the original one.
  Object.defineProperty(this.canvas, 'width', this.realWidthProp);
}

Reflect 为这个特定的用例提供了一些不错的(r) 语法。

下面的演示实现了一个"WidthPatch"类,该类覆盖了"width"属性,返回一个是实际值2倍的值,同时通过新的"actualWidth"属性提供对实际值的访问。 "UnPatch"方法删除修改的属性,恢复为原始行为。

class WidthPatch {
    static #propertyName = 'width';
    static #shadowPropertyName = 'actualWidth';
    
    static Patch(instance) {
        Reflect.defineProperty(instance, WidthPatch.#shadowPropertyName, {
            get: _ => WidthPatch.#get(instance)
        });
        Reflect.defineProperty(instance, WidthPatch.#propertyName, {
            configurable: true,
            enumerable: true,
            get: _ => 2 * WidthPatch.#get(instance),
            set: value => Reflect.set(Reflect.getPrototypeOf(instance), WidthPatch.#propertyName, value, instance)
        }); 
    }
    
    static UnPatch(instance) {
        Reflect.deleteProperty(instance, WidthPatch.#propertyName);
    }
    
    static #get(instance) {
        return Reflect.get(Reflect.getPrototypeOf(instance), WidthPatch.#propertyName, instance);
    }
}

class Demo {
    static {
    
        const canvas = document.createElement('canvas');
        
        console.log(`width (initial): ${canvas.width}`);
        
        WidthPatch.Patch(canvas);        
        console.log(`width (after patching): ${canvas.width}, actualWidth: ${canvas.actualWidth}`);
        
        canvas.width = 200;
        console.log(`width (after setting to 200): ${canvas.width}, actualWidth: ${canvas.actualWidth}`);
        
        WidthPatch.UnPatch(canvas);
        console.log(`width (after unpatching): ${canvas.width}, actualWidth: ${canvas.actualWidth}`);
        
    }
}