一篇文章带你彻底搞懂VUE响应式原理

目录
  • 响应式原理图
  • 编译
    • 创建compile类
    • 操作fragment
    • 获取元素节点上的信息
    • 获取文本节点信息
    • 操作fragment
  • 响应式
    • 数据劫持
    • 收集依赖
  • 响应式代码完善
    • Dep类
    • 全局watcher用完清空
    • 依赖的update方法
    • 需要注意的一个地方
  • 双剑合璧
  • 总结

首先上图,下面这张图,即为MVVM响应式原理的整个过程图,我们本篇都是围绕着这张图进行分析,所以这张图是重中之重。

响应式原理图

一脸懵逼?没关系,接下来我们将通过创建一个简单的MVVM响应系统来一步步了解这个上图中的全过程。全文分为两大块,首先介绍实例模板的编译过程,然后详细介绍响应式,这里先介绍编译是为了给介绍响应式奠定基础。

编译

我们把我们创建的这个微型响应系统命名为miniVue,我们按照平常使用Vue的模式,首先创建一个miniVue的实例。

<scirpt>
    const vm = new miniVue({
        el: '#app',
        data: {
            obj: {
                name: "miniVue",
                auth: 'xxx'
            },
            msg: "this is miniVue",
            htmlStr: "<h3>this is htmlStr</h3>"
        },
        methods: {
            handleClick() {
                console.log(this);
            }
        }
    });
</scirpt>

我们根据这个实例,我们可以创建出miniVue的类,这个类中我们肯定要保存该实例所绑定的DOM以及数据对象data。然后我们要开始解析模板,即解析我们所绑定的DOM

class miniVue {
    constructor(options) {
        this.$el = options.el
        this.$data = options.data
        this.$options = options
    }
    if(this.$el) {
        // 解析模板 to Compile
    }
}

这里我们来创建一个compile类来进行解析模板的操作

创建compile类

Compile类是用来解析模板的,所以肯定要传入要解析的DOM。拿到DOM后直接操作这个DOM会导致页面频繁的回流和重绘,所以我们把这个DOM放到一个文档碎片中,然后操作这个文档碎片。操作这个文档碎片的过程中我们需要获取到数据对象data中的属性来填充一些节点的内容,所以我们还需要传入实例对象。最后将操作好的文档碎片追加到原本的DOM上。

class Compile {
    constructor(el, vm) {
        // 判断的原因是因为传入的el有可能是DOM,也有可能是选择器例如‘#app'
        this.el = this.isElementNode(el) ? el : document.querySelector(el)
        this.vm = vm
        // 新建文档碎片存储DOM
        const fragment = this.toFragment(this.el)

        // 操作文档碎片 to handle fragment

        // 将操作好的文档碎片追加到原本的DOM上面
        this.el.appendChild(fragment)
    }

    // 判断是否为元素节点
    isElementNode(node) {
        return node.nodeType === 1
    }
    // dom碎片化
    toFragment(el) {
        const f = document.createDocumentFragment()
        f.appendChild(el.clone(true))
    }
}
// 上面的miniVue实例相应的改为
class miniVue {
    constructor(options) {
        this.$el = options.el
        this.$data = options.data
        this.$options = options
    }
    if(this.$el) {
        // 解析模板 to Compile
        new Compile(this.$el, this) // 这里的this就是miniVue实例
    }
}

操作fragment

操作保存好的文档碎片,我们可以专门定义一个函数,然后把文档碎片通过参数传入进来。

操作文档碎片我们又可以分为两步。因为针对文本节点元素节点,我们需要进行不同的操作,所以我们在遍历所有节点后的第一步应该先判断它是元素节点还是文本节点

handleFragment(fragment) {
    // 获取文档碎片的子节点
    const childNodes =  fragment.childNodes
    // 遍历所有子节点
    [...childNodes].forEach((child) => {
        if(this.isElementNode(child)) {
            // 元素节点
            this.compileElement(child)
        } else {
            // 文本节点
            this.compileText(child)
        }

        // 递归遍历
        if(child.childNodes && child.childNodes.length) {
            handleFragment(child)
        }
    })
}

// 同样的我们需要完善一下compile的构造函数
constructor(el, vm) {
    this.el = this.isElementNode(el) ? el : document.querySelector(el)
    this.vm = vm
    // 新建文档碎片存储DOM
    const fragment = this.toFragment(this.el)

    // 操作文档碎片 to handle fragment
    this.handleFragment(fragment)

    // 将操作好的文档碎片追加到原本的DOM上面
    this.el.appendChild(fragment)
}

获取元素节点上的信息

元素节点上的信息主要就是这个元素节点上面的属性,然后拿到绑定在节点上面的vue指令,分离出来vue指令的名称和值(注意:@开的头的指令需要额外处理)。然后还有很重要的一步,那就是去掉这些指令(这些指令updater是不认的)

compileElement(node) {
    const attrs = node.attributes
    // 遍历节点上的全部属性
    [...attrs].forEach(({name, value}) => {
        // 分类看指令以什么开头
        if(this.headWithV(name)) {
            // 以v开头
            const [,directive] = name.split("-") //分离出具体指令
            const [dir,event] = directive.split(":") // 考虑v-on的情况 例如v-on:click

            // 将指令的名称、值、node节点、整个vm实例、事件名(如果有的话)一起传给最后真正操作的node的函数
            handleNode[dir](node, value, this.vm, event)

        }else if(this.headWithoutV(name)) {
            // 以@开投
            const [, event] = name.split("@")
            // 和上面一样,但是指令名字是确定的,为“on” 因为@是v-on的语法糖
            handleNode["on"](node, value, this.vm, event)
        }
    })
}
​
headWithV(name) {
    return name.startsWith("v-");
}
headWithoutV(name){
    return name.startsWith("@");
}

获取文本节点信息

文本节点和元素节点类似,只不过文本节点的信息存储在节点的textContent里面,主要用来替换mustache语法,(双大括号插值)需要通过正则识别额外处理。如果是正常的文本节点,则不进行处理(原模原样展示即可)。

compileText(node) {
    const content = node.textContent
    if(!/{{(.+?)}}/.test(content)) return
    // 识别到是mustache语法 处理方法其实和v-text一样
    handleNode["text"](node, content,this.vm)
}

操作fragment

前面铺垫了这么多,终于到了操作文档碎片这一步了。按照上面的思路,handleNode应该是一个对象,里面有多个属性对应不同的指令的处理方法。

// node--操作的node节点  exp--指令的值(或者是mustache语法内部插入的内容)  vm--vm实例  event--事件名称
const handleNode = {
    // v-html
    html(node, exp, vm) {
        // 去vm实例中找到这个表达式所对应的值
        const value = this._get(vm, exp)
        // 更新node
        updater.htmlUpdater(node, value)
    },
    // v-model
    model(node, exp, vm) {
        // 同html
        const value = this._get(vm, exp)
        updater.modelUpdater(node, value)
    },
    // v-on
    on(node, exp, vm, event) {
        // v-on特殊一点,我们需要为该node绑定事件监听器
        const listener = vm.$options.methods && vm.$options.methods[exp] // 获取监听器的回调
        // 绑定监听器,注意回调绑定使用bind把this指向vm实例,false代表事件冒泡时触发监听器
        node.addEventListener(event, listener.bind(this), false)
    },
    // v-text
    text(node, exp, vm) {
        // v-text是最复杂的,需要考虑两种情况,一种是通过v-text指令操作node,另一种则是通过mustache语法操作node,需分类
        let value
        if(exp.indexOf("{{") !== -1) {
            // mustache语法操作node
            // 捕捉到所有的mustache语法,将其整个替换为vm实例中属性对应的值
            // 拿我们最初初始化实例的一个数据举例:{{obj.auth}} -- 'xxx'
            value = exp.replace(/{{(.+?)}}/g, this._get(vm, exp))
        }else {
            // v-text操作node
            value = this._get(vm, exp)
        }
        // 更新node
        updater.textUpdater(node, value);
    },
}
​
// 根据表达式去数据对象里面获取值
_get(vm, exp) {
    const segments = exp.split('.')
    // 这里使用reduce是为了获取嵌套对象内部属性的值,不熟悉的话去补一补reduce
    // 比如data.a.b.c,那么每次遍历的值为data[a],data[a][b],最终结果是data[a][b][c]
    segments.reduce((pre, key) => {
        return pre[key]
    }, vm.$data)
}

// 更新node (终于到了更新node这一步)
const updater = {
    textUpdater(node, value) {
        node.textContent = value;
    },
    htmlUpdater(node, value) {
        node.innerHTML = value;
    },
    modelUpdater(node, value){
        node.value = value;
    }
}

至此我们已经实现了vue实例模板编译,并更新了node,其实到现在我们还没有涉及到响应式这三个字。下面我们开始介绍本篇的核心,即vue是如何实现响应式的。

响应式

数据劫持

关键点:Object.defineProperty(具体用法参考MDN)

主要目的:为data中每个属性添加gettersetter,然后在gettersetter中进行数据劫持

思路很简单,其实就是从最外层的data层开始遍历属性,通过Object.defineProperty给这些属性都添加上gettersetter,需要注意对象的嵌套,所以需要使用递归来为嵌套的属性添加gettersetter

function observe(data) {
    if(typeof data !== 'object') return
    Object.keys(data).forEach((key) => {
        defineReactive(data, key, data[key])
    })
}

function defineReactive(data, key, value) {
    // 递归子属性
    observe(value)

    Object.defineProperty(data, key, {
        get() {
            // 数据劫持 在这个地方进行相关操作
            return value
        }
        set(newVal) {
            if(newVal == value) return
            value = newVal
            // 为新数据添加getter和setter
            observe(newVal)
            // 数据劫持 在这个地方进行相关操作
        }
    })
}

收集依赖

依赖其实说白了,就是数据的依赖,data中的某个属性,可能在DOM中好几个地方进行了使用,那DOM中使用到该属性的地方就都会产生一个对于该属性的依赖,也就是watcher。当该属性的值发生了变化,那么就可以通知watcher来使得页面中使用到这个属性的地方进行视图更新。为每个属性绑定watcher的过程其实就是订阅,反过来,当属性的值发生了变化,通知所有watcher的过程就是发布

下面我们来将依赖抽象化,即实现watcher

class Watcher {
    // data--最外层数据对象  exp--表达式  cb--数据更新后需要执行的回调
    // 通过data和exp可以获取watcher所依赖属性的具体值
    constructor(data, exp, cb) {
	this.data = data
        this.exp = exp
        this.cb = cb
        // 每次初始化watcher实例时,对依赖属性进行订阅
        this.value = this.subscribe()
    }
    // 订阅
    subscribe() {
        // 获取依赖属性的值
	const value = _get(this.data, this.exp)
        return value
    }
    // 更新
    update() {
        // 获取新值
	this.value = _get(this.data, this.exp)
        cb()
    }
}

// 根据表达式去数据对象里面获取值 其实上面已经定义过一个了,功能是一样的,这里重复定义加深一下印象,也方便阅读
function _get(obj, exp) {
    const segments = exp.split('.')
    // 这里使用reduce是为了获取嵌套对象内部属性的值,不熟悉的话去补一补reduce
    // 比如data.a.b.c,那么每次遍历的值为data[a],data[a][b],最终结果是data[a][b][c]
    segments.reduce((pre, key) => {
	return pre[key]
    }, obj)
}

依赖我们大概清楚了,但是我们上面讲,需要把一个属性全部的依赖(watcher)收集起来,所以我们该如何收集依赖呢?

首先我们先想第一个问题,一个属性会有一个或者好多个watcher,我们应该如何保存这些watcher呢,这个我们很容易想到,我们可以专门拿一个数组保存一个属性的全部watcher,我们把这个数组命名为dep(dependency)。

第二个问题,我们应该什么时候进行收集watcher的操作呢。还记得我们上面提到的订阅吗,我们每次初始化watcher时,会为该watcher订阅属性,订阅的过程中我们会首先获取这个属性的值,这时就可以发挥数据劫持的作用了,获取这个属性值的时候,我们就会进到这个属性的getter方法中,所以我们可以在这个时候完成收集watcher的操作。

第三个问题,我们说watcher的作用其实就是监听到订阅属性的变化(即监听发布),监听到变化后执行其update方法,即执行更新回调,来更新视图。那么我们怎样才能让watcher监听到“发布”呢,这时我们又需要用到数据劫持,即在setter中通知这个属性所有的watcher

function defineReactive(data, key, value) {
    // 新建用于存储watcher的数据
    const dep = []

    // 递归子属性
    observe(value)

    Object.defineProperty(data, key, {
        get() {
            // 数据劫持 在这个地方进行相关操作
            dep.push(watcher) // 收集依赖
                        return value
        }
        set(newVal) {
            if(newVal == value) return
            value = newVal
            // 为新数据添加getter和setter
            observe(newVal)

            // 数据劫持 在这个地方进行相关操作
            dep.notify() // 通知依赖
    	}
    })
}

现在我觉得我有必要理一下这个依赖收集的全过程。首先页面初次渲染的时候,会遇到我们在data中定义的属性(注意:此时属性上面已经定义好getter和setter了),遇到属性后会初始化一个watcher实例,在此过程中watcher实例会获取这个属性的值,于是会进入到这个属性的getter中,于是我们通过数据劫持来收集这个watcher。那么又出现了一个问题,我们此时在getter中,如何获取到初始化的watcher实例呢,也就是dep.push的时候,其实我们是没有办法直接拿到这个watcher的。因此,我们需要在初始化watcher的时候,把这个watcher放到全局,比如window.target

subscribe() {
    // 获取依赖属性的值
    window.target = this // 这里的this即为此时初始化的watcher实例
	const value = _get(this.data, this.exp)
    return value
}
function defineReactive(data, key, value) {
    // 新建用于存储watcher的数据
    const dep = []
    observe(value)
    Object.defineProperty(data, key, {
        get() {
            dep.push(window.target) // 改为window.target

            return value
        }
        set(newVal) {
            if(newVal == value) return
            value = newVal
            observe(newVal)

            dep.notify()
    	}
    })
}

响应式代码完善

Dep类

我们可以讲dep数组抽象为一个类

class Dep {
    constructor() {
        this.subs = []
    }
    // 收集依赖
    addSub(watcher) {
        this.subs.push(watcher)
    }
    // 通知依赖
    notify() {
        [...this.subs].forEach((watcher) => {
            watcher.update()
        })
    }
}

defineReactive也要做出相应的调整

function defineReactive(data, key, value) {
    // 新建用于存储watcher的数据
    const dep = new Dep()
    observe(value)
    Object.defineProperty(data, key, {
        get() {
            // 收集依赖
            dep.addSub(window.target)
            return value
        }
        set(newVal) {
            if(newVal == value) return
            value = newVal
            observe(newVal)
            // 通知依赖
            dep.notify()
    	}
    })
}

全局watcher用完清空

下面有一个场景,我们在访问到data中的一个属性a后,实例化了一个watcher1,此时在实例化这个watcher1的过程中,会把window.target设置为watcher1,之后我们在没有实例化其他watcher的情况下直接去访问其他的属性,例如属性b,那么属性b中的getter会直接把watcher1推入到它的依赖数组中。这样是不合理的,所以我们每次将watcher推入到依赖数组中后,要将这个watcher从全局中收回。(window.target这里改成Dep.target了,其实都是一样的)

subscribe() {
    Dep.target = this // 这里的this即为此时初始化的watcher实例
    const value = _get(this.data, this.exp)
    Dep.target = null // 清空暴露在全局中的watcher
    return value
}

// 同时在收集依赖时添加一层过滤
addSub(watcher) {
    if(watcher) {
        this.subs.push(watcher)
    }
}

依赖的update方法

上面我们在watcher的update方法中更新了值并且执行了数据更新后的回调,为了让丰富回调中的操作,我们可以将回调的this指向我们的最外层数据对象,这样在回调中就可以通过this任意获取数据对象中的其他属性,并且将更新之前的旧值和新值一起传入到update里面

update() {
  const oldValue = this.value // 获取旧值
  this.value = parsePath(this.data, this.expression) // 获取新值
  this.cb.call(this.data, this.value, oldValue)
}

需要注意的一个地方

下面是watcher中获取所依赖属性值的方法,这里需要说明一下,对于存在对象嵌套的情况,每一层属性的依赖数组中都会添加这个watcher,想不明白的话可以看一下下面的注解。

// 根据表达式去数据对象里面获取值
function _get(obj, exp) {
    const segments = exp.split('.')
    /*
    比如data.a.b.c,那么每次遍历的值为data[a],data[a][b],最终结果是data[a][b][c]
    遍历到data[a]、data[a][b]时,肯定会去访问这两个属性的值,于是会进入到这两个属性的getter里面
    所以这个watcher不仅仅会被添加到最内层属性的getter中,中间每一层属性的getter中都会有这个watcher
    即如果data[a]的值发生了变化,也会通知这个watcher去更新视图
    */
    segments.reduce((pre, key) => {
        return pre[key]
    }, obj)
}

双剑合璧

怎样将上面的编译响应式整合到一起形成一个完整的具有响应式的miniVue类呢。其实很简单,从我们最上面那张图就可以看出来。总结一下就两点,在我们通过各种指令操作node节点的时候,同时初始化watcher,另一点即为初始化watcher时指定的回调内部需要执行updater里面对应的方法来更新视图

两点分别对应下图的这两根线:

这样是不是就清晰多了。至此”双剑合璧“完成,下面贴一下合璧后的代码(只放需要合成的部分,这样更清晰一点)

// node--操作的node节点  exp--指令的值(或者是mustache语法内部插入的内容)  vm--vm实例  event--事件名称
const handleNode = {
    // v-html
    html(node, exp, vm) {
        const value = this._get(vm, exp)
        // 新建watcher实例,并绑定更新回调
        new Watcher(vm, exp, (newVal, oldVal) => {
            // 这里是所依赖数据更新以后更新视图
            this.updater.htmlUpdater(node, newVal);
        })
        // 这里是编译的时候更新视图
        updater.htmlUpdater(node, value)
    },
    // v-model
    model(node, exp, vm) {
        const value = this._get(vm, exp)
        // 新建watcher实例,并绑定更新回调
        new Watcher(vm, exp, (newVal, oldVal) => {
            this.updater.modelUpdater(node, newVal);
        });
        updater.modelUpdater(node, value)
    },
    // v-on
    on(node, exp, vm, event) {
        // watcher只针对属性 v-on这里不会生成watcher(方法名也没什么好监听的,一般也不会操作方法名让方法名发生变化)
        const listener = vm.$options.methods && vm.$options.methods[exp]
        node.addEventListener(event, listener.bind(this), false)
    },
    // v-text
    text(node, exp, vm) {
        let value
        if(exp.indexOf("{{") !== -1) {
            // mustache语法操作node
            value = exp.replace(/{{(.+?)}}/g, this._get(vm, exp))
            
        }else {
            // v-text操作node
            value = this._get(vm, exp)
        }
        // 新建watcher实例,并绑定更新回调
        new Watcher(vm, exp, (newVal, oldVal) => {
            this.updater.textUpdater(node, newVal);
        });
        updater.textUpdater(node, value);
    },
}
_get(vm, exp) {
    const segments = exp.split('.')
    segments.reduce((pre, key) => {
        return pre[key]
    }, vm.$data)
}
​
const updater = {
    textUpdater(node, value) {
        node.textContent = value;
    },
    htmlUpdater(node, value) {
        node.innerHTML = value;
    },
    modelUpdater(node, value){
        node.value = value;
    }
}

最后的最后,修改一下我们最开始定义miniVue类的构造函数

class miniVue {
    constructor(options) {
        this.$el = options.el
        this.$data = options.data
        this.$options = options
    }
    if(this.$el) {
        // 添加数据劫持
        this.observe()
        // 编译
        new Compile(this.$el, this);
    }
}

大功告成。

总结

如果你是第一次阅读本文,看到最后应该还是会感觉到些许混乱。下面允许我为大家概括一下整体的流程。建议结合我们最上方的中心图。

  • 1.初始化minivue实例 执行其构造函数,首先对实例的数据对象data中全部属性添加数据劫持功能(getterand setter
  • 2.开始编译实例绑定的模板。
  • 3.首先编译做准备,创建compile类,拿到模板的整个DOM对象,遍历其子节点,获取到每个子节点上的信息,这些信息中凡是引用过vm实例data中的属性的,一律都新增一个watcher实例
  • 4.初始化watcher实例的时候,会访问这个属性,然后进入这个属性的getter中,在getter中,将这个watcher添加到这个属性的Dep类中
  • 5.最后更新node,至此初始化编译完成
  • 6.当data中某一个属性的值发生变化,会进入这个属性的setter中,setter会通知该属性的Dep
  • 7.Dep类会通知存储的所有相关watcher进行更新,于是这些watcher分别执行自己update中的回调。回调即会更新node

到此这篇关于一篇文章带你彻底搞懂VUE响应式原理的文章就介绍到这了,更多相关 VUE响应式原理内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 详解vue数据响应式原理之数组

    目录 src/core/observer/index.js src/core/observer/array.js arrayMethods 总结 src/core/observer/index.js src/core/observer/array.js arrayMethods 当data的数组对象中本来没有某个属性,然后点击按钮动态增加某个属性的时候,此时此属性是没有get和set的,也就是没有响应式机制,如果想要让你动态增加的某个属性有响应式变化,那么就直接在数据的源头给他初始化这个属性,具

  • 详解Vue3的响应式原理解析

    目录 Vue2响应式原理回顾 Vue3响应式原理剖析 嵌套对象响应式 避免重复代理 总结 Vue2响应式原理回顾 // 1.对象响应化:遍历每个key,定义getter.setter // 2.数组响应化:覆盖数组原型方法,额外增加通知逻辑 const originalProto = Array.prototype const arrayProto = Object.create(originalProto) ;['push', 'pop', 'shift', 'unshift', 'splic

  • VUE响应式原理的实现详解

    目录 总结 前言 相信vue学习者都会发现,vue使用起来上手非常方便,例如双向绑定机制,让我们实现视图.数据层的快速同步,但双向绑定机制实现的核心数据响应的原理是怎么样的呢,接下来让我们开始介绍: function observer(value) { //给所有传入进来的data 设置一个__ob__对象 一旦value有__ob__ 说明该value已经做了响应式处理 Object.defineProperty(value, '__ob__', { value: this, //当前实例 也

  • Vue 响应式系统依赖收集过程原理解析

    目录 背景 目标 源码解读 入口函数:observe class Observer Observe 如何处理数组 Observe 如何处理对象 class Dep Dep.target class Watcher Watcher 的应用 何时触发依赖收集? 数据变化时,如何进行更新? 总结 参考资料 背景 在 Vue 的初始化阶段,_init 方法执行的时候,会执行 initState(vm) ,它的定义在 src/core/instance/state.js 中.在初始化 data 和 pro

  • Vue响应式系统的原理详解

    目录 vue响应式系统的基本原理 1.回顾一下Object.defineProperty的用法 2.实战1:使用 Object.defineProperty 对 person的age属性 进行监听 3.数据代理 4.vue中实现响应式思路 总结 1.Vue中的数据代理: 2.Vue中数据代理的好处: 3.基本原理: 4.vue中实现响应式思路 vue响应式系统的基本原理 我们使用vue时,对数据进行操作,就能影响对应的视图.那么这种机制是怎么实现的呢? 思考一下,是不是就好像我们对数据的操作 被

  • vue3.0响应式函数原理详细

    目录 1.reactive 2.ref 3.toRefs 4.computed 前言: Vue3重写了响应式系统,和Vue2相比底层采用Proxy对象实现,在初始化的时候不需要遍历所有的属性再把属性通过defineProperty转换成get和set.另外如果有多层属性嵌套的话只有访问某个属性的时候才会递归处理下一级的属性所以Vue3中响应式系统的性能要比Vue2好. 接下来我们自己实现Vue3响应式系统的核心函数(reactive/ref/toRefs/computed/effect/trac

  • Vue响应式原理的示例详解

    Vue 最独特的特性之一,是非侵入式的响应系统.数据模型仅仅是普通的 JavaScript 对象.而当你修改它们时,视图会进行更新.聊到 Vue 响应式实现原理,众多开发者都知道实现的关键在于利用 Object.defineProperty , 但具体又是如何实现的呢,今天我们来一探究竟. 为了通俗易懂,我们还是从一个小的示例开始: <body> <div id="app"> {{ message }} </div> <script> v

  • 一篇文章带你彻底搞懂VUE响应式原理

    目录 响应式原理图 编译 创建compile类 操作fragment 获取元素节点上的信息 获取文本节点信息 操作fragment 响应式 数据劫持 收集依赖 响应式代码完善 Dep类 全局watcher用完清空 依赖的update方法 需要注意的一个地方 双剑合璧 总结 首先上图,下面这张图,即为MVVM响应式原理的整个过程图,我们本篇都是围绕着这张图进行分析,所以这张图是重中之重. 响应式原理图 一脸懵逼?没关系,接下来我们将通过创建一个简单的MVVM响应系统来一步步了解这个上图中的全过程.

  • 一篇文章带你彻底搞懂Redis 事务

    目录 Redis 事务简介 Redis 事务基本指令 实例分析 Redis 事务与 ACID 总结 Redis 事务简介 Redis 只是提供了简单的事务功能.其本质是一组命令的集合,事务支持一次执行多个命令,在事务执行过程中,会顺序执行队列中的命令,其他客户端提交的命令请求不会插入到本事务执行命令序列中.命令的执行过程是顺序执行的,但不能保证原子性.无法像 MySQL 那样,有隔离级别,出了问题之后还能回滚数据等高级操作.后面会详细分析. Redis 事务基本指令 Redis 提供了如下几个事

  • 一篇文章带你轻松搞懂Golang的error处理

    目录 Golang中的error error的几种玩法 哨兵错误 自定义错误类型 Wrap error Golang1.13版本error的新特性 errors.UnWrap() errors.Is() errors.As() error处理最佳实践 优先处理error 只处理error一次 不要反复包装error 不透明的错误处理 简化错误处理 bufio.scan errWriter 何时该用panic 小补充 总结 Golang中的error Golang中的 error 就是一个简单的接

  • 一文带你搞懂Spring响应式编程

    目录 1. 前言 1.1 常用函数式编程 1.2 Stream操作 2. Java响应式编程 带有中间处理器的响应式流 3. Reactor 3.1 Flux & Mono 3.2 Flux Mono创建与使用 4. WebFlux Spring WebFlux示例 基于注解的WebFlux 基于函数式编程的WebFlux Flux与Mono的响应式编程延迟示例 总结 哈喽,大家好,我是指北君. 相信响应式编程经常会在各种地方被提到.本篇就为大家从函数式编程一直到Spring WeFlux做一次

  • Vue响应式原理及双向数据绑定示例分析

    目录 前言 响应式原理 双向数据绑定 前言 之前公司招人,面试了一些的前端同学,因为公司使用的前端技术是Vue,所以免不了问到其响应式原理和Vue的双向数据绑定.但是这边面试到的80%的同学会把两者搞混,通常我要是先问响应式原理再问双向数据绑定原理,来面试的同学大都会认为是一回事,那么这里我们就说一下二者的区别. 响应式原理 是Vue的核心特性之一,数据驱动视图,我们修改数据视图随之响应更新,就很优雅~ Vue2.x是借助Object.defineProperty()实现的,而Vue3.x是借助

  • Vue响应式原理Observer、Dep、Watcher理解

    开篇 最近在学习Vue的源码,看了网上一些大神的博客,看起来感觉还是蛮吃力的.自己记录一下学习的理解,希望能够达到简单易懂,不看源码也能理解的效果

  • 详细分析vue响应式原理

    前言 响应式原理作为 Vue 的核心,使用数据劫持实现数据驱动视图.在面试中是经常考查的知识点,也是面试加分项. 本文将会循序渐进的解析响应式原理的工作流程,主要以下面结构进行: 分析主要成员,了解它们有助于理解流程 将流程拆分,理解其中的作用 结合以上的点,理解整体流程 文章稍长,但大部分是代码实现,还请耐心观看.为了方便理解原理,文中的代码会进行简化,如果可以请对照源码学习. 主要成员 响应式原理中,Observe.Watcher.Dep这三个类是构成完整原理的主要成员. Observe,响

  • vue响应式原理与双向数据的深入解析

    了解object.defineProperty 实现响应式 清楚 observe/watcher/dep 具体指的是什么 了解 发布订阅模式 以及其解决的具体问题 在Javascript里实现数据响应式一般有俩种方案,分别对应着vue2.x 和 vue3.x使用的方式,他们分别是: 对象属性拦截 (vue2.x) Object.defineProperty 对象整体代理 (vue3.x) Proxy 提示:以下是本篇文章正文内容,下面案例可供参考 vue-响应式是什么? Vue 最独特的特性之一

  • Vue响应式原理模拟实现原理探究

    目录 前置知识 数据驱动 数据响应式的核心原理 Vue 2.x Vue 3.x 发布订阅和观察者模式 发布/订阅模式 观察者模式 Vue响应式原理模拟实现 Vue Observer对data中的属性进行监听 Compiler Watcher Dep 测试代码 前置知识 数据驱动 数据响应式——Vue 最标志性的功能就是其低侵入性的响应式系统.组件状态都是由响应式的 JavaScript 对象组成的.当更改它们时,视图会随即自动更新. 双向绑定——数据改变,视图改变:视图改变,数据也随之改变 数据

随机推荐