学习一下mvvm 双向绑定拉

概括一下 mvvm 就是
发布(watcher)-订阅 (dep)+ defineProperty
通过 Observer类监听data中的数据,compile类的话解析html中的数据格式(input/{{}}),
dep起一个数组作用,收集订阅者,订阅者就是所谓的watcher负责订阅数据变化并且绑定更新数据
同时,如果页面中的数据发生改变,通过defineProperty数据劫持的set方法,通过dep订阅器调用watcher中的update方法实现数据更新

首先,我们来给定一下数据

// new Wue实例
let app = new Wue ({
    el: '#app',
    data: {
        song: '发如雪',
        album: {
        	name: '十一月的肖邦',
        	theme: '夜曲'
    	},
        singer: '周杰伦'
    }
})
    <div id="app">
      <h1>{{song}}</h1>
        <p>{{album.name}}》是{{singer}}200511月发行的专辑</p>
        <p>主打歌为{{album.theme}}</p>
        <p>作词人为{{singer}}等人。</p>
        为你弹奏肖邦的{{album.theme}}
		<input v-model="input" type="text">
    </div>

我们先定义一下Wue类

wue类 就是用来生成wue实例哒

class Wue {
    constructor (options) {
        const vm = this
        vm.$options = options
        vm.$watcher = function(value, cb) {
            new Watcher(vm, value, cb)
        }
        let data = vm._data = vm.$options.data
        for (let key in vm._data) {
            proxy(vm, '_data', key)
        }
        
        //监听数据
        observer(vm._data)
        new Compile(options.el, this)
    }
}

[外链图片转存失败(img-eMvNDE9S-1566818271624)(D:\2019.5月秋招准备\面试题\vue双向绑定\1.png)]

如上图,就是挂载后的结果 vm即vue实例

Compile类

在定义compile解析类时,要先在Wue类中引入compile鸭

class Wue {
    ...
    new Compile(options.el, this)
}
class Compile {
    constructor (el, vm) {
        // document.querySelector("#app")
        vm.$el = document.querySelector(el) //这一步就是获取id为app的div
        this.replace(vm.$el, vm)
    }
    replace(frag, vm) {
        Array.from(frag.childNodes).forEach(node => {
            let txt = node.textContent
            let reg = /\{\{(.*?)\}\}/g
            // 如果是元素属性中的文本内容 如<h1></h1> <p></p>
            if (node.nodeType === 3 && reg.test(txt) {
                let arr = RegExp.$1.split('.')
                let val = vm
                arr.forEach (key => {
                    //这里进行赋值 要对data进行代理处理
                    val = val[key] //如key为song val = Wue[song] = '发如雪'
                })
            	//这一步实现了讲data的值显示在页面上
            	node.textContent = txt.replace(reg, val).trim();1
            	//使用watcher监听data中的属性
            	vm.$watch(RegExp.$1, function(newVal) {
                	txt.textContent = txt.replace(reg, newVal).trim()
            	})
        	}
            // 当节点是元素时
            if (node.nodeType === 1) {
            	let nodeAttr = node.attributes //获取dom上的所有属性,是个类数组
                Array.from(nodeAttr).forEach(attr => {
                    // 如v-model type
                    let name = attr.name
                    // 如song text
                    let exp = attr.value
                    // 实现一下v-model
                    if (name.includes('v-')) {
                        node.value = vm[exp]
                    }
                    vm.$watch(exp, function(newVal) {
                        node.value = newVal
                    })
                    node.addEventListener('input', e => {
                        let newVal = e.target.value
                        let arr = exp.split('.')
                        let val = vm
                        arr.forEach((key, i) => {
                            // 比如 如果是album.theme  咱们要取的肯定是最后theme嘛
                            if (i === arr.length - 1) {
                                val[key] = newVal
                                return 
                            }
                            val = val[key]
                        })
                    })
                })
       		 }
        // 如果还有子节点,就循环递归replace
        if (node.childNodes && node.childNodes.length) {
            this.replace(node, vm)
        }
        })
    }
}

当 时,其dom上的所有属性如下

[外链图片转存失败(img-KMsslu5j-1566818271625)(D:\2019.5月秋招准备\面试题\vue双向绑定\6.png)]

vm.$el.childNodes为

[外链图片转存失败(img-y0ZV0dw7-1566818271625)(D:\2019.5月秋招准备\面试题\vue双向绑定\2.png)]

比如第一个 txt为{{song}} 则 arr为[song]

[外链图片转存失败(img-zuUZKeRv-1566818271626)(D:\2019.5月秋招准备\面试题\vue双向绑定\3.png)]

这里存在一个bug

一个txt中有两个{{}}待解析的话 会忽略另外一个

以上就实现了让data显示在页面上

对data进行代理 proxy

_data: {
    song: '发如雪',
    album: {
    	name: '十一月的肖邦',
    	theme: '夜曲'
	},
    singer: '周杰伦'
}
// key: song/ album/ theme
for (let key in vm._data) {
    proxy(vm, '_data', key)
}

      function proxy (target, sourceKey, key) {
        Object.defineProperty(target, key, {
          configurable: true,
          get: function proxyGetter () {
              //vm[_data][song]
            return target[sourceKey][key]
          },
          set: function proxySetter (newVal) {
              // vm[_data][song]
            target[sourceKey][key] = newVal
          }
        }) 
      }

我们把data的值显示到了页面上,如果data的值被人为改动了呢?会反应到页面上吗?**

当然是不行的拉!!!所以我们首先需要保证我们监听到了这个data中的属性。

那用什么监听呢?

我们需要定义一个watcher类,来订阅数据的变化

watcher类

class Watcher {
    constructor (vm, expression, cb) {
        this.vm = this.vm
        this.cb = cb
        this.expression = expression
        this.value = this.getVal()
    }
    getVal () {
        pushTarget(this)
        let val = this.vm
        this.expression.split('.').forEach((key) => {
            // 获取val[key]值时,就会默认调用Observer下的get方法
            val = val[key]
        })
        popTarget()
        return val
    }
    addDep (dep) {
        // 这里的话就要去把watcher添加进去 因为要把watcher添加, 所以要在watcher类下添加 这里的this指向watcher
        dep.addSubs(this)
    }
    update () {
     	// 因为要改变的数据还是存在在vm下的,所以也要获取
        let val = this.vm
        this.expression.split('.').forEach((key) => {
            val = val[key]
        })
        this.cb.call(this.vm, val).trim()
    }
}

pushTarget 作用是 当前订阅者读取被订阅数据的最新更新后的值时,通知订阅者管理员收集当前订阅者

Dep.target = null
let targetStack = []
function pushTarget (_target) {
    //Dep.target其实就是一个watcher对象
    if (Dep.target) {
        targetStack.push(Dep.target)
    }
    Dep.target = _target
}
function popTarget () {
    Dep.target = targetStack.pop()
}

Observer

class Observer {
    constructor (value) {
        this.value = value
        this.walk(value)
    }
    walk (obj) {
        Object.keys(obj).forEach((key) => {
            if (typeof obj[key] === 'object') {
                this.walk(obj[key])
            }
        })
        defineReactive(obj, key, obj[key])
    }
    defineReactive(obj, key, value) {
        let dep = new Dep()
        get () {
            if (Dep.target) {
                // 这里,因为你需要去获取wue下的data数据,那么就要把这个wather添加到dep消息订阅器中
                dep.addDepend()
            }
            return value
        }
        set (newVal) {
            if (newVal === value) {
                return 
            }
            value = newVal
            // 调用更新数据方法
            dep.notify()
        }
    }
    
}

Dep

class Dep {
    constructor{
        this.subs = [] //存放watcher订阅者
    }
	addDepend () {
        // 调用watcher下的addDep方法
        Dep.target.addDep(this) // this是dep实例本身
    }
	addSubs (sub) {
        this.subs.push(sub)
    }
	notify () {
        for(let sub of subs) {
            sub.update()
        }
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值