vue响应式原理源码:带你一步精通vue

约2万字-Vue源码解读汇总篇(续更) 未完待续...Vue源码解读:06Vue3探索篇Vue源码解读:05生命周期篇Vue源码解读:04模板编译篇Vue源码解读:03虚拟Dom篇Vue源码解读:02变化侦测篇Vue源码解读:01核心思想篇Vue源码解读:前言篇从写下的代码,到用户见到界面,大致经历以下几个流程:模板字符串=》ast树=》render函数=》vnode=》用户见到的界面。将开发者写下的模板字符串,经过一些列处理,生成render函数的这一过程就是模板编译。简单地说,模板编译解析就是为了得到render函数。 阅读详情

学过 vue 如果不了解响应式的原理,怎么能说自己熟练使用 vue,要是没有写过一个简易版的 vue 怎么能说自己精通vue,这篇文章通过300多行代码,带你写一个简易版的 vue,主要实现 vue 数据响应式 (数据劫持结合发布者-订阅者)、数组的变异方法、编译指令,数据的双向绑定的功能。

本文需要有一定 vue 基础,并不适合新手学习。在文章最后附有vue学习链接。因为本文用到了很多知识点,在文章最后也有相关知识点链接和 vue 源码地址,大家自己到文章最后看哦~

文章较长,且有些难度,建议大家,找一个安静的环境,并在看之前沐浴更衣,保持编程的神圣感。下面是实现的简易版 vue 的源码地址,一定要先下载下来!因为文章中的并非全部的代码。

github 源码地址:https://github.com/young-monk/myvue.git

在开始学习之前,我们先来了解一下什么是 MVVM ,什么是数据响应式。

我们都知道 vue 是一个典型的 MVVM 思想,由数据驱动视图。

那么什么是 MVVM 思想呢?

MVVM 是 Model-View-ViewModel,是把一个系统分为了模型( model )、视图( view )和 view-model 三个部分。

vue 在 MVVM 思想下,view 和model 之间没有直接的联系,但是 view 和 view-model 、model 和 view-model之间时交互的,当 view 视图进行 dom 操作等使数据发生变化时,可以通过 view-model 同步到 model 中,同样的 model 数据变化也会同步到 view 中。

那么实现数据响应式都有什么方法呢?

1、发布者-订阅者模式:当一个对象(发布者)状态发生改变时,所有依赖它的对象(订阅者)都会得到通知。通俗点来讲,发布者就相当于报纸,而订阅者相当于读报纸的人。

2、脏值检查:通过存储旧的数据,和当前新的数据进行对比,观察是否有变更,来决定是否更新视图。angular.js就是通过脏值检查的方式。最简单的实现方式就是通过 setInterval() 定时轮询检测数据变动,但这样无疑会增加性能,所以,angular 只有在指定的事件触发时进入脏值检测。

3、数据劫持:通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时触发相应的方法。

vue 是如何实现数据响应式的呢?

vue.js 则是通过数据劫持结合发布者-订阅者模式的方式。

(图片来自网络)

当执行 new Vue() 时,Vue 就进入了初始化阶段,vue会对指令进行解析(初始化视图,增加订阅者,绑定更新函数),同时通过Obserber 会遍历数据并通过Object.defineProperty 的 getter 和 setter 实现对的监听, 当数据发生变化的时候,Observer 中的 setter 方法被触发,setter 会立即调用Dep.notify(),Dep 开始遍历所有的订阅者,并调用订阅者的 update 方法,订阅者收到通知后对视图进行相应的更新。

我来依次介绍一下图中的重要的名词:

Observer :数据监听器,能够对数据对象的所有属性进行监听,如有变动可拿到最新值并通知订阅者,内部采用Object.defineProperty 的 getter 和 setter 来实现

Compile :指令解析器,它的作用对每个元素节点的指令进行扫描和解析,根据指令模板替换数据,以及绑定相应的更新函数

Dep :订阅者收集器或者叫消息订阅器都可以,它在内部维护了一个数组,用来收集订阅者,当数据改变触发 notify 函数,再调用订阅者的 update 方法

Watcher :订阅者,它是连接 Observer 和 Compile 的桥梁,收到消息订阅器的通知,更新视图

Updater:视图更新

所以我们想要实现一个vue响应式,需要完成 数据劫持、依赖收集、 发布者订阅者模式。

下面我来介绍我模仿源码实现的功能:

  1. 数据的响应式、双向绑定,能够对数据对象的所有属性进行监听,如有变动可拿到最新值并通知订阅者
  2. 解析 vue 常用的指令 v-html,v-text,v-bind,v-on,v-model,包括( @ 和 : )
  3. 数组变异方法的处理
  4. 在 vue 中使用 this 访问或改变 data 中的数据

我们想要完成以上的功能,需要实现如下类和方法:

  1. 实现Observer类:对所有的数据进行监听
  2. 实现 array 工具方法:对变异方法的处理
  3. 实现 Dep 类:维护订阅者
  4. 实现 Watcher 类:接收 Dep 的更新通知,用于更新视图
  5. 实现 Compile 类:用于对指令进行解析
  6. 实现一个 compileUtils 工具方法,实现通过指令更新视图、绑定更新函数Watcher
  7. 实现this. d a t a 代 理 : 实 现 对 t h i s . data代理:实现对 this. data代理:实现对this.data 代理,可以直接在vue中使用 this 获取当前数据

我是使用了webpack作为构建工具来协同开发的,所以在我实现的vue响应式中会用到ES6模块化,webpack的相关知识。知识链接在文章最后。

1、实现 Observer 类

我们都知道要用 Obeject.defineProperty() 来监听属性的数据变化,我们需要对 Observer 的数据对象进行递归遍历,包括子属性对象的属性,都加上 setter 和 getter ,这样的话,当给这个对象的某个值赋值,就会触发 setter,那么就能监听到了数据变化。当然我们在新增加数据的时候,也要对新的数据对象进行递归遍历,加上 setter 和 getter 。

但我们要注意数组,在处理数组时并不是把数组中的每一个元素都加上 setter 和 getter ,我们试想一下,一个从后端返回的数组数据是非常庞大的,如果为每个属性都加上 setter和getter ,性能消耗是十分巨大的。我们想要得到的效果和所消耗的性能不成正比,所以在数组方面,我们通过对数组的7 个变异方法来实现数据的响应式。只有通过数组变异方法来修改和删除数组时才会重新渲染页面。

那么监听到变化之后是如何通知订阅者来更新视图的呢?我们需要实现一个Dep(消息订阅器),其中有一个 notify()方法,是通知订阅者数据发生了变化,再让订阅者来更新视图。

我们怎么添加订阅者呢?我们可以通过 new Dep(),通过 Dep 中的 addSaubs() 方法来添加订阅者。我们来看一下具体代码。

我们首先需要声明一个Observer类,在创建类的时候,我们需要创建一个消息订阅器,判断一下是否是数组,如果是数组,我们便改造数组,如果是对象,我们便需要为对象的每一个属性都加入 setter和getter 。

import { arrayMethods } from './array' //数组变异方法处理 
class Observer {
  constructor(data) {
    //用于对数组进行处理,存放数组的观察者watcher
    this.dep = new Dep()
    if (Array.isArray(data)) {
      //如果是数组,使用数组的变异方法
      data.__proto__ = arrayMethods
      //把数组数据添加 __ob__ 一个Observer,当使用数组变异方法时,可以更新视图
      data.__ob__ = this
      //给数组的每一项添加数据劫持(setter/getter处理)
      this.observerArray(data)
    } else {
      //非数组数据添加数据劫持(setter/getter处理)
      this.walk(data)
    }
  }
}

在上面,我们给 data 的__proto__原型链重新赋值,我们来看一下 arrayMethods 是什么,arrayMethods 是 array.js 文件中,抛出的一个新的 Array 原型

// 获取Array的原型链
const arrayProto = Array.prototype;
// 重新创建一个含有对应原型的对象,在下面称为新Array
const arrayMethods = Object.create(arrayProto);
// 处理7个数组变异方法
['push', 'pop', 'shift', 'unshift', 'reverse', 'sort', 'splice'].forEach(ele => {
    //修改新Array的对应的方法
    arrayMethods[ele] = function () {
        // 执行数组的原生方法,完成其需要完成的内容
        arrayProto[ele].call(this, ...arguments)
        // 获取Observer对象
        const ob = this.__ob__
        // 更新视图
        ob.dep.notify()
    }
})
export {
    arrayMethods
}

此时呢,我们就拥有了数组的变异方法,我们还需要通过 observerArray 方法为数组的每一项添加 getter 和setter ,注意,此时的每一项只是最外面的一层,并非递归遍历。

//循环遍历数组,为数组每一项设置setter/getter
observerArray(items) {
    for (let i = 0; i < items.length; i++) {
      this.observer(items[i])
    }
}

如果是一个对象的话,我们就要对 对象 的每一个属性递归遍历,通过walk() 方法

walk(data) {
	//数据劫持
    if (data && typeof data === "object") {
      for (const key in data) {
        //绑定setter和getter
        this.defineReactive(data, key, data[key])
      }
    }
}

在上面的调用了 defineReactive() ,我们来看看这个方法是干什么的?这个方法就是设置数据劫持的,每一行都有注释。

//数据劫持,设置 setter/getteer
  defineReactive(data, key, value) {
    //如果是数组的话,需要接受返回的Observer对象
    let arrayOb = this.observer(value)
    //创建订阅者/收集依赖
    const dep = new Dep()
    //setter和getter处理
    Object.defineProperty(data, key, {
      //可枚举的
      enumerable: true,
      //可修改的
      configurable: false,
      get() {
        //当 Dep 有 watcher 时, 添加 watcher
        Dep.target && dep.addSubs(Dep.target)
        //如果是数组,则添加上数组的观察者
        Dep.target && arrayOb && arrayOb.dep.addSubs(Dep.target)
        return value
      },
      set: (newVal) => {
        //新旧数据不相等时更改
        if (value !== newVal) {
          //为新设置的数据添加setter/getter
          arrayOb = this.observer(newVal);
          value = newVal
          //通知 dep 数据发送了变化
          dep.notify()
        }
      }
    })
  }
}

我们需要注意的是,在上面的图解中,在 Observer 中,如果数据发生变化,会通知消息订阅器,那么在何时绑定消息订阅器呢?就是在设置 setter 和 getter 的时候,创建一个Dep,并为Dep添加订阅者,Dep.target && dep.addSubs(Dep.target),通过调用 dep 的 addSubs 方法添加订阅者

2、实现 Dep

Dep是消息订阅器,它的作用就是维护一个订阅者数组,当数据发送变化是,通知对应的订阅者,Dep中有一个notify()方法,作用就是通知订阅者,数据发送了变化

// 订阅者收集器
export default class Dep {
    constructor() {
        //管理的watcher的数组
        this.subs = []
    }
    addSubs(watcher) {
        //添加watcher
        this.subs.push(watcher)
    }
    notify() {
        //通知watcher更新dom
        this.subs.forEach(w => w.update())
    }
}

3、实现 watcher

Watcher就是订阅者,watcher是 Observer 和 Compile 之间通信的桥梁,当数据改变时,接收到 Dep 的通知(Dep 的notify()方法),来调用自己的update()方法,触发 Compile 中绑定的回调,达到更新视图的目的。

import Dep from './dep'
import { complieUtils } from './utils'
export default class Watcher {
    constructor(vm, expr, cb) {
        //当前的vue实例
        this.vm = vm;
        //表达式
        this.expr = expr;
        //回调函数,更新dom
        this.cb = cb
        //获取旧的数据,此时获取旧值的时候,Dep.target会绑定上当前的this
        this.oldVal = this.getOldVal()
    }
    getOldVal() {
        //将当前的watcher绑定起来
        Dep.target = this
        //获取旧数据
        const oldVal = complieUtils.getValue(this.expr, this.vm)
        //绑定完成后,将绑定的置空,防止多次绑定
        Dep.target = null
        return oldVal
    }
    update() {
        //更新函数
        const newVal = complieUtils.getValue(this.expr, this.vm)
        if (newVal !== this.oldVal || Array.isArray(newVal)) {
            //条用更新在compile中创建watcher时传入的回调函数
            this.cb(newVal)
        }
    }
}

上面中用到了 complieUtils 中的 getValue() 方法,会在下面讲,主要作用是获取到指定表达式的值。

我们把整个流程分成两条路线的话:

new Vue() ==> Observer数据劫持 ==> 绑定Dep ==> 通知watcher ==> 更新视图

new Vue() ==> Compile解析模板指令 ==> 初始化视图 和 绑定watcher

此时,我们第一条线的内容已经实现了,我们再来实现一下第二条线。

4、实现 Compile

compile主要做的事情是解析模板指令,将模板中的变量替换成数据,初始化渲染页面视图。同时也要绑定更新函数,添加订阅者。

因为在解析的过程中,会多次的操作dom,为提高性能和效率,会先将vue实例根节点的 el 转换成文档碎片fragment进行解析编译操作,解析完成,再将fragment添加回原来的真实dom节点中,文档碎片知识点在文章最后面的知识点链接中。

class Complie {
    constructor(el, vm) {
        this.el = this.isNodeElement(el) ? el : document.querySelector(el);
        this.vm = vm;
        // 1、将所有的dom对象放到fragement文档碎片中,防止重复操作dom,消耗性能
        const fragments = this.nodeTofragments(this.el)
        // 2、编译模板
        this.complie(fragments)
        // 3、追加子元素到根元素
        this.el.appendChild(fragments)
    }  
}

我们可以看到,Complie 中主要进行了三步,第一步 nodeTofragments 是讲所有的dom节点放到文档碎片中操作,最后一步,是把解析好的dom元素,从文档碎片重新加入到页面中,这两步的具体方法,大家去下载我的源码,看一下就明白了,有注释。我就不再解释 了。

我们来看一下第二步,编译模板:

 complie(fragments) {
    //获取所有节点
    const nodes = fragments.childNodes;
    [...nodes].forEach(ele => {
        if (this.isNodeElement(ele)) {
            //1. 编译元素节点
            this.complieElement(ele)
        } else {
            //编译文本节点
            this.complieText(ele)
        }
        //如果有子节点,循环遍历,编译指令
        if (ele.childNodes && ele.childNodes.length) {
            this.complie(ele)
        }
    })
}

我们要知道,模板可能有两种情况,一种是文本节点(含有双大括号的插值表达式)和元素节点(含有指令)。我们获取所有节点后对每个节点进行判断,如果是元素节点,则用解析元素节点的方法,如果是文本节点,则调用解析文本的方法。

complieElement(node) {
    //1.获取所有的属性
    const attrs = node.attributes;
    //2.筛选出是属性的
    [...attrs].forEach(attr => {
        //attr是一个对象,name是属性名,value是属性值
        const {name,value} = attr
        //判断是否含有v-开头 如:v-html
        if (name.startsWith("v-")) {
            //将指令分离  text, html, on:click
            const [, directive] = name.split("-")
            //处理on:click或bind:name的情况 on,click
            const [dirName, paramName] = directive.split(":") 
            //编译模板
            complieUtils[dirName](node, value, this.vm, paramName)
            //删除属性,在页面中的dom中不会再显示v-html这种指令的属性
            node.removeAttribute(name)
        } else if (name.startsWith("@")) {
            // 如果是事件处理 @click='handleClick'
            let [, paramName] = name.split('@');
            complieUtils['on'](node, value, this.vm, paramName);
            node.removeAttribute(name);
        } else if (name.startsWith(":")) {
            // 如果是事件处理 :href='...'
            let [, paramName] = name.split(':');
            complieUtils['bind'](node, value, this.vm, paramName);
            node.removeAttribute(name);
        }
    })

}

我们在编译模板中调用了 complieUtils[dirName](node, value, this.vm, paramName)方法,这是工具类中的一个方法,用于处理指令

我们再来看看文本节点,文本节点就相对比较简单,只需要匹配{{}}形式的插值表达式就可以了,同样的调用工具方法,来解析。

complieText(node) {
    //1.获取所有的文本内容
    const text = node.textContent
    //匹配{{}}
    if (/\{\{(.+?)\}\}/.test(text)) {
        //编译模板
        complieUtils['text'](node, text, this.vm)
    }
}

上面用来这么多工具方法,我们来看看到底是什么

5、实现 complieUtils 工具方法

这个方法主要是对指令进行处理,获取指令中的值,并在页面中更新相应的值,同时我们在这里要绑定watcher的回调函数。

我来以v-text指令来解释,其他指令都有注释,大家自己看。

import Watcher from './watcher'
export const complieUtils = {
    //处理text指令
    text(node, expr, vm) {
        let value;
        if (/\{\{.+?\}\}/.test(expr)) {
            //处理 {{}}
            value = expr.replace(/\{\{(.+?)\}\}/g, (...args) => {
                //绑定观察者/更新函数
                new Watcher(vm, args[1], () => {
                    //第二个参数,传入回调函数
                    this.updater.updaterText(node, this.getContentVal(expr, vm))
                })
                return this.getValue(args[1], vm)
            })
        } else {
            //v-text
            new Watcher(vm, expr, (newVal) => {
                this.updater.updaterText(node, newVal)
            })
            //获取到value值
            value = this.getValue(expr, vm)
        }
        //调用更新函数
        this.updater.updaterText(node, value)
    },
}

text处理函数是对dom元素的textContent进行操作的,所以有两种情况,一种是使用v-text指令,会更新元素的textContent,另一种情况是{{}} 的插值表达式,也是更新元素的 textContent。

在此方法中我们先判断是哪一种情况,如果是v-text指令,那么就绑定一个watcher的回调,获取到textContent的值,调用updater.updaterText在下面讲,是更新元素的方法。如果是双大括号的话,我们就要对其进行特殊处理,首先是将双大括号替换成指定的变量的值,同时为其绑定 watcher 的回调。

//通过表达式, vm获取data中的值, person.name
getValue(expr, vm) {
    return expr.split(".").reduce((data, currentVal) => {
        return data[currentVal]
    }, vm.$data)
},

获取 textContent 的值是用一个 reduce 函数,用法在最后面的链接中,因为数据可能是 person.name 我们需要获取到最深的对象的值。

 //更新dom元素的方法
updater: {
    //更新文本
    updaterText(node, value) {
        node.textContent = value
    }
}

updater.updaterText更新dom的方法,其实就是对 textContent 重新赋值。

我们再来将一下v-model指令,实现双向的数据绑定,我们都知道,v-model其实实现的是 input 事件和 value 之间的语法糖。所以我们这里同样的监听一下当前dom元素的 input 事件,当数据改变时,调用设置新值的方法

//处理model指令
model(node, expr, vm) {
    const value = this.getValue(expr, vm)
    //绑定watcher
    new Watcher(vm, expr, (newVal) => {
        this.updater.updaterModel(node, newVal)
    })
    //双向数据绑定
    node.addEventListener("input", (e) => {
        //设值方法
        this.setVal(expr, vm, e.target.value)
    })
    this.updater.updaterModel(node, value)
},

这个方法同样是通过 reduce 方法,为对应的变量设置成新的值,此时数据改变了,会自动调用更新视图的方法,我们在之前已经实现了。

//通过表达式,vm,输入框的值,实现设置值,input中v-model双向数据绑定
setVal(expr, vm, inputVal) {
    expr.split(".").reduce((data, currentVal) => {
        data[currentVal] = inputVal
    }, vm.$data)
},

6、实现vue

最后呢,我们就要来整合这些类和工具方法,在创建一个vue实例的时候,我们先获取options中的参数,然后对起进行数据劫持和编译模板

class Vue {
    constructor(options) {
        //获取模板
        this.$el = options.el;
        //获取data中的数据
        this.$data = options.data;
        //将对象中的属性存起来,以便后续使用
        this.$options = options
        //1.数据劫持,设置setter/getter
        new Observer(this.$data)
        //2.编译模板,解析指令
        new Complie(this.$el, this)
    }
}

此时我们想要使用 vue 中的数据,比如我们想要在 vm 对象中使用person.name, 必须用 this.$data.person.name 才能获取到,如果我们想在vm对象中使用 this.person.name 直接修改数据,就需要代理一下 this.$data 。其实就是将当前的 this.$data 中的数据放到全局中进行监听。

export default class Vue {
    constructor(options) {
        //...
        //1.数据劫持,设置setter/getter
        //2.编译模板,解析指令
        if (this.$el) { //如果有模板
            //代理this
            this.proxyData(this.$data)
        }
    }
    proxyData(data) {
        for (const key in data) {
            //将当前的数据放到全局指向中
            Object.defineProperty(this, key, {
                get() {
                    return data[key];
                },
                set(newVal) {
                    data[key] = newVal
                }
            })
        }
    }
}

文章到了这里,就实现了一个简易版的vue,建议大家反复学习,仔细体验,细细品味。

在文章的最后,我通过问、答的形式,来解答一些常见的面试题:

问:什么时候页面会重新渲染?

答:数据发生改变,页面就会重新渲染,但数据驱动视图,数据必须先存在,然后才能实现数据绑定,改变数据,页面才会重新渲染。


问:什么时候页面不会重新渲染?

答:有3种情况不会重新渲染

  1. 未经声明和未使用的变量,修改他们,都不会重新渲染页面

  2. 通过索引的方式和更改长度的方式更改数组,都不会重新渲染页面

  3. 增加和删除对象的属性,不会重新渲染页面


问:如何使 未声明/未使用的变量、增加/删除对象属性可以使页面重新渲染?

答:添加利用vm.$set/Vue.set,删除利用vm.$delete/Vue.delete方法


问:如何更改数组可以使页面重新渲染?

答:可以使用数组的变异方法(共 7 个):push、pop、unshift、shift、splice、sort、reverse


问:数据更新后,页面会立刻重新渲染么?

答:更改数据后,页面不会立刻重新渲染,页面渲染的操作是异步执行的,执行完同步任务后,才会执行异步的

同步队列,异步队列(宏任务、微任务 )


问:如果更改了数据,想要在页面重新渲染后再做操作,怎么办?

答:可以使用 vm.$nextTick 或 Vue.nextTick


问:来介绍一下vm.$nextTick 和 Vue.nextTick 吧

答:我们来看个小例子就明白啦

<div id="app">{{ name }}</div>
<script>
    const vm = new Vue({
      el: '#app',
      data: {
        name: 'monk'
      }
    })
    vm.name = 'the young monk';
    console.log(vm.name); // the young monk   此时数据已更改
    console.log(vm.$el.innerHTML); // monk    此时页面还未重新渲染
     // 1. 使用vm.$nextTick
    vm.$nextTick(() => {
        console.log(vm.$el.innerHTML); // the young monk  此时数据已更改
    })
    // 2. 使用Vue.nextTick
    Vue.nextTick(() => {
        console.log(vm.$el.innerHTML); // the young monk  此时数据已更改
    })
</script>

问: vm.$nextTick 和 Vue.nextTick 有什么区别呢 ?

答:Vue.nextTick内部函数的this指向window,vm.$nextTick内部函数的this指向Vue实例对象。

Vue.nextTick(function () {
	console.log(this); // window
})
vm.$nextTick(function () {
	console.log(this); // vm实例
})

问: vm.$nextTick 和 Vue.nextTick 是通过什么实现的呢 ?

答:二者都是等页面渲染后执行的任务,都是使用微任务。

  if(typeof Promise !== 'undefined') {
    // 微任务
    // 首先看一下浏览器中有没有promise
    // 因为IE浏览器中不能执行Promise
    const p = Promise.resolve();

  } else if(typeof MutationObserver !== 'undefined') {
    // 微任务
    // 突变观察
    // 监听文档中文字的变化,如果文字有变化,就会执行回调
    // vue的具体做法是:创建一个假节点,然后让这个假节点稍微改动一下,就会执行对应的函数
  } else if(typeof setImmediate !== 'undefined') {
    // 宏任务
    // 只在IE下有
  } else {
    // 宏任务
    // 如果上面都不能执行,那么则会调用setTimeout
  }

同样的这也是vue的一个小缺点:vue一直是等主线程执行完以后再执行渲染任务,如果主线程卡死,则永远渲染不出来。


问:利用 Object.defineProperty 实现响应式有什么缺点?

答:

  1. 天生就需要进行递归
  2. 监听不到数组不存在的索引的改变
  3. 监听不到数组长度的改变
  4. 监听不到对象的增删

vue前置知识学习链接:

vue源码地址:https://github.com/vuejs/vue-devtools

webpack 知识点链接:https://blog.youkuaiyun.com/Newbie___/article/details/104782796

es6 模块化相关知识链接:https://blog.youkuaiyun.com/Newbie___/article/details/104523097

es6 类的相关知识:https://blog.youkuaiyun.com/Newbie___/article/details/104450311

DocumentFragment 文档碎片知识点:https://developer.mozilla.org/zh-CN/docs/Web/API/DocumentFragment

Obeject.defineProperty方法的使用:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty

Array.reduce() 方法使用:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce

事件循环相关知识:https://blog.youkuaiyun.com/Newbie___/article/details/105450311

vue基础知识学习链接:

好似大家都在用vue,vue好在哪?https://blog.youkuaiyun.com/Newbie___/article/details/105028758

vue中插值表达式和14个vue指令详解 https://blog.youkuaiyun.com/Newbie___/article/details/105179351

一篇文章带你“深入”了解vue的计算属性computed和侦听属器watch https://blog.youkuaiyun.com/Newbie___/article/details/105224887

axios在企业级vue项目中的应用(附带axios正确打开方式)https://blog.youkuaiyun.com/Newbie___/article/details/105238226

vue的生命周期详解:从入门到精通 https://blog.youkuaiyun.com/Newbie___/article/details/105347733

vue组件和父子组件间通信的13种方式(包含动态、异步组件和常用的prop、$emit、插槽等)https://blog.youkuaiyun.com/Newbie___/article/details/105480827

vueRouter路由详解:从入门到精通 https://blog.youkuaiyun.com/Newbie___/article/details/105766504

vue中的vuex状态管理:从入门到精通 https://blog.youkuaiyun.com/Newbie___/article/details/105766702
5450311

Vue源码系列(一):Vue源码解读的正确姿势 作为一名开发人员,阅读源码是非常好的学习方式,尤其Vue又是当下很受欢迎的前端框架,随着用的人越来越多,不断推动着源码地完善。在源码中有着各位大佬多年积淀下来的精华,而这些精华就非常值得我们学习。 阅读详情

相关推荐

超详细Vue3响应式原理介绍加源码阅读

本章介绍Vue中另一个非常重要的模块,响应式。介绍了基本原理(含图)简单实现以及如何阅读源码。

weixin_47042983的博客 3192

vue 源码解析

watch:watcher解析表达式,收集依赖,在依赖(表达式值)改变时触发回调(handler);调用所有watcher的update更新。数据双向绑定:用Object.defineProperty方法重写data中每个数据的get,set方法,在get中执行依赖收集(执行 Dep.depend 方法)和set中派发更新通知(执行 Dep.notify 方法)。computed。

wanglun_的博客 3793

vue中使用Mock

mock是当前端工程师搭建好了页面只差数据,但是后端还没完成接口开发时候使用的前端虚拟数据 mock的特性 前后端分离 增加单元测试的真实性(通过随机数据,模拟各种场景) 简单实用(符合真实的接口) 数据类型丰富(支持生成的文字,字符串,日期,邮箱,boolean,颜色,图片等) 方便扩展(支持自定义类型函数) Mock.js地址 .........

Debug的博客 1734

Vue源码解析(详细篇)

vue数据观测实现原理,模版编译过程,模版挂载过程分析

weixin_46996561的博客 8300

DINOv3实战指南:如何利用零样本视觉模型革新工业质检

本文详细介绍了如何利用DINOv3零样本视觉模型革新工业质检流程。通过无需标注数据的自监督学习技术,DINOv3能快速部署高精度检测系统,显著降低成本和提升效率。文章涵盖模型选型、数据准备、实时检测优化及典型行业解决方案,为工业质检提供革命性AI实践指南。

ansible6ops的博客 124

【Python&RS】Rasterio库安装+基础函数使用教程

Rasterio是一个Python库,专门用于栅格数据的读写操作。它支持多种栅格数据格式,如GeoTIFF、ENVI和HDF5,为处理和分析栅格数据提供了强大的工具。RasterIO适用于各种栅格数据应用,如卫星遥感、地图制作等。这篇文章跟大家介绍一下Rasterio库的安装以及一些基本函数的使用教程,附带源码。

Remote Sensing迷途小书童 1万+

VUE 响应式原理源码:带你一步精通 VUE | 原力计划

作者 | 爱编程的小和尚责编 | 王晓曼出品 | 优快云博客学过 VUE 如果不了解响应式的原理,怎么能说自己熟练使用 VUE,要是没有写过一个简易版的 VUE 怎么能说自己精通 VU...

优快云资讯 2649

基于PSIM仿真的单相全桥逆变电路

根据单极性SPWM调制电路、双极性SPWM调制电路和逆变器拓扑可以得到完整的逆变器仿真电路模型。

weixin_59329579的博客 1万+

provide vue 响应式_VUE 响应式原理源码:带你一步精通 VUE

作者 | 爱编程的小和尚责编 | 王晓曼出品 | 优快云博客学过 VUE 如果不了解响应式的原理,怎么能说自己熟练使用 VUE,要是没有写过一个简易版的 VUE 怎么能说自己精通 VUE,这篇文章通过300多行代码,带你写一个简易版的 VUE,主要实现 VUE 数据响应式 (数据劫持结合发布者-订阅者)、数组的变异方法、编译指令,数据的双向绑定的功能。本文需要有一定 VUE 基础,并不适合新手学...

weixin_39710396的博客 88

电力现货市场出清机制与节点电价计算实战解析

本文深入解析了电力现货市场的出清机制,特别是节点边际电价的计算原理与实战应用。通过一个基于Matlab和IEEE30节点系统的完整案例,详细演示了如何利用最优潮流模型求解市场出清价格,并提取节点电价,揭示了电网阻塞如何影响实时电价的形成。

weixin_29061425的博客 950

微信删除的聊天记录怎么恢复(2024年最新攻略合集)

随着微信对我们生活中方方面面的渗透,微信已然从一款简单的社交软件过渡成为现时代的一种生活方式,而微信中的聊天记录就是我们和朋友之间历程的最好证明。对此,今天我们来聊聊微信删除的聊天记录怎么恢复的问题。

m0_70960708的博客 945

vue 数组 指定位置添加数据_VUE 响应式原理源码:带你一步精通 VUE

作者 | 爱编程的小和尚责编 | 王晓曼出品 | 优快云博客学过 VUE 如果不了解响应式的原理,怎么能说自己熟练使用 VUE,要是没有写过一个简易版的 VUE 怎么能说自己精通 VUE,这篇文章通过300多行代码,带你写一个简易版的 VUE,主要实现 VUE 数据响应式 (数据劫持结合发布者-订阅者)、数组的变异方法、编译指令,数据的双向绑定的功能。本文需要有一定 VUE 基础,并不适合新手学...

weixin_39917576的博客 1644

智能小车倒车入库功能的实现(python,cpp,ubuntu)

智能小车倒车入库功能的实现(python,cpp,ubuntu)

weixin_62135607的博客 5793

provide vue 响应式_VUE 响应式原理源码:带你一步精通 VUE | 原力计划

作者 | 爱编程的小和尚责编 | 王晓曼出品 | 优快云博客学过 VUE 如果不了解响应式的原理,怎么能说自己熟练使用 VUE,要是没有写过一个简易版的 VUE 怎么能说自己精通 VUE,这篇文章通过300多行代码,带你写一个简易版的 VUE,主要实现 VUE 数据响应式 (数据劫持结合发布者-订阅者)、数组的变异方法、编译指令,数据的双向绑定的功能。本文需要有一定 VUE 基础,并不适合新手学...

weixin_34566429的博客 101

YOLO十年演进:从CNN到Transformer再到MoE,我看懂了2026年目标检测的落地风向标

技术永远是为业务服务的,能解决真实问题的技术,才是有价值的。YOLO的十年演进,从CNN到Transformer再到MoE,从来都不是学术上的炫技,而是一步一步解决工业落地的痛点:从让实时检测成为可能,到解决复杂场景的漏检误检,再到打破精度和算力的死局,每一步都踩在了工业落地的真实需求上。很多新入行的朋友,总喜欢追新,今天换Transformer,明天堆MoE,觉得模型越新、参数越大,就越厉害。

专注于Python爬虫开发,分享爬虫技巧、项目实战与反爬经验,使用Scrapy、BeautifulSoup等工具,解决数据抓取难题。 748

深入RKNN Toolkit2:自定义算子与内存优化秘籍

本文系统探讨了RKNNToolkit2在模型转换中的关键技术与优化策略。首先介绍了该工具包的基础功能及模型转换流程,重点阐述了自定义算子的实现方法,包括算子分析、代码编写和注册集成等步骤,并以ReduceLogSumExp算子为例进行案例分析。在内存优化方面,详细论述了模型量化、内存复用和参数配置等策略,通过具体案例展示优化效果。最后结合智能安防项目实践,分享了YOLOv5模型部署经验及问题解决方案。文章指出,随着AI技术发展,RKNNToolkit2将在算子支持、量化技术和硬件适配等方面持续优化,为边缘计

远程部署调试 运行安装 项目调试 二次开发 项目技术新 持续迭代 部分源码免费分享 1389

vue 数组 指定位置添加数据_VUE 响应式原理源码:带你一步精通 VUE | 原力计划...

作者 | 爱编程的小和尚责编 | 王晓曼出品 | 优快云博客学过 VUE 如果不了解响应式的原理,怎么能说自己熟练使用 VUE,要是没有写过一个简易版的 VUE 怎么能说自己精通 VUE,这篇文章通过300多行代码,带你写一个简易版的 VUE,主要实现 VUE 数据响应式 (数据劫持结合发布者-订阅者)、数组的变异方法、编译指令,数据的双向绑定的功能。本文需要有一定 VUE 基础,并不适合新手学...

weixin_39624367的博客 685

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

【手撕源码】vue3响应式原理解析

new Proxy(target,handler)表示生成一个Proxy实例,target参数表示所要拦截的目标对象,它可以是任意类型的对象,包括内置的数组,函数等,handler也是一个对象,用来定制拦截行为,当发生某些操作时触发该对象。

编码世界 6万+

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

efm32 入门.pdf

本PDF共计22页,介绍的是操作驱动API,下载与否自己斟酌。

小白一步一步阅读vuejs源码

本篇不直接解读vuejs的双向绑定的原理,而是已读源码的视角来看vue是怎么做的。 1、下载vuejs源码 mkdir project && cd project && git clone https://github.com/vuejs/vue.git npm i 2、开始一步一步读代码 打开package.json。看看scripts,我们先只看dev,这...

3605

SAP B1中文培訓教材_SDK

公司内部培训资料,对开发工具 ,DIAPI, UIAPI,Add-ON有详细解说

Vue 源码分析:深入响应式原理

简单来说,响应式就是当数据发生变化时,视图能够自动更新。在 Vue 中,当我们修改一个响应式数据时,所有依赖于该数据的视图都会自动重新渲染。在创建 Vue 实例时,Vue 会遍历 data 对象的所有属性,并使用observe函数将它们转换为响应式属性。当我们在模板中使用响应式数据时,Vue 会创建一个 Watcher 对象来监听该数据的变化。在 Watcher 对象的get()方法中,会触发响应式数据的 getter 函数。在 getter 函数中,会将当前的 Watcher 对象添加到该数据的。

q68686的博客 1262

【Vue2源码】响应式原理

vue2 中响应式原理主要就是通过数据劫持,依赖收集,派发更新的方式来实现的

zhenshu_guo的博客 3182

深入讲解一下 Vue 3 的核心源码库 vuejs/core

track 负责“依赖收集”,trigger 负责“派发更新”。

u011091936的博客 1393

源码解析 深入vue响应式原理

深入响应式原理 大家应该都知道响应式原理的核心API是Object.defineProperty。如果不了解请点击: Object.defineProperty 去MDN了解下这个API的用途。 那这个API是如何做到响应式对象的呢。 接下来我们从源码的角度来进行分析: 我们知道在组件的data中定义的数据,最终都能响应式,看下Vue2.x的源码是如何初始化data的: export function initState (vm: Component) { vm._watchers = [] con

m0_52544128的博客 1691

Vue3 源码解析(六):响应式原理与 reactive

在今天的文章中,笔者先带大家回顾了 Vue2 的响应式原理,又开始介绍 Vue3 的响应式原理,通过比较 Vue2 和 Vue3 的响应式系统的区别引出 Vue3 响应式系统的提升之处,尤其是其中最主要的调整将 Object.defineProperty 替换为 Proxy 代理对象。为了让大家属性 Proxy 对响应式系统的影响,笔者着重介绍了响应式基础 API:reactive。

rita_0567的博客 1399

vue源码解析和vue响应式原理

源码的获取项目地址:为什么分析? 新的版本发布后,现有项目不会升级到,还有很长的一段过渡期。项目地址源码目录结构(在目录下面定义的就是源码内容): ​ 在目录是的核心库,在目录下面,也定义了很多的文件夹,下面我们先简单来看一下。目录下面定义的是组件。:定义的是中的静态方法。,,,等。:创建的实例,定义了的构造函数,初始化,以及生命周期的钩子函数等。:定义响应式机制的位置,:定义公共成员。:定义虚拟这里我们来介绍一下,关于源码中使用的打包方式。打包工具 所使用的打包工具为,比更加轻量,是把所有的文件(

panerchao的博客 1098

【vue3响应式原理】

setup是一个新的组件选项,它是组件内使用Composition API的入口 // 调用时刻是初始化属性确定后,beforeCreate之前。// 响应化:接收一个对象,返回一个响应式的代理对象 const state = reactive({ count: 0 })文件件内,实际上源码主要分为两部分,编译器和运行时环境。解决方式:将之前代理结果缓存,get时直接使用。建立响应数据key和更新函数之间的对应关系。vue 代码入口,整合编译器和运行时。// 返回对象将和渲染函数上下文合并。

demotang的博客 1054
上一篇: vue中的vuex状态管理:从入门到精通
下一篇: 初识React和JSX语法:带你走入React世界,让你彻底明白JSX语法
七钥
博客等级 码龄9年 744粉丝 53原创
评论 18
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值