Vue框架的简单理解


写在最开始的一些话:整体Vue最开始做的工作是先在Vue这个构造方法上挂载各种各样的方法方便后续的操作,之后new的时候初始化用这些方法,生命周期也就是在这个时候开始,

Vue.prototype._init = function (options?: Object) {
  // 省略...

  // expose real self
  vm._self = vm
  initLifecycle(vm)
  initEvents(vm)
  initRender(vm)
  callHook(vm, 'beforeCreate')
  initInjections(vm) // resolve injections before data/props
  initState(vm)
  initProvide(vm) // resolve provide after data/props
  callHook(vm, 'created')

  // 省略...

  if (vm.$options.el) {
    vm.$mount(vm.$options.el)
  }
}

请添加图片描述

理解Vue的混入Vue.mixin({})

Vue.mixin({
//会在每个组件created状态的时候调动这个函数
  created: function () {
    var myOption = this.$options.myOption
    //直接访问会报错this.$options.myOption['A'],直接访问会触发this.$options的get()
    console.log(this.$options.myOption);
    if (myOption) {
      console.log(myOption['A'])
    }
  }
})
// 全局混入
const A=new Vue({
  myOption: {
    A:'111'
  },
  router,
  store,
  render: h => h(App),
}).$mount('#app')
//第二种混入方式,都是全局的混入,对于data和钩子函数混入的方式不同
//1.同名钩子函数将合并为一个数组,因此都将被调用。另外,混入对象的钩子将在组件自身钩子之前调用。
//2.当组件和混入对象含有同名选项时,这些选项将以恰当的方式进行“合并”。
//比如,数据对象在内部会进行递归合并,并在发生冲突时以组件数据优先。
//3.值为对象的选项,例如 methods、components 和 directives,将被合并为同一个对象。两个对象键名冲突时,取组件对象的键值对。
var mixin = {
  data: function () {
    return {
      message: 'hello',
      foo: 'abc'
    }
  }
}

new Vue({
  mixins: [mixin],
  data: function () {
    return {
      message: 'goodbye',
      bar: 'def'
    }
  },
  created: function () {
    console.log(this.$data)
    // => { message: "goodbye", foo: "abc", bar: "def" }
  }
})

理解Vue的自定义指令

<template>
  <div>
     <input v-focus/>
       <p>Scroll down the page</p>
       <p v-pin:[direction]="function(){const a=1;return a}" >Stick me 200px from the top of the page</p>
  </div>
</template>
<script>
import {mapState} from 'vuex'
export default {
  data () {
    return {
      direction:'left'//传递的是形参
    };
  },
  mounted () {
  },
  methods:{
    //测试store在vue的实例上
    increment(){
      // this.$store.commit('increment',10)
      this.$store.state.count=10+this.$store.state.count
    }
  },
  components: {
  },
  directives: {
  focus: {
// 一个指令定义对象可以提供如下几个钩子函数 (均为可选):
// bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
// inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。
// update:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新 (详细的钩子函数参数见下)。
// componentUpdated:指令所在组件的 VNode 及其子 VNode 全部更新后调用。
// unbind:只调用一次,指令与元素解绑时调用。
    // 指令的定义
    bind: function (el,binding,vnode,oldVnode) {
      console.log(el,binding,vnode,oldVnode);
      //传递过来的就是这个DOM元素
      console.log('调用')
      console.log(el);
      el.focus()
    }
  },
  //动态的绑定参数创建一个自定义指令,用来通过固定布局将元素固定在页面上
  pin:function(el,binding,){
    //value的值可以是字符串,对象(包括函数)
    console.log(binding.value());
      //  el.style.position='fixed',
      //  el.style.top=binding.value+'px'
  }
},
  // methods:{
  //    //在一种情况下,外面转入的参数书五花八门,我们统一除了成我们需要的参数,最终执行我们想要执行的函数
  // },
  // computed: {
  //   count () {
  //     return this.$store.state.count
  //   }
  // },
  //使用map更为简单的映射
  computed:mapState(['count']),
  //  computed:mapState({
  //    'count':state=>state.count
  //  }),
  
  watch: {}
};
</script>

从new Vue()开始理解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);
}
//在这里混入函数需要的方法,通过Vue.prototype绑定参数,在初始化的时候使用this._init(options);
initMixin(Vue);
stateMixin(Vue);
eventsMixin(Vue);
lifecycleMixin(Vue);
renderMixin(Vue);

this._init(options);

function initMixin (Vue) {
  Vue.prototype._init = function (options) {
    var vm = this;
    // a uid
    vm._uid = uid$3++;

    var startTag, endTag;
    /* istanbul ignore if */
    if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
      startTag = "vue-perf-start:" + (vm._uid);
      endTag = "vue-perf-end:" + (vm._uid);
      mark(startTag);
    }

    // a flag to avoid this being observed
    vm._isVue = true;
    // merge options
    if (options && options._isComponent) {
      // optimize internal component instantiation
      // since dynamic options merging is pretty slow, and none of the
      // internal component options needs special treatment.
      initInternalComponent(vm, options);
    } else {
      vm.$options = mergeOptions(
        resolveConstructorOptions(vm.constructor),
        options || {},
        vm
      );
    }
    /* istanbul ignore else */
    if (process.env.NODE_ENV !== 'production') {
      initProxy(vm);
    } else {
      vm._renderProxy = vm;
    }
    // expose real self
    vm._self = vm;
    initLifecycle(vm);
    initEvents(vm);
    initRender(vm);
    callHook(vm, 'beforeCreate');
    initInjections(vm); // resolve injections before data/props
    initState(vm);
    initProvide(vm); // resolve provide after data/props
    callHook(vm, 'created');

    /* istanbul ignore if */
    if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
      vm._name = formatComponentName(vm, false);
      mark(endTag);
      measure(("vue " + (vm._name) + " init"), startTag, endTag);
    }

    if (vm.$options.el) {
      vm.$mount(vm.$options.el);
    }
  };
}

Vue 选项的合并(option====》合并策略=====》 o p t i o n s ( 实 际 上 options(实际上 options(options上属性都是合并策略上执行返回,类似于预处理参数的功能))

Vue.options===>new Vue({options})=========》(Vue.extends)生成子组件
1.这里parentVal是Vue.options,childVal   2.这时候parentVal是new Vue({options})new Vue({options})的option               childVal  Vue.extends({options})

Vue.options = {
  components: {
      KeepAlive,
      Transition,
      TransitionGroup
  },
  directives:{
      model,
      show
  },
  filters: Object.create(null),
  _base: Vue
}

设计到的合并的选项有el,propsData,data以及钩子函数

el,propsData的合并

//实际上 defaultStrat 函数就如同它的名字一样,它是一个默认的策略,当一个选项不需要特殊处理的时候就使用默
//认的合并策略,它的逻辑很简单:只要子选项不是 undefined 那么就是用子选项,否则使用父选项。
const defaultStrat = function (parentVal: any, childVal: any): any {
  return childVal === undefined
    ? parentVal
    : childVal
}

data的合并

实际上在最后生成的$options中data是个函数,当初始化的时候会调用这个函数,实际上调用的就是mergeData 
//实际上 defaultStrat 函数就如同它的名字一样,它是一个默认的策略,当一个选项不需要特殊处理的时候就使用默
//认的合并策略,它的逻辑很简单:只要子选项不是 undefined 那么就是用子选项,否则使用父选项。
//mergeData 的具体做法就是像上面 mergeData 函数的代码段中所注释的那样,对 from 对象的 key 进行遍历:

//如果 from 对象中的 key 不在 to 对象中,则使用 set 函数为 to 对象设置 key 及相应的值。

//如果 from 对象中的 key 在 to 对象中,且这两个属性的值都是纯对象则递归地调用 mergeData 函数进行深度合并。

//其他情况不做处理。

//上面提到了一个 set 函数,根据 options.js 文件头部的引用关系可知:这个函数来自于 //core/observer/index.js 文件,实际上这个 set 函数就是 Vue 暴露给我们的全局API Vue.set。在这里由于
//我们还没有讲到 set 函数的具体实现,所以你就可以简单理解为 set 函数的功能与我们前面遇到过的 extend 工
//具函数功能相似即可。

//所以我们知道了 mergeData 函数的执行结果才是真正的数据对象,由于 mergedDataFn 和 //mergedInstanceDataFn 这两个函数的返回值就是 mergeData 函数的执行结果,所以 mergedDataFn 和 //mergedInstanceDataFn 函数的执行将会得到数据对象,我们还知道 data 选项会被 mergeOptions 处理成函
//数,比如处理成 mergedInstanceDataFn,所以:最终得到的 data 选项是一个函数,且该函数的执行结果就是最
//终的数据对象

生命周期钩子选项的合并策略(实际上可以填写数组,不一定要是函数)

//如上就是对 mergeHook 函数的解读,我们可以发现,在经过 mergeHook 函数处理之后,组件选项的生命周期钩子
//函数被合并成一个数组。第一个三目运算符需要注意,它判断是否有 childVal,即组件的选项是否写了生命周期钩
//子函数,如果没有则直接返回了 parentVal,这里有个问题:parentVal 一定是数组吗?答案是:如果有 //parentVal 那么其一定是数组,如果没有 parentVal 那么 strats[hooks] 函数根本不会执行。我们以 //created 生命周期钩子函数为例:
/**
 * Hooks and props are merged as arrays.
 */
function mergeHook (
  parentVal: ?Array<Function>,
  childVal: ?Function | ?Array<Function>
): ?Array<Function> {
  return childVal
    ? parentVal
      ? parentVal.concat(childVal)
      : Array.isArray(childVal)
        ? childVal
        : [childVal]
    : parentVal
}

LIFECYCLE_HOOKS.forEach(hook => {
  strats[hook] = mergeHook
})

合并components,components,filter

function mergeAssets (
  parentVal: ?Object,
  childVal: ?Object,
  vm?: Component,
  key: string
): Object {
  const res = Object.create(parentVal || null)
  if (childVal) {
    process.env.NODE_ENV !== 'production' && assertObjectType(key, childVal, vm)
    return extend(res, childVal)
  } else {
    return res
  }
}
上面的代码本身逻辑很简单,首先以 parentVal 为原型创建对象 res,然后判断是否有 childVal,如果有的话使用 extend 函数将 childVal 上的属性混合到 res 对象上并返回。如果没有 childVal 则直接返回 res。

举个例子,大家知道任何组件的模板中我们都可以直接使用 <transition/> 组件或者 <keep-alive/> 等,但是我们并没有在我们自己的组件实例的 components 选项中显式地声明这些组件。那么这是怎么做到的呢?其实答案就在 mergeAssets 函数中。以下面的代码为例:
var v = new Vue({
  el: '#app',
  components: {
    ChildComponent: ChildComponent
  }
})
上面的代码中,我们创建了一个 Vue 实例,并注册了一个子组件 ChildComponent,此时 mergeAssets 方法内的 childVal 就是例子中的 components 选项:

components: {
  ChildComponent: ChildComponent
}
而 parentVal 就是 Vue.options.components,我们知道 Vue.options 如下:

Vue.options = {
	components: {
	  KeepAlive,
	  Transition,
	  TransitionGroup
	},
	directives: Object.create(null),
	directives:{
	  model,
	  show
	},
	filters: Object.create(null),
	_base: Vue
}
所以 Vue.options.components 就应该是一个对象:

{
  KeepAlive,
  Transition,
  TransitionGroup
}
也就是说 parentVal 就是如上包含三个内置组件的对象,所以经过如下这句话之后:

const res = Object.create(parentVal || null)
你可以通过 res.KeepAlive 访问到 KeepAlive 对象,因为虽然 res 对象自身属性没有 KeepAlive,但是它的原型上有。

然后再经过 return extend(res, childVal) 这句话之后,res 变量将被添加 ChildComponent 属性,最终 res 如下:

res = {
  ChildComponent
  // 原型
  __proto__: {
    KeepAlive,
    Transition,
    TransitionGroup
  }
}
所以这就是为什么我们不用显式地注册组件就能够使用一些内置组件的原因,同时这也是内置组件的实现方式,通过 Vue.extend 创建出来的子类也是一样的道理,一层一层地通过原型进行组件的搜索

选项 watch 的合并策略

合并的watch可以是数组也可以是函数

/**
 * Watchers.
 *
 * Watchers hashes should not overwrite one
 * another, so we merge them as arrays.
 */
strats.watch = function (
  parentVal: ?Object,
  childVal: ?Object,
  vm?: Component,
  key: string
): ?Object {
  // work around Firefox's Object.prototype.watch...
  if (parentVal === nativeWatch) parentVal = undefined
  if (childVal === nativeWatch) childVal = undefined
  /* istanbul ignore if */
  if (!childVal) return Object.create(parentVal || null)
  if (process.env.NODE_ENV !== 'production') {
    assertObjectType(key, childVal, vm)
  }
  if (!parentVal) return childVal
  const ret = {}
  extend(ret, parentVal)
  for (const key in childVal) {
    let parent = ret[key]
    const child = childVal[key]
    if (parent && !Array.isArray(parent)) {
      parent = [parent]
    }
    ret[key] = parent
      ? parent.concat(child)
      : Array.isArray(child) ? child : [child]
  }
  return ret
}

选项 props、methods、inject、computed 的合并策略

接下来我们要看的一段代码如下:

/**
 * Other object hashes.
 */
strats.props =
strats.methods =
strats.inject =
strats.computed = function (
  parentVal: ?Object,
  childVal: ?Object,
  vm?: Component,
  key: string
): ?Object {
  if (childVal && process.env.NODE_ENV !== 'production') {
    assertObjectType(key, childVal, vm)
  }
  if (!parentVal) return childVal
  const ret = Object.create(null)
  extend(ret, parentVal)
  if (childVal) extend(ret, childVal)
  return ret
}
这段代码的作用是在 strats 策略对象上添加 props、methods、inject 以及 computed 策略函数,顾名思义这些策略函数是分别用来合并处理同名选项的,并且所使用的策略相同。

对于 props、methods、inject 以及 computed 这四个选项有一个共同点,就是它们的结构都是纯对象,虽然我们在书写 props 或者 inject 选项的时候可能是一个数组,但是在 Vue的思路之选项的规范化 一节中我们知道,Vue 内部都将其规范化为了一个对象。所以我们看看 Vue 是如何处理这些对象散列的。

策略函数内容如下:

// 如果存在 childVal,那么在非生产环境下要检查 childVal 的类型
if (childVal && process.env.NODE_ENV !== 'production') {
  assertObjectType(key, childVal, vm)
}
// parentVal 不存在的情况下直接返回 childVal
if (!parentVal) return childVal
// 如果 parentVal 存在,则创建 ret 对象,然后分别将 parentVal 和 childVal 的属性混合到 ret 中,注意:由于 childVal 将覆盖 parentVal 的同名属性
const ret = Object.create(null)
extend(ret, parentVal)
if (childVal) extend(ret, childVal)
// 最后返回 ret 对象。
return ret
首先,会检测 childVal 是否存在,即子选项是否有相关的属性,如果有的话在非生产环境下需要使用 assertObjectType 检测其类型,保证其类型是纯对象。然后会判断 parentVal 是否存在,不存在的话直接返回子选项。

如果 parentVal 存在,则使用 extend 方法将其属性混合到新对象 ret 中,如果 childVal 也存在的话,那么同样会再使用 extend 函数将其属性混合到 ret 中,所以如果父子选项中有相同的键,那么子选项会把父选项覆盖掉。

以上就是 props、methods、inject 以及 computed 这四个属性的通用合并策略

选项 provide 的合并策略

最后一个选项的合并策略,就是 provide 选项的合并策略,只有一句代码,如下:

strats.provide = mergeDataOrFn
也就是说 provide 选项的合并策略与 data 选项的合并策略相同,都是使用 mergeDataOrFn 函数。

选项处理小结

现在我们了解了 Vue 中是如何合并处理选项的,接下来我们稍微做一个总结:

对于 el、propsData 选项使用默认的合并策略 defaultStrat。
对于 data 选项,使用 mergeDataOrFn 函数进行处理,最终结果是 data 选项将变成一个函数,且该函数的执行结果为真正的数据对象。
对于 生命周期钩子 选项,将合并成数组,使得父子选项中的钩子函数都能够被执行
对于 directives、filters 以及 components 等资源选项,父子选项将以原型链的形式被处理,正是因为这样我们才能够在任何地方都使用内置组件、指令等。
对于 watch 选项的合并处理,类似于生命周期钩子,如果父子选项都有相同的观测字段,将被合并为数组,这样观察者都将被执行。
对于 props、methods、inject、computed 选项,父选项始终可用,但是子选项会覆盖同名的父选项字段。
对于 provide 选项,其合并策略使用与 data 选项相同的 mergeDataOrFn 函数。
最后,以上没有提及到的选项都将使默认选项 defaultStrat。
最最后,默认合并策略函数 defaultStrat 的策略是:只要子选项不是 undefined 就使用子选项,否则使用父选项。
至此,我们大概介绍完了 Vue 对选项的处理,但留心的同学一定注意到了,options.js 文件的代码我们都基本逐行分析,唯独剩下一个函数我们始终没有提到,它就是 resolveAsset 函数。这个函数我们暂且不在这里讲,后面随着我们的深入,自然会再次碰到它,到那个时候应该是讲它的最好时机。

访问vm.key(data中的key)

初始化 merageOption(和农)====>($options.data)(执行)=====>vm._data(key)=======>(代理)vm.key

#简单实现$watch

//$watch(render,render)===>收集依赖并且触发key的get====>将依赖放到dep中====》触发set======>render()(修改DOM)  
const data = {
  name: '霍春阳',
  age: 24
}

function render () {
  return document.write(`姓名:${data.name}; 年龄:${data.age}`)
}
$watch(render, render)
function $watch (exp, fn) {
  Target = fn
  let pathArr,
      obj = data
  // 如果 exp 是函数,直接执行该函数
  if (typeof exp === 'function') {
    exp()
    return
  }
  if (/\./.test(exp)) {
    pathArr = exp.split('.')
    pathArr.forEach(p => {
      obj = obj[p]
    })
    return
  }
  data[exp]
}
function walk (data) {
  for (let key in data) {
    const dep = []
    let val = data[key]
    // 如果 val 是对象,递归调用 walk 函数将其转为访问器属性
    const nativeString = Object.prototype.toString.call(val)
    if (nativeString === '[object Object]') {
      walk(val)
    }
    Object.defineProperty(data, key, {
      set (newVal) {
        if (newVal === val) return
        val = newVal
        dep.forEach(fn => fn())
      },
      get () {
        dep.push(Target)
        return val
      }
    })
  }
}

walk(data)

数据响应状态

watch(Target(存储的就是依赖)) ==》 $watch(render,render)触发get ====》 get在这里先用dep缓存依赖 =====》 set触发依赖

在vm_data上挂载收集依赖使用_ob_,每个属性在设置set和get时会使用闭包缓存一个dep,set和get都是对这个dep操作,但在最外层也会挂一个_ob_,里面也有dep,目的时为了Vue.$set使用。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值