Proxy代理

530 阅读2分钟

Proxy

扩展(增强)对象一些功能,是一种设计模式,一种代理模式

语法:

const p=new Proxy(target,handler);

target: 被代理的对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。)

handler: 对代理对象做的什么操作

handler={
    set(){},
    get(){},
    deleteProperty(){},
    has(){},
    apply(){},
    ...
}

作用: 比如vue拦截、预警、上报、扩展功能、统计、增强对象等等

  • set()设置拦截
let obj = new Proxy({}, {
      set(target, prop, value) {
        console.log(target, prop, value);
        if (prop == 'age') {
          if (Number.isIntege(value)) {
            throw new TypeError('年龄必须是整数');
          }

          if (value > 200) {
            throw new RangeError('...');
          }
        }

        target[prop] = value;
        
        // 或者使用Reflectfan射
        //   return Reflect.set(...arguments);
      }
    })


    obj.a = 123;
    obj.name = 'strive';
  • deleteProperty() 删除属性
let json={a:1,b:2};
let newObj=new Proxy(json,{
    deleteProperty(target,property){
        delete target[property]
    }
})

delete newObj.a

注意:如果代码报错

[Vue warn]: Error in created hook: "TypeError: 'deleteProperty' on proxy: trap returned falsish for property 'a'"

TypeError: 'deleteProperty' on proxy: trap returned falsish for property 'a'

解决方法:

MDN上明确的说明了set方法应该返回一个布尔值。 返回true表示赋值成功。如果set方法返回false,并且分配发生在严格模式代码中,则会引发TypeError。

deleteProperty(target, property) {
    if (prop in target){
      delete target[prop]
      console.log('property removed: ' + prop)
      return true
    }
    else {
      console.log('property not found: ' + prop)
      return false
    }
  }
  • has()
has(target,property){
    return property in target;
}
  • apply()
function fn(){
    return `我是函数`;
}

let newFn=new Proxy(fn,{
    apply(){
        return '函数';
    }
})

newFn(); // 函数

示例:

  1. 需求:访问之前操作
let obj={name:'strive'};
let newObj=new Proxy(obj,{
    get(target,property){
        console.log(target.property);
        return target[property];
    }
})

newObj.name
  1. 需求:实现访问一个对象身上的属性,默认不存在的时候给了undefined,希望如果不存在输出错误(警告)信息
let obj={name:'strive'};
let newObj=new Proxy(obj,{
    get(target,property){
        if(property in target){
            return property[target];
        }else{
            console.warn() // 打印错误
            throw new ReferenceError(`${property}属性上不存在此对象`)
        }
    }
})

newObj.name
newObj.age

  1. 类似react DOM
    const DOM = new Proxy({}, {
     get(target, property) {

       console.log(target, property);
       return function (attr = {}, ...children) {

         const el = document.createElement(property);
         // 添加属性
         for (let child of children) {
           if (typeof child == 'string') {
             child = document.createTextNode(child);
           }

           el.appendChild(child)
         }

         console.log(el);

         return el;
       }
     }
   })


   let oDiv = DOM.div({
       id: 'div1',
       class: 'aaa'
     },
     '我是div', 'hello', DOM.a({
       href: ''
     }, '访问地址'))
   document.body.append(oDiv)
  1. new vue()
 <div id="box">
    {{msg}}
    <div>{{name}}</div>
    <input type="text" v-model="inputVal">{{inputVal}}
  </div>
    class vue extends EventTarget {
      constructor(option) {
        super();
        this.option = option;
        this._data = this.option.data;
        this.el = document.querySelector(this.option.el);
        this.observe(this._data);
        this.complileNode(this.el);
      }
      // 监听数据
      observe(data) {
        let _this = this;
        this._data = new Proxy(data, {
          set(target, prop, newVal) {
            let event = new CustomEvent(prop, {
              detail: newVal
            }); //创建自定义事件
            _this.dispatchEvent(event); // 分发事件
            console.log(newVal)
            return Reflect.set(...arguments);
          }
        })
      }

      complileNode(el) {
        let child = el.childNodes;
        [...child].forEach(node => {
          if (node.nodeType === 3) { // 文本节点
            let text = node.textContent;
            let reg = /\{\{\s*([^\s\{\}]+)\s*\}\}/;
            if (reg.test(text)) { //匹配{{msg}}{{name}}并替换,赋值
              let $1 = RegExp.$1;
              console.log($1);

              console.log(this._data[$1])
              this._data[$1] && (node.textContent = text.replace(reg, this._data[$1]));
              // 监听事件
              this.addEventListener($1, e => {
                node.textContent = text.replace(reg, e.detail);
              })

            }
          } else if (node.nodeType === 1) { // 元素节点
            let attributes = node.attributes;
            if (attributes.hasOwnProperty('v-model')) {
              let keyName = attributes["v-model"].nodeValue;
              node.value = this._data[keyName];
              node.addEventListener('input', e => {
                this._data[keyName] = node.value;
              })
            }
            console.log(attributes);


            this.complileNode(node);
          }
        })
      }
    }

    let vm = new vue({
      el: '#box',
      data: {
        name: 'lili',
        msg: '测试数据',
        inputVal: "hello word"
      }
    })