资讯专栏INFORMATION COLUMN

【Vue 2.0】核心源码解读 -- 不定期更新

sunsmell / 2920人阅读

摘要:观察员由模板解析指令创建的观察员负责模板中的更新视图操作。观察员种类目前了解情况来看主要分三类视图指令的计算属性的用户自定义的

介绍

关于 Vue.js 的原理一直以来都是一个话题。经过几天的源码学习和资料介绍,我将一些个人理解的经验给写下来,希望能够与大家共勉。

附上GITHUB源码地址, 如果有任何不解 可以在 文章下面提出或者写下issue, 方便大家回答和学习, 有兴趣可以Star.
最后附上 LIVE DEMO

简单图解 Vue.js 内置对象

构造实例对象

应用创建时需要使用的构造函数对象, data 为我们的数据模型

const vm = new Vue({
  data: {
      foo: "hello world"
  }
});
数据双向绑定 被观察者 observe

被观察对象,data属性里的值的变化,get时检查是否有新的观察员需要加入观察员集合, set时通知观察员集合里的观察员更新视图,被观察者有一个观察员的集合对象。

观察员集合对象 Dep

一个观察员的收集器, depend()负责将当前的 Dep.target 观察员加入观察员集合, data 中的每一项数据都会有对应的闭包dep对象, 数据对象会有一个内置的dep对象,用来通知嵌套的数据对象变化的情况。

观察员 watcher

由模板解析指令创建的观察员, 负责模板中的更新视图操作。保留旧的数据,以及设置钩子函数 update(), 等待被观察者数据通知更新,对比新的value与旧数据, 从而更新视图。

数据代理 proxyData

我们的关注点在与创建后的vm, 其 options.data, 被挂载至vm._data, 同时被代理至 vm 上, 以至于 vm._data.foo 等价于 vm.foo, 代理函数代码如下:

const noop = function () {}
const sharedPropertyDefinition = {
  enumerable: true,
  configurable: true,
  get: noop,
  set: noop
}

function proxy (target, sourceKey, key) {
  sharedPropertyDefinition.get = function proxyGetter () {
    return this[sourceKey][key]
  }
  sharedPropertyDefinition.set = function proxySetter (val) {
    this[sourceKey][key] = val
  }
  Object.defineProperty(target, key, sharedPropertyDefinition)
}

// initState 时执行 initData
function initData () {
    
    const keys = Object.keys(data)
    let i = keys.length
    while (i--) {
      const key = keys[i]
      // key不以 $ 或 _开头
      if (!isReserved(key)) {
        proxy(vm, `_data`, key)
      }
    }
    // do something
}
数据劫持 defineProperty
/**
 * Define a reactive property on an Object.
 */
function defineReactive(obj, key, val) {

  // 观察员集合
  const dep = new Dep();
  
  // data 内属性描述 
  const property = Object.getOwnPropertyDescriptor(obj, key);

  // 属性不可再次修改
  if (property && property.configurable === false) {
    return;
  }

  // 属性预定义 getter/setters
  const getter = property && property.get;
  const setter = property && property.set;

  // 如果val为对象, 获取val的 被观察数据对象 __ob__
  let childOb = observe(val);
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter() {
      // 被观察数据被使用时, 获取被观察员最新的数据
      const value = getter ? getter.call(obj) : val
      
      // 观察员在new时或使用 get()时, 注入给被观察员对象集合
      if (Dep.target) {
        // 将当前的 watcher 传递给 观察员
        dep.depend();
        if (childOb) {
          // 将当前的 watcher 传递给子对象的 观察员
          childOb.dep.depend();
        }
      }
      return val;
    },
    set: function reactiveSetter(newVal) {
      const value = getter ? getter.call(obj) : val;
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return;
      }
      if (setter) {
        setter.call(obj, newVal);
      } else {
        val = newVal;
      }
      // 新value设置被观察者对象 __ob__
      childOb = observe(newVal);
      
      // 通知数据对象依赖的观察员, 更新 update()
      dep.notify();
    }
  });
}
计算属性介绍 初始化执行过程

计算属性的定义和使用

var vm = new Vue({
    data: {
        firstname: "li",
        lastname: "yanlong"
    },
    computed: {
        fullname () {
            return this.firstname + this.lastname;
        }
    }
});
console.log(vm.fullname);
核心代码解读
const computedWatcherOptions = {lazy: true};
function initComputed (vm, computedOptions) {
    // 创建计算属性对应的观察员对象
    // 获取计算属性时收集 内置数据对象的 dep
    const watchers = vm._computedWatchers = Object.create(null)
    
    for (const key in computed) {
        const userDef = computed[key]
        const getter = typeof userDef === "function" ? userDef : userDef.get
        // create internal watcher for the computed property.
        watchers[key] = new Watcher(
          vm,
          getter || noop,
          noop,
          computedWatcherOptions
        );
        if (!(key in vm)) {
          defineComputed(vm, key, userDef)
        } 
    }
}

function defineComputed (target, key, userDef) {
    // 如果不为服务端渲染,则使用缓存value
    const shouldCache = !isServerRendering()
    
    // sharedPropertyDefinition 共享属性配置
    if (typeof userDef === "function") {
      sharedPropertyDefinition.get = shouldCache
        ? createComputedGetter(key)
        : userDef
      sharedPropertyDefinition.set = noop
    } else {
      sharedPropertyDefinition.get = userDef.get
        ? shouldCache && userDef.cache !== false
          ? createComputedGetter(key)
          : userDef.get
        : noop
      sharedPropertyDefinition.set = userDef.set
        ? userDef.set
        : noop
    }
    // 给 vm对象定义计算属性 
    Object.defineProperty(target, key, sharedPropertyDefinition)
}

// 创建
function createComputedGetter (key) {
  return function computedGetter () {
    const watcher = this._computedWatchers && this._computedWatchers[key]
    if (watcher) {
      // 计算属性的 watcher 有数据更新过, 重新计算
      if (watcher.dirty) {
        watcher.evaluate()
      }
      
      // 视图指令 使用了计算属性
      // 将计算属性的watcher依赖传递给视图指令的 watcher
      if (Dep.target) {
        // 源码地址
        // https://github.com/vuejs/vue/blob/master/src/core/observer/watcher.js#L210
        watcher.depend()
      }
      return watcher.value
    }
  }
}
计算属性知识点介绍

1. 计算属性的 watcher 对象
计算属性函数在读取它本身的value时, 使用一个watcher观察员进行代理. 通过对原始数据的劫持, 将watcher 观察员添加到原始数据的dep依赖集合中.

2. deps的数据对象发生更新
举例,如果firstname 或者 lastname 任意一个更新,那么就会设置计算属性的watcher.dirty = true, 而当其它指令或者函数使用,计算属性会重新计算值,如果是视图指令,还会重新该指令的watcher的数据对象依赖。

Watcher 观察员种类

目前了解情况来看, 主要分三类

视图指令的 watcher

计算属性的 watcher

用户自定义的 watcher

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/88829.html

相关文章

  • vuex 2.0 源码解读

    摘要:至此它便作为一个唯一数据源而存在。改变中的状态的唯一途径就是显式地提交。这样使得可以方便地跟踪每一个状态的变化,从而能够实现一些工具帮助更好地了解应用背后的基本思想,借鉴了和参考源码解读一架构入门教程状态管理 准备工作 1.下载安装运行 这里以2.0.0-rc.6为例官网github下载链接(对应版本):https://github.com/vuejs/vuex...点击下载到本地 ...

    yvonne 评论0 收藏0
  • Vue 2.0源码学习

    摘要:今年的月日,的版本正式发布了,其中核心代码都进行了重写,于是就专门花时间,对的源码进行了学习。本篇文章就是源码学习的总结。实现了并且将静态子树进行了提取,减少界面重绘时的对比。的最新源码可以去获得。 Vue2.0介绍 从去年9月份了解到Vue后,就被他简洁的API所吸引。1.0版本正式发布后,就在业务中开始使用,将原先jQuery的功能逐步的进行迁移。 今年的10月1日,Vue的2...

    Joyven 评论0 收藏0
  • 来一打前端博客压压惊

    前言 本文所有内容全部发布再个人博客主页 https://github.com/muwoo/blogs欢迎订阅。不过最近因为事情比较多,有一段时间没有更新了,后面打算继续不断学习更新,欢迎小伙伴一起沟通交流~ 最近更新 前端单测的那些事 基于virtual dom 的canvas渲染 js Event loop 机制简介 axios 核心源码实现原理 JS 数据类型、赋值、深拷贝和浅拷贝 j...

    wangbinke 评论0 收藏0
  • 来一打前端博客压压惊

    前言 本文所有内容全部发布再个人博客主页 https://github.com/muwoo/blogs欢迎订阅。不过最近因为事情比较多,有一段时间没有更新了,后面打算继续不断学习更新,欢迎小伙伴一起沟通交流~ 最近更新 前端单测的那些事 基于virtual dom 的canvas渲染 js Event loop 机制简介 axios 核心源码实现原理 JS 数据类型、赋值、深拷贝和浅拷贝 j...

    villainhr 评论0 收藏0
  • 来一打前端博客压压惊

    前言 本文所有内容全部发布再个人博客主页 https://github.com/muwoo/blogs欢迎订阅。不过最近因为事情比较多,有一段时间没有更新了,后面打算继续不断学习更新,欢迎小伙伴一起沟通交流~ 最近更新 前端单测的那些事 基于virtual dom 的canvas渲染 js Event loop 机制简介 axios 核心源码实现原理 JS 数据类型、赋值、深拷贝和浅拷贝 j...

    xiaoqibTn 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<