zoukankan      html  css  js  c++  java
  • Vue源码(上篇)

    某课网有个488人名币的源码解读视频看不起,只能搜很多得资料慢慢理解,看源码能知道大佬的功能模块是怎么分块写的,怎么复用的,已经vue是怎么实现的

    资料来自
    vue源码
    喜欢唱歌的小狮子
    web喵喵喵
    Vue.js源码全方位深入解析
    恰恰虎的博客
    learnVue
    最后四集视频

    总文件目录

    • scripts:包含构建相关的脚本和配置文件。作者声明一般开发不需要关注此目录
    • dist:构建出的不同分发版本,只有发布新版本时才会跟新,开发分支的新特性不会反映在此
    • packages:包含服务端渲染和模板编译器两种不同的NPM包,是提供给不同使用场景使用的
    • test:包含所有测试代码
    • flow:这是一个类型检查工具,可以加入类型的限制,提高代码质量,导致源码代码变成了这样
    function sum(a: number, b:number) {
      return a + b;
    }
    
    • src:Vue的源码,使用ES6和Flow类型注释编写的
    • types:使用TypeScript定义的类型声明,并且包含了测试文件,不太明白为什么要同时使用两种静态类型检查语言

    src文件夹

    • Compiler 编译器
      • parser:解析器的作用是将模板转换成元素AST对象。
      • optimizer:优化器负责检测静态抽象树的渲染进行优化。
      • codegen:代码生成器直接从AST对象生成代码字符串。
    • Core 核心
      • Observer:观察者系统,实现监测数据变化的功能。
      • Vdom:Vue虚拟节点树,实现虚拟节点的创建和刷新功能。
      • instance:Vue类,包含构造函数和原型方法的创建。
      • Global-API:全局API。
      • Components:通用抽象组件。
      • util:辅助函数。
    • Platforms 平台,不同平台的区别代码
    • Server 服务器渲染,ssr
    • Sfc 单文件组件文件编译,这一文件夹目前只包含了一个叫parser.js的文件,用来将单文件组件解析为SFC描述对象,输出给编译器继而执行模板编译。
    • Shared 共享常量和函数,是一个放工具的文件夹

    Global-api

    • util:虽然暴露了一些辅助方法,但官方并不将它们列入公共API中,不鼓励外部使用。
    • set:设置响应式对象的响应式属性,强制触发视图更新,在数组更新中非常实用,不适用于根数据属性。
    • delete:删除响应式属性强制触发视图更新, 使用情境较少。
    • nextTick:结束此轮循环后执行回调,常用于需要等待DOM更新或加载完成后执行的功能。
    • use:安装插件,自带规避重复安装。
    • mixin:常用于混入插件功能,不推荐在应用代码中使用。
    • extend:创建基于Vue的子类并扩展初始内容。
    • directive:注册全局指令。
    • component:注册全局组件。
    • filter:注册全局过滤器。

    Vue的核心

    • 创建Vue的构造函数,src/core/instance/index.js
    function Vue (options) {
      // 安全性判断,如果不是生产环境且不是Vue的实例,在控制台输出警告
      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)
    }
    
    // 下面这些Mixin都是往Vue函数的原型对象里添加方法
    // 挂载初始化方法
    initMixin(Vue)
    // 挂载状态处理相关方法
    stateMixin(Vue)
    // 挂载事件响应相关方法
    eventsMixin(Vue)
    // 挂载生命周期相关方法
    lifecycleMixin(Vue)
    // 挂载视图渲染方法
    renderMixin(Vue)
    
    // 这里就是暴露给Global-api去添加全局的方法
    export default Vue
    
    • initMixin,添加_init方法,里面有后续的所有重要的
    export function initMixin (Vue: Class<Component>) {
      // 在Vue类的原型上挂载_init()方法
      // 接收类型为原始对象的options形参,此参数为非必选参数
      Vue.prototype._init = function (options?: Object) {
        // 将实例对象赋值给vm变量
        // 这里会再次进行Component类型检查确保vm接收到的是Vue类的实例
        const vm: Component = this
    
        // 合并options对象
        if (options && options._isComponent) {
          // 内部组件的options初始化
          initInternalComponent(vm, options)
        } else {
          // 否则执行合并options函数,并赋值给vm的公共属性
          // 在这里的合并函数主要是解决与继承自父类的配置对象的合并
          vm.$options = mergeOptions(
            resolveConstructorOptions(vm.constructor),
            options || {},
            vm
          )
        }
        // 暴露实例对象
        vm._self = vm
        // 初始化实例的生命周期相关属性
        initLifecycle(vm)
        // 初始化事件相关属性和监听功能
        initEvents(vm)
        // 初始化渲染相关属性和功能
        initRender(vm)
        // 调用生命周期钩子函数beforeCreate
        callHook(vm, 'beforeCreate')
        // 初始化父组件注入属性
        initInjections(vm) // resolve injections before data/props
        // 初始化状态相关属性和功能
        initState(vm)
        // 初始化子组件属性提供器
        initProvide(vm) // resolve provide after data/props
        // 调用生命周期钩子函数created
        callHook(vm, 'created')
        // 执行DOM元素挂载函数
        if (vm.$options.el) {
          vm.$mount(vm.$options.el)
        }
      }
    }
    

    生命周期相关

    • lifecycleMixin
    // 导出lifecycleMixin函数,接收形参Vue,
    // 使用Flow进行静态类型检查指定为Component类
    export function lifecycleMixin (Vue: Class<Component>) {
      // 为Vue原型对象挂载_update私有方法
      // 接收vnode虚拟节点类型参数和一个可选的布尔值hydrating
      Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) { ... }
    
      // 为Vue实例挂载$forceUpdate方法,实现强制更新
      Vue.prototype.$forceUpdate = function () { ... }
    
      // 为Vue实例挂载$destroy方法
      Vue.prototype.$destroy = function () { ... }
    }
    
    • initLifecycle,主要是在生命周期开始之前设置一些相关的属性的初始值,组件也有自己的生命周期
    // 导出initLifecycle函数,接受一个Component类型的vm参数
    export function initLifecycle (vm: Component) {
      // 获取实例的$options属性,赋值为options变量
      const options = vm.$options
    
      let parent = options.parent
      // 判断是否存在且非抽象
      if (parent && !options.abstract) {
        // 遍历寻找最外层的非抽象父级
        while (parent.$options.abstract && parent.$parent) {
          parent = parent.$parent
        }
        // 将实例添加到最外层非抽象父级的子组件中
        parent.$children.push(vm)
      }
    
      // 初始化实例的公共属性
      // 设置父级属性,如果之前的代码未找到父级,则vm.$parent为undefined
      vm.$parent = parent
      // 设置根属性,没有父级则为实例对象自身
      vm.$root = parent ? parent.$root : vm
    
      // 初始化$children和$refs属性
      // vm.$children是子组件的数组集合
      // vm.$refs是指定引用名称的组件对象集合
      vm.$children = []
      vm.$refs = {}
    
      // 生命周期相关的私有属性
      vm._isMounted = false
      vm._isDestroyed = false
      vm._isBeingDestroyed = false
    }
    

    调用生命周期的方法callHook

    export function callHook (vm: Component, hook: string) {
      //记录当前watch的实例
      pushTarget()
      //获取相应钩子的事件处理方法数组
      const handlers = vm.$options[hook]
      //执行对应的事件方法
      if (handlers) {
        for (let i = 0, j = handlers.length; i < j; i++) {
          try {
            handlers[i].call(vm)
          } catch (e) {
            handleError(e, vm, `${hook} hook`)
          }
        }
      }
      //触发@hook:定义的钩子方法
      if (vm._hasHookEvent) {
        // 发布订阅调用模式,$emit来自initEvent
        vm.$emit('hook:' + hook)
      }
      //释放当前的watch实例
      popTarget()
    }
    

    函数相关

    • eventsMixin,添加几个用于订阅发布的方法
    export function eventsMixin (Vue: Class<Component>) {
    
      Vue.prototype.$on = function (event: string | Array<string>, fn: Function): Component {
        // 定义实例变量
        const vm: Component = this
        // 如果传入的event参数是数组,遍历event数组,为所有事件注册fn监听函数
        if (Array.isArray(event)) {
          for (let i = 0, l = event.length; i < l; i++) {
            this.$on(event[i], fn)
          }
        }
        // 返回实例本身
        return vm
      }
    
      // 为Vue原型对象挂载$once方法
      // 参数event只接受字符串,fn是监听函数
      Vue.prototype.$once = function (event: string, fn: Function): Component { ... }
      
      Vue.prototype.$off = function (event?: string | Array<string>, fn?: Function): Component { ... }
      // 为Vue原型对象挂载$emit方法,只接受单一event
      Vue.prototype.$emit = function (event: string): Component { ... }
    }
    
    • initEvents
    // 定义并导出initEvents函数,接受Component类型的vm参数
    export function initEvents (vm: Component) {
      //初始化vm._events对象
      // 存储父组件绑定当前子组件的事件,保存到vm._events中
      vm._events = Object.create(null);
      //是否存在hook:钩子事件
      vm._hasHookEvent = false;
      // init parent attached events
      var listeners = vm.$options._parentListeners;
      if (listeners) {
        updateComponentListeners(vm, listeners);
      }
    }
    

    渲染相关

    • renderMixin
    export function renderMixin (Vue: Class<Component>) {
    
      Vue.prototype.$nextTick = function (fn: Function) { ... }
    
      Vue.prototype._render = function (): VNode {
        const vm: Component = this
        const { render, _parentVnode } = vm.$options
    
        vm.$vnode = _parentVnode
        // render 就是 render表达式,
        // vnode 就是 虚拟dom
        let vnode = render.call(vm._renderProxy, vm.$createElement)
        return vnode
      }
    }
    
    • initRender,就是定义几个属性而已
    export function initRender (vm: Component) {
      // 初始化实例的根虚拟节点
      vm._vnode = null
      // 定义实例的静态树节点
      vm._staticTrees = null
      // 获取配置对象
      const options = vm.$options
      // 设置父占位符节点
      const parentVnode = vm.$vnode = options._parentVnode
      // renderContext存储父节点有无声明上下文
      const renderContext = parentVnode && parentVnode.context
      vm.$slots = resolveSlots(vm.$options._renderChildren, renderContext)
      vm.$scopedSlots = emptyObject
    
      /*将createElement函数绑定到该实例上,该vm存在闭包中,不可修改,vm实例则固定。这样我们就可以得到正确的上下文渲染*/
      vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)
      /*常规方法被用于公共版本,被用来作为用户界面的渲染方法*/
      vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)
      
      // $attrs & $listeners这个属性进行数据监听
      const parentData = parentVnode && parentVnode.data
      defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, null, true)
      defineReactive(vm, '$listeners', options._parentListeners || emptyObject, null, true)
    }
    

    参数处理

    • stateMixin
    export function stateMixin (Vue: Class<Component>) {
    
      const dataDef = {}
      dataDef.get = function () { return this._data }
      const propsDef = {}
      propsDef.get = function () { return this._props }
    
      Object.defineProperty(Vue.prototype, '$data', dataDef)
      Object.defineProperty(Vue.prototype, '$props', propsDef)
    
      Vue.prototype.$set = set
      Vue.prototype.$delete = del
    
      Vue.prototype.$watch = function (
        expOrFn: string | Function,
        cb: any,
        options?: Object
      ): Function {
        const vm: Component = this
        if (isPlainObject(cb)) {
          return createWatcher(vm, expOrFn, cb, options)
        }
        options = options || {}
        options.user = true
        const watcher = new Watcher(vm, expOrFn, cb, options)
        if (options.immediate) {
          cb.call(vm, watcher.value)
        }
        return function unwatchFn () {
          watcher.teardown()
        }
      }
    }
    
    • initState,这里才真正跟MVVM连接起来
    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)
      }
    }
    
    // 这里展示下initData方法,其他也差不多
    function initData (vm: Component) {
      let data = vm.$options.data
      data = vm._data = typeof data === 'function'
        ? getData(data, vm)
        : data || {}
    
      // 判断props对象跟data对象跟methods对象里有没有名字一样的key值
      const keys = Object.keys(data)
      const props = vm.$options.props
      const methods = vm.$options.methods
      let i = keys.length
      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.`
            )
          }
        }
        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.`
          )
        } else if (!isReserved(key)) {
          // 代理data的值,当访问this.message时,实际上访问的是this[_data][message]
          proxy(vm, `_data`, key)
        }
      }
      // 对内容进行监听
      observe(data, true)
    }
    

    上面出现的几个重要的方法

    • proxy,用于代理参数,当访问this.message时,实际上访问的是this[_data][message]
    • defineReactive,最核心的对象监听方法
    • observe和Observe,这两个都是在内部调用了defineReactive

    总结所有的init

    image.png

    当参数处理完毕,data,prop等数据已经被监听,Dep数组也已经创建好,但是是空的,这时可以看到上面的init的生命周期才写到created,我们知道在生命周期mounted前是没有dom元素的,所以在$mount阶段才开始生成虚拟dom,$mount方法在哪里定义的呢,查看下一篇笔记

  • 相关阅读:
    cs231n --- 3 : Convolutional Neural Networks (CNNs / ConvNets)
    cs231n --- 1:线性svm与softmax
    Python的下划线_
    Python的类(class)
    python self
    MFC中应用对象的成员:窗口指针m_pMainWnd说明
    MSTP+VRRP组合组网
    VRRP组网下同网段内配置基于全局地址池的DHCP服务器
    路由器/交换机Console口登录密码丢失后如何恢复
    交换机处于同一网络作为DHCP中继与服务器
  • 原文地址:https://www.cnblogs.com/pengdt/p/12304000.html
Copyright © 2011-2022 走看看