>  기사  >  웹 프론트엔드  >  Vue의 MVVM 원리 및 구현 방법에 대한 자세한 설명

Vue의 MVVM 원리 및 구현 방법에 대한 자세한 설명

青灯夜游
青灯夜游앞으로
2020-07-15 16:44:062693검색

Vue의 MVVM 원리 및 구현 방법에 대한 자세한 설명

Vue에서 MVVM 원리 분석 및 구현

우선 Vue에 대해 어느 정도 이해하고 MVVM을 알아야 합니다. 이는 다음 원칙의 읽기, 학습 및 쓰기를 성공적으로 완료하는 데 도움이 될 것입니다.

1. Vue 데이터 양방향 바인딩 핵심 코드 모듈 및 구현 원리

2. 구독자-출판자 모델이 데이터를 구동하는 방식, 뷰가 데이터를 구동한 다음 뷰를 구동함

3. 요소 노드에 대한 지침을 구문 분석하고 구독자를 연결하여 보기 업데이트를 구현하는 방법

1. 아이디어 구성

구현을 위한 흐름 차트:

우리는 원한다 MVVM과 유사한 Vue 프레임워크의 간단한 버전은 다음 사항을 구현해야 합니다. Vue의 MVVM 원리 및 구현 방법에 대한 자세한 설명

1. 데이터 개체의 모든 속성을 모니터링하는 데이터 모니터링 관찰자를 구현하면 최신 값을 얻을 수 있습니다. 구독자에게 알리기 위해.

2. 페이지 노드 지침을 구문 분석하고 뷰를 초기화하는 파서 컴파일을 구현합니다.

3. 관찰자 감시자를 구현하고, 데이터 변경 사항을 구독하고 관련 업데이트 기능을 바인딩합니다. 그리고 관찰자 컬렉션 Dep에 자신을 넣으십시오. Dep은 Observer와 Watcher 사이의 가교 역할을 하며, 데이터 변경 사항이 Dep에 통보되고 Dep는 해당 Watcher에게 뷰를 업데이트하도록 알립니다.

2. 구현

다음은 ES6로 작성했는데 비교적 간결해서 300줄 정도의 코드로 간단한 MVVM 프레임워크를 구현했습니다

.

1. html 페이지 구현

Vue의 작성 방법에 따라 페이지에 일부 데이터 및 지침을 정의하고 JS 파일 두 개를 도입합니다. 먼저 MVue 개체를 인스턴스화하고 el, 데이터 및 메서드 매개 변수를 전달합니다. 나중에 살펴보겠습니다. Mvue.js 파일이 무엇인가요?

html

<body>
  <div id="app">
    <h2>{{person.name}} --- {{person.age}}</h2>
    <h3>{{person.fav}}</h3>
    <h3>{{person.a.b}}</h3>
    <ul>
      <li>1</li>
      <li>2</li>
      <li>3</li>
    </ul>
    <h3>{{msg}}</h3>
    <div v-text="msg"></div>
    <div v-text="person.fav"></div>
    <div v-html="htmlStr"></div>
    <input type="text" v-model="msg">
    <button v-on:click="click111">按钮on</button>
    <button @click="click111">按钮@</button>
  </div>
  <script src="./MVue.js"></script>
  <script src="./Observer.js"></script>
  <script>
    let vm = new MVue({
      el: &#39;#app&#39;,
      data: {
        person: {
          name: &#39;星哥&#39;,
          age: 18,
          fav: &#39;姑娘&#39;,
          a: {
            b: &#39;787878&#39;
          }
        },
        msg: &#39;学习MVVM实现原理&#39;,
        htmlStr: &#39;<h4>大家学的怎么样</h4>&#39;,
      },
      methods: {
        click111() {
          console.log(this)
          this.person.name = &#39;学习MVVM&#39;
          // this.$data.person.name = &#39;学习MVVM&#39;
        }
      }
    })
  </script>

</body>
2. 파서 및 관찰자 구현

MVue.js

// 先创建一个MVue类,它是一个入口
Class MVue {
    construction(options) {
        this.$el = options.el
        this.$data = options.data
        this.$options = options
    }
    if(this.$el) {
        // 1.实现一个数据的观察者     --先看解析器,再看Obeserver
        new Observer(this.$data)
        // 2.实现一个指令解析器
        new Compile(this.$el,this)
    }
}
?
// 定义一个Compile类解析元素节点和指令
class Compile {
    constructor(el,vm) {
        // 判断el是否是元素节点对象,不是就通过DOM获取
        this.el = this.isElementNode(el) ? el : document.querySelector(el)
        this.vm = vm
        // 1.获取文档碎片对象,放入内存中可以减少页面的回流和重绘
        const fragment = this.node2Fragment(this.el)

        // 2.编辑模板
        this.compile(fragment)

        // 3.追加子元素到根元素(还原页面)
        this.el.appendChild(fragment)
    }

    // 将元素插入到文档碎片中
    node2Fragment(el) {
        const f = document.createDocumnetFragment();
        let firstChild
        while(firstChild = el.firstChild) {
            // appendChild
            // 将已经存在的节点再次插入,那么原来位置的节点自动删除,并在新的位置重新插入。
            f.appendChild(firstChild)
        }
        // 此处执行完,页面已经没有元素节点了
        return f
    }

    // 解析模板
    compile(frafment) {
        // 1.获取子节点
        conts childNodes = fragment.childNodes;
        [...childNodes].forEach(child => {
            if(this.isElementNode(child)) {
                // 是元素节点
                // 编译元素节点
                this.compileElement(child)
            } else {
                // 文本节点
                // 编译文本节点
                this.compileText(child)
            }

            // 嵌套子节点进行遍历解析
            if(child.childNodes && child.childNodes.length) {
                this.compule(child)
            }
        })
    }

    // 判断是元素节点还是属性节点
    isElementNode(node) {
        // nodeType属性返回 以数字值返回指定节点的节点类型。1-元素节点 2-属性节点
        return node.nodeType === 1
    }

    // 编译元素节点
    compileElement(node) {
        // 获得元素属性集合
        const attributes = node.attributes
        [...attributes].forEach(attr => {
            const {name, value} = attr
            if(this.isDirective(name)) { // 判断属性是不是以v-开头的指令
                // 解析指令(v-mode v-text v-on:click 等...)
                const [, dirctive] = name.split('-')
                const [dirName, eventName] = dirctive.split(':')
                // 初始化视图 将数据渲染到视图上
                compileUtil[dirName](node, value, this.vm, eventName)

                // 删除有指令的标签上的属性
                node.removeAttribute('v-' + dirctive)
            } else if (this.isEventName(name)) { //判断属性是不是以@开头的指令
                // 解析指令
                let [, eventName] = name.split('@')
                compileUtil['on'](node,val,this.vm, eventName)

                // 删除有指令的标签上的属性
                node.removeAttribute('@' + eventName)
            } else if(this.isBindName(name)) { //判断属性是不是以:开头的指令
                // 解析指令
                let [, attrName] = name.split(':')
                compileUtil['bind'](node,val,this.vm, attrName)

                // 删除有指令的标签上的属性
                node.removeAttribute(':' + attrName)
            }
        })
    }

    // 编译文本节点
    compileText(node) {
        const content = node.textContent
        if(/\{\{(.+?)\}\}/.test(content)) {
            compileUtil['text'](node, content, this.vm)
        }
    }

    // 判断属性是不是指令
    isDirective(attrName) {
        return attrName.startsWith('v-')
    }
    // 判断属性是不是以@开头的事件指令
    isEventName(attrName) {
        return attrName.startsWith('@')
    }
    // 判断属性是不是以:开头的事件指令
    isBindName(attrName) {
        return attrName.startsWith(':')
    }
}
?
?
// 定义一个对象,针对不同指令执行不同操作
const compileUtil = {
    // 解析参数(包含嵌套参数解析),获取其对应的值
    getVal(expre, vm) {
        return expre.split('.').reduce((data, currentVal) => {
            return data[currentVal]
        }, vm.$data)
    },
    // 获取当前节点内参数对应的值
    getgetContentVal(expre,vm) {
        return expre.replace(/\{\{(.+?)\}\}/g, (...arges) => {
            return this.getVal(arges[1], vm)
        })
    },
    // 设置新值
    setVal(expre, vm, inputVal) {
        return expre.split('.').reduce((data, currentVal) => {
            return data[currentVal] = inputVal
        }, vm.$data)
    },

    // 指令解析:v-test
    test(node, expre, vm) {
        let value;
        if(expre.indexOf('{{') !== -1) {
            // 正则匹配{{}}里的内容
            value = expre.replace(/\{\{(.+?)\}\}/g, (...arges) => {

                // new watcher这里相关的先可以不看,等后面讲解写到观察者再回头看。这里是绑定观察者实现     的效果是通过改变数据会触发视图,即数据=》视图。
                // 没有new watcher 不影响视图初始化(页面参数的替换渲染)。
                // 订阅数据变化,绑定更新函数。
                new watcher(vm, arges[1], () => {
                    // 确保 {{person.name}}----{{person.fav}} 不会因为一个参数变化都被成新值
                    this.updater.textUpdater(node, this.getgetContentVal(expre,vm))
                })

                return this.getVal(arges[1],vm)
            })
        } else {
            // 同上,先不看
            // 数据=》视图
            new watcher(vm, expre, (newVal) => {
            // 找不到{}说明是test指令,所以当前节点只有一个参数变化,直接用回调函数传入的新值
        this.updater.textUpdater(node, newVal)
          })

            value = this.getVal(expre,vm)
        }

        // 将数据替换,更新到视图上
        this.updater.textUpdater(node,value)
    },
    //指令解析: v-html
    html(node, expre, vm) {
        const value = this.getVal(expre, vm)

        // 同上,先不看
        // 绑定观察者 数据=》视图
        new watcher(vm, expre (newVal) => {
            this.updater.htmlUpdater(node, newVal)
        })

        // 将数据替换,更新到视图上
        this.updater.htmlUpdater(node, newVal)
    },
    // 指令解析:v-mode
    model(node,expre, vm) {
        const value = this.getVal(expre, vm)

        // 同上,先不看
        // 绑定观察者 数据=》视图
        new watcher(vm, expre, (newVal) => {
            this.updater.modelUpdater(node, newVal)
        })

        // input框  视图=》数据=》视图
        node.addEventListener('input', (e) => {
            //设置新值 - 将input值赋值到v-model绑定的参数上
            this.setVal(expre, vm, e.traget.value)
        })
        // 将数据替换,更新到视图上
        this.updater.modelUpdater(node, value)
    },
    // 指令解析: v-on
    on(node, expre, vm, eventName) {
        // 或者指令绑定的事件函数
        let fn = vm.$option.methods && vm.$options.methods[expre]
        // 监听函数并调用
        node.addEventListener(eventName,fn.bind(vm),false)
    },
    // 指令解析: v-bind
    bind(node, expre, vm, attrName) {
        const value = this.getVal(expre,vm)
        this.updater.bindUpdate(node, attrName, value)
    }

// updater对象,管理不同指令对应的更新方法
updater: {
        // v-text指令对应更新方法
        textUpdater(node, value) {
            node.textContent = value
        },
        // v-html指令对应更新方法
        htmlUpdater(node, value) {
            node.innerHTML = value
        },
        // v-model指令对应更新方法
        modelUpdater(node,value) {
            node.value = value
        },
        // v-bind指令对应更新方法
        bindUpdate(node, attrName, value) {
            node[attrName] = value
        }
    },
}

3. 데이터 하이재킹 및 모니터링 구현

데이터 모니터링이 필요합니다. 하나 관찰자는 뷰 업데이트를 트리거할 수 있습니다. 업데이트를 실행하려면 데이터 변경이 필요하기 때문에 모든 관찰자(옵저버 수집)를 수집하고 관찰자와 관찰자를 연결하려면 브릿지 Dep가 필요합니다. 데이터 변경 사항은 Dep에게 알리고 Dep는 해당 관찰자에게 뷰를 업데이트하도록 알립니다.

Observer.js

// 定义一个观察者
class watcher {
    constructor(vm, expre, cb) {
        this.vm = vm
        this.expre = expre
        this.cb =cb
        // 把旧值保存起来
        this.oldVal = this.getOldVal()
    }
    // 获取旧值
    getOldVal() {
        // 将watcher放到targe值中
        Dep.target = this
        // 获取旧值
        const oldVal = compileUtil.getVal(this.expre, this.vm)
        // 将target值清空
        Dep.target = null
        return oldVal
    }
    // 更新函数
    update() {
        const newVal =  compileUtil.getVal(this.expre, this.vm)
        if(newVal !== this.oldVal) {
            this.cb(newVal)
        }
    }
}
// 定义一个观察者集合
class Dep {
    constructor() {
        this.subs = []
    }
    // 收集观察者
    addSub(watcher) {
        this.subs.push(watcher)
    }
    //通知观察者去更新
    notify() {
        this.subs.forEach(w => w.update())
    }
}
// 定义一个Observer类通过gettr,setter实现数据的监听绑定
class Observer {
    constructor(data) {
        this.observer(data)
    }

    // 定义函数解析data,实现数据劫持
    observer (data) {
        if(data && typeof data === 'object') {
            // 是对象遍历对象写入getter,setter方法
            Reflect.ownKeys(data).forEach(key => {
                this.defineReactive(data, key, data[key]);
            })
        }
    }

    // 数据劫持方法
    defineReactive(obj,key, value) {
        // 递归遍历
        this.observer(data)
        // 实例化一个dep对象
        const dep = new Dep()
        // 通过ES5的API实现数据劫持
        Object.defineProperty(obj, key, {
            enumerable: true,
            configurable: false,
            get() {
                // 当读当前值的时候,会触发。
                // 订阅数据变化时,往Dep中添加观察者
                Dep.target && dep.addSub(Dep.target)
                return value
            },
            set: (newValue) => {
                // 对新数据进行劫持监听
                this.observer(newValue)
                if(newValue !== value) {
                    value = newValue
                }
                // 告诉dep通知变化
                dep.notify()
            }
        })
    }

}

추천 튜토리얼: "JavaScript Video Tutorial"

위 내용은 Vue의 MVVM 원리 및 구현 방법에 대한 자세한 설명의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 csdn.net에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제