前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >手写Vue数据绑定

手写Vue数据绑定

作者头像
切图仔
发布2022-09-08 17:00:55
8350
发布2022-09-08 17:00:55
举报
文章被收录于专栏:生如夏花绚烂
小试牛刀

我们先来看如下vue实例有什么属性

代码语言:javascript
复制
 var vm = new Vue({
            el:'#app',
            data:{
                name:'hello',
                sex:1
            }
        })
 console.log(vm);

可以看到我们设置的data属性被提升到Vue实例上了

代码语言:javascript
复制
Vue
$attrs: (...)
$listeners: (...)
sex: (...)
name: (...)
$data: (...)
$props: (...)
$isServer: (...)
$ssrContext: (...)
...

他是怎样做到的? 继续往下面看我们发现他使用了代理,将data的属性代理到vue上,所以才能进行this.属性名进行访问或设置

代码语言:javascript
复制
...
$el: div#app
get $attrs: ƒ reactiveGetter()
set $attrs: ƒ reactiveSetter(newVal)
get $listeners: ƒ reactiveGetter()
set $listeners: ƒ reactiveSetter(newVal)
get sex: ƒ proxyGetter()
set sex: ƒ proxySetter(val)
get name: ƒ proxyGetter()
set name: ƒ proxySetter(val)

我们也可以简单实现 首先创建一个vue实例,构造方法接收options

代码语言:javascript
复制
 class Vue{
            constructor(options){
                this.$options = options
                this.$el = document.querySelector(this.$options.el)
                this.proxyData()
            }
            //代理data
            proxyData(){
                for(let key in this.$options.data){
                    Object.defineProperty(this,key,{
                        enumerable:true,
                        configurable:false,
                        get(){
                            return this.$options.data[key]
                        },
                        set(val){
                            this.$options.data[key] = val
                        }
                    })
                }
            }
}

打印实例结果返回

代码语言:javascript
复制
var vm = new Vue({
            el:'#app',
            data:{
                name:'zh',
                sex:'1'
            }
})
console.log(vm);

//
Vue {$options: {…}, $el: div#app}
name: (...)
sex: (...)
$options: {el: "#app", data: {…}}
$el: div#app
get name: ƒ get()
set name: ƒ set(val)
get sex: ƒ get()
set sex: ƒ set(val)
__proto__: Object

此时我们可以使用 vm.name获取到data的name vm.name=123设置data的name

我们大概知道他的机制之后来进行一个数据绑定的实现

实现数据绑定

如下结构 当我们修改vue的data属性值的时候 对应的html绑定的相关属性也要进行改变

代码语言:javascript
复制
 <div id="app">
        <input type="text" v-model="name" />
        <h1>{{name}}</h1>
        <h2 v-html="name"></h2>
        <button type="button" @click="change">修改name</button>
</div>

这里要用到事件机制,当某属性值被修改时触发某事件对html绑定的对应属性值进行更新 所以基于上面的代码,我们还要在data属性值改变时进行操作 定义方法observe

代码语言:javascript
复制
...
observe(){
    for(let key in this.$options.data){
        var value = this.$options.data[key]
        var that  = this
        Object.defineProperty(this.$options.data,key,{
            enumerable:true,
            configurable:false,
            get(){
                console.log('获取属性');
                return value

            },
            set(val){
                console.log('设置属性');
                value = val
       
            }
        })
    }
}

定义observe方法监听data属性值的变化

我们发现可以在属性值被修改时进行一些操作,我们完成“当属性值被修改时改变html”不就可以了吗? 如何实现? 我们的html可能有很多元素/元素的属性都绑定了该data的属性 如

代码语言:javascript
复制
    <h1>{{name}}</h1>
    <h2 v-html="name"></h2>

现在的问题是如何在值变化的时候修改所有绑定了相应属性的html元素 这里我们用一个对象watchEvent来存储data属性发生改变时要触发的事件 如

代码语言:javascript
复制
watchEvent = {
    event:[event,event],
    name:[event1,event2],
    sex:[event1,event2]        
}
代码语言:javascript
复制
...
this.$el = document.querySelector(this.$options.el)
this.$watchEvent = {}
...

eventn是一个事件对象,这个事件对象包括绑定了该data属性值的信息;如哪个节点绑定的,节点绑定的属性是什么等, 我们在先外面定义这个对象

代码语言:javascript
复制
//生成事件对象
class Watch{
    //vm: app(vue实例)对象
    //key data对应的属性
    //node html节点
    //attr html节点属性
    constructor(vm,key,node,attr){
        this.vm = vm
        this.key = key
        this.node = node 
        this.attr = attr
    }
    //更新属性
    update(){
        this.node[this.attr] = this.vm[this.key]
    }
}

当data属性发生变化时外面将调用这个update方法对象节点进行更新 在observe

代码语言:javascript
复制
set(val){
    console.log('设置属性');
    value = val
    //循环调用事件对象,使绑定值更新
    if(that.$watchEvent[key]){
       that.$watchEvent[key].forEach((item,i)=>{
           item.update()
       })
    }
}

接下来的问题是我们如何知道某个节点绑定了data的属性? 我们要对html进行编译,拿到el的子节点进行操作 vue对象定义 complie方法

代码语言:javascript
复制
...
//编译html
compile(){
    //遍历el下的所有子节点 
    this.$el.childNodes.forEach((item,index)=>{
        // console.log(item);
        //判断当前的节点类型 (nodeType==1==节点)和文本类型(nodeType==3==文本)
        if(item.nodeType==1){
            //v-html
            if(item.hasAttribute('v-html')){
                //获取v-html属性绑定的data属性
                let vmKey = item.getAttribute('v-html').trim()
                //设置属性值
                item.innerHTML = this[vmKey]
                //生成事件对象
                let watcher = new Watch(this,vmKey,item,'innerHTML')
                //将事件对象添加到 watchEvent、
                if(this.$watchEvent[vmKey]){
                    this.$watchEvent[vmKey].push(watcher)
                }else{
                    this.$watchEvent[vmKey] = []
                    this.$watchEvent[vmKey].push(watcher)
                }
           
            }
        }
       
    })
}
...

编译html在初始的时候直接访问到data的值,并根据绑定的属性值生成事件对象class Watch,存储到 watchEvent 这样当属性值修改时html也会发生变化

接下来我们实现数据双向绑定(v-model)

代码语言:javascript
复制
//v-model
if(item.hasAttribute('v-model')){
    //数据双向绑定
    let vmKey = item.getAttribute('v-model').trim()
    if(this.hasOwnProperty(vmKey)){
        item.value = this[vmKey]
         //生成事件对象
         let watcher = new Watch(this,vmKey,item,'value')
        //将事件对象添加到 watchEvent、
        if(this.$watchEvent[vmKey]){
            this.$watchEvent[vmKey].push(watcher)
        }else{
            this.$watchEvent[vmKey] = []
            this.$watchEvent[vmKey].push(watcher)
        }
        item.addEventListener('input',()=>{
            this[vmKey] = item.value
        })
    }
}

在基于之前的操作我们实现数据双向绑定就很简单了,直接监听input事件即可

接下来完成基于事件的数据绑定 在原来的vue对象添加methods

代码语言:javascript
复制
var vm = new Vue({
    el:'#app',
    data:{
        name:'zh'
    },
    methods:{
        change(){
            this.name='点了一下'
        }
    }
})
代码语言:javascript
复制
if(item.nodeType==1){
...
//@click
if(item.hasAttribute('@click')){
    //获取method
    let method = item.getAttribute('@click').trim()
    item.addEventListener('click',(e)=>{
        this.eFn = this.$options.methods[method].bind(this)
        this.eFn(e)

    })

}
}
...

按钮点击时触发change方法,对name值进行修改

接下来完成文本节点的更新

由于我们要实现的文本节点还包了一个h1

代码语言:javascript
复制
<h1>{{name}}</h1>

默认el的文本节点只能找到el的子节点,并不能找到子节点的子节点,为了解决这个问题我们要对complie进行改进,使其递归查找

代码语言:javascript
复制
 class Vue{
        ...  
        this.proxyData()
        this.observe()
        //传入操作节点
        this.compile(this.$el)
        ...
        
        
 compile(cNode){    
    cNode.childNodes.forEach((item,index)=>{
        ...
        if(item.childNodes.length>0){
               this.compile(item)
        }
    })
        
 }    

文本节点部分

代码语言:javascript
复制
if(item.nodeType==1){
...
}else if(item.nodeType==3){
    //text
    let reg = /\{\{(.*?)\}\}/g
    let text = item.textContent
    item.textContent = text.replace(reg,(match,vmKey)=>{
        vmKey = vmKey.trim()

        if(this.hasOwnProperty(vmKey)){
             //生成事件对象
             let watcher = new Watch(this,vmKey,item,'textContent')
            //将事件对象添加到 watchEvent、
            if(this.$watchEvent[vmKey]){
                this.$watchEvent[vmKey].push(watcher)
            }else{
                this.$watchEvent[vmKey] = []
                this.$watchEvent[vmKey].push(watcher)
            }
         
        }

    
    })

}

到此一个简单的数据绑定完成

本示例源码已上传至 GitHub

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2020-09-02 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 小试牛刀
  • 实现数据绑定
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档