Vue源码解析

Vue工作机制

Vue.js的工作机制主要包括以下几个方面:

  1. 响应式数据绑定:Vue.js使用数据劫持和发布订阅模式来实现响应式数据绑定。当数据发生变化时,视图也会随之更新。
  2. 组件系统:Vue.js 2.0采用组件化的方式构建前端应用。组件是可重用的独立单位,可以包含模板、样式和逻辑。组件可以通过props和事件进行通信,使应用的结构更加清晰和可维护。
  3. 模板语法:Vue.js使用基于HTML的模板语法,方便开发者快速上手。模板中可以使用插值、指令和事件等语法,实现数据的动态展示和交互。
  4. 生命周期:Vue.js的生命周期指的是组件在创建、更新、销毁等不同阶段所执行的一系列方法。通过生命周期方法,我们可以对组件的状态进行控制和操作,实现复杂的业务逻辑。
  5. 指令:Vue.js提供了一些内置指令,如v-if、v-for、v-bind等,用于实现条件渲染、列表渲染、数据绑定等功能。指令可以接收参数,并且支持动态绑定。
  6. 插件系统:Vue.js 2.0的插件系统允许开发者扩展框架的功能。插件可以通过全局API或局部注册的方式使用,使应用的功能更加丰富和灵活。
  7. 组件通信:Vue.js提供了多种组件通信方式,如父子组件传递props、使用事件进行通信、使用Vuex进行状态管理等。这些通信方式使得组件之间的数据流动更加可控和可预测。
    在这里插入图片描述

Vue响应式的原理defineProperty

Vue.js 的响应式原理主要是通过ES5的Object.defineProperty方法对数据进行劫持,并利用getter和setter方法进行数据的获取和设置。当读取data中的数据时,会自动调用getter方法;当修改数据时或者数据发生改变时,会自动调用setter方法去侦听检测数据的变化。同时,会通知观察者(watcher)自动重新触发渲染。

class KVue {
    constructor(options) {
        this._data = options.data;
        this.observer(this._data);
    }
    observer(value) {
        if (!value || typeof value !== "object") {
            return;
        }
        Object.keys(value).forEach(key => {
            this.defineReactive(value, key, value[key]);
        });
    }
    defineReactive(obj, key, val) {
        Object.defineProperty(obj, key, {
            enumerable: true /* 属性可枚举 */,
            configurable: true /* 属性可被修改或删除 */,
            get() {
                return val;
            },
            set(newVal) {
                if (newVal === val) return;
                this.cb(newVal);
            }
        });
    }
    cb(val) {
        console.log("更新数据了", val);
    }
}
let o = new KVue({
    data: {
        test: "I am test."
    }
});
o._data.test = "hello,kaikeba";

依赖收集与追踪

Vue.js使用依赖收集和追踪系统来管理组件之间的依赖关系。该系统使用静态分析来查找模板中的依赖关系,并在编译期间收集依赖项。然后,这些依赖项被存储在Vue实例的__dep__数组中,用于在更新周期中追踪和触发依赖项的重新计算。
当一个依赖项发生变化时,Vue会使用__dep__数组来查找所有依赖于该依赖项的组件,并触发重新渲染。这使得Vue能够有效地更新视图,而不需要手动调用Vue实例的方法或属性。
此外,Vue还使用虚拟DOM(VNode)来比较新旧视图之间的差异,并仅更新需要更新的部分。这使得Vue在处理大量数据时能够保持高效的性能。

new Vue({
    template:
        `<div>
    <span>{{text1}}</span>
    <span>{{text2}}</span>
    <div>`,
    data: {
        text1: 'name1'
    },
    created() {
        this.text1 = "这是一段文本"
    }
});

text1被修改,所以视图更新,但是text2视图没用到,所以不需要更新,如何实现呢,就需要我们的依赖收集。

// 依赖收集
class Dep {
    constructor() {
        // 存数所有的依赖
        this.deps = []
    }
    // 在deps中添加一个监听器对象
    addDep(dep) {
        this.deps.push(dep)
    }
    // 通知所有监听器去更新视图
    notify() {
        this.deps.forEach((dep) => {
            dep.update()
        })
    }
}
class Watcher {
    constructor() {
        // 在new一个监听器对象时将该对象赋值给Dep.target,在get中会用到
        Dep.target = this
    }
    // 更新视图的方法
    update() {
        console.log("视图更新了!")
    }
}

在这里插入图片描述
我们在增加了一个 Dep 类的对象,用来收集 Watcher 对象。读数据的时候,会触发 reactiveGetter 函数把当前的Watcher 对象(存放在 Dep.target 中)收集到 Dep 类中去。
写数据的时候,则会触发 reactiveSetter 方法,通知Dep 类调用 notify 来触发所有 watcher 对象的update 方法更新对应视图。

constructor(options) {
    this._data = options.data
    this.observer(this._data)
    // 新建一个Watcher观察者对象,这时候Dep.target会指向这个Watcher对象
    new Watcher();
    // 在这里模拟render的过程,为了触发test属性的get函数
    console.log('模拟render,触发test的getter', this._data.test);
}
defineReactive(obj, key, val) {
    const dep = new Dep()
    Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get: function reactiveGetter() {
            // 将Dep.target(即当前的Watcher对象存入Dep的deps中)
            dep.addDep(Dep.target)
            return val
        },
        set: function reactiveSetter(newVal) {
            if (newVal === val) return
            // 在set的时候触发dep的notify来通知所有的Watcher对象更新视图
            dep.notify()
        }
    })
}

编译compile

核心逻辑 获取dom,遍历dom,获取{{}}、k-和@开头的 ,设置响应式。
在这里插入图片描述
目标功能

<body>
<div id="app">
    <body>
    <div id="app">
        <p>{{name}}</p>
        <p k-text="name"></p>
        <p>{{age}}</p>
        <p>
            {{doubleAge}}
        </p>
        <input type="text" k-model="name">
        <button @click="changeName">呵呵</button>
        <div k-html="html"></div>
    </div>
    <script src='./compile.js'></script>
    <script src='./kaikeba-vue.js'></script>
    <script>
        let kaikeba = new KVue({
            el: '#app',
            data: {
                name: "I am test.",
                age: 12,
                html: '<button>这是一个按钮</button>'
            },
            created() {
                console.log('开始啦')
                setTimeout(() => {
                    this.name = '我是蜗牛'
                }, 1500)
            },
            methods: {
                changeName() {
                    this.name = '哈喽,开课吧'
                    this.age = 1
                    this.id = 'xx'
                    console.log(1, this)
                }
            }
        })
    </script>
    </body>

    <p>{{name}}</p>
    <p k-text="name"></p>
    <p>{{age}}</p>
    <p>
        {{doubleAge}}
    </p>
    <input type="text" k-model="name">
    <button @click="changeName">呵呵</button>
    <div k-html="html"></div>
</div>
<script src='./compile.js'></script>
<script src='./kaikeba-vue.js'></script>
<script>
    let kaikeba = new KVue({
        el: '#app',
        data: {
            name: "I am test.",
            age: 12,
            html: '<button>这是一个按钮</button>'
        },
        created() {
            console.log('开始啦')
            setTimeout(() => {
                this.name = '我是蜗牛'
            }, 1500)
        },
        methods: {
            changeName() {
                this.name = '哈喽,开课吧'
                this.age = 1
                this.id = 'xx'
                console.log(1, this)
            }
        }
    })
</script>
</body>

compile.js

class Compile {
    constructor(el, vm) {
        this.$vm = vm
        this.$el = document.querySelector(el)
        if (this.$el) {
            this.$fragment = this.node2Fragment(this.$el)
            this.compileElement(this.$fragment)
            this.$el.appendChild(this.$fragment)
        }
    }

    node2Fragment(el) {
        // 新建文档碎片 dom接口
        let fragment = document.createDocumentFragment()
        let child
        // 将原生节点拷贝到fragment
        while (child = el.firstChild) {
            fragment.appendChild(child)
        }
        return fragment
    }

    compileElement(el) {
        let childNodes = el.childNodes
        Array.from(childNodes).forEach((node) => {
            let text = node.textContent
            // 表达式文本
            // 就是识别{{}}中的数据
            let 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) {
        let nodeAttrs = node.attributes
        Array.from(nodeAttrs).forEach((attr) => {
            // 规定:指令以 v-xxx 命名
            // 如 <span v-text="content"></span> 中指令为 v-text
            let attrName = attr.name // v-text
            let exp = attr.value // content
            if (this.isDirective(attrName)) {
                let dir = attrName.substring(2) // text
                // 普通指令
                this[dir] && this[dir](node, this.$vm, exp)
            }
            if (this.isEventDirective(attrName)) {
                let dir = attrName.substring(1) // text
                this.eventHandler(node, this.$vm, exp, dir)
            }
        })
    }

    compileText(node, exp) {
        this.text(node, this.$vm, exp)
    }

    isDirective(attr) {
        return attr.indexOf('k-') == 0
    }

    isEventDirective(dir) {
        return dir.indexOf('@') === 0
    }

    isElementNode(node) {
        return node.nodeType == 1
    }

    isTextNode(node) {
        return node.nodeType == 3
    }

    text(node, vm, exp) {
        this.update(node, vm, exp, 'text')
    }

    html(node, vm, exp) {
        this.update(node, vm, exp, 'html')
    }

    model(node, vm, exp) {
        this.update(node, vm, exp, 'model')
        let val = vm.exp
        node.addEventListener('input', (e) => {
            let newValue = e.target.value
            vm[exp] = newValue
            val = newValue
        })
    }

    update(node, vm, exp, dir) {
        let updaterFn = this[dir + 'Updater']
        updaterFn && updaterFn(node, vm[exp])
        new Watcher(vm, exp, function (value) {
            updaterFn && updaterFn(node, value)
        })
    }

    // 事件处理
    eventHandler(node, vm, exp, dir) {
        let fn = vm.$options.methods && vm.$options.methods[exp]
        if (dir && fn) {
            node.addEventListener(dir, fn.bind(vm), false)
        }
    }

    textUpdater(node, value) {
        node.textContent = value
    }

    htmlUpdater(node, value) {
        node.innerHTML = value
    }
    
    modelUpdater(node, value) {
        node.value = value
    }
}

入口文件

class KVue {
    constructor(options) {
        this.$data = options.data
        this.$options = options
        this.observer(this.$data)
        // 新建一个Watcher观察者对象,这时候Dep.target会指向这个Watcher对象
        // new Watcher()
        // 在这里模拟render的过程,为了触发test属性的get函数
        console.log('模拟render,触发test的getter', this.$data)
        if (options.created) {
            options.created.call(this)
        }
        this.$compile = new Compile(options.el, this)
    }

    observer(value) {
        if (!value || (typeof value !== 'object')) {
            return
        }
        Object.keys(value).forEach((key) => {
            this.proxyData(key)
            this.defineReactive(value, key, value[key])
        })
    }

    defineReactive(obj, key, val) {
        const dep = new Dep()
        Object.defineProperty(obj, key, {
            enumerable: true,
            configurable: true,
            get() {
                // 将Dep.target(即当前的Watcher对象存入Dep的deps中)
                Dep.target && dep.addDep(Dep.target)
                return val
            },
            set(newVal) {
                if (newVal === val) return
                val = newVal
                // 在set的时候触发dep的notify来通知所有的Watcher对象更新视图
                dep.notify()
            }
        })

    }

    proxyData(key) {
        Object.defineProperty(this, key, {
            configurable: false,
            enumerable: true,
            get() {
                return this.$data[key]
            },
            set(newVal) {
                this.$data[key] = newVal
            }
        })
    }
}

依赖收集 Dep

class Dep {
    constructor() {
        // 存数所有的依赖
        this.deps = []
    }

    // 在deps中添加一个监听器对象
    addDep(dep) {
        this.deps.push(dep)
    }

    depend() {
        Dep.target.addDep(this)
    }

    // 通知所有监听器去更新视图
    notify() {
        this.deps.forEach((dep) => {
            dep.update()
        })
    }
}

监听器

class Watcher {
    constructor(vm, key, cb) {
        // 在new一个监听器对象时将该对象赋值给Dep.target,在get中会用到
        // 将 Dep.target 指向自己
        // 然后触发属性的 getter 添加监听
        // 最后将 Dep.target 置空
        this.cb = cb
        this.vm = vm
        this.key = key
        this.value = this.get()
    }

    get() {
        Dep.target = this
        let value = this.vm[this.key]
        return value
    }

    // 更新视图的方法
    update() {
        this.value = this.get()
        this.cb.call(this.vm, this.value)
    }
}

总结

以上就是Vue.js的源码分析了,希望对大家有所帮助。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值