Vue2之核心原理

Vue2剖析源码

打包、入口分析

目录结构

  • .circleci 持续集成
  • benchmarks 性能评测
  • dist 输出目录
  • examples 案例
  • flow flow声明文件
  • packages vue中的包
  • scripts 工程化
  • src 源码目录
  • test 测试相关
  • types ts声明文件
├─compiler       # 编译的相关逻辑
│  ├─codegen
│  ├─directives
│  └─parser
├─core           # vue核心代码
│  ├─components  # vue中的内置组件 keep-alive
│  ├─global-api  # vue中的全局api
│  ├─instance    # vue中的核心逻辑
│  ├─observer    # vue中的响应式原理
│  ├─util        
│  └─vdom        # vue中的虚拟dom模块
├─platforms      # 平台代码
│  ├─web	     # web逻辑 - vue
│  │  ├─compiler
│  │  ├─runtime
│  │  ├─server
│  │  └─util
│  └─weex        # weex逻辑 - app
│      ├─compiler
│      ├─runtime
│      └─util
├─server         # 服务端渲染模块
├─sfc            # 用于编译.vue文件
└─shared         # 共享的方法和常量

打包流程

"build": "node scripts/build.js",  
"build:ssr": "npm run build -- web-runtime-cjs,web-server-renderer",
"build:weex": "npm run build -- weex",

核心是使用node执行 scripts/build.js,通过传递参数来实现不同的打包结果,这里的--代表后面的内容是参数。

build.js

既然是打包,那我们肯定要找到打包的入口点,所以这里的关键就是查找打包的入口!

// 1.获取不同的打包的配置 
let builds = require('./config').getAllBuilds()

// 2.根据执行打包时的参数进行过滤
if (process.argv[2]) {
  const filters = process.argv[2].split(',')
  builds = builds.filter(b => {
    return filters.some(f => b.output.file.indexOf(f) > -1 || b._name.indexOf(f) > -1)
  })
} else {
  // 默认不打包weex相关代码
  builds = builds.filter(b => {
    return b.output.file.indexOf('weex') === -1
  })
}
// 3.进行打包
build(builds)

不同的打包配置指的是:

  • web / weex 不同的平台

    这里我们不关注weex,web指代的就是我们常用的vue

  • Runtime only / Runtime + compiler 是否带编译模块

    带有compiler的会将模板转化成render函数

  • CommonJS / es / umd 打包出不同模块规范

    umd模块是整合了CommonJS和AMD两个模块定义规范的方法,当不支持时两种模块时会将其添加到全局变量中

打包入口

src/platforms/web/entry-runtime.js
src/platforms/web/entry-runtime-with-compiler.js

我们可以通过打包的配置找到我们需要的入口,这两个区别在于是否涵盖compiler逻辑,我们在开发时一般使用的是entry-runtime,可以减小vue的体积,但是同样在开发时也不能再使用template,.vue文件中的template是通过vue-loader来进行编译的,和我们所说的compiler无关哈。

new Vue({
	template:`<div></div>`
})

这样的template必须要使用带compiler的入口才能进行模板的解析

入口分析

这里为了剖析vue完整的代码,我们就来分析带有compiler的文件。

我们观察这两个入口的文件不难发现他们都引入了runtime/index.js

entry-runtime-with-compiler.js

import Vue from './runtime/index' //    1.引入运行时代码
const mount = Vue.prototype.$mount; //  2.获取runtime中的$mount方法
Vue.prototype.$mount = function (el,hydrating) { // 3. 重写$mount方法
  el = el && query(el)
  const options = this.$options
  if (!options.render) { // 4.没有render方法就进行编译操作
    let template = options.template
    if(template){ // 5.将模板编译成函数
        const { render, staticRenderFns } = compileToFunctions(template, {
            outputSourceRange: process.env.NODE_ENV !== 'production',
            shouldDecodeNewlines,
            shouldDecodeNewlinesForHref,
            delimiters: options.delimiters,
            comments: options.comments
        }, this)
        options.render = render // 6.将render函数放到options中
    }
    // todo...
  }
  return mount.call(this, el, hydrating) // 7.进行挂载操作
}
export default Vue

带有compiler的文件仅仅是对$mount方法进行了重写,增添了将template变成render函数的功能

Vue的构造函数

image-20240719102923569

  • instance/index.js 真正的Vue的构造函数,并在Vue的原型上扩展方法
  • core/index.js 增加全局API方法
  • runtime/index.js 扩展$mount方法及平台对应的代码

全局API & 执行流程

全局API

Vue.util
// exposed util methods.
// NOTE: these are not considered part of the public API - avoid relying on
// them unless you are aware of the risk.
Vue.util = {
    warn,
    extend,
    mergeOptions,
    defineReactive
}

暴露的工具方法。这些方法不被视为公共API的一部分,除非你知道里面的风险,否则避免使用。(这个util是Vue内部的工具方法,可能会发生变动),例如:在Vue.router中就使用了这个工具方法

Vue.set / Vue.delete

set方法新增响应式数据

export function set (target: Array<any> | Object, key: any, val: any): any {
  // 1.是开发环境 target 没定义或者是基础类型则报错
  if (process.env.NODE_ENV !== 'production' &&
    (isUndef(target) || isPrimitive(target))
  ) {
    warn(`Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`)
  }
  // 2.如果是数组 Vue.set(array,1,100); 调用我们重写的splice方法 (这样可以更新视图)
  if (Array.isArray(target) && isValidArrayIndex(key)) {
    target.length = Math.max(target.length, key)
    target.splice(key, 1, val)
    return val
  }
  // 3.如果是对象本身的属性,则直接添加即可
  if (key in target && !(key in Object.prototype)) {
    target[key] = val
    return val
  }
  const ob = (target: any).__ob__
  // 4.如果是Vue实例 或 根数据data时 报错
  if (target._isVue || (ob && ob.vmCount)) {
    process.env.NODE_ENV !== 'production' && warn(
      'Avoid adding reactive properties to a Vue instance or its root $data ' +
      'at runtime - declare it upfront in the data option.'
    )
    return val
  }
  // 5.如果不是响应式的也不需要将其定义成响应式属性
  if (!ob) {
    target[key] = val
    return val
  }
  // 6.将属性定义成响应式的
  defineReactive(ob.value, key, val)
  ob.dep.notify() // 7.通知视图更新
  return val
}
export function del (target: Array<any> | Object, key: any) {
  if (process.env.NODE_ENV !== 'production' &&
    (isUndef(target) || isPrimitive(target))
  ) {
    warn(`Cannot delete reactive property on undefined, null, or primitive value: ${(target: any)}`)
  }
  // 1.如果是数组依旧调用splice方法
  if (Array.isArray(target) && isValidArrayIndex(key)) {
    target.splice(key, 1)
    return
  }
  const ob = (target: any).__ob__
  if (target._isVue || (ob && ob.vmCount)) {
    process.env.NODE_ENV !== 'production' && warn(
      'Avoid deleting properties on a Vue instance or its root $data ' +
      '- just set it to null.'
    )
    return
  }
  // 2.如果本身就没有这个属性什么都不做
  if (!hasOwn(target, key)) {
    return
  }
  // 3.删除这个属性
  delete target[key]
  if (!ob) {
    return
  }
  // 4.通知更新
  ob.dep.notify()
}

Vue的缺陷:新增之前不存在的属性不会发生视图更新,修改数组索引不会发生视图更新 (解决方案就是通过$set方法,数组通过splice进行更新视图,对象则手动通知)

Vue.nextTick
const callbacks = []; // 存放nextTick回调
let pending = false; 
function flushCallbacks () { // 清空队列
  pending = false
  const copies = callbacks.slice(0)
  callbacks.length = 0
  for (let i = 0; i < copies.length; i++) {
    copies[i]()
  }
}
let timerFunc
export function nextTick (cb?: Function, ctx?: Object) {
  let _resolve
  callbacks.push(() => {
    if (cb) {
      try {
        cb.call(ctx) // 1.将回调函数存入到callbacks中
      } catch (e) {
        handleError(e, ctx, 'nextTick')
      }
    } else if (_resolve) {
      _resolve(ctx)
    }
  })
  if (!pending) {
    pending = true
    timerFunc();     // 2.异步刷新队列
  }
  // 3.支持promise写法
  if (!cb && typeof Promise !== 'undefined') {
    return new Promise(resolve => { 
      _resolve = resolve
    })
  }
}

不难看出nextTick原理就是将回调函数存入到一个队列中,最后异步的清空这个队列

timerFunc

// 1.默认先使用Promise 因为mutationObserver有bug可能不工作
if (typeof Promise !== 'undefined' && isNative(Promise)) {
  const p = Promise.resolve()
  timerFunc = () => {
    p.then(flushCallbacks)
    // 解决队列不刷新问题
    if (isIOS) setTimeout(noop)
  }
  isUsingMicroTask = true
// 2.使用MutationObserver
} else if (!isIE && typeof MutationObserver !== 'undefined' && (
  isNative(MutationObserver) ||
  
  MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
  let counter = 1
  const observer = new MutationObserver(flushCallbacks)
  const textNode = document.createTextNode(String(counter))
  observer.observe(textNode, {
    characterData: true
  })
  timerFunc = () => {
    counter = (counter + 1) % 2
    textNode.data = String(counter)
  }
  isUsingMicroTask = true
// 3.使用 setImmediate
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
  timerFunc = () => {
    setImmediate(flushCallbacks)
  }
// 4.使用setTimeout
} else {
  timerFunc = () => {
    setTimeout(flushCallbacks, 0)
  }
}

采用EventLoop中的微任务和宏任务,先采用微任务并按照优先级优雅降级的方式实现异步刷新

Vue.observable

2.6新增的方法,将对象进行观测,并返回观测后的对象。可以用来做全局变量,实现数据共享

Vue.observable = <T>(obj: T): T => {
    observe(obj)
    return obj
}
Vue.options

存放全局的组件、指令、过滤器的一个对象,及拥有_base属性保存Vue的构造函数

ASSET_TYPES.forEach(type => {
    Vue.options[type + 's'] = Object.create(null)
})
Vue.options._base = Vue
extend(Vue.options.components, builtInComponents) // 内置了 keep-alive
Vue.use

Vue.use 主要的作用就是调用插件的install方法,并将Vue作为第一个参数传入,这样做的好处是可以避免我们编写插件时需要依赖Vue导致版本问题。

initUse(Vue)

Vue.use = function (plugin: Function | Object) {
    const installedPlugins = (this._installedPlugins || (this._installedPlugins = []))
    // 1.如果安装过这个插件直接跳出
    if (installedPlugins.indexOf(plugin) > -1) {
      return this
    }
    // 2.获取参数并在参数中增加Vue的构造函数
    const args = toArray(arguments, 1)
    args.unshift(this)
    // 3.执行install方法
    if (typeof plugin.install === 'function') {
      plugin.install.apply(plugin, args)
    } else if (typeof plugin === 'function') {
      plugin.apply(null, args)
    }
    // 4.记录安装的插件
    installedPlugins.push(plugin)
    return this
}
Vue.mixin

全局混合方法,可以用来提取公共方法及状态等.

Vue.mixin = function (mixin: Object) {
    this.options = mergeOptions(this.options, mixin)
    return this
}

Vue对不同的属性做了不同的合并策略

export function mergeOptions (
  parent: Object,
  child: Object,
  vm?: Component
): Object {
  if (!child._base) { // 1.组件先将自己的extends和mixin与父属性合并
    if (child.extends) {
      parent = mergeOptions(parent, child.extends, vm)
    }
    if (child.mixins) {
      for (let i = 0, l = child.mixins.length; i < l; i++) {
        parent = mergeOptions(parent, child.mixins[i], vm)
      }
    }
  }
  // 2.再用之前合并后的结果,与自身的属性进行合并
  const options = {}
  let key
  for (key in parent) {
    mergeField(key)
  }
  for (key in child) {
    if (!hasOwn(parent, key)) {
      mergeField(key)
    }
  }
  function mergeField (key) {
    const strat = strats[key] || defaultStrat; // 3.采用不同的合并策略
    options[key] = strat(parent[key], child[key], vm, key)
  }
  return options
}

你可以通过查看strats这个对象来了解不同的合并策略

Vue.extend

Vue中非常核心的一个方法,可以通过传入的对象获取这个对象的构造函数,后续在组件初始化过程中会用到此方法

Vue.extend = function (extendOptions: Object): Function {
    // ...
    const Sub = function VueComponent (options) {
      this._init(options)
    }
    Sub.prototype = Object.create(Super.prototype)
    Sub.prototype.constructor = Sub
    Sub.options = mergeOptions( // 子组件的选项和Vue.options进行合并
      Super.options,
      extendOptions
    )
    // ...
    return Sub; 
  }

extend 创建的是 Vue 构造器,我们可以自己实例化并且将其挂载在任意的元素上

组件、指令、过滤器
initAssetRegisters(Vue)

ASSET_TYPES.forEach(type => {
    Vue[type] = function (
      id: string,
      definition: Function | Object
    ): Function | Object | void {
      if (!definition) {
        return this.options[type + 's'][id]
      } else {
        /* istanbul ignore if */
        if (process.env.NODE_ENV !== 'production' && type === 'component') {
          validateComponentName(id)
        }
        if (type === 'component' && isPlainObject(definition)) {
          definition.name = definition.name || id
          definition = this.options._base.extend(definition)
        }
        if (type === 'directive' && typeof definition === 'function') {
          definition = { bind: definition, update: definition }
        }
        this.options[type + 's'][id] = definition; // 将指令、过滤器、组件 绑定在Vue.options上

        // 备注:全局组件、指令过滤器其实就是定义在 Vue.options中,这样创建子组件时都会和Vue.options进行合并,所以子组件可以拿到全局的定义

        return definition
      }
    }
})

初始化全局的api,Vue.component、Vue.directive、Vue.filter,这里仅仅是格式化用户传入的内容,将其绑定在Vue.options选项上

Vue的初始化过程

function Vue (options) {
  if (process.env.NODE_ENV !== 'production' &&
    !(this instanceof Vue)
  ) {
    warn('Vue is a constructor and should be called with the `new` keyword')
  }
  this._init(options) // 当new Vue时会调用 _init方法
}

initMixin(Vue)       // 初始化_init方法
stateMixin(Vue)      // $set / $delete / $watch
eventsMixin(Vue)     // $on $once $off $emit
lifecycleMixin(Vue)  // _update
renderMixin(Vue)     // _render $nextTick
Vue的初始化

通过Vue的_init方法,我们可以看到内部又包含了很多初始化的过程

export function initMixin (Vue: Class<Component>) {
  Vue.prototype._init = function (options?: Object) {
    const vm: Component = this
    // 1.每个vue的实例上都有一个唯一的属性_uid
    vm._uid = uid++ 
    // 2.表示是Vue的实例
    vm._isVue = true

    // 3.选项合并策略
    if (options && options._isComponent) {
      initInternalComponent(vm, options)
    } else {
      vm.$options = mergeOptions(
        resolveConstructorOptions(vm.constructor),
        options || {},
        vm
      )
    }
    vm._self = vm
    // 4.进行初始化操作
    initLifecycle(vm)
    initEvents(vm)
    initRender(vm)
    callHook(vm, 'beforeCreate')
    initInjections(vm) 
    initState(vm)
    initProvide(vm) 
    callHook(vm, 'created')

    // 5.如果有el就开始进行挂载
    if (vm.$options.el) {
      vm.$mount(vm.$options.el)
    }
  }
}
_init方法中的初始化

这个代码写的真是一目了然,我们先看看每个方法"大概"干了什么事,切记不要死追到底!

initLifecycle(vm) // 初始化组件间的父子关系 
initEvents(vm)    // 更新组件的事件
initRender(vm)    // 初始化_c方法
initInjections(vm)// 初始化inject
initState(vm)     // 初始化状态
initProvide(vm)   // 初始化provide
挂载流程
// 1.如果有el就开始挂载 
if (vm.$options.el) {
    vm.$mount(vm.$options.el)
}

// 2.组件的挂载
Vue.prototype.$mount = function (el,hydrating){
  el = el && inBrowser ? query(el) : undefined
  return mountComponent(this, el, hydrating);
}

// 3.创建渲染watcher进行渲染
export function mountComponent (vm,el,hydrating) {
  vm.$el = el
  let updateComponent
  updateComponent = () => {
      vm._update(vm._render(), hydrating)
  }
  new Watcher(vm, updateComponent, noop, { // 创建渲染Watcher
    before () {
      if (vm._isMounted && !vm._isDestroyed) {
        callHook(vm, 'beforeUpdate')
      }
    }
  }, true /* isRenderWatcher */)
  return vm
}

响应式变化原理

数据劫持

进行了细化和拆分,对不同的属性做了不同的初始化操作

export function initState (vm: Component) {
  vm._watchers = []
  const opts = vm.$options
  if (opts.props) initProps(vm, opts.props)
  if (opts.methods) initMethods(vm, opts.methods)
  if (opts.data) {
    initData(vm)
  } else {
    observe(vm._data = {}, true /* asRootData */)
  }
  if (opts.computed) initComputed(vm, opts.computed)
  if (opts.watch && opts.watch !== nativeWatch) {
    initWatch(vm, opts.watch)
  }
}
数据的初始化

这里我们先关心数据是如何进行初始化操作的

function initData (vm: Component) {
  let data = vm.$options.data
  data = vm._data = typeof data === 'function'
    ? getData(data, vm)
    : data || {}
  // 1.数据不是对象则发生异常
  if (!isPlainObject(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
    )
  }
  const keys = Object.keys(data)
  const props = vm.$options.props
  const methods = vm.$options.methods
  let i = keys.length
  // 2.校验数据是否在method中已经声明过
  while (i--) {               
    const key = keys[i]
    if (process.env.NODE_ENV !== 'production') {
      if (methods && hasOwn(methods, key)) {
        warn(
          `Method "${key}" has already been defined as a data property.`,
          vm
        )
      }
    }
    // 3.校验数据是否在属性中已经声明过
    if (props && hasOwn(props, key)) {
      process.env.NODE_ENV !== 'production' && warn(
        `The data property "${key}" is already declared as a prop. ` +
        `Use prop default value instead.`,
        vm
      )
    } else if (!isReserved(key)) {
      // 4.将_data代理到实例上
      proxy(vm, `_data`, key) 
    }
  }
  // 5.观测数据
  observe(data, true /* asRootData */)
}

这里主要是检测属性是否被重复声明,并对属性进行观测

观测数据
export function observe (value: any, asRootData: ?boolean): Observer | void {
  // 1.如果不是对象直接return
  if (!isObject(value) || value instanceof VNode) {
    return
  }
  let ob: Observer | void
  // 2.如果已经观测过则直接返回上次观测的实例
  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) { // 
    ob = value.__ob__
  } else if (
    shouldObserve &&
    !isServerRendering() &&
    (Array.isArray(value) || isPlainObject(value)) &&
    Object.isExtensible(value) &&
    !value._isVue
  ) {
  // 3.如果可以观测就进行观测
    ob = new Observer(value)
  }
  // 4.如果是根数据  vmCount标注为1
  if (asRootData && ob) {
    ob.vmCount++
  }
  return ob
}

只观测对象数据类型,已经观测的不在进行观测,不能扩展的属性不进行观测。

export class Observer {
  constructor (value: any) {
    this.value = value
    this.dep = new Dep()
    this.vmCount = 0
    def(value, '__ob__', this)
    // 1.数组的话重写数组原型方法 
    if (Array.isArray(value)) { 
      if (hasProto) {
        protoAugment(value, arrayMethods)
      } else {
        copyAugment(value, arrayMethods, arrayKeys)
      }
      // 2.观测数组中是对象类型的数据
      this.observeArray(value) 
    } else { 
      // 3.对象的话使用defineProperty重新定义属性
      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])
    }
  }
}

这里要区分对象和数组,如果是数组不能使用Object.defineProperty会造成性能浪费,所以采用重写可以更改数组本身的方法的方式

对象的观测

对象的观测就是将所有属性使用defineProperty进行重新定义

export function defineReactive (
  obj: Object,
  key: string,
  val: any,
  customSetter?: ?Function,
  shallow?: boolean
) {
  // 1.如果对象不可配置则直接退出
  const property = Object.getOwnPropertyDescriptor(obj, key)
  if (property && property.configurable === false) {
    return
  }

  // 2.获取getter和setter
  const getter = property && property.get
  const setter = property && property.set
  
  // 3.重新定义set和get方法
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      const value = getter ? getter.call(obj) : val
      return value
    },
    set: function reactiveSetter (newVal) {
      const value = getter ? getter.call(obj) : val
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return
      }
      if (getter && !setter) return
      if (setter) {
        setter.call(obj, newVal)
      } else {
        val = newVal
      }
    }
  })
}

对象的属性劫持已经烂大街了,非常简单就是通过defineProperty来实现的,如果你还不会那得好好反思一下了。这里提一下:想减少观测可以使用Object.freeze冻结对象

image-20240719111419317

数组的观测

数组的观测就是通过重写原型方法来实现的

export const arrayMethods = Object.create(arrayProto)
const methodsToPatch = [
  'push',
  'pop',
  'shift',
  'unshift',
  'splice',
  'sort',
  'reverse'
]
methodsToPatch.forEach(function (method) {
  // cache original method
  const original = arrayProto[method]
  def(arrayMethods, method, function mutator (...args) {
    const result = original.apply(this, args)
    const ob = this.__ob__
    let inserted
    switch (method) {
      case 'push':
      case 'unshift':
        inserted = args
        break
      case 'splice':
        inserted = args.slice(2)
        break
    }
    // 对新增的属性再次进行观测
    if (inserted) ob.observeArray(inserted)
    return result
  })
})

所谓的数据观测就是当数据变化时我们可以知道,像对象更改时可以出发set方法,像数组调用push方法可以触发我们自己写的push

image-20240719111518407

依赖收集

vue的渲染过程是通过渲染watcher来实现的

let updateComponent = updateComponent = () => {
      vm._update(vm._render(), hydrating)
}
new Watcher(vm, updateComponent, noop, {}, true /* isRenderWatcher */)

在我们创建watcher时,会对变量进行取值

对象依赖收集

对于对象而言,取值就会触发get方法,我们可以在defineProperty的get中进行依赖收集,在set中通知watcher进行更新操作

class Watcher {
  constructor (
    vm: Component,
    expOrFn: string | Function, // updateComponent
    cb: Function,
    options?: ?Object,
    isRenderWatcher?: boolean
  ) {
    this.vm = vm
    if (isRenderWatcher) {
      vm._watcher = this
    }
    this.cb = cb
    this.id = ++uid // uid for batching
    this.active = true
    this.dirty = this.lazy // for lazy watchers
    this.deps = []
    this.newDeps = []
    this.depIds = new Set()
    this.newDepIds = new Set()
    this.expression = process.env.NODE_ENV !== 'production'
      ? expOrFn.toString()
      : ''
    // 将updateComponent 放到this.getter上
    this.getter = expOrFn
    this.value = this.lazy
      ? undefined
      : this.get() // 执行get方法
  }
  get () {
    pushTarget(this)  // Dep.target = 渲染watcher
    let value
    const vm = this.vm
    try {
      value = this.getter.call(vm, vm) // 开始取值 那么在get方法中就可以获取到这个全局变量Dep.target        
    } catch (e) {
      if (this.user) {
        handleError(e, vm, `getter for watcher "${this.expression}"`)
      } else {
        throw e
      }
    } finally {
      // "touch" every property so they are all tracked as
      // dependencies for deep watching
      if (this.deep) {
        traverse(value)
      }
      popTarget() // 结束后进行清理操作
      this.cleanupDeps()
    }
    return value
  }
}

渲染watcher,默认会调用get方法也就是我们传入的updateComponent方法,在调用此方法前先将watcher存到全局中,这样再取值时可以获取到这个watcher。

const dep = new Dep()
get: function reactiveGetter () {
    const value = getter ? getter.call(obj) : val
    if (Dep.target) { // 如果有watcher 将dep和watcher对应起来
        dep.depend()
    }
    return value
}
set: function reactiveSetter (newVal) {
    dep.notify();    // 当属性更新时通知dep中的所有watcher进行更新操作
}

异步更新

为了防止多次更改同一个属性或者多次修改不同属性(他们依赖的watcher相同) 会导致频繁更新渲染

export function queueWatcher (watcher: Watcher) {
  const id = watcher.id
  // 1.判断watcher是否已经存放了
  if (has[id] == null) {
    has[id] = true
  // 2.将watcher存放到队列中
    queue.push(watcher)
    // queue the flush
    if (!waiting) {
      waiting = true
      nextTick(flushSchedulerQueue) // 在下一队列中清空queue
    }
  }
}

对相同watcher进行过滤操作,当同步的更改状态完毕时再去更新watcher

image-20240719112342241

Vue2核心原理

使用Rollup搭建开发环境

安装rollup环境

npm install @babel/preset-env @babel/core rollup rollup-plugin-babel rollup-plugin-serve cross-env -D

rollup.config.js文件

// rollup.config.js

import babel from 'rollup-plugin-babel';
import serve from 'rollup-plugin-serve';
export default {
    input: './src/index.js',
    output: {
        format: 'umd', // 模块化类型
        file: 'dist/umd/vue.js', 
        name: 'Vue', // 打包后的全局变量的名字
        sourcemap: true
    },
    plugins: [
        babel({
            exclude: 'node_modules/**'
        }),
        process.env.ENV === 'development'?serve({
            open: true,
            openPage: '/public/index.html',
            port: 3000,
            contentBase: ''
        }):null
    ]
}

配置.babelrc文件

// .babelrc

{
    "presets": [
        "@babel/preset-env"
    ]
}

执行脚本配置

"scripts": {
    "build:dev": "rollup -c",
    "serve": "cross-env ENV=development rollup -c -w"
}

Vue的渲染流程

  • 先初始化数据
  • 将模板进行编译
  • 调用render函数
  • 生成虚拟节点
  • 生成真实的dom
  • 扔到页面上

Vue响应式原理

相关文章:https://blog.csdn.net/qq_40588441/article/details/140337629?spm=1001.2014.3001.5501

Vue并不是一个MVVM框架,只是借鉴了MVVM思想,所谓MVVM是指数据变化视图会更新,视图变化数据会被影响,不能跳动数据去更新视图

但是Vue可以通过$ref跳过数据去更新视图

导出vue构造函数

import {initMixin} from './init';

function Vue(options) {
    this._init(options);
}
initMixin(Vue); // 给原型上新增_init方法
export default Vue;

init方法中初始化vue状态

import {initState} from './state';
export function initMixin(Vue){
    Vue.prototype._init = function (options) {
        const vm  = this;
        vm.$options = options
        // 初始化状态
        initState(vm)
    }
}

根据不同属性进行初始化操作

export function initState(vm){
    const opts = vm.$options;
    if(opts.props){
        initProps(vm);
    }
    if(opts.methods){
        initMethod(vm);
    }
    if(opts.data){
        // 初始化data
        initData(vm);
    }
    if(opts.computed){
        initComputed(vm);
    }
    if(opts.watch){
        initWatch(vm);
    }
}
function initProps(){}
function initMethod(){}
function initData(){}
function initComputed(){}
function initWatch(){}

初始化数据

这里又进行了细化和拆分,对不同的属性做了不同的初始化操作,原来我们常用的api都在这里做的初始化

import {observe} from './observer/index.js'
function initData (vm: Component) {
  let data = vm.$options.data
  data = vm._data = typeof data === 'function'
    ? getData(data, vm)
    : data || {}
  // 1.数据不是对象则发生异常
  if (!isPlainObject(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
    )
  }
  const keys = Object.keys(data)
  const props = vm.$options.props
  const methods = vm.$options.methods
  let i = keys.length
  // 2.校验数据是否在method中已经声明过
  while (i--) {               
    const key = keys[i]
    if (process.env.NODE_ENV !== 'production') {
      if (methods && hasOwn(methods, key)) {
        warn(
          `Method "${key}" has already been defined as a data property.`,
          vm
        )
      }
    }
    // 3.校验数据是否在属性中已经声明过
    if (props && hasOwn(props, key)) {
      process.env.NODE_ENV !== 'production' && warn(
        `The data property "${key}" is already declared as a prop. ` +
        `Use prop default value instead.`,
        vm
      )
    } else if (!isReserved(key)) {
      // 4.将_data代理到实例上
      proxy(vm, `_data`, key) 
    }
  }
  // 5.观测数据
  observe(data, true /* asRootData */)
}

递归属性劫持

消耗大性能差,不存在的属性,如果新增的话不能重新渲染视图

所以在Vue3中使用Proxy来解决,不需要以上来就进行递归(性能好,但是兼容性差)

class Observer { // 观测值
    constructor(value){
        this.walk(value);
    }
    walk(data){ // 让对象上的所有属性依次进行观测
        let keys = Object.keys(data);
        for(let i = 0; i < keys.length; i++){
            let key = keys[i];
            let value = data[key];
            defineReactive(data,key,value);
        }
    }
}
function defineReactive(data,key,value){
    observe(value);
    Object.defineProperty(data,key,{
        get(){
            return value
        },
        set(newValue){
            if(newValue == value) return;
            observe(newValue);
            value = newValue
        }
    })
}
export function observe(data) {
    if(typeof data !== 'object' || data == null){
        return;
    }
    return new Observer(data);
}

数组方法的劫持

函数劫持之后,调用数组的方法,则触发更新视图

数组中如果是对象数据类型也会进行递归劫持

数组的索引和长度变化是无法监控到的(可以通过$set解决[本质是splice方法])

import {arrayMethods} from './array';
class Observer { // 观测值
    constructor(value){
        if(Array.isArray(value)){
            value.__proto__ = arrayMethods; // 重写数组原型方法
            this.observeArray(value);
        }else{
            this.walk(value);
        }
    }
    observeArray(value){
        for(let i = 0 ; i < value.length ;i ++){
            observe(value[i]);
        }
    }
}

重写数组原型方法

let oldArrayProtoMethods = Array.prototype;
export let arrayMethods = Object.create(oldArrayProtoMethods);
let methods = [
    'push',
    'pop',
    'shift',
    'unshift',
    'reverse',
    'sort',
    'splice'
];
methods.forEach(method => {
    arrayMethods[method] = function (...args) {
        const result = oldArrayProtoMethods[method].apply(this, args);
        const ob = this.__ob__;
        let inserted;
        switch (method) {
            case 'push':
            case 'unshift':
                inserted = args;
                break;
            case 'splice':
                inserted = args.slice(2)
            default:
                break;
        }
        if (inserted) ob.observeArray(inserted); // 对新增的每一项进行观测
        return result
    }
})

增加__ob__属性

class Observer { 
    constructor(value){
        Object.defineProperty(value,'__ob__',{
            enumerable:false,
            configurable:false,
            value:this
        });
        // ...
    }
 }

数据代理

function proxy(vm,source,key){
    Object.defineProperty(vm,key,{
        get(){
            return vm[source][key];
        },
        set(newValue){
            vm[source][key] = newValue;
        }
    });
}
function initData(vm){
    let data = vm.$options.data;
    data = vm._data = typeof data === 'function' ? data.call(vm) : data;
    for(let key in data){ // 将_data上的属性全部代理给vm实例
        proxy(vm,'_data',key)
    }
    observe(data);
}

模板编译

Vue.prototype._init = function (options) {
    const vm = this;
    vm.$options = options;
    // 初始化状态
    initState(vm);
    // 页面挂载
    if (vm.$options.el) {
    	vm.$mount(vm.$options.el);
    }
}
Vue.prototype.$mount = function (el) {
    const vm = this;
    const options = vm.$options;
    el = document.querySelector(el);

    // 如果没有render方法
    if (!options.render) {
        let template = options.template;
        // 如果没有模板但是有el
        if (!template && el) {
        	template = el.outerHTML;
        }
        const render= compileToFunctions(template);
        options.render = render;
    }
}

template编译成render函数

解析标签和内容

const ncname = `[a-zA-Z_][\\-\\.0-9_a-zA-Z]*`;  
const qnameCapture = `((?:${ncname}\\:)?${ncname})`;
const startTagOpen = new RegExp(`^<${qnameCapture}`); // 标签开头的正则 捕获的内容是标签名
const endTag = new RegExp(`^<\\/${qnameCapture}[^>]*>`); // 匹配标签结尾的 </div>
const attribute = /^\s*([^\s"'<>\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/; // 匹配属性的
const startTagClose = /^\s*(\/?)>/; // 匹配标签结束的 >
const defaultTagRE = /\{\{((?:.|\r?\n)+?)\}\}/g
function start(tagName,attrs){
    console.log(tagName,attrs)
}
function end(tagName){
    console.log(tagName)
}
function chars(text){
    console.log(text);
}
function parseHTML(html){
    while(html){
        let textEnd = html.indexOf('<');
        if(textEnd == 0){
            const startTagMatch = parseStartTag();
            if(startTagMatch){
                start(startTagMatch.tagName,startTagMatch.attrs);
                continue;
            }
            const endTagMatch = html.match(endTag);
            if(endTagMatch){
                advance(endTagMatch[0].length);
                end(endTagMatch[1]);
                continue;
            }
        }
        let text;
        if(textEnd >= 0){
            text = html.substring(0,textEnd);
        }
        if(text){
            advance(text.length);
            chars(text);
        }
    }
    function advance(n){
        html = html.substring(n);
    }
    function parseStartTag(){
        const start = html.match(startTagOpen);
        if(start){
            const match = {
                tagName:start[1],
                attrs:[]
            }
            advance(start[0].length);
            let attr,end;
            while(!(end = html.match(startTagClose)) && (attr = html.match(attribute))){
                advance(attr[0].length);
                match.attrs.push({name:attr[1],value:attr[3]});
            }
            if(end){
                advance(end[0].length);
                return match
            }
        }
    }
}
export function compileToFunctions(template){
    parseHTML(template);
    return function(){}
}

生成ast语法树

语法树就是用对象描述js语法

{
    tag:'div',
    type:1,
    children:[{tag:'span',type:1,attrs:[],parent:'div对象'}],
    attrs:[{name:'hs',age:10}],
    parent:null
}
let root;
let currentParent;
let stack = [];
const ELEMENT_TYPE = 1;
const TEXT_TYPE = 3;

function createASTElement(tagName,attrs){
    return {
        tag:tagName,
        type:ELEMENT_TYPE,
        children:[],
        attrs,
        parent:null
    }
}
function start(tagName, attrs) {
    let element = createASTElement(tagName,attrs);
    if(!root){
        root = element;
    }
    currentParent = element;
    stack.push(element);
}
function end(tagName) {
    let element = stack.pop();
    currentParent = stack[stack.length-1];
    if(currentParent){
        element.parent = currentParent;
        currentParent.children.push(element);
    }
}
function chars(text) {
    text = text.replace(/\s/g,'');
    if(text){
        currentParent.children.push({
            type:TEXT_TYPE,
            text
        })
    }
}

生成代码

template转化成render函数的结果

<div style="color:red">hello {{name}} <span></span></div>
render(){
   return _c('div',{style:{color:'red'}},_v('hello'+_s(name)),_c('span',undefined,''))
}

实现代码生成

function gen(node) {
    if (node.type == 1) {
        return generate(node);
    } else {
        let text = node.text
        if(!defaultTagRE.test(text)){
            return `_v(${JSON.stringify(text)})`
        }
        let lastIndex = defaultTagRE.lastIndex = 0
        let tokens = [];
        let match,index;
        
        while (match = defaultTagRE.exec(text)) {
            index = match.index;
            if(index > lastIndex){
                tokens.push(JSON.stringify(text.slice(lastIndex,index)));
            }
            tokens.push(`_s(${match[1].trim()})`)
            lastIndex = index + match[0].length;
        }
        if(lastIndex < text.length){
            tokens.push(JSON.stringify(text.slice(lastIndex)))
        }
        return `_v(${tokens.join('+')})`;
    }
}
function getChildren(el) { // 生成儿子节点
    const children = el.children;
    if (children) {
        return `${children.map(c=>gen(c)).join(',')}`
    } else {
        return false;
    }
}
function genProps(attrs){ // 生成属性
    let str = '';
    for(let i = 0; i<attrs.length; i++){
        let attr = attrs[i];
        if(attr.name === 'style'){
            let obj = {}
            attr.value.split(';').forEach(item=>{
                let [key,value] = item.split(':');
                obj[key] = value;
            })
            attr.value = obj;
        }
        str += `${attr.name}:${JSON.stringify(attr.value)},`;
    }
    return `{${str.slice(0,-1)}}`;
}
function generate(el) {
    let children = getChildren(el);
    let code = `_c('${el.tag}',${
        el.attrs.length?`${genProps(el.attrs)}`:'undefined'
    }${
        children? `,${children}`:''
    })`;
    return code;
}
let code = generate(root);

生成render函数

export function compileToFunctions(template) {
    parseHTML(template);
    let code = generate(root);
    let render = `with(this){return ${code}}`;
    let renderFn = new Function(render);
    return renderFn
}

创建渲染watcher

初始化渲染Watcher

import {mountComponent} from './lifecycle'
Vue.prototype.$mount = function (el) {
    const vm = this;
    const options = vm.$options;
    el = document.querySelector(el);

    // 如果没有render方法
    if (!options.render) {
        let template = options.template;
        // 如果没有模板但是有el
        if (!template && el) {
            template = el.outerHTML;
        }

        const render= compileToFunctions(template);
        options.render = render;
    }
    mountComponent(vm,el);
}

lifecycle.js

export function lifecycleMixin() {
    Vue.prototype._update = function (vnode) {}
}
export function mountComponent(vm, el) {
    vm.$el = el;
    let updateComponent = () => {
        // 将虚拟节点 渲染到页面上
        vm._update(vm._render());
    }
    new Watcher(vm, updateComponent, () => {}, true);
}

render.js

export function renderMixin(Vue){
    Vue.prototype._render = function () {}
}

watcher.js

let id = 0;
class Watcher {
    constructor(vm, exprOrFn, cb, options) {
        this.vm = vm;
        this.exprOrFn = exprOrFn;
        if (typeof exprOrFn == 'function') {
            this.getter = exprOrFn;
        }
        this.cb = cb;
        this.options = options;
        this.id = id++;
        this.get();
    }
    get() {
        this.getter();
    }
}

export default Watcher;

先调用_render方法生成虚拟dom,通过_update方法将虚拟dom创建成真实的dom

生成虚拟dom

import {createTextNode,createElement} from './vdom/create-element'
export function renderMixin(Vue){
    Vue.prototype._v = function (text) { // 创建文本
        return createTextNode(text);
    }
    Vue.prototype._c = function () { // 创建元素
        return createElement(...arguments);
    }
    Vue.prototype._s = function (val) {
        return val == null? '' : (typeof val === 'object'?JSON.stringify(val):val);
    }
    Vue.prototype._render = function () {
        const vm = this;
        const {render} = vm.$options;
        let vnode = render.call(vm);
        return vnode;
    }
}

创建虚拟节点

export function createTextNode(text) {
    return vnode(undefined,undefined,undefined,undefined,text)
}
export function createElement(tag,data={},...children){
    let key = data.key;
    if(key){
        delete data.key;
    }
    return vnode(tag,data,key,children);
}
function vnode(tag,data,key,children,text){
    return {
        tag,
        data,
        key,
        children,
        text
    }
}

生成真实DOM元素

将虚拟节点渲染成真实节点

import {patch} './observer/patch'
export function lifecycleMixin(Vue){
    Vue.prototype._update = function (vnode) {
        const vm = this;
        vm.$el = patch(vm.$el,vnode);
    }
}
export function patch(oldVnode,vnode){
    const isRealElement = oldVnode.nodeType;
    if(isRealElement){
        const oldElm = oldVnode;
        const parentElm = oldElm.parentNode;
        
        let el = createElm(vnode);
        parentElm.insertBefore(el,oldElm.nextSibling);
        parentElm.removeChild(oldVnode)
   		return el;
    } 
}
function createElm(vnode){
    let {tag,children,key,data,text} = vnode;
    if(typeof tag === 'string'){
        vnode.el = document.createElement(tag);
        updateProperties(vnode);
        children.forEach(child => { 
            return vnode.el.appendChild(createElm(child));
        });
    }else{
        vnode.el = document.createTextNode(text);
    }
    return vnode.el
}
function updateProperties(vnode){
    let newProps = vnode.data || {}; // 获取当前老节点中的属性 
    let el = vnode.el; // 当前的真实节点
    for(let key in newProps){
        if(key === 'style'){ 
            for(let styleName in newProps.style){
                el.style[styleName] = newProps.style[styleName]
            }
        }else if(key === 'class'){
            el.className= newProps.class
        }else{ // 给这个元素添加属性 值就是对应的值
            el.setAttribute(key,newProps[key]);
        }
    }
}

生命周期的合并

Mixin原理

import {mergeOptions} from '../util/index.js'
export function initGlobalAPI(Vue){
    Vue.options = {};

    Vue.mixin = function (mixin) {
        // 将属性合并到Vue.options上
        this.options = mergeOptions(this.options,mixin);
        return this;
    }
}

合并生命周期

export const LIFECYCLE_HOOKS = [
    'beforeCreate',
    'created',
    'beforeMount',
    'mounted',
    'beforeUpdate',
    'updated',
    'beforeDestroy',
    'destroyed',
]
const strats = {};
function mergeHook(parentVal, childValue) {
    if (childValue) {
        if (parentVal) {
            return parentVal.concat(childValue);
        } else {
            return [childValue]
        }
    } else {
        return parentVal;
    }
}
LIFECYCLE_HOOKS.forEach(hook => {
    strats[hook] = mergeHook
})
export function mergeOptions(parent, child) {
    const options = {}
    for (let key in parent) {
        mergeField(key)
    }
    for (let key in child) {
        if (!parent.hasOwnProperty(key)) {
            mergeField(key);
        }
    }
    function mergeField(key) {
        if (strats[key]) {
            options[key] = strats[key](parent[key], child[key]);
        } else {
            if (typeof parent[key] == 'object' && typeof child[key] == 'object') {
                options[key] = {
                    ...parent[key],
                    ...child[key]
                }
            }else{
                options[key] = child[key];
            }
        }
    }
    return options
}

调用生命周期

export function callHook(vm, hook) {
    const handlers = vm.$options[hook];
    if (handlers) {
        for (let i = 0; i < handlers.length; i++) {
            handlers[i].call(vm);
        }
    }
}

初始化流程中调用生命周期

Vue.prototype._init = function (options) {
    const vm = this;
    vm.$options = mergeOptions(vm.constructor.options,options);
    // 初始化状态
    callHook(vm,'beforeCreate');
    initState(vm);
    callHook(vm,'created');
    if (vm.$options.el) {
    	vm.$mount(vm.$options.el);
    }
}

依赖收集

每个属性都要有一个dep,每个dep中存放着watcher,同一个watcher会被多个dep所记录。

在渲染时存储watcher

class Watcher{
    // ...
    get(){
        pushTarget(this);
        this.getter();
        popTarget();
    }
}
let id = 0;
class Dep{
    constructor(){
        this.id = id++;
    }
}
let stack = [];
export function pushTarget(watcher){
    Dep.target = watcher;
    stack.push(watcher);
}
export function popTarget(){
    stack.pop();
    Dep.target = stack[stack.length-1];
}
export default Dep;

对象依赖收集

let dep = new Dep();
Object.defineProperty(data, key, {
    get() {
        if(Dep.target){ // 如果取值时有watcher
            dep.depend(); // 让watcher保存dep,并且让dep 保存watcher
        }
        return value
    },
    set(newValue) {
        if (newValue == value) return;
        observe(newValue);
        value = newValue;
        dep.notify(); // 通知渲染watcher去更新
    }
});

Dep实现

class Dep{
    constructor(){
        this.id = id++;
        this.subs = [];
    }
    depend(){
        if(Dep.target){
            Dep.target.addDep(this);// 让watcher,去存放dep
        }
    }
    notify(){
        this.subs.forEach(watcher=>watcher.update());
    }
    addSub(watcher){
        this.subs.push(watcher);
    }
}

watcher

constructor(){
	this.deps = [];
	this.depsId = new Set();
}
addDep(dep){
    let id = dep.id;
    if(!this.depsId.has(id)){
        this.depsId.add(id);
        this.deps.push(dep);
        dep.addSub(this);
    }
}
update(){
    this.get();
}

数组的依赖收集

this.dep = new Dep(); // 专门为数组设计的
if (Array.isArray(value)) {
	value.__proto__ = arrayMethods;
	this.observeArray(value);
} else {
	this.walk(value);
}	

function defineReactive(data, key, value) {
    let childOb = observe(value);
    let dep = new Dep();
    Object.defineProperty(data, key, {
        get() {
            if(Dep.target){
                dep.depend();
                if(childOb){ 
                    childOb.dep.depend(); // 收集数组依赖
                }
            }
            return value
        },
        set(newValue) {
            if (newValue == value) return;
            observe(newValue);
            value = newValue;
            dep.notify();
        }
    })
}


arrayMethods[method] = function (...args) {
    	// ...
        ob.dep.notify()
        return result;
}

递归收集数组依赖

if(Dep.target){
    dep.depend();
    if(childOb){
        childOb.dep.depend(); // 收集数组依赖
        if(Array.isArray(value)){ // 如果内部还是数组
            dependArray(value);// 不停的进行依赖收集
        }
    }
}
function dependArray(value) {
    for (let i = 0; i < value.length; i++) {
        let current = value[i];
        current.__ob__ && current.__ob__.dep.depend();
        if (Array.isArray(current)) {
            dependArray(current)
        }
    }
}

实现Vue异步更新之nextTick

实现队列机制

update(){
    queueWatcher(this);
}

scheduler

import {
    nextTick
} from '../util/next-tick'
let has = {};
let queue = [];

function flushSchedulerQueue() {
    for (let i = 0; i < queue.length; i++) {
        let watcher = queue[i];
        watcher.run()
    }
    queue = [];
    has = {}
}
let pending = false
export function queueWatcher(watcher) {
    const id = watcher.id;
    if (has[id] == null) {
        has[id] = true;
        queue.push(watcher);
        if(!pending){
            nextTick(flushSchedulerQueue)
            pending = true;
        }
    }
}

nextTick实现原理

util/next-tick.js

let callbacks = [];
function flushCallbacks() {
    callbacks.forEach(cb => cb());
}
let timerFunc;
if (Promise) { // then方法是异步的
    timerFunc = () => {
        Promise.resolve().then(flushCallbacks)
    }
}else if (MutationObserver) { // MutationObserver 也是一个异步方法
    let observe = new MutationObserver(flushCallbacks); // H5的api
    let textNode = document.createTextNode(1);
    observe.observe(textNode, {
        characterData: true
    });
    timerFunc = () => {
        textNode.textContent = 2;
    }
}else if (setImmediate) {
    timerFunc = () => {
        setImmediate(flushCallbacks)
    }
}else{
    timerFunc = () => {
        setTimeout(flushCallbacks, 0);
    }
}
export function nextTick(cb) {
    callbacks.push(cb);
    timerFunc();
}

image-20240719100856598

Watch & Computed

Watch实现原理

let vm = new Vue({
    el: '#app',
    data(){
    	return {name:'hs'}
    },
    watch:{
        name(newValue,oldValue){
        	console.log(newValue,oldValue);
    	}
    }
});

watch用于监控用户的data变化,数据变化后会触发对应的watch的回调方法

if (opts.watch) {
	initWatch(vm,opts.watch);
}

选项中如果有watch则对watch进行初始化

function initWatch(vm, watch) {
    for (const key in watch) {
        const handler = watch[key];
        // 如果结果值是数组循环创建watcher
        if (Array.isArray(handler)) {
            for (let i = 0; i < handler.length; i++) {
                createWatcher(vm,key,handler[i]);
            }
        }else{
            createWatcher(vm,key,handler)
        }
    }
}
function createWatcher(vm,exprOrFn,handler,options){
    // 如果是对象则提取函数 和配置
    if(isObject(handler)){
        options = handler;
        handler = handler.handler;
    }
    // 如果是字符串就是实例上的函数
    if(typeof handler == 'string'){
        handler = vm[handler];
    }
    return vm.$watch(exprOrFn,handler,options);
}

这里涉及了watch的三种写法,1.值是对象、2.值是数组、3.值是字符串 (如果是对象可以传入一些watch参数),最终会调用vm.$watch来实现

扩展Vue原型上的方法,都通过mixin的方式来进行添加的。

stateMixin(Vue);
export function stateMixin(Vue) {
    Vue.prototype.$watch = function(exprOrFn, cb, options = {}) {
        options.user = true; // 标记为用户watcher
        // 核心就是创建个watcher
        const watcher = new Watcher(this, exprOrFn, cb, options);
        if(options.immediate){
            cb.call(vm,watcher.value)
        }
    }
}
class Watcher {
    constructor(vm, exprOrFn, callback, options) {
        // ...
        this.user = !! options.user
        if(typeof exprOrFn === 'function'){
            this.getter = exprOrFn; 
        }else{
            this.getter = function (){ // 将表达式转换成函数
                let path = exprOrFn.split('.');
                let obj = vm;
                for(let i = 0; i < path.length;i++){
                    obj = obj[path[i]];
                }
                return obj;
            }
        }
        this.value = this.get(); // 将初始值记录到value属性上
    }
    get() {
        pushTarget(this); // 把用户定义的watcher存起来  
        const value = this.getter.call(this.vm); // 执行函数 (依赖收集)
        popTarget(); // 移除watcher
        return value;
    }
    run(){
        let value = this.get();    // 获取新值
        let oldValue = this.value; // 获取老值
        this.value = value;
        if(this.user){ // 如果是用户watcher 则调用用户传入的callback
            this.callback.call(this.vm,value,oldValue)
        }
    }
}

还是借助vue响应式原理,默认在取值时将watcher存放到对应属性的dep中,当数据发生变化时通知对应的watcher重新执行

Computed实现原理

if (opts.computed) {
    initComputed(vm,opts.computed);
}
function initComputed(vm, computed) {
    // 存放计算属性的watcher
    const watchers = vm._computedWatchers = {};
    for (const key in computed) {
        const userDef = computed[key];
        // 获取get方法
        const getter = typeof userDef === 'function' ? userDef : userDef.get;
        // 创建计算属性watcher
        watchers[key] = new Watcher(vm, userDef, () => {}, { lazy: true });
        defineComputed(vm, key, userDef)
    }
}

每个计算属性也都是一个watcher,计算属性需要表示lazy:true,这样在初始化watcher时不会立即调用计算属性方法

class Watcher {
    constructor(vm, exprOrFn, callback, options) {
        this.vm = vm;
        this.dirty = this.lazy
        // ...
        this.value = this.lazy ? undefined : this.get(); // 调用get方法 会让渲染watcher执行
    }
}    

默认计算属性需要保存一个dirty属性,用来实现缓存功能

function defineComputed(target, key, userDef) {
    if (typeof userDef === 'function') {
        sharedPropertyDefinition.get = createComputedGetter(key)
    } else {
        sharedPropertyDefinition.get = createComputedGetter(userDef.get);
        sharedPropertyDefinition.set = userDef.set;
    }
    // 使用defineProperty定义
    Object.defineProperty(target, key, sharedPropertyDefinition)
}

创建缓存getter

function createComputedGetter(key) {
    return function computedGetter() {
        const watcher = this._computedWatchers[key];
        if (watcher) {
            if (watcher.dirty) { // 如果dirty为true
                watcher.evaluate();// 计算出新值,并将dirty 更新为false
            }
            // 如果依赖的值不发生变化,则返回上次计算的结果
            return watcher.value
        }
    }
}
watcher.evaluate
evaluate() {
    this.value = this.get()
    this.dirty = false
}
update() {
    if (this.lazy) {
        this.dirty = true;
    } else {
        queueWatcher(this);
    }
}

当依赖的属性变化时,会通知watcher调用update方法,此时我们将dirty置换为true。这样再取值时会重新进行计算。

if (watcher) {
    if (watcher.dirty) {
        watcher.evaluate();
    }
    if (Dep.target) { // 计算属性在模板中使用 则存在Dep.target
        watcher.depend()
    }
	return watcher.value
}
depend() {
	let i = this.deps.length
	while (i--) {
		this.deps[i].depend()
	}
}

如果计算属性在模板中使用,就让计算属性中依赖的数据也记录渲染watcher,这样依赖的属性发生变化也可以让视图进行刷新

Vue组件原理解析

组件的渲染流程

  • 调用Vue.component
  • 内部用的Vue.extend就是产生一个子类来继承父类
  • 等会创建子类实例时,会调用父类的_init方法,再$mount即可
  • 组件的初始化就是new 这个组件的构造函数并且调用$mount方法
  • 创建虚拟节点,根据表情筛出组件对应,生成组件的虚拟节点,componentOPtions里面包含Ctor,children
  • 组件创建真实don时(先渲染的是父组件) 遇到的是组件的虚拟节点时,去调用init方法,让组件初始化挂载,组件的 m o u n t 无参数会把渲染后的 d o m 放到 mount无参数会把渲染后的dom放到 mount无参数会把渲染后的dom放到vm.el上(vnode.conponentInstance中,这样渲染时就会获取这个对象的$el属性来渲染)

全局组件的解析

<div id="app">
    <my-component></my-component>
    <my-component></my-component>
</div>
<script>
Vue.component('my-component',{
	template:'<button>点我</button>',
});
let vm = new Vue({
	el:'#app'
});
</script>

我们可以通过Vue.component注册全局组件,之后可以在模板中进行使用

export function initGlobalAPI(Vue){
    // 整合了所有的全局相关的内容
    Vue.options ={}
    initMixin(Vue);

    // _base 就是Vue的构造函数
    Vue.options._base = Vue;
    Vue.options.components = {}

    // 注册API方法
    initAssetRegisters(Vue);
}

Vue.component方法

export default function initAssetRegisters(Vue) {
    Vue.component = function (id, definition) {
        definition.name = definition.name || id;
        definition = this.options._base.extend(definition);
        this.options['components'][id] = definition;
    }
}

Vue.component内部会调用Vue.extend方法,将定义挂载到Vue.options.components上。这也说明所有的全局组件最终都会挂载到这个变量上

export function initGlobalAPI(Vue){
    // 整合了所有的全局相关的内容
    Vue.options ={}
    initMixin(Vue);
    // _base 就是Vue的构造函数
    Vue.options._base = Vue;
    Vue.options.components = {}

    // initExtend
+   initExtend(Vue);

    // 注册API方法
    initAssetRegisters(Vue);
}

Vue.extend方法

import {mergeOptions} from '../util/index'
export default function initExtend(Vue) {
    let cid = 0;
    Vue.extend = function (extendOptions) {
        const Super = this;
        const Sub = function VueComponent(options) {
            this._init(options)
        }
        Sub.cid = cid++;
        Sub.prototype = Object.create(Super.prototype);
        Sub.prototype.constructor = Sub;
        Sub.options = mergeOptions(
            Super.options,
            extendOptions
        );
        return Sub
    }
}

extend方法就是创建出一个子类,继承于Vue,并返回这个类

属性合并

function mergeAssets(parentVal,childVal){
    const res = Object.create(parentVal);
    if(childVal){
        for(let key in childVal){
            res[key] = childVal[key];
        }
    }
    return res;
}
strats.components = mergeAssets;

初始化合并

vm.$options = mergeOptions(vm.constructor.options,options);

组件的渲染

function makeMap(str) {
    const map = {};
    const list = str.split(',');
    for (let i = 0; i < list.length; i++) {
        map[list[i]] = true;
    }
    return (key)=>map[key];
}

export const isReservedTag = makeMap(
    'a,div,img,image,text,span,input,p,button'
)

在创建虚拟节点时我们要判断当前这个标签是否是组件,普通标签的虚拟节点和组件的虚拟节点有所不同

创建组件虚拟节点

export function createElement(vm,tag, data = {}, ...children) {
    let key = data.key;
    if (key) {
        delete data.key;
    }
    if (typeof tag === 'string') {
        if (isReservedTag(tag)) {
            return vnode(tag, data, key, children, undefined);
        } else {
            // 如果是组件需要拿到组件的定义,通过组件的定义创造虚拟节点
            let Ctor = vm.$options.components[tag];
            return createComponent(vm,tag,data,key,children,Ctor)
        }
    }
}
function createComponent(vm,tag,data,key,children,Ctor){
    // 获取父类构造函数t 
    const baseCtor = vm.$options._base; 
    if(isObject(Ctor)){
        Ctor = baseCtor.extend(Ctor);
    }
    data.hook = { // 组件的生命周期钩子
        init(){}
    }
    return vnode(`vue-component-${Ctor.cid}-${tag}`,data,key,undefined,{Ctor,children});
}
function vnode(tag, data, key, children, text, componentOptions) {
    return {tag, data, key, children, text, componentOptions}
}

创建组件的真实节点

export function patch(oldVnode,vnode){
    // 1.判断是更新还是要渲染
    if(!oldVnode){
        return createElm(vnode);
    }else{
        // ...
    }
}
function createElm(vnode){ // 根据虚拟节点创建真实的节点
    let {tag,children,key,data,text} = vnode;
    // 是标签就创建标签
    if(typeof tag === 'string'){
        // createElm需要返回真实节点
        if(createComponent(vnode)){
            return vnode.componentInstance.$el;
        }
        vnode.el = document.createElement(tag);
        updateProperties(vnode);
        children.forEach(child=>{ // 递归创建儿子节点,将儿子节点扔到父节点中
            return vnode.el.appendChild(createElm(child))
        })
    }else{
        // 虚拟dom上映射着真实dom  方便后续更新操作
        vnode.el = document.createTextNode(text)
    }
    // 如果不是标签就是文本
    return vnode.el;
}
function createComponent(vnode) {
    let i = vnode.data;
    if((i = i.hook) && (i = i.init)){
        i(vnode);
    }
    if(vnode.componentInstance){
        return true;
    }
}

调用init方法,进行组件的初始化

data.hook = {
    init(vnode){
        let child = vnode.componentInstance = new Ctor({});
        child.$mount(); // 组件的挂载
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值