IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> JavaScript知识库 -> VUE响应式原理三 -> 正文阅读

[JavaScript知识库]VUE响应式原理三

前两篇文章教会我们手写一个简单的响应式系统。这篇文章主要带领我们潜入vue.js的源码来看待响应式系统。首先创建一个vue application:

    <div id="app">
      <h1>{{ product }}</h1>
    </div>

    <script src="vue.js"></script>
    
    <script>
    var app = new Vue({
      el: '#app',
      data: {
        product: "Socks"
      } 
    })
    </script>

在声明data的时候,通过getter, setter实现了响应式系统。从前两章中我们知道,在get data的时候我们增加了一个依赖,在set data的时候运行所有的依赖。
在这里我们先下载一份vue源码,在examples目录下创建一个test,html文件如下所示。然后我们将使用谷歌浏览器的debugger工具逐步深入data初始化的过程。

  <div id="app">
    {{ msg }}
  </div>
  <!-- <script src="../dist/vue.js"></script> -->
  <script src="../dist/vue.js"></script>
  <script>
    debugger
    new Vue({
      el: '#app',
      data: {
        msg: 'hello vue'
      }
    })
  </script>

一开始,在/src/core/instance/index.js 中我们会看到这样的代码:

import { initMixin } from './init'
import { stateMixin } from './state'
import { renderMixin } from './render'
import { eventsMixin } from './events'
import { lifecycleMixin } from './lifecycle'
import { warn } from '../util/index'

function Vue (options) {
  if (process.env.NODE_ENV !== 'production' &&   // 判断存在node环境
    !(this instanceof Vue)  // Vue 构造函数不存在
  ) {
    warn('Vue is a constructor and should be called with the `new` keyword')
  }
  this._init(options)   // 调用vue原型上的init方法
}

initMixin(Vue)  // 从这里开始执行
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)

export default Vue

当我们运行let app = new Vue({...})的时候,上述的根Vue 函数帮我们创建了一个实例。随后调用原型上定义的_init方法,该方法在initMixin 函数中添加。在initMixin中我们做了许多事情,但此时我们只关注initState, 因为它与初始化data, 添加响应式密切相关,除此之外initState方法还与初始化props, methods, computed密切相关。
/src/core/instance/init.js

    export function initMixin (Vue: Class<Component>) {
      Vue.prototype._init = function (options?: Object) {
        const vm: Component = this
        ...  // 省略
        vm._self = vm
        initLifecycle(vm)
        initEvents(vm)
        initRender(vm) // defineReactive attrs and listeners
        callHook(vm, 'beforeCreate') // Notice this lifecycle call hook 
        initInjections(vm) // resolve injections before data/props
        initState(vm)  // <---
        initProvide(vm) // resolve provide after data/props
        callHook(vm, 'created') // Notice this lifecycle call hook 
        ...
        vm.$mount(vm.$options.el)
      }
    }

/src/core/instance/state.js

import {
  set,
  del,
  observe, // 观察数据
  defineReactive,
  toggleObserving
} from '../observer/index'// 引入一系列方法

function initData (vm: Component) {
  let data = vm.$options.data
  data = vm._data = typeof data === 'function'
    ? getData(data, vm)
    : data || {}
  if (!isPlainObject(data)) {. // data 需要是纯对象
    data = {}
    process.env.NODE_ENV !== 'production' && warn(
      'data functions should return an object:\n' +
      'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
      vm
    )
  }
  ...

  // 观察数据,接下来找到observe方法
  observe(data, true /* asRootData */)
}

/src/core/observer/index.js

observe方法: 为每个值创建一个观察类实例

export function observe (value: any, asRootData: ?boolean): Observer | void {
  //value 是 {msg: 'hello vue'}
  ...
  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
    ob = value.__ob__ // 有observer就用原来的
  } else if (
...
  ) {
    ob = new Observer(value) // 没有则新建一个
  }
...
  return ob
}

Observer类,可以发现此处正式定义了响应式方法defineReactive, defineReactive 就与前两篇中写的简易响应式原理衔接上了。
/src/core/observer/index.js

export class Observer {
...
  constructor (value: any) {
    this.value = value  // 例子 { msg: 'hello vue'}
    this.dep = new Dep(). // 
    this.vmCount = 0
    def(value, '__ob__', this)
    if (Array.isArray(value)) {  // 处理data是数组的情况
     ...
      this.observeArray(value)
    } else {
      this.walk(value) // 遍历每个属性
    }
  }
  walk (obj: Object) {
    const keys = Object.keys(obj)
    for (let i = 0; i < keys.length; i++) {
      defineReactive(obj, keys[i])  // 正式定义响应式
    }
  }
  observeArray (items: Array<any>) {
    for (let i = 0, l = items.length; i < l; i++) {
      observe(items[i])  // 数组的时候递归遍历知道简单数据类型
    }
  }
}

defineReactive方法,终于到这里啦。在这里我们终于使用到了之前讲过很多次的Object.defineProperty, 在数据获取的时候存储依赖,在数据更新的时候重新运行依赖。
/src/core/observer/index.js

export function defineReactive (
  obj: Object,
  key: string, // "msg"
  val: any,   // "hello" 
  ...
) {
  const dep = new Dep() 
  ...
  // 关键api defineProperty
  Object.defineProperty(obj, key, { // 为对象的每个元素设置getter, setter
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      const value = getter ? getter.call(obj) : val   // 已经有get了则直接复用
      if (Dep.target) {
        // 如果target存在,则加入依赖
        dep.depend()
        if (childOb) {   // 存在子对象时遍历
          childOb.dep.depend()
          if (Array.isArray(value)) {
            dependArray(value)
          }
        }
      }
      return value
    },
    set: function reactiveSetter (newVal) {
      const value = getter ? getter.call(obj) : val
      /* eslint-disable no-self-compare */
      if (newVal === value || (newVal !== newVal && value !== value)) {
        // 如果值一样,则返回
        return
      }
      ...
      if (setter) {
        ...
      } else {
        val = newVal  // 设置新值
      }
      childOb = !shallow && observe(newVal)
      dep.notify()   // 重新运行依赖中的所有方法
    }
  })
}

在上一段代码中我们可以看到,使用到了Dep类,这个类前两篇的时候也出现过,是我们自己写的简易版本。VUE中真正的这个类还比较复杂,在了解这个之前还有watcher类要了解下。这个watcher做了一些什么呢?它会将需要被再次运行的匿名函数暂时记录下来(例如我们平常使用的计算属性中的方法),运行一次,然后清除它,这是使用到自身定义的get方法。还会有一个update方法,让这些watcher实例能够排队运行,因为有时候不是需要立即运行匿名函数代码并重新计算。要注意的时候对于data对象上的属性来说,watcher类的实例化发生在mount的时候,因为在渲染真实dom的时候这些属性第一次被获取,然后触发依赖收集。而对于computed来说创建计算属性的时候就要收集依赖,即实例化watcher类了。
/src/core/observer/watcher.js

    export default class Watcher {
      ...
      get() {
        pushTarget(this) // 将Dep.target 存入watcher 对象中
        ...
        value = this.getter.call(vm, vm) // vm 虚拟dom,即VUE实例,上面挂在创建一个根节点所需要的所有属性
        ...
        popTarget() // 一次只存一个,用完之后清除
        return value 
      }
      update() {
        if (this.lazy) {
          this.dirty = true
        } else if (this.sync) {
          this.run() // shown below
        } else {
          queueWatcher(this) // queue this to run later
        }
      }
      run() {
        if (this.active) {
          const value = this.get() 
        }
      }
      addDep(dep: Dep) { // 开始追踪一个依赖
        ...
        this.newDeps.push(dep) // The watcher also track's deps
        dep.addSub(this) // Calls back to the dep (below)
      }
    }

最后再来看一下dep类。可以看到订阅者中保存的是watcher类型了。这个功能就跟我们之前写的dep类基本类似。被获取的时候收集依赖,修改时触发匿名函数重新命名。
//src/core/observer/dep.js

    export default class Dep {
      ...
      subs: Array<Watcher>;  // subs里面保存的是watcher类.
      constructor () {
        this.subs = []  // Our subscribers we need to keep track of
      }
      addSub(sub: Watcher) {  // You can think of this sub Watcher as our target
        this.subs.push(sub)
      }
      ...
      depend() { // <-- There's our depend function
        if (Dep.target) {  // If target exists
          Dep.target.addDep(this)  // Add this as a dependency, which ends up calling addSub function above.  Pushing this watcher.
        }
      }
      notify() { // <--- There's our notify function
        const subs = this.subs.slice()
        for (let i = 0, l = subs.length; i < l; i++) {
          subs[i].update()  // <-- Queue and run each subscriber
        }
      }
    }

以上是本人看了教程后对vue响应式原理的一点点简单的理解。

  JavaScript知识库 最新文章
ES6的相关知识点
react 函数式组件 & react其他一些总结
Vue基础超详细
前端JS也可以连点成线(Vue中运用 AntVG6)
Vue事件处理的基本使用
Vue后台项目的记录 (一)
前后端分离vue跨域,devServer配置proxy代理
TypeScript
初识vuex
vue项目安装包指令收集
上一篇文章      下一篇文章      查看所有文章
加:2022-04-06 16:08:57  更:2022-04-06 16:10:18 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 2:28:56-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码