1.7 其他自定义策略

Vue自定义选项策略还有很多,我们继续列举其他几个例子。

1.7.1 el合并

我们只在创建vue的实例时才会执行节点挂载,在子类或者子组件中无法定义el选项,代码实现如下

  1. strats.el = function (parent, child, vm, key) {
  2. if (!vm) { // 只允许vue实例才拥有el属性,其他子类构造器不允许有el属性
  3. warn(
  4. "option \"" + key + "\" can only be used during instance " +
  5. 'creation with the `new` keyword.'
  6. );
  7. }
  8. return defaultStrat(parent, child)
  9. };
  10. // 用户自定义选项策略
  11. var defaultStrat = function (parentVal, childVal) {
  12. return childVal === undefined
  13. ? parentVal
  14. : childVal
  15. };

1.7.2 data合并

另一个合并的重点是data的合并策略,data在vue创建实例时传递的是一个对象,而在组件内部定义时只能传递一个函数,

  1. strats.data = function (parentVal, childVal, vm) {
  2. if (!vm) {
  3. if (!vm) {// 判断是否为Vue创建的实例,否则为子父类的关系
  4. if (childVal && typeof childVal !== 'function') { // 必须保证子类的data类型是一个函数而不是一个对象
  5. warn('The "data" option should be a function ' + 'that returns a per-instance value in component ' + 'definitions.',vm);
  6. return parentVal
  7. }
  8. return mergeDataOrFn(parentVal, childVal)
  9. }
  10. return mergeDataOrFn(parentVal, childVal, vm); // vue实例时需要传递vm作为函数的第三个参数
  11. };

做了data选项的检验后,重点关注mergeDataOrFn函数的内部逻辑,代码中依然通过vm来区分是否为子类构造器的data合并。

  1. function mergeDataOrFn ( parentVal, childVal, vm ) {
  2. if (!vm) {
  3. if (!childVal) { // 子类不存在data选项,则合并结果为父类data选项
  4. return parentVal
  5. }
  6. if (!parentVal) { // 父类不存在data选项,则合并结果为子类data选项
  7. return childVal
  8. }
  9. return function mergedDataFn () { // data选项在父类和子类同时存在的情况下返回的是一个函数
  10. // 子类实例和父类实例,分别将子类和父类实例中data函数执行后返回的对象传递给mergeData函数做数据合并
  11. return mergeData(
  12. typeof childVal === 'function' ? childVal.call(this, this) : childVal,
  13. typeof parentVal === 'function' ? parentVal.call(this, this) : parentVal
  14. )
  15. }
  16. } else {
  17. // vue构造函数实例对象
  18. return function mergedInstanceDataFn () {
  19. var instanceData = typeof childVal === 'function'
  20. ? childVal.call(vm, vm)
  21. : childVal;
  22. var defaultData = typeof parentVal === 'function'
  23. ? parentVal.call(vm, vm)
  24. : parentVal;
  25. if (instanceData) {
  26. // 当实例中传递data选项时,将实例的data对象和Vm构造函数上的data属性选项合并
  27. return mergeData(instanceData, defaultData)
  28. } else {
  29. // 当实例中不传递data时,默认返回Vm构造函数上的data属性选项
  30. return defaultData
  31. }
  32. }
  33. }
  34. }

如何实现数据合并,数据合并时,vue会将数据变化加入响应式系统中,我们先跳过响应式系统的构建部分,只关注单纯的数据合并。数据合并的原则是,将父类的数据整合到子类的数据选项中, 如若父类数据和子类数据冲突时,保留子类数据。

  1. function mergeData (to, from) {
  2. if (!from) { return to }
  3. var key, toVal, fromVal;
  4. var keys = hasSymbol
  5. ? Reflect.ownKeys(from)
  6. : Object.keys(from);
  7. for (var i = 0; i < keys.length; i++) {
  8. key = keys[i];
  9. toVal = to[key];
  10. fromVal = from[key];
  11. if (!hasOwn(to, key)) {
  12. set(to, key, fromVal); // 当子类数据选项不存在父类的选项时,将父类数据合并到子类数据中,并加入响应式系统中。
  13. } else if ( // 处理深层对象,当合并的数据为多层嵌套对象时,需要递归调用mergeData进行比较合并
  14. toVal !== fromVal &&
  15. isPlainObject(toVal) &&
  16. isPlainObject(fromVal)
  17. ) {
  18. mergeData(toVal, fromVal);
  19. }
  20. }
  21. return to
  22. }

思考一个问题,为什么Vue组件的data是一个函数,而不是一个对象呢?我觉得这样可以方便理解:组件的目的是为了复用,每次通过函数创建相当于在一个独立的内存空间中生成一个data的副本,这样每个组件之间的数据不会互相影响。

1.7.3 watch 选项合并

对于 watch 选项的合并处理,类似于生命周期钩子,只要父选项有相同的观测字段,则合并为数组,在选项改变时同时执行父类选项的监听代码。处理方式和生命钩子选项的区别在于,生命钩子选项必须是函数或者数据,而watch选项则为对象。

  1. strats.watch = function (parentVal,childVal,vm,key) {
  2. if (parentVal === nativeWatch) { parentVal = undefined; }
  3. if (childVal === nativeWatch) { childVal = undefined; }
  4. if (!childVal) { return Object.create(parentVal || null) }
  5. {
  6. assertObjectType(key, childVal, vm);
  7. }
  8. if (!parentVal) { return childVal }
  9. var ret = {};
  10. extend(ret, parentVal);
  11. for (var key$1 in childVal) {
  12. var parent = ret[key$1];
  13. var child = childVal[key$1];
  14. if (parent && !Array.isArray(parent)) {
  15. parent = [parent];
  16. }
  17. ret[key$1] = parent
  18. ? parent.concat(child)
  19. : Array.isArray(child) ? child : [child];
  20. }
  21. return ret
  22. };

1.7.4 props,methods, inject, computed 合并

  1. // 其他选项合并策略
  2. strats.props =
  3. strats.methods =
  4. strats.inject =
  5. strats.computed = function (parentVal,childVal,vm,key) {
  6. if (childVal && "development" !== 'production') {
  7. assertObjectType(key, childVal, vm);
  8. }
  9. if (!parentVal) { return childVal } // 父类不存在该选项,则返回子类的选项
  10. var ret = Object.create(null);
  11. extend(ret, parentVal); //
  12. if (childVal) { extend(ret, childVal); } // 子类选项会覆盖父类选项的值
  13. return ret
  14. };

至此,vue初始化选项合并逻辑分析完毕。