MVVM极易理解版

栏目: 编程工具 · 发布时间: 6年前

内容简介:前段时间面试,MVVM原理成为了一道必考题。由于理解不够深,最近详细了解以结构图流程分析原理。

前段时间面试,MVVM原理成为了一道必考题。由于理解不够深,最近详细了解以结构图流程分析原理。

一张原详细的理图

MVVM极易理解版

使用MVVM双向绑定

  • 定义双向绑定,传入元素,和数据。
var vm = new MVVM({
    el: '#mvvm-app',
    data: {
        someStr: 'hello ',
        className: 'btn',
        htmlStr: '<span style="color: #f00;">red</span>',
        child: {
            someStr: 'World !'
        }
    }
});
复制代码

MVVM类

  • 新建劫持数据
  • 编译绑定数据
class MVVM {
    constructor(options) {
        this.$options = options || {};
        var data = this._data = this.$options.data;
        Object.keys(data).forEach(key => {
            this._proxyData(key);
        })
        //数据劫持
        observe(data, this);
        //编译
        this.$compile = new Compile(options.el || document.body, this);
    }
    _proxyData(key, setter, getter) {
        Object.defineProperty(this, key, {
            configurable: false,
            enumerable: true,
            get: function proxyGetter() {
                return this._data[key];
            },
            set: function proxySetter(newVal) {
                this._data[key] = newVal;
            }
        })
    }
}
复制代码

Compile类

  • 将真实DOM移动到虚拟DOM中
  • 解析元素中的指令
  • 指令新建订阅传入更新函数
class Compile {
    constructor(el, vm) {
        this.$vm = vm;
        this.$el = this.isElementNode(el) ? el : document.querySelector(el);
        if (this.$el) {
            //生成文档碎片
            this.$fragment = this.node2Fragment(this.$el);
            //编译
            this.init()
            //文档碎片加回容器中
            this.$el.appendChild(this.$fragment);
        }
    }
    node2Fragment(el) {
        var fragment = document.createDocumentFragment(),
            child;
        while (child = el.firstChild) {
            fragment.appendChild(child);         
        };
        return fragment;
    }
    init() {
        this.compileElement(this.$fragment);
    }
    compileElement(el) {        

        var childNodes = el.childNodes;

        [].slice.call(childNodes).forEach(node => {
            var text = node.textContent;
            var reg = /\{\{(.*)\}\}/;
            
            if(this.isElementNode(node)){
                //指令解析
                this.compile(node);
            }else if(this.isTextNode(node) && reg.test(text)){
                this.compileText(node, RegExp.$1)
            }

            if(node.childNodes && node.childNodes.length){
                this.compileElement(node);
            }
        })
    }
    compile(node){
        var nodeAttrs = node.attributes;

        [].slice.call(nodeAttrs).forEach(attr => {
            var attrName = attr.name;
            
            if(this.isDirective(attrName)){
                var exp = attr.value;
                var dir = attrName.substring(2);
                //事件指令
                
                if(this.isEventDirective(dir)){
                    compileUtil.eventHandler(node, this.$vm, exp, dir);
                }else{
                    compileUtil[dir] && compileUtil[dir](node, this.$vm, exp);
                }
                node.removeAttribute(attrName);
            }
        })
    }
    isDirective(attr){
        return attr.indexOf('v-') === 0;
    }
    isEventDirective(attr){
        return attr.indexOf('on') === 0;
    }
    isElementNode(node) {
        return node.nodeType == 1;
    }
    isTextNode(node) {
        return node.nodeType == 3;
    }
    compileText(node, exp) {
        compileUtil.text(node, this.$vm, exp);
    }
}

//指令处理集合
var compileUtil = {
   ...
}

var updater = {
   ...
}
复制代码

Observer类

  • 劫持数据
  • 数据变化通知Watcher
//数据劫持
class Observer {
    constructor(data) {
        this.data = data;
        this.walk(data);
    }
    walk(data) {
        Object.keys(data).forEach(key => {
            this.convert(key, data[key]);
        })
    }
    convert(key, val) {
        this.defineReactive(this.data, key, val);
    }
    //绑定数据,添加发布订阅,核心**
    defineReactive(data, key, val) {
        var dep = new Dep();
        var childObj = observe(val);
        Object.defineProperty(data, key, {
            enumerable: true, //可枚举
            configurable: false, //不能再define
            get: function(){
                if(Dep.target){
                    console.log(Dep.target, 'Dep.target');
                    dep.depend();
                }
                return val;
            },
            set: function(newVal){             
                if(newVal === val){
                    return;
                }                
                val = newVal;
                // 新的值object的话,进行监听
                childObj = observe(newVal);
                console.log(newVal);
                //通知订阅者
                dep.notify();
            }
        })
    }
}

function observe(value, vm) {
    if (!value || typeof value !== 'object') {
        return;
    }
    return new Observer(value);
}

复制代码

Dep类

  • 发布订阅类
var uid = 0;
class Dep {
    constructor() {
        this.id == uid++;
        this.subs = [];
    }
    addSub(sub) {
        this.subs.push(sub);
    }
    depend() {
        Dep.target.addDep(this)
    }
    removeSub(sub) {
        this.subs.remove(sub);
    }
    notify() { 
        this.subs.forEach(sub => {
            sub.update();
        })
    }
}

Dep.target = null;
复制代码

Watcher类

  • 监控数据变化,发布消息,执行订阅函数。
class Watcher{
    constructor(vm, expOrFn, cb){
        this.cb = cb;
        this.vm = vm;
        this.expOrFn = expOrFn;
        this.depIds = {};
        
        if(typeof expOrFn === 'function') {
            this.getter = expOrFn;
        }else{
            this.getter = this.parseGetter(expOrFn);
        }
        this.value = this.get();
    }
    update(){
        this.run();
    }
    run(){
        var value = this.get();
        var oldVal = this.value;
        if (value !== oldVal) {
            this.value = value;
            this.cb.call(this.vm, value, oldVal);
        }
    }
    get(){
        Dep.target = this;
        var value = this.getter.call(this.vm, this.vm);
        Dep.target = null;
        return value;
    }
    parseGetter(exp){
        if(/[^\w.$]/.test(exp)) return;
        var exps = exp.split(',');
        return function(obj) {
            for (let i = 0; i < exps.length; i++) {
                if(!obj) return;
                obj = obj[exps[i]];
            }
            return obj;
        }
    }
    addDep(dep){
        if (!this.depIds.hasOwnProperty(dep.id)) {
            dep.addSub(this);
            this.depIds[dep.id] = dep;
        }
    }
}
复制代码

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Scratch少儿趣味编程

Scratch少儿趣味编程

[ 日] 阿部和广 / 陶 旭 / 人民邮电出版社 / 2014-11 / 59.00元

Scratch 是麻省理工学院设计开发的一款编程工具,是适合少儿学习编程和交流的工具和平台,有中文版且完全免费。本书结合孩子们学习的语文、数学、科学、社会、音乐、体育等科目,手把手地教大家如何用Scratch 设计程序(如设计一个自动写作文的程序),配合各式卡通形象,通俗易懂,寓教于乐。麻省理工学院教授米切尔•瑞斯尼克作序推荐。 本书图文并茂,生动风趣,适合中小学生等初学者自学或在家长的帮助......一起来看看 《Scratch少儿趣味编程》 这本书的介绍吧!

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具