ES6(二)数据响应式

一、数据劫持

1. 使用setter getter 方式实现数据劫持
  • getter 是一种获得属性值的方法,负责查询值,它不带任何参数
  • setter是一种设置属性值的方法,setter负责设置键值,值是以参数的形式传递
let obj = {
    
    
    $name: "kkb",
    get name(){
    
     // 获取 name 属性时触发
        //console.log("想要获取name属性");
        return this.$name;// 想要让 obj 在获取name 属性时,拿到的值
    },
    set name(newVal){
    
     // 设置 name 属性时 触发
        console.log(newVal); // 给 name 设置的新值
        this.$name = newVal;
    }
};
//console.log(obj.name);
obj.name = "开课吧集团";
// console.log(obj.name);
// console.log(obj);
//通俗的讲 get  如果想要在获取对象的属性的时候做一些别的操作,set 如果想要在设置对象的属性的时候做一些别的操作
2.利用 defineProperty 实现数据劫持

语法:

Object.defineProperty(obj, prop, descriptor)

参数:

  • obj 要在其上定义属性的对象。
  • prop 要定义或修改的属性的名称。
  • descriptor 将被定义或修改的属性描述符。

数据描述符

  • configurable 当且仅当该属性的 configurable 为 true时,该属性描述符才能够被改变,同时该属性也能从对应的对象上被删除。默认为 true。
  • enumerable 当且仅当该属性的enumerable为true时,该属性才能够出现在对象的枚举属性中。默认为 true。。
  • value 该属性对应的值。可以是任何有效的 JavaScript 值(数值,对象,函数等)。默认为 undefined。
  • writable 当且仅当该属性的writable为true时,value才能被赋值运算符改变。默认为 true。

存取描述符

  • get 一个给属性提供 getter 的方法,如果没有 getter 则为undefined。当访问该属性时,该方法会被执行,方法执行时没有参数传入,但是会传入this对象(由于继承关系,这里的this并不一定是定义该属性的对象)。默认为 undefined。
  • set 一个给属性提供 setter 的方法,如果没有 setter 则为undefined。当属性值修改时,触发执行该方法。该方法将接受唯一参数,即该属性新的参数值。默认为 undefined。
let obj = {
    
    
    name: "kkb",
    age: 8
};
Object.defineProperty(obj,"name",{
    
    
    configurable: true, //配置是否允许被删除 true(默认值) 可以被删除,false 不能被删除
    enumerable: true, //配置是否允许被枚举 true(默认值) 可以被枚举,false 不能被枚举
    set(newVal){
    
    
        console.log("这个想要设置的新值",newVal);
    },
    get(){
    
    
        return "你猜名字是啥";
    }
});
// for(let s in obj){
    
    
//     console.log(s);
// }
obj.name = "开课吧";
console.log(obj.name);
//console.log(obj);
2、MVVM框架中编译数据到视图
MVC 架构

MVC:前端的MVC与后端类似,具备着View,Controller和Model.
Model :负责保存应用数据,与后端数据同步
Controller:负责业务逻辑,根据用户行为对Model数据进行修改
View:负责视图展示,将model中的数据可视化出来。

MVVM架构

ViewModel通过实现一套数据响应式机制自动响应Model中的数据变化;
同时ViewModel会实现一套更新策略自动将数据变化转换为视图更新;
通过事件监听响应View中用户交互修改Model中数据。核心是双向时间绑定。
这样在ViewModel中就减少了大量的DOM操作代码。MVVM在保持View和Model低耦合的同时,还减少了维护它们关系的代码,是用户专注于业务逻辑,兼顾开发效率。

  • MVC,MVP和MVVM都是框架模式,它们设计的目标都是为了解决Model和View的耦合问题。
  • MVC模式出现较早主要应用在后端,如Spring MVC、ASP.NET MVC等,在前端领域的早期也有应用,如Backbone.js。它的优点是分层清晰,缺点是数据就混乱,灵活性带来的维护性问题。
  • MVP 模式是MVC的进化形式,Presenter作为中间层负责MV通信,解决了两者耦合的问题,但P层过于臃肿会导致维护问题。
  • MVVM 模式在前端领域有广泛的应用,它不仅解决MV耦合问题,还同时解决了维护两者映射关系的大量繁杂代码和DOM操作代码,在提高开发效率、可读性同时还保持了优越的性能表现。
数据响应式

数据响应式是指当数据改变后,会通知到使用该数据的代码。例如,视图渲染中使用了数据,数据改变后,视图也会自动更新。
数据响应式实现原理:

  1. 利用数据劫持 (defineProperty, Proxy) 监听对应数据的修改设置
  2. 利用观察者模式(或事件机制),给每一项数据添加相应的事件监听,当数据修改时触发该事件,然后驱动视图进行修改
    双向绑定:
  3. 利用数据响应式,完成数据对视图修改
  4. 添加 change 或 input 等事件,监听视图的修改,当视图改变时修改数据

模拟vue

<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 type="text/javascript">
	class KVue {
    
    
    constructor(option){
    
    
        this.$option = option;
        let el = document.querySelector(option.el);
        this.compileNode(el);  
        this.observe(this.$option.data);
    }
   // 给数据添加数据劫持
    observe(data){
    
    
        let keys = Object.keys(data);
        keys.forEach(key=>{
    
    
            this.dataProxy(data,key,data[key]);
        });
    }
    // 完成数据劫持,在数据修改时去触发视图的变化
     dataProxy(data,key,value){
    
    
        let _this = this;
        Object.defineProperty(data,key,{
    
    
            configurable: true,
            enumerable: true,
            get(){
    
    
                return value;
            },
            set(newVal){
    
    
                value = newVal;
                let event = new Event(key);
                event.value = newVal;
                _this.dispatchEvent(event);
            }
        });
    }
    // 根据当前元素的结构,将我们的数据编译进去
    compileNode(el){
    
    
        let child = el.childNodes; // 找到元素下的所有节点
        child.forEach(node => {
    
    
            if(node.nodeType == 1){
    
     // 如果该节点是元素节点
                let attrs = node.attributes;
                [...attrs].forEach(attr=>{
    
    //attrs 不是数组没有forEach方法,它是一个可迭代对象,通过结构成一个数组
                    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];
                        } else if(attrName == "v-model"){
    
     // 这是一个双向绑定指令
                            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]);
                        return this.$option.data[arg[1]];
                    });
                }
            }
        });
    }
}  
let kvue = new KVue({
    
    
    el: "#app",
    data: {
    
    
       message: "Hello KKB",
       modelData: "呵呵",
       name: "kkb",
       age: 8,
       htmlData: "<strong>圣诞节要陪我一起过吗</strong>"
    }
});
</script>
3、实现数据驱动视图更新
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 keys = Object.keys(data);
        keys.forEach(key=>{
    
    
            this.dataProxy(data,key,data[key]);
        });
    }
    // 完成数据劫持,在数据修改时去触发视图的变化
    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,"进行了修改");
                               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>"
    }
});
setTimeout(()=>{
    
    
    kvue.$option.data.htmlData = "Hello 开课吧";
},1000);
setInterval(()=>{
    
    
    kvue.$option.data.age++;
},1000);

使用js自带的事件机制 EventTarget来实现数据相应

class KVue extends EventTarget {
    
    
    constructor(option){
    
    
        super();
        this.$option = option;
        let el = document.querySelector(option.el);
        this.compileNode(el);  
        this.observe(option.data);
    }
    // 给数据添加数据劫持
    observe(data){
    
      
        let keys = Object.keys(data);
        keys.forEach(key=>{
    
    
            this.dataProxy(data,key,data[key]);
        });
    }
    // 完成数据劫持,在数据修改时去触发视图的变化
    dataProxy(data,key,value){
    
     
        let _this = this;
        Object.defineProperty(data,key,{
    
    
            configurable: true,
            enumerable: true,
            set(newVal){
    
    
                value = newVal;
                let event = new Event(key);
                event.name = key;
                // new Event(事件名称) // 新建一个事件对象
                _this.dispatchEvent(event);
                //_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];
                            node.addEventListener(attrVal,()=>{
    
    
                                node.innerHTML = this.$option.data[attrVal];
                            });
                        } else if(attrName == "v-model"){
    
     // 这是一个双向绑定指令
                            node.value = this.$option.data[attrVal];
                            this.addEventListener(attrVal,(e)=>{
    
    
                                console.log(e);
                                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)=>{
    
    
                        this.addEventListener(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>"
    }
});
setTimeout(()=>{
    
    
    kvue.$option.data.htmlData = "Hello 开课吧";
},1000);
setInterval(()=>{
    
    
    kvue.$option.data.age++;
},1000);
</script>    

猜你喜欢

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