为HTMLImageElement魔改setAttribute的方法

3/6/2021 JavaScript

# 背景

由于历史原因,前端升级 webp 图片的时候,需要给 img 的 src 地址加上形如:/xxxxx.png?webp=true 的参数。 但是不想改动特别多的代码,这里找到一个捷径记录一下。

核心:由于是在 react 环境中,像<img src='xxx' />的 jsx 代码会被 babel 转译成 React.createElement("img", { src: "xxx" });,最后通过setAttribute给创建的HTMLImageElement设置src属性。

我们的思路就是想办法拦截setAttribute的方法,拼接上?webp=true的逻辑

# Object.getOwnPropertyDescriptor

Object.getOwnPropertyDescriptor(obj, prop)返回指定对象obj上一个自有属性prop对应的属性描述符。

# Object.defineProperty

Object.defineProperty(obj, prop, descriptor) 方法会直接在一个对象obj上定义一个新属性prop,或者修改一个对象的现有属性,并返回此对象。其中descriptor是属性的描述符

# 实现过程

思路:将目标对象的目标属性替换成传入进来的配置形式,同时添加一个以“$”开头的同名属性,用于调用这个属性的原始实现。经过尝试我们无法直接 getter.call(this),会报错,所以只能继续采用临时替换属性定义的方式来调用到属性的原始实现,所幸实现起来比直接调用 getter 更简单一些(直接调用 getter 的话还要考虑原始属性是否是 value 定义而非存取器)

export const changeProperty = (
  obj: any,
  key: string,
  descriptor: PropertyDescriptor
): any => {
  try {
    let target = getTarget(obj, key);
    // 保存要修改的属性装饰器
    let originDescriptor: PropertyDescriptor =
      Object.getOwnPropertyDescriptor(target, key) || descriptor;
    // 魔改属性装饰器
    Object.defineProperty(target, key, descriptor);
    // 插入一个$开头的同名属性,用于外界使用,this.$xxx用来触发之前保存好的属性装饰器
    Object.defineProperty(target, `$${key}`, {
      configurable: originDescriptor.configurable,
      enumerable: originDescriptor.enumerable,
      get() {
        /* 先恢复魔改之前属性,用于触发 */
        Object.defineProperty(target, key, originDescriptor);
        let res: any = this[key];
        /* 在恢复魔改状态 */
        Object.defineProperty(target, key, descriptor);
        return res;
      },
      set(value: any) {
        /* 先恢复魔改之前属性,用于触发 */
        Object.defineProperty(target, key, originDescriptor);
        this[key] = value;
        /* 在恢复魔改状态 */
        Object.defineProperty(target, key, descriptor);
      },
    });
  } catch (error) {
    console.log("changeProperty error: ", error);
  }
};

/* 实现魔改setAttribute */
changeProperty(HTMLImageElement, "setAttribute", {
    enumerable: true,
    configurable: true,
    writable: true,
    value(...args: any[]) {
      // do some magic things
      let [key, imageSrc] = args;
      let newArgs = [key, imageSrc + '?webp=true'];
      this.$setAttribute(newArgs)
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
Last Updated: 2/21/2022, 11:35:56 AM