vue源码解析

1.1.1 基础用法

  引入 vue.js并且 new一个 Vue实例,并且挂在到 #app上。

<div id="app"></div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.8/dist/vue.js"></script>
<script>
var vm = new Vue({
  el: '#app',
  data: {
    message: 'Test'
  },
})
</script>

1.1.2 Vue构造器

(function (global, factory) {
  // 遵循UMD规范
  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
  typeof define === 'function' && define.amd ? define(factory) :
  (global = global || self, global.Vue = factory());
}(this, function () { 'use strict';
  ···
  // Vue 构造函数
  function Vue (options) {
    // 保证了无法直接通过Vue()去调用,只能通过new的方式去创建实例
    if (!(this instanceof Vue)
    // 实例this在不在Vue构造函数中
    ) {
      warn('Vue is a constructor and should be called with the `new` keyword');
    }
    this._init(options);
  }
  return Vue
})

1.1.3 定义原型属性方法

vue之所以能适应基础的场景开发,除了经常提到的支持组件开发,除了它有完善的响应式系统之外,还提供了一些丰富的 api方法,不管是静态还是原型方法,它们都丰富到足以满足我们日常基础的开发需求。

  首先是原型上的属性方法,在构造函数的定义之后,有这样五个函数,他们分别针对不同场景定义了 Vue原型上的属性和方法。

  // 定义Vue原型上的init方法(内部方法)
  initMixin(Vue);
  // 定义原型上跟数据相关的属性方法
  stateMixin(Vue);
  //定义原型上跟事件相关的属性方法
  eventsMixin(Vue);
  // 定义原型上跟生命周期相关的方法
  lifecycleMixin(Vue);
  // 定义渲染相关的函数
  renderMixin(Vue);

initMixin
initMixin定义了内部在实例化 Vue时会执行的初始化代码,他是一个内部使用方法。混入了Vue.prototype._init方法,用于初始化Vue实例(即构造函数中调用的_init方法)

function initMixin(Vue){
  Vue.prototype._init = function (options) {
    const this = vm; //将当前实例保存为vm(view-model的缩写)
    vm._uid = uid++; //为当前实例打上唯一标记
    if(options && options._isComponent){
      // 如果是组件,则采用组件专用的初始化方法,效率更高
      initInternalComponent(vm, options);
    } else {
      // 将传入的options与vm构造函数的默认options合并,得到完整的options
      vm.$options = mergeOptions(
        resolveContructorOptions(vm.constructor),
        options || {}, vm
      )
    }
    ...
    // 如果options存在el属性,就执行挂载
    //(否则需要使用vm.$mount手动挂载)
    if (vm.$options.el) {
      vm.$mount(vm.$options.el)
    }
  }
}

stateMixin
stateMixin方法会定义跟数据相关的属性方法,例如代理数据的访问,我们可以在实例上通过 this.$datathis.$props访问到 data,props的值,并且也定义了使用频率较高的 this.$set,this.$delte等方法。

function stateMixin (Vue) {
  var dataDef = {};
  dataDef.get = function () { return this._data };
  var propsDef = {};
  propsDef.get = function () { return this._props };
  {
    dataDef.set = function () {
      warn(
        'Avoid replacing instance root $data. ' +
        'Use nested data properties instead.',
        this
      );
    };
    propsDef.set = function () {
      warn("$props is readonly.", this);
    };
  }
  // 代理了_data,_props的访问
  Object.defineProperty(Vue.prototype, '$data', dataDef);
  Object.defineProperty(Vue.prototype, '$props', propsDef);
  // $set, $del
  Vue.prototype.$set = set;
  Vue.prototype.$delete = del;
  // $watch
  Vue.prototype.$watch = function (expOrFn, cb, options) {
    const vm = this
    // 判断接收的参数cb是否为纯对象,就调用createWatcher,
    if (isPlainObject(cb)) {
      return createWatcher(vm, expOrFn, cb, options)
    }
    options = options || {}
    options.user = true
    const watcher = new Watcher(vm, expOrFn, cb, options)
    // 触发data的get函数收集到这个 watcher 实例。数据变化的时候就能触发 set,调用 watcher 实例的 update 方法,最终触发这里传入的 cb。
    if (options.immediate) {
      cb.call(vm, watcher.value)
    }
    return function unwatchFn () {
      watcher.teardown() // 取消监听
    }
    //如果 options.immediate 为真,则立即调用 cb,但是参数只有 newVal,没有 oldVal。
  };
}


// 实现内层obj响应式
function observer(obj){
	let value;
  for(const key in obj){
    if(typeof obj[key] === 'object'){
      arguments.callee(obj[key]) // 调用自己,实现递归函数
    } else {
      value = obj[key]
      Object.defineProperty(obj, key, {
        get:function(){
          return value
        },
        set:function(newValue){
          value = newValue 
        }
      })
    }
	}
}


// this.$set()源码
function set(target: Array(any) | Object, key:any, val:any ):any {
  if(process.env.NODE_ENV !== 'production' && (isUndef(target) || isPrimitive(target)))
  {
    warn(`Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`)
  }
  // 数组
  if (Array.isArray(target) && isValidArrayIndex(key)) {
    // 设置数组的 length 属性,设置的属性值是 "数组原长度" 和 "key" 中的最大值
    target.length = Math.max(target.length, key)
    target.splice(key, 1, val)
    return val
  }
  // 对象
  // 这里用于处理 key 已经存在于 target 中的情况
  if (key in target && !(key in Object.prototype)) {
    target[key] = val
    return val
  }
  const ob = (target: any).__ob__
  // 如果 target 没有 __ob__ 属性的话,说明 target 并不是一个响应式的对象
  // 所以在这里也不需要做什么额外的处理,将 val 设到 target 上,并且返回这个 val 即可
  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
  }
  if (!ob) {
    target[key] = val
    return val
  }
  // 在这里使用 defineReactive 将 val 变成 getter/setter 的形式
  defineReactive(ob.value, key, val)
  // 因为新增了一个属性,所以 ob.value 变化了,所以在这里需要出发依赖的更新
  ob.dep.notify()
  return val
}


// this.$delete()
function del(target: Array(any) | Object, key:any, val:any):any{
  if(Array.isArray(target) && isValidArrayIndex(key)){
    // 执行数组原型上的 splice 方法,该方法会执行删除的操作,并且会出发依赖的更新
    target.splice(key, 1)
    return
  }
  const ob = (target: any).__ob__
  if (!hasOwn(target, key)) {
    return
  }
  delete target[key]
  // 在这里判断 target 是不是响应式的,如果不是的话,就不用出发依赖的更新操作了。在这里,直接 return
  if (!ob) {
    return
  }
  ob.dep.notify()
}


// this.$watch()
class watch{
  constructor(vm, expOrFn, cb){
    this.cb = cb  //调用$watch时候传进来的回调
    this.vm = vm
    this.expOrFn = expOrFn // 要监听的属性和方法,也就是$watch方法的第一个参数
    this.value = this.get() // 调用自己的get方法
  }
  get(){
    Dep.target = this //将Dep身上的target 赋值为Watcher对象
    const value = this.vm._data[this.expOrFn]
    // 声明value,使用this.vm._data进行赋值,并且触发_data[a]的get事件
    Dep.target = null
    return value
  }
}


isUndefisPrimitive方法,isUndef是判断 target是不是等于 undefined或者 nullisPrimitive是判断 target的数据类型是不是 string、number、symbol、boolean中的一种。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值