VUE双向绑定原理实践
栏目: JavaScript · 发布时间: 5年前
内容简介:近几年,前端框架层出不穷,在技术瞬息万变的时代里,关注JS语言本身,探究一些框架底层实现原理也许会让我们走得更深更远。下面是自己看vue源码的一些理解和实践,主要是对vue双向绑定原理和观察者模式做了一些实践,以v-model为例。整个过程分为以下几步:主要涉及到几个对象:
近几年,前端框架层出不穷,在技术瞬息万变的时代里,关注JS语言本身,探究一些框架底层实现原理也许会让我们走得更深更远。下面是自己看vue源码的一些理解和实践,主要是对vue双向绑定原理和观察者模式做了一些实践,以v-model为例。
双向绑定原理解析
整个过程分为以下几步:
1. compile:vue对template模板中进行编译,编译成真正的html,在编译的过程中对vue的指令解析 2. observe:在对template编译的过程中,对一些v-model,{{}}之类的指令使用在data中定义的变量来初始化,同时开启一个对该变量的watcher,相当于开启观察者模式,发布者是data中我们定义的变量,订阅者是我们需要更新的dom视图。 3. 发布者data中的变量改变,通知订阅者做相关操作,更新dom视图。 复制代码
主要涉及到几个对象:
1. 模拟vue的原型 2. 观察者原型watcher 3. Dep对象,封装了对订阅者的操作,一个data中的变量对应不同的watcher,这些watcher都存在在一个dep对象的数组中。 复制代码
对象原型解析
- Dep 对订阅者的操作可以抽象成一个Dep的原型。这里的对象属性subscriber是订阅者的数组,target是每次要加入subscriber中的目标订阅者,每次都只能加一个target到subscriber中。
function Dep(cb) { this.subscriber = [] this.target = null } Dep.prototype = { addSub(sub) { // 加入到订阅者数组中 this.subscriber.push(sub) }, notify() { // this.subscriber.forEach((item) => { item.update() }) } } 复制代码
- 然后,我们再来抽象一个watcher对象。exp是我们在data中定义的变量,cb是订阅者要执行的回调函数。get获取的是变量的值,update对应的当变量值发生更新时,执行订阅者的cb函数。初始化一个watcher的时候,调用get可把该watcher加入到订阅者的数组里。
function Watcher(vm, exp, cb) { this.vm = vm this.exp = exp this.cb = cb // 添加到订阅者列表中 this.value = this.get() } Watcher.prototype = { get() { Dep.target = this let value = this.vm.$data[this.exp] Dep.target = null return value }, update() { let oldVal = this.value let newVal = this.vm.$data[this.exp] if (newVal !== oldVal ) { console.log('更新', this.exp) this.value = newVal this.cb.call(this.vm, newVal) } } } 复制代码
- 最后,我们先模拟一个vue的原型。每一个data中的变量对应一个Dep对象,用于收集这个变量所对应的所有订阅者。这里,使用了原生JS中的Object.defineProperty方法,当get的时候把相关订阅者加入到dep对象中,当set的时候通知订阅者执行相关回调。
function Vue(options) { let vm = this vm._init(options) //开启观察者模式观察数据变化 vm._observe(options.data) // 编译dom vm._compile() } Vue.prototype._init = function(options) { var vm = this vm.$el = options.el vm.$data = options.data } Vue.prototype._observe = function(data) { Object.entries(data).forEach(([key, value]) => { var dep = new Dep() let property = Object.getOwnPropertyDescriptor(data, key) if (property && !property.configurable) { return } let getter = property && property.get let setter = property && property.set Object.defineProperty(data, key, { enumerable: true, configurable: true, get() { let val = getter ? getter.call(data) : value // get时候添加订阅者 if (Dep.target) { dep.addSub(Dep.target) } return val }, set(newValue) { let val = getter ? getter.call(data) : value // 脏检查,排除NaN !== NaN if (newValue === val || (newValue !== newValue)) { return } if (setter) { setter.call(data, newValue) } else { value = newValue } // 通知订阅者 dep.notify() } }) }) } Vue.prototype._compile = function() { let rootNode = document.querySelector(this.$el) let compile = (rootNode) => { if (rootNode.childNodes) { Array.prototype.forEach.call(rootNode.childNodes, (node) => { if (node.attributes && node.attributes.hasOwnProperty('v-model')) { compileVmodel(this, node) } if (/{{.*}}/.test(node.innerHTML)) { compileBrace(this, node) } if (node.childNodes) { compile(node) } }) } } compile(rootNode) } function compileVmodel(vm, node) { // 检测到有v-model属性,则添加对应watcher let exp = node.getAttribute('v-model') new Watcher(vm, exp, (val) => { node.setAttribute('v-model', val) }) // 监听input事件 node.addEventListener('input', () => { if (node.value !== vm.$data[exp]) { vm.$data[exp] = node.value } }, false) } 复制代码
这里用到了两个函数compileVmodel和compileBrace,只是作为模拟,实际vue解析的过程中用到了AST。
function compileVmodel(vm, node) { // 检测到有v-model属性,则添加对应watcher let exp = node.getAttribute('v-model') new Watcher(vm, exp, (val) => { node.setAttribute('v-model', val) }) // 监听input事件 node.addEventListener('input', () => { if (node.value !== vm.$data[exp]) { vm.$data[exp] = node.value } }, false) } function compileBrace(vm, node) { // 解析{{}}中值 let exp = node.innerHTML.match(/{{(.*)}}/)[1] console.log('compileBrace', exp) new Watcher(vm, exp, (val) => { console.log('新的值:', node) // let innerHTML = node.innerHTML.replace(/{{.*}}/g, val) node.textContent = val // console.log(innerHTML) }) } 复制代码
验证和实践
以上就完成了对vue双向绑定原理的简单建模,现在写段代码来实践验证下
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>vue双向绑定原理实践</title> </head> <body> <div id="app"> <input type="testVmodel" name="testVmodel" v-model="inputValue"> <p>{{inputValue}}</p> </div> <script type="text/javascript"> // 这里是引入上面的原型 window.onload = (function(window) { let app = new Vue({ el: '#app', data: { inputValue: '初始化inputValue值' } }) // 开启观察者模式监测 inputValue变化 new Watcher(app, 'inputValue', function(value) { let inputEl = document.querySelector('input') inputEl.value = value }) app.$data.inputValue = '测试更新' setTimeout(() => { app.$data.inputValue = '测试更新2' }, 1000) })(window) </script> </body> </html> 复制代码
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Software Paradigms
Stephen H. Kaisler / Wiley-Interscience / 2005-03-17 / USD 93.95
Software Paradigms provides the first complete compilation of software paradigms commonly used to develop large software applications, with coverage ranging from discrete problems to full-scale applic......一起来看看 《Software Paradigms》 这本书的介绍吧!