上一主题 下一主题
ScriptCat,新一代的脚本管理器脚本站,与全世界分享你的用户脚本油猴脚本开发指南教程目录
返回列表 发新帖

[油猴脚本开发指南]Proxy的使用

[复制链接]
  • TA的每日心情
    开心
    2023-2-28 23:59
  • 签到天数: 191 天

    [LV.7]常住居民III

    632

    主题

    5170

    回帖

    6048

    积分

    管理员

    非物质文化遗产社会摇传承人

    积分
    6048

    荣誉开发者管理员油中2周年生态建设者喜迎中秋

    发表于 2021-7-24 17:15:01 | 显示全部楼层 | 阅读模式

    本文参考

    本文参考的是MDN文档https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy

    对其进行了一定的整理以及编排,以便符合讲解的顺序。

    Proxy是什么?

    Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。

    如果说数据劫持是对对象中某个属性进行了劫持,那Proxy可以理解为对一个对象进行劫持。

    语法以及参数

    const p = new Proxy(target, handler)

    target要使用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。``

    handler一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理 p 的行为。

    handler的方法

    handler.getPrototypeOf()

    是Object.getPrototypeOf方法的捕捉器。

    当读取代理对象的原型时,使用该函数进行劫持。

    const handler = {
      getPrototypeOf(target) {
        return {name:'8888'};
      }
    };
    
    const proxy1 = new Proxy(obj, handler);

    handler.setPrototypeOf()

    是Object.setPrototypeOf 方法的捕捉器。

    当设置代理对象的原型时,使用该函数进行劫持,如果不想设置一个新的原型,可以返回false

    var handlerReturnsFalse = {
        setPrototypeOf(target, newProto) {
            return false;
        }
    };
    
    var newProto = {}, target = {};
    
    var p1 = new Proxy(target, handlerReturnsFalse);
    Object.setPrototypeOf(p1, newProto); // throws a TypeError
    Reflect.setPrototypeOf(p1, newProto); // returns false

    handler.preventExtensions()

    Object.preventExtensions 方法的捕捉器。

    Object.preventExtensions 是让对象变得不可拓展的函数,当设置不可拓展时,会触发handler.preventExtensions

    注意,如果对象是可拓展的,那么只能返回false

    var p = new Proxy({}, {
      preventExtensions: function(target) {
        console.log('called');
        Object.preventExtensions(target);
        return true;
      }
    });
    
    console.log(Object.preventExtensions(p)); // "called"
                                              // false

    handler.isExtensible()

    Object.isExtensible 方法的捕捉器。

    该方法用于拦截Object.isExtensible()。,而Object.isExtensible用于判断对象是否是一个可拓展的对象

    注意,该函数返回必须为true

    var p = new Proxy({}, {
      isExtensible: function(target) {
        console.log('called');
        return true;//也可以return 1;等表示为true的值
      }
    });
    
    console.log(Object.isExtensible(p)); // "called"
                                         // true

    handler.getOwnPropertyDescriptor()

    handler.getOwnPropertyDescriptor()方法是 Object.getOwnPropertyDescriptor()的钩子。

    该方法用于拦截Object.getOwnPropertyDescriptor(),Object.getOwnPropertyDescriptor获取对象自有属性的描述符,自有属性指直接赋予对象的属性,而且原型链中的属性

    getOwnPropertyDescriptor 必须返回一个 object 或 undefined。
    如果属性作为目标对象的不可配置的属性存在,则该属性无法报告为不存在。
    如果属性作为目标对象的属性存在,并且目标对象不可扩展,则该属性无法报告为不存在。
    如果属性不存在作为目标对象的属性,并且目标对象不可扩展,则不能将其报告为存在。
    属性不能被报告为不可配置,如果它不作为目标对象的自身属性存在,或者作为目标对象的可配置的属性存在。
    Object.getOwnPropertyDescriptor(target)的结果可以使用 Object.defineProperty 应用于目标对象,也不会抛出异常。
    var p = new Proxy({ a: 20}, {
      getOwnPropertyDescriptor: function(target, prop) {
        console.log('called: ' + prop);
        return { configurable: true, enumerable: true, value: 10 };
      }
    });
    
    console.log(Object.getOwnPropertyDescriptor(p, 'a').value); // "called: a"
                                                                // 10

    configurable

    当且仅当该属性的 configurable 键值为 true 时,该属性的描述符才能够被改变,同时该属性也能从对应的对象上被删除。**
    **默认为** false。**

    enumerable

    当且仅当该属性的 enumerable 键值为 true 时,该属性才会出现在对象的枚举属性中。**
    **默认为 false**。**

    以下代码则违反了规则。

    var obj = { a: 10 };
    Object.preventExtensions(obj);
    var p = new Proxy(obj, {
      getOwnPropertyDescriptor: function(target, prop) {
        return undefined;
      }
    });
    
    Object.getOwnPropertyDescriptor(p, 'a'); // TypeError is thrown

    handler.defineProperty(

    Object.defineProperty 方法的捕捉器。

    Object.defineProperty是啥我就不多说了

    var p = new Proxy({}, {
      defineProperty: function(target, prop, descriptor) {
        console.log('called: ' + prop);
        return true;
      }
    });
    
    var desc = { configurable: true, enumerable: true, value: 10 };
    Object.defineProperty(p, 'a', desc); // "called: a"

    handler.has()

    in操作符的捕捉器。

    如果指定的属性在指定的对象或其原型链中,则**in 运算符****返回true。**

    const handler1 = {
      has(target, key) {
        if (key[0] === '_') {
          return false;
        }
        return key in target;
      }
    };
    
    const monster1 = {
      _secret: 'easily scared',
      eyeCount: 4
    };
    
    const proxy1 = new Proxy(monster1, handler1);
    console.log('eyeCount' in proxy1);
    // expected output: true
    
    console.log('_secret' in proxy1);
    // expected output: false
    
    console.log('_secret' in monster1);
    // expected output: true
    

    handler.get()

    属性读取操作的捕捉器。get的意思与Object.defineProperty 中的get一致

    需要注意的是

    如果要访问的目标属性是不可写以及不可配置的,则返回的值必须与该目标属性的值相同。
    如果要访问的目标属性没有配置访问方法,即get方法是undefined的,则返回值必须为undefined。
    var p = new Proxy({}, {
      get: function(target, prop, receiver) {
        console.log("called: " + prop);
        return 10;
      }
    });
    
    console.log(p.a); // "called: a"
                      // 10

    handler.set()

    属性设置操作的捕捉器。set的意思与Object.defineProperty 中的set一致

    const monster1 = { eyeCount: 4 };
    
    const handler1 = {
      set(obj, prop, value) {
        if ((prop === 'eyeCount') && ((value % 2) !== 0)) {
          console.log('Monsters must have an even number of eyes');
        } else {
          return Reflect.set(...arguments);
        }
      }
    };
    
    const proxy1 = new Proxy(monster1, handler1);
    
    proxy1.eyeCount = 1;
    // expected output: "Monsters must have an even number of eyes"
    
    console.log(proxy1.eyeCount);
    // expected output: 4
    
    proxy1.eyeCount = 2;
    console.log(proxy1.eyeCount);
    // expected output: 2
    

    handler.deleteProperty()

    delete操作符的捕捉器。

    **delete 操作符****用于删除对象的某个属性;如果没有指向这个属性的引用,那它最终会被释放。**

    如果目标对象的属性是不可配置的,那么该属性不能被删除。

    var p = new Proxy({}, {
      deleteProperty: function(target, prop) {
        console.log('called: ' + prop);
        return true;
      }
    });
    
    delete p.a; // "called: a"

    handler.ownKeys()

    Object.getOwnPropertyNames方法和 Object.getOwnPropertySymbols方法的捕捉器。

    Object.getOwnPropertyNames()**方法返回一个由指定对象的所有自身属性的属性名(包括不可枚举属性但不包括Symbol值作为名称的属性)组成的数组。**

    var arr = ["a", "b", "c"];
    console.log(Object.getOwnPropertyNames(arr).sort()); // ["0", "1", "2", "length"]
    
    // 类数组对象
    var obj = { 0: "a", 1: "b", 2: "c"};
    console.log(Object.getOwnPropertyNames(obj).sort()); // ["0", "1", "2"]

    Object.getOwnPropertySymbols() 方法返回一个给定对象自身的所有 Symbol 属性的数组。(常规开发涉及较少,这里就不做讲解了)

    约束

    ownKeys 的结果必须是一个数组.
    数组的元素类型要么是一个 String ,要么是一个 Symbol.
    结果列表必须包含目标对象的所有不可配置(non-configurable )、自有(own)属性的key.
    如果目标对象不可扩展,那么结果列表必须包含目标对象的所有自有(own)属性的key,不能有其它值.
    var p = new Proxy({}, {
      ownKeys: function(target) {
        console.log('called');
        return ['a', 'b', 'c'];
      }
    });
    
    console.log(Object.getOwnPropertyNames(p)); // "called"
                                                // [ 'a', 'b', 'c' ]
    var obj = {};
    Object.defineProperty(obj, 'a', {
      configurable: false,
      enumerable: true,
      value: 10 }
    );
    
    var p = new Proxy(obj, {
      ownKeys: function(target) {
        return [123, 12.5, true, false, undefined, null, {}, []];
      }
    });
    
    console.log(Object.getOwnPropertyNames(p));
    
    // TypeError: proxy [[OwnPropertyKeys]] 必须返回一个数组
    // 数组元素类型只能是String或Symbol

    handler.apply()

    函数调用操作的捕捉器。

    handler.apply** 方法用于拦截函数的调用。**

    var p = new Proxy(function() {}, {
      apply: function(target, thisArg, argumentsList) {
        console.log('called: ' + argumentsList.join(', '));
        return argumentsList[0] + argumentsList[1] + argumentsList[2];
      }
    });
    
    console.log(p(1, 2, 3)); // "called: 1, 2, 3"
                             // 6

    handler.construct()

    new操作符的捕捉器。

    handler.construct() 方法用于拦截new 操作符. 为了使new操作符在生成的Proxy对象上生效,用于初始化代理的目标对象自身必须具有[[Construct]]内部方法(即 new target 必须是有效的)。

    注意,必须返回一个对象

    var p = new Proxy(function() {}, {
      construct: function(target, argumentsList, newTarget) {
        console.log('called: ' + argumentsList.join(', '));
        return { value: argumentsList[0] * 10 };
      }
    });
    
    console.log(new p(1).value); // "called: 1"
                                 // 10

    下面的代码违反了约定.

    var p = new Proxy(function() {}, {
      construct: function(target, argumentsList, newTarget) {
        return 1;
      }
    });
    
    new p(); // TypeError is thrown

    下面的代码未能正确的初始化Proxy。Proxy初始化时,传给它的target 必须具有一个有效的constructor供new操作符调用。

    var p = new Proxy({}, {
      construct: function(target, argumentsList, newTarget) {
        return {};
      }
    });
    
    new p(); // TypeError is thrown, "p" is not a constructor

    结语

    那么到这里你已经了解Proxy的handle函数以及操作方法,如果有不明白的可以先放一放,了解如何使用就可以了,以后在使用的过程中可以慢慢查阅。

    祝大家有一个愉快的油猴编程之旅

    混的人。
    ------------------------------------------
    進撃!永遠の帝国の破壊虎---李恒道

    入驻了爱发电https://afdian.net/a/lihengdao666
    个人宣言:この世界で私に胜てる人とコードはまだ生まれていません。死ぬのが怖くなければ来てください。
  • TA的每日心情
    慵懒
    2023-1-14 20:47
  • 签到天数: 6 天

    [LV.2]偶尔看看I

    0

    主题

    1

    回帖

    5

    积分

    助理工程师

    积分
    5
    发表于 2022-11-27 20:28:30 | 显示全部楼层
    苦涩难懂,不能精简下,不要超过10行。
    回复

    使用道具 举报

  • TA的每日心情
    开心
    2023-2-28 23:59
  • 签到天数: 191 天

    [LV.7]常住居民III

    632

    主题

    5170

    回帖

    6048

    积分

    管理员

    非物质文化遗产社会摇传承人

    积分
    6048

    荣誉开发者管理员油中2周年生态建设者喜迎中秋

    发表于 2022-11-27 20:31:04 | 显示全部楼层
    viewtheard 发表于 2022-11-27 20:28
    苦涩难懂,不能精简下,不要超过10行。

    越到后边越复杂的
    proxy相比可能算入门级的了....
    混的人。
    ------------------------------------------
    進撃!永遠の帝国の破壊虎---李恒道

    入驻了爱发电https://afdian.net/a/lihengdao666
    个人宣言:この世界で私に胜てる人とコードはまだ生まれていません。死ぬのが怖くなければ来てください。
    回复

    使用道具 举报

    发表回复

    本版积分规则

    快速回复 返回顶部 返回列表