Vue原理分析

开始vuejs原理分析

vue会遇到的一些问题

  • 点击修改不生效的例子
<template>
  <div>
    <ul>
      <li v-for="(item, index) in arr" :key="index">
        {{item.a}}
        <button @click="change(index)">修改数组元素</button>
        <button @click="change2(index)">修改数组元素内的属性值</button>
      </li>
    </ul>
  </div>
</template>
<script>
  export default {
    data () {
      return {
        arr: [
          {a: 111, b: 222},
          {a: 111, b: 222},
          {a: 111, b: 222},
          {a: 111, b: 222},
        ],
        num: 1
      }
    },
    methods: {
      change(index){
        this.arr[index] = {a: 444}
        // this.$set(this.arr,index,{a: 555})
      },
      change2(index){
        this.num++
        this.arr[index].a = this.num
      }
    },
    
  }
</script>

看例子
弹窗例子

  • 改变数组或者对象中的值,但是视图没有更新,为什么用set可以,看源码
    数组方法
  • 使用refs调用弹窗中的方法,报错,未找到该方法
  • 多次赋值,为什么页面只显示最后的值,this.a = ‘111’; this.a = '222’看源码
  • nextTick还是setTimeout看源码
  • v-show,v-if到底用哪个好
  • v-html里写插值为什么不生效
  • 。。。。

开始原理

百度“vue原理”这个关键词,90%的几率你会看到类似的一句话:

“vue使用Object.defineProperty对数据进行getter与setter的转化,但由于ie8及以下的浏览器不支持Object.defineProperty这个属性,所以vue不兼容ie8及以下的浏览器”

那么,Object.defineProperty到底是个什么东西呢?

mdn的解释

  • value:属性对应的值,可以使任意类型的值,默认为undefined
  • writable:属性的值是否可以被重写
  • enumerable:此属性是否可以被枚举
  • configurable:是否可以删除目标属性或是否可以再次修改属性的特性
  • get:获取对象值的时候触发
  • set:设置对象值的时候触发
var data = {
    name: 'lhl'
}

Object.keys(data).forEach((key) => {
    Object.defineProperty(data, key, {
        enumerable:true,
        configurable:true,
        get(){
            console.log('get');
        },
        set(val){
            console.log('监听到数据发生了变化');
        }
    })
});
data.name //控制台会打印出 “get”
data.name = 'hxx' //控制台会打印出 "监听到数据发生了变化"

看看核心的图

[外链图片转存失败(img-zMhaZckd-1565858353102)(https://shop.io.mi-img.com/app/shop/img?id=shop_5de7af21d4c2de951720c006f84b98fc.png)]
[外链图片转存失败(img-4v7jp2CL-1565858353103)(https://shop.io.mi-img.com/app/shop/img?id=shop_c99b7083298b335f04c39fc6f99f88bd.jpeg)]

vue的三个核心类

  • Observer:用来劫持并监听所有属性,如果有变动的,就通知订阅者
  • Dep:Observer与Watcher的中转站,对依赖进行收集并通知变化
  • Watcher:可以收到属性的变化通知并执行相应的函数,从而更新视图

Observer

Observer的核心是通过Obeject.defineProperty()来监听数据的变动,这个函数内部可以定义setter和getter,每当数据发生变化,就会触发setter。这时候Observer就要通知订阅者,订阅者就是Watcher。

Watcher

Watcher订阅者作为Observer和Compile之间通信的桥梁,主要做的事情是:

在自身实例化时往属性订阅器(dep)里面添加自己
自身必须有一个update()方法
待属性变动dep.notice()通知时,能调用自身的update()方法,并触发Compile中绑定的回调

Compile

Compile主要做的事情是解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图。

手摸手写一个简易版vuejs帮助理解以下几点

  • 实现数据双向绑定
  • 实现事件监听
  • 实现生命周期
  • 实现watch
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <!-- <script src="https://cdn.bootcss.com/vue/2.5.16/vue.js"></script> -->
</head>
<body>
    <div id="app">
        <p>{{msg}}</p>
        <p>{{msg}}</p>
        <p>{{msg}}</p>
        <p>{{num}}</p>
        <input type="text" v-model="num">
        <button @click="change">change</button>
    </div>



    <script>
    // Dep的作用是作为Observer与Watcher的中转站,接收Observer发出的变化通知,转告给Watcher并进行变化后相应的处理
    class Dep{
        constructor(){
            // subs是一个依赖池,里面放了所有的Watcher
            this.subs = []
        }
        addSub(watcher){
            this.subs.push(watcher)
        }
        // notify用来通知变化给Watcher
        notify(val, oldVal){
            for (const watcher of this.subs) {
                watcher.update(val, oldVal)
            }
        }
    }
    // Watcher的作用是接收变化的通知,并且执行相应的函数
    class Watcher{
        constructor(vm, key, cb){
            this.vm = vm
            this.key = key
            this.cb = cb
            this.get()
        }
        // 当数据发生变化的时候就会触发这个事件
        update(val, oldVal){
            this.cb.call(this.vm , val, oldVal)
        }
        // 当我new的时候就触发这个函数,把自己丢进Dep中
        get(){
            Dep.target = this
            let value = this.vm[this.key]
            Dep.target = null
        }
    }
    // Observer的作用将数据进行getter与setter的转化,使其变为可响应的
    class Observer{
        constructor(data){
            this.data = data
            this.init(this.data)
        }
        init(data){
            // init方法使用核心的Object.defineProperty进行实际的转化操作
            if( !data || typeof(data) !== 'object') return
            for (const key in data) {
                this.defiReactive(data, key, data[key])
                // 递归调用数据层级别比较深的数据
                this.init(data[key])
            }
        }
        defiReactive(data, key, val){
            let oldVal = val
            let dep = new Dep()
            Object.defineProperty(data, key, {
                enumerable:true,
                configurable:true,
                get(){
                    // console.log('get');
                    // 为保证不会重复添加依赖,使用Dep.target进行判断
                    if(Dep.target){
                        dep.addSub(Dep.target)
                    }
                    return oldVal
                },
                set(val){
                    // 进行简单的值对比,相同就不触发后续操作
                    if(oldVal === val) return
                    dep.notify(val, oldVal)
                    // console.log(val, '监听到数据发生了变化');
                    oldVal = val
                }
            })
        }
    }
    // Complie是用于进行模板解析的(这部分和源码完全不同,纯粹是为了简单实现渲染)
    class Compile{
        constructor(vm, el, data){
            this.vm = vm
            this.el = el
            this.data = data
            // childNodes可以用来获取dom下的所有子节点
            let nodes = document.querySelector(el).childNodes
            this.compileNodes(nodes)
        }
        compileNodes(nodes){
            for (const node of nodes) {
                // 判断节点的类型,1为标签类型,3为文本类型
                switch (node.nodeType) {
                    case 1:
                        this.compileNode(node)
                        break;
                
                    case 3:
                        this.compileText(node)
                        break;
                
                    default:
                        break;
                }
                // 这里判断是否有标签嵌套情况,然后进行递归
                node.childNodes && node.childNodes.length && this.compileNodes(node.childNodes)
            }
        }
        // 对于文本类型,我们需要解析内部的属性,用来填充内部的文本
        compileText(node){
            let reg = /\{\{(.*)\}\}/
            if(node.textContent !== '' && reg.test(node.textContent)){
                // 获取文本绑定的data属性的key
                let key = reg.exec(node.textContent)[1]
                node.textContent = this.vm[key]
                // new Watcher用来进行依赖的收集,当我绑定的这个值发生改变的时候再执行相应的函数
                new Watcher(this.vm, key, (val) => {
                    node.textContent = val
                })
            }
        }
        // 对于标签类型,我们需要解析attr属性用来获取标签上监听了什么事件
        compileNode(node){
            let attrs = node.attributes
            for (const attr of attrs) {
                if(attr.name.indexOf('@') > -1){
                    let event = attr.name.split('@')[1]
                    let key = attr.value
                    node.addEventListener(event, () => {
                        this.vm[key]()
                    })
                }
                if(attr.name.indexOf('v-model') > -1){
                    let key = attr.value
                    node.value = this.vm[key]
                    node.addEventListener('input', (event) => {
                        this.vm[key] = event.target.value
                    })
                    new Watcher(this.vm, key, (val) => {
                        node.value = this.vm[key]
                    })
                }
            }
        }

    }
    class Vue{
        constructor({el, data, methods, watch, created, mounted}){
            this.el = el
            this.data = data()
            this.methods = methods
            this.watch = watch
            // 用来代理vue的各种属性
            this.proxy()
            // 进行数据劫持
            new Observer(this.data)
            created && created.call(this)
            // 初始化watch
            this.initWatch()
            // 模板解析
            new Compile(this, el, this.data)
            mounted && mounted.call(this)
        }
        initWatch(){
            for (const key in this.watch) {
                console.log(this.watch[key], 'wawawa')
                new Watcher(this, key, this.watch[key])
            }
        }
        // 代理vue,用来实现this.title替代this.data.title的效果
        proxy(){
            for (const key in this.data) {
                Object.defineProperty(this, key, {
                    enumerable:false,
                    configurable:true,
                    get(){
                        // console.log('get');
                        return this.data[key]
                    },
                    set(val){
                        // console.log(val, '监听到数据发生了变化');
                        this.data[key] = val
                    }
                })
            }
            for (const key in this.methods) {
                Object.defineProperty(this, key, {
                    enumerable:false,
                    configurable:true,
                    get(){
                        // console.log('get');
                        return this.methods[key]
                    },
                    set(val){
                        // console.log(val, '监听到数据发生了变化');
                        this.methods[key] = val
                    }
                })
            }
        }
    }
    var vm = new Vue({
        el: '#app',
        data(){
            return {
                msg: 'hello vue',
                msg2: 'hello vue2',
                num: 1
            }
        },
        methods: {
            change(){
                // console.log(e)
                console.log('click')
                this.num++
                this.msg = 'hello one'
            }
        },
        watch: {
            num(val, oldVal){
                console.log(val,'watch')
            }
        },
        created(){
            console.log('created')
        },
        mounted(){
            console.log('mounted')
        }
    })
    vm.num = 444
    </script>
</body>
</html>

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值