ES6 高阶之模仿vue

vue的简单使用

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="app">
    {{message}}
    <input v-model="modelData" />{{modelData}}
</div>
<script>
    /* 
        1.引入cdn
        2.el是挂载点,就是所有vue指令、表达式都是需要在挂载点中使用
        3.{{}} -> 表达式
        4.v-model -> 指令,双向绑定;

        问:
        vue的表达式是如何实现的呢?
        你问过vue的双向绑定,如何实现的呢?
    */
    let vm = new Vue({
        el:"#app",
        data:{
            message:"测试数据",
            modelData:"双绑数据"
        }
    });
</script>
</body>
</html>

模拟vue表达式的实现

/* 
    表达式的实现:
    一、初次渲染
        1.在作用域只能找到表达式
        2.把message拿到,和数据进行查找,填充到表达式的位置
    二、数据的响应
        数据在更改的时候,视图也跟着更改
    代码实现:
        1.拿到数据,渲染到表达式里面
        正则匹配到表达式,去数据里找表达式的数据进行替换
            complle();//渲染视图函数
                1.找到el(节点);
                2.找到子节点
                    childNodes -> 伪数组
                3.如何找到里面的内容呢?
                    转化伪数组 循环找每一个的 nodeType;
                    进行判断分离 -> 文本节点(1)与元素节点(3);
                4.得到节点的值
                    node.textContent -> dfasdf{{message}}sadfasd
                5.使用正则找到节点内容中的表达式
                        /\{\{\s*(\S+)\s*\}\}/g;
                    匹配得到的值 是否 包含正则;
                        if(reg.test(textContent)){}
                    拿到正则匹配的内容 分组()
                        let $1 = RegExp.$1;
                6.替换文本的值
                    1.得到数据
                        this._data[$1];
                    2.替换
                        node.textContent = textContent.replace(reg,this._data[$1]);
                    3.修正正则bug
                        /\{\{\s*([^\s\{\}]+)\s*\}\}/g;
                7.元素节点的渲染 -> 递归compileNode
        思维总结:先找到作用域节点里面所有节点,区分文本节点和元素节点,判断文本节点有没有表达式(正则匹配),有表达式就找到下标内容,内容(message)对象的值,替换到表达式中;元素节点就重复一遍(递归);
*/
class Vue{
    constructor(options){
        this.options = options;//传进来的参数
        this._data = this.options.data;//模范vue的_data
        this.complle();
    }
    // 渲染视图
    complle(){
        let ele = document.querySelector(this.options.el);//大节点
        this.compileNode(ele);
    }
    compileNode(ele){
        let childNodes = ele.childNodes;//得到所有子节点
        [...childNodes].forEach(node=>{
            if(node.nodeType === 3){//文本节点;
                let textContent = node.textContent; //dfasdf{{message}}sadfasd
                let reg = /\{\{\s*([^\s\{\}]+)\s*\}\}/g;
                if(reg.test(textContent)){//判断是否有正则匹配内容 -> test();
                    let $1 = RegExp.$1;//message
                    node.textContent = textContent.replace(reg,this._data[$1]);
                }
            }else if(node.nodeType === 1){//元素节点;
                if(node.childNodes.length>0){
                    this.compileNode(node);
                }
            }
        })
    }
}

模仿vue数据改变视图也改变

数据劫持defineProperty

{
    /* 
        参数:
            对象(空对象|对象)、key(对象名称)、配置
        一些配置:
            get(){} : 当访问的时候触发
            set(newValue){} : 当修改的时候触发 -> newValue = 修改后的值
            configurable:true : 配置
            enumerable:true : for in
    */
    // 新建defineProperty()属性
    let obj = Object.defineProperty({},"name",{
        configurable:true,//是否可以配置 -> 默认不可以配置 -> 不能执行删除操作
        enumerable:true,//for in等一些操作 -> 默认不可操作
        get(){
            console.log("get...");
            return "张三";
            // get 里面需要return;
        },
        set(newValue){
            console.log("set... -> ",newValue);
        }
    });
    // obj.name;
    // obj.name = "李四";

    // 默认不能配置
    // delete obj['name'];
    // console.log(obj);

    // 默认不可for in
    // for(let i in obj){
    //     console.log(obj[i]);
    // }
}
{
    // 修改defineProperty属性
    let obj = {
        name:'张三',
        get:'李四'
    }
    // 可以为它设置
    Object.defineProperty(obj,"name",{
        configurable:true,
        enumerable:true,
        get(){
            console.log("get...");
            return "李四";
        },
        set(newValue){
            console.log("set... -> ",newValue);
        }
    });
    console.log(obj);
    // 缺点,当有多个的时候 需要进行循环;
}
{
    /* 
        es6 Proxy
        不需要进行循环
        参数:
            1.obj
            2.配置
    */
    let obj = {
        name:"张三",
        age:20
    }
    let newObj = new Proxy(obj,{
        get(traget,key){
            console.log("get...");
            return traget[key];
        },
        set(traget,key,newValue){
            console.log("set...->",newValue);
        }
    });
    newObj.name = "lisi";
}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="./myvue.js"></script>
</head>
<body>
<div id="app">
    dfasdf{{message}}s{{message}}adfasd
    <div>
        {{message}}asdfadf
        <div></div>
    </div>
    <input v-model="myData" type="text">{{myData}}
</div>
<script>
let vm = new Vue({
    el:"#app",
    data:{
        message:"测试数据",
        myData:"我的数据"
    }
});
console.log(vm._data);
vm._data.message = 2;
</script>
</body>
</html>
/* 
    表达式的实现:
    一、初次渲染
        1.在作用域只能找到表达式
        2.把message拿到,和数据进行查找,填充到表达式的位置
    二、数据的响应
        数据在更改的时候,视图也跟着更改
    代码实现:
        1.拿到数据,渲染到表达式里面
        正则匹配到表达式,去数据里找表达式的数据进行替换
            complle();//渲染视图函数
                1.找到el(节点);
                2.找到子节点
                    childNodes -> 伪数组
                3.如何找到里面的内容呢?
                    转化伪数组 循环找每一个的 nodeType;
                    进行判断分离 -> 文本节点(1)与元素节点(3);
                4.得到节点的值
                    node.textContent -> dfasdf{{message}}sadfasd
                5.使用正则找到节点内容中的表达式
                        /\{\{\s*(\S+)\s*\}\}/g;
                    匹配得到的值 是否 包含正则;
                        if(reg.test(textContent)){}
                    拿到正则匹配的内容 分组()
                        let $1 = RegExp.$1;
                6.替换文本的值
                    1.得到数据
                        this._data[$1];
                    2.替换
                        node.textContent = textContent.replace(reg,this._data[$1]);
                    3.修正正则bug
                        /\{\{\s*([^\s\{\}]+)\s*\}\}/g;
                7.元素节点的渲染 -> 递归compileNode
        思维总结:先找到作用域节点里面所有节点,区分文本节点和元素节点,判断文本节点有没有表达式(正则匹配),有表达式就找到下标内容,内容(message)对象的值,替换到表达式中;元素节点就重复一遍(递归);
*/
/* class Vue{
    constructor(options){
        this.options = options;//传进来的参数
        this._data = this.options.data;//模范vue的_data
        this.complle();
    }
    // 渲染视图
    complle(){
        let ele = document.querySelector(this.options.el);//大节点
        this.compileNode(ele);
    }
    compileNode(ele){
        let childNodes = ele.childNodes;//得到所有子节点
        [...childNodes].forEach(node=>{
            if(node.nodeType === 3){//文本节点;
                let textContent = node.textContent; //dfasdf{{message}}sadfasd
                let reg = /\{\{\s*([^\s\{\}]+)\s*\}\}/g;
                if(reg.test(textContent)){//判断是否有正则匹配内容 -> test();
                    let $1 = RegExp.$1;//message
                    node.textContent = textContent.replace(reg,this._data[$1]);
                }
            }else if(node.nodeType === 1){//元素节点;
                if(node.childNodes.length>0){
                    this.compileNode(node);
                }
            }
        })
    }
} */

/* 
    劫持数据:
    1.为_data添加get()和set(); -> observer()
    2.for in循环每一个key有define...
        注意get的return;
    3.当数据修改渲染视图:
        一、把设置的值改到视图中 -> 自定义事件
            1.继承自定义事件
            2.通过自定义事件传新值 视图渲染;
            3.使用正则替换内容
                let newValue = e.detail;
                let oldValue = this._data[$1];
                let reg = new RegExp(oldValue,"g");
                node.textContent = node.textContent.replace(reg,newValue);


class Vue extends EventTarget {
    constructor(options) {
        super();
        this.options = options;//传进来的参数
        this._data = this.options.data;//模范vue的_data
        this.observer(this._data);
        this.complle();
    }
    observer(data) {
        for (let key in data) {
            let value = data[key];
            let _this = this;
            Object.defineProperty(data, key, {
                configurable: true,
                enumerable: true,
                get() {
                    return value;
                },
                set(newValue) {
                    // console.log("set... -> ",newValue);
                    let event = new CustomEvent(key, {
                        detail: newValue
                    });
                    _this.dispatchEvent(event);
                    value = newValue;
                }
            })
        }
    }
    // 渲染视图
    complle() {
        let ele = document.querySelector(this.options.el);//大节点
        this.compileNode(ele);
    }
    compileNode(ele) {
        let childNodes = ele.childNodes;//得到所有子节点
        [...childNodes].forEach(node => {
            if (node.nodeType === 3) {//文本节点;
                let textContent = node.textContent; //dfasdf{{message}}sadfasd
                let reg = /\{\{\s*([^\s\{\}]+)\s*\}\}/g;
                if (reg.test(textContent)) {//判断是否有正则匹配内容 -> test();
                    let $1 = RegExp.$1;//message
                    node.textContent = textContent.replace(reg, this._data[$1]);

                    {
                        this.addEventListener($1, e => {
                            let newValue = e.detail;
                            let oldValue = this._data[$1];
                            let reg = new RegExp(oldValue,"g");
                            node.textContent = node.textContent.replace(reg,newValue);
                        });
                    }
                }
            } else if (node.nodeType === 1) {//元素节点;
                if (node.childNodes.length > 0) {
                    this.compileNode(node);
                }
            }
        })
    }
}
*/

/* 
    发布订阅模式:

class Vue extends EventTarget {
    constructor(options) {
        super();
        this.options = options;//传进来的参数
        this._data = this.options.data;//模范vue的_data
        this.observer(this._data);
        this.complle();
    }
    observer(data) {
        for (let key in data) {
            let value = data[key];
            // 实例化收集器:
            let dep = new Dep();
            Object.defineProperty(data, key, {
                configurable: true,
                enumerable: true,
                get() {
                    if (Dep.traget) {
                        // 触发的时候收集
                        dep.addSub(Dep.traget);//-> 等同于把实例化对象给了收集器
                    } 
                    return value;
                },
                set(newValue) {
                    // 当值修改的时候执行'通知'函数
                    dep.notifi(newValue);
                    value = newValue;
                }
            })
        }
    }
    // 渲染视图
    complle() {
        let ele = document.querySelector(this.options.el);//大节点
        this.compileNode(ele);
    }
    compileNode(ele) {
        let childNodes = ele.childNodes;//得到所有子节点
        [...childNodes].forEach(node => {
            if (node.nodeType === 3) {//文本节点;
                let textContent = node.textContent; //dfasdf{{message}}sadfasd
                let reg = /\{\{\s*([^\s\{\}]+)\s*\}\}/g;
                if (reg.test(textContent)) {//判断是否有正则匹配内容 -> test();
                    let $1 = RegExp.$1;//message
                    node.textContent = textContent.replace(reg, this._data[$1]);

                    new Watcher(this._data,$1,(newValue) => {
                        console.log(newValue);
                        // 可以渲染视图了!
                        let oldValue = this._data[$1];
                        let reg = new RegExp(oldValue,"g");
                        node.textContent = node.textContent.replace(reg,newValue);
                    });
                }
            } else if (node.nodeType === 1) {//元素节点;
                if (node.childNodes.length > 0) {
                    this.compileNode(node);
                }
            }
        })
    }
}

// 收集器
class Dep {
    constructor() {
        this.subs = [];//所有订阅者都放在这里面
    }
    addSub(sub) {//有一个订阅者过来就执行一下
        this.subs.push(sub);
    }
    // 通知; -> 通知每一个订阅者做一些事情
    notifi(newValue) {//传值
        // 触发update
        this.subs.forEach(sub => {
            sub.update(newValue);
        });
    }
}

// 订阅者
class Watcher {//什么时候实例呢? 属性有多少就实例多少 -> message myData...
    constructor(data,key,cb) {
        this.cb = cb;
        Dep.traget = this;//静态属性;
        // 等同于触发了get();
        data[key];
        Dep.traget = null;
    }
    update(newValue) {
        // console.log("更新了..?");
        // console.log(newValue);
        this.cb(newValue);
    }
}
*/

/* 
    双向绑定
        1.元素节点中处理:
        2.找指令
            循环attrs 判断v-开头的
            let attrs = node.attributes;
            [...attrs].forEach(attr=>{
                let attrName = attr.name;
                let attrValue = attr.value;
                if(attrName.indexOf("v-")==0){
                    
                }
            })
*/
class Vue extends EventTarget {
    constructor(options) {
        super();
        this.options = options;//传进来的参数
        this._data = this.options.data;//模范vue的_data
        this.observer(this._data);
        this.complle();
    }
    observer(data) {
        for (let key in data) {
            let value = data[key];
            // 实例化收集器:
            let dep = new Dep();
            Object.defineProperty(data, key, {
                configurable: true,
                enumerable: true,
                get() {
                    if (Dep.traget) {
                        // 触发的时候收集
                        dep.addSub(Dep.traget);//-> 等同于把实例化对象给了收集器
                    } 
                    return value;
                },
                set(newValue) {
                    // 当值修改的时候执行'通知'函数
                    dep.notifi(newValue);
                    value = newValue;
                }
            })
        }
    }
    // 渲染视图
    complle() {
        let ele = document.querySelector(this.options.el);//大节点
        this.compileNode(ele);
    }
    compileNode(ele) {
        let childNodes = ele.childNodes;//得到所有子节点
        [...childNodes].forEach(node => {
            if (node.nodeType === 3) {//文本节点;
                let textContent = node.textContent; //dfasdf{{message}}sadfasd
                let reg = /\{\{\s*([^\s\{\}]+)\s*\}\}/g;
                if (reg.test(textContent)) {//判断是否有正则匹配内容 -> test();
                    let $1 = RegExp.$1;//message
                    node.textContent = textContent.replace(reg, this._data[$1]);

                    new Watcher(this._data,$1,(newValue) => {
                        // 可以渲染视图了!
                        let oldValue = this._data[$1];
                        let reg = new RegExp(oldValue,"g");
                        node.textContent = node.textContent.replace(reg,newValue);
                    });
                }
            } else if (node.nodeType === 1) {//元素节点;
                let attrs = node.attributes;
                [...attrs].forEach(attr=>{
                    let attrName = attr.name;
                    let attrValue = attr.value;
                    if(attrName.indexOf("v-")==0){
                        attrName = attrName.substr(2);//取消 v-;
                        if(attrName === "model"){
                            node.value = this._data[attrValue];
                            // 监听input事件
                            node.addEventListener("input",e=>{
                                // console.log(e.target.value);
                                let value = e.target.value;
                                this._data[attrValue] = value;
                            })
                        }
                    }
                })
                if (node.childNodes.length > 0) {
                    this.compileNode(node);
                }
            }
        })
    }
}

// 收集器
class Dep {
    constructor() {
        this.subs = [];//所有订阅者都放在这里面
    }
    addSub(sub) {//有一个订阅者过来就执行一下
        this.subs.push(sub);
    }
    // 通知; -> 通知每一个订阅者做一些事情
    notifi(newValue) {//传值
        // 触发update
        this.subs.forEach(sub => {
            sub.update(newValue);
        });
    }
}

// 订阅者
class Watcher {//什么时候实例呢? 属性有多少就实例多少 -> message myData...
    constructor(data,key,cb) {
        this.cb = cb;
        Dep.traget = this;//静态属性;
        // 等同于触发了get();
        data[key];
        Dep.traget = null;
    }
    update(newValue) {
        // console.log("更新了..?");
        // console.log(newValue);
        this.cb(newValue);
    }
}

/* 
    总结:
        表达式是如何实现的?
            1.通过正则找到花括号的表达式,渲染到视图
            2.数据更新视图也更新: 通过数据劫持get和set,更新数据渲染到视图;
            当然这两步也包含了很多知识点:正则,元素节点...
        双向绑定是如何实现的?
            1.通过元素节点判断 v- 开头
            2.当取消v-的时候等于双向绑定的指令时,将数据渲染到input.value中,监听 input 的改动;修改数据;数据修改后视图自动渲染;
*/

猜你喜欢

转载自www.cnblogs.com/Afanadmin/p/12401946.html