ES6(三)基于Proxy的数据代理

1、 Proxy 作用

Proxy 对象用于定义基本操作的自定义行为(如属性查找、赋值、枚举、函数调用等)。

2、defineProperty 默认值问题

let proxy = new Proxy(target, handler);
  • target 是用Proxy包装的被代理对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)
  • handler 是一个对象,其声明了代理target 的一些操作,其属性是当执行一个操作时定义代理的行为的函数。

3、handler 对象的方法

1、get 方法用来处理获取数据时的劫持行为;
2、set 方法用来处理设置数据时的劫持行为;
3、has 方法用来处理在判断是否有该属性时的劫持行为 ,return true 存在该属性,false 不存在该属性
4、apply 方法用来代理函数的执行,要求 target 必须是一个函数,在函数执行的时候做一个拦截
5、construct 方法用于拦截 new 操作符.
6、defineProperty 方法用于拦截 defineProperty 操作 return Object.defineProperty
7、deleteProperty 用于拦截对象属性的删除操作
8、getOwnPropertyDescriptor 方法用于拦截 getOwnPropertyDescriptor 操作
getOwnPropertyDescriptor 必须返回一个 object 或 undefined

   Object.getOwnPropertyDescriptor() 方法返回指定对象上一个自有属性对应的属性描述符

9、getPrototypeOf 用于拦截对象调用 getPrototypeOf 方法

 Object.getPrototypeOf 查找对象的原型方法

10、setPrototypeOf 方法主要用来拦截 Object.setPrototypeOf().

 Object.setPrototypeOf 设置对象的原型方法

11、isExtensible 用于拦截对象的isExtensible方法
12、preventExtensions 用于拦截 Object.preventExtensions
13、ownKeys 会拦截一下操作:
Object.keys()

let data = {
    
    
    name: "mt",
    age: 18,
    price: 5000
};
let proxyData = new Proxy(data,{
    
    
    get(target,key){
    
     // 获取的时候拦截
        //console.log(arg);
        if(key == "price"){
    
    
            return target[key]*.9;
        }
        return target[key];
    },
    set(target,key,newVal){
    
      // 设置的时候拦截
        // if(key == "price"){
    
    
        //     if(newVal > target[key]){
    
    
        //         throw "败家媳妇给家里余钱吧";
        //     }
        // }
        // if(key == "price"){
    
    
        //     if(isNaN(newVal)){
    
    
        //         throw "对不起请给现金"
        //     }
        // }
        target[key] = newVal;
    },
    has(target,key){
    
     //判断某个值存不存
        //console.log(target,key);
        if(key == "gf"){
    
    
            return true;
        }
        return (key in target);
    }
});
console.log(proxyData);
// console.log(proxyData.price);
// console.log(proxyData.age);
/*
  Proxy.get 在对数据进行获取操作的时候,进行拦截 
*/
// proxyData.price = 600;
// proxyData.price = 700;
//console.log("gf" in proxyData);
function fn(){
    
    
    console.log(this,111,arguments);
}
// fn.apply(1);

fn = new Proxy(fn,{
    
    
    apply(target,thisArg,...arg){
    
    
        //console.log(target,thisArg,...arg);
        //throw "该函数是一个类,不能直接调用";
        //console.log(thisArg);
        if(typeof thisArg !== "object"){
    
    
            throw "该函数只支持事件和对象的方法调用,请勿直接调用";
        }
        target.apply(thisArg,arg);
        //target(arg);
    }
});

// apply 在函数执行的时候,进行一个拦截, (通过 new 调用这个函数,不会触发 apply 的代理)
//document.onclick = fn;
fn.call(document,"a","b","c");
// new fn;
//fn();
function Person(name,age){
    
    
    //console.log(this,111,arguments);
    this.name = name;
    this.age = age;
}

Person = new Proxy(Person,{
    
    
    apply(){
    
    
        throw "Person是一个类,请勿直接调用";
    },
    construct(target,arg){
    
    
        return new target(...arg); // construct 一定要返回一个对象
    }
});
//console.log(Person);
//Person.call(document,"a","b","c");
let p = new Person("mt",18);
let data = {
    
    
    name: "mt",
    age: 18,
    price: 5000
};
let proxyData = new Proxy(data,{
    
    
    get(target,key){
    
    
        if(key == "price"){
    
    
            return target[key]*.9;
        }
        return target[key];
    },
    set(target,key,newVal){
    
     
        target[key] = newVal;
    },
    has(target,key){
    
    
        if(key == "gf"){
    
    
            return true;
        }
        return (key in target);
    },
    defineProperty(target,key,descriptor){
    
     // 当调用了 Object.defineProperty 时执行
        //console.log(target,key,descriptor);
        return Object.defineProperty(target,key,descriptor);
    }
});
Object.defineProperty(proxyData,"child",{
    
    
    configurable: true,
    enumerable: true,
    get(){
    
    
        return "不要关心人家家事"
    },
    set(val){
    
    
        console.log("这不是我的孩子",val);
    }
});
//proxyData.child = 10;
console.log(proxyData.child);

基于proxy的数据响应式

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
<div id="app">
    {
    
    {
    
     message }}
    <div>
        <p>姓名:{
    
    {
    
     name }},年龄: {
    
    {
    
    age}}</p>
        <p v-html="htmlData"></p>
        <input type="text" v-model="modelData" />
        <p>{
    
    {
    
    modelData}}</p>
    </div>
    就是一段纯文本
</div>     
<script>  
class Event {
    
     
    events = {
    
    } // 事件池记录所有的相关事件及处理函数
    on(eventName,fn){
    
    
        if(!this.events[eventName]){
    
    
            this.events[eventName] = [];
        }
        this.events[eventName].push(fn);
    }
    off(eventName,fn){
    
     // 删除一个事件处理 eventName 事件名称 fn 对应的处理函数
        if(!this.events[eventName]){
    
    
            return ;
        }
        this.events[eventName] = this.events[eventName].filter(item=>item!=fn);
    }
    /*
        dispatch 负责把触发到的事件给执行了
    */
    dispatch(eventName){
    
    
        if(!this.events[eventName]){
    
    
            return ;
        }
        this.events[eventName].forEach(item => {
    
    
            item.call(this);
        });
    }
}  
class KVue extends Event {
    
    
    constructor(option){
    
    
        super();
        this.$option = option;
        let el = document.querySelector(option.el);
        this.compileNode(el);  
        this.observe(option.data);
    }
    // 给数据添加数据劫持
    observe(data){
    
      
        let _this = this; 
        this.$option.data = new Proxy(data,{
    
    
            get(target,key){
    
    
               return target[key]; 
            },
            set(target,key,newVal){
    
      
                target[key] = newVal;
                _this.dispatch(key);
                return true;
            }
        });
    }
    // // 完成数据劫持,在数据修改时去触发视图的变化
    // dataProxy(data,key,value){
    
     
    //     let _this = this;
    //     Object.defineProperty(data,key,{
    
    
    //         configurable: true,
    //         enumerable: true,
    //         set(newVal){
    
    
    //             value = newVal;
    //             _this.dispatch(key);
    //             //console.log("数据已经修改了该触发视图的修改了",key);
    //         },
    //         get(){
    
    
    //             return value;
    //         }
    //     });
    // }
    // 根据当前元素的结构,将我们的数据编译进去
    compileNode(el){
    
    
        let child = el.childNodes; // 找到元素下的所有节点
        child.forEach(node => {
    
    
            if(node.nodeType == 1){
    
     // 如果该节点是元素节点
                let attrs = node.attributes;
                [...attrs].forEach(attr=>{
    
    
                    let attrName = attr.name;
                    if(attrName.indexOf("v-") == 0){
    
    
                        let attrVal = attr.value;
                        //console.log(attrName,attrVal);
                        if(attrName === "v-html" ){
    
     // 这是一个v-html指令,我们应该用数据替换该元素的内容
                            node.innerHTML = this.$option.data[attrVal];
                            this.on(attrVal,()=>{
    
    
                               // console.log(attrVal,"进行了修改");
                               node.innerHTML = this.$option.data[attrVal];
                            })
                        } else if(attrName == "v-model"){
    
     // 这是一个双向绑定指令
                            node.value = this.$option.data[attrVal];
                            this.on(attrVal,()=>{
    
    
                               // console.log(attrVal,"进行了修改");
                               console.log(1);
                               node.value = this.$option.data[attrVal];
                            })
                            // 监听视图发生了变化,同步修改我们的数据
                            node.addEventListener("input",({
    
    target})=>{
    
    
                                this.$option.data[attrVal] = target.value;
                            });
                        }
                    }
                });

               if(node.childNodes.length > 0){
    
     // 如果该元素还有子元素继续想要查找  
                    this.compileNode(node);  
               }
            } else if(node.nodeType == 3){
    
     // 如果该节点是文本节点
                // console.log(node);
                //console.dir(node);
                let startContent = node.textContent;
                let reg = /\{\{\s*(\S+)\s*\}\}/g;
                //console.log(reg.test(startContent),startContent);
                if(reg.test(startContent)){
    
    
                    node.textContent = startContent.replace(reg,(...arg)=>{
    
    
                        //console.log(arg[1]);
                        this.on(arg[1],()=>{
    
    
                            node.textContent = startContent.replace(reg,(...arg)=>{
    
    
                                return this.$option.data[arg[1]];
                            });
                        })
                        return this.$option.data[arg[1]];
                    });
                }
            }
        });
    }
}  
let kvue = new KVue({
    
    
    el: "#app",
    data: {
    
    
       message: "Hello KKB",
       modelData: "呵呵",
       name: "kkb",
       age: 8,
       htmlData: "<strong>圣诞节要陪我一起过吗</strong>"
    }
});
/*
    数据响应式: 
        监听数据发生变化,如果数据发生了变化,就同步视图进行改变
        实现原理:
            1. 编译模板,找出需要插入数据的位置,把数据插入进去
            2. 利用数据劫持,监听数据发生改变,如果数据发生改变,则找到对应的插入数据的位置,修改视图
    数据和视图双向绑定:
            1. 利用数据响应式监听数据发生改变,然后同步视图
            2. 利用 change 或 input 等事件监听视图发生改变,然后修改数据

    !!! 最起码面试之前,一定找出来,在回顾一遍
*/
</script>    
</body>
</html>

猜你喜欢

转载自blog.csdn.net/literarygirl/article/details/106457144