基於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>
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章