Vue原理:初始化methods和cpmputed

141 阅读2分钟

这是我参与2022首次更文挑战的第1天,活动详情查看:2022首次更文挑战

在前文已对Vue初始化流程中initDatainitProps进行梳理,接下来继续看看其他选项初始化工作做了哪些事情,首先回顾一下initState初始化过程的整个过程

export function initState(vm) {
    const opts = vm.$options; // 获取用户传递的所有选项

    // 初始化 props
    if (opts.props) initProps(vm, opts.props);

    // 初始化 methods
    if (opts.methods) initMethods(vm, opts.methods);

    // 初始化 data
    if (opts.data) {
        initData(vm);
    }

    // 初始化 computed
    if (opts.computed) initComputed(vm, opts.computed);

    // 初始化 watch
    if (opts.watch && opts.watch !== nativeWatch) {
        initWatch(vm, opts.watch);
    }
}

initMethods

在初始化methods过程非常简单

const initMethods = (vm, methods) => {
    for (const key in methods) {		
        vm[key] = typeof methods[key] !== "function" ? noop : bind(methods[key], vm);
    }
};

核心在于判断用于在methods的配置是否为function类型,若是则通过bind改变函数的this为当前的实例

需要注意的是noop

function noop () {}

noop属于hack行为,只是为了兼容flow而已,不会在编译的过程中留下无用的代码

源码中当然不会这么简单,在开发环境多了一些健壮性、容错处理,代码主要是一些控制台输出警告,避免直接贴代码不利于阅读,总结三点:

  • 判断是否配置的是否为function类型:typeof methods[key] !== 'function'
  • 判断和props是否存在key命名冲突:props && hasOwn(props, key)
  • 命名规范校验:不建议命名以$或者_开始

initComputed

计算属性在开发过程中非常常用,熟悉Vue的小伙伴都可熟练的使用,在介绍源码之前先介绍两个关于computed小点

  • 计算属性具备缓存特性,其本质是一种Watcher,和render watcheruser watcher差异在于其是lazy wacther,根据其内部使用的响应式数据进行缓存

  • 定义计算属性的两种方式

Vue.component('only-read', {
  computed: {
    // 仅读取方式
    getName() {
      return this.name
    },
    // 可读取、设置
    getAge: {
      get() {
        return this.age
      },
      set(newVal) {
        this.age = newVal
      }
    }
  }
})

接下来看一下initComputed具体实现

const computedWatcherOptions = { lazy: true };
const initComputed = (vm, computed) => {
    const watchers = Object.create(null);

    for (const key in computed) {
        const userDef = computed[key];
        const getter = typeof userDef === "function" ? userDef : userDef.get

        // 计算属性本质就是一个 Watcher
        watchers[key] = new Watcher(
            vm,
            getter || noop,
            noop,
            computedWatcherOptions
        );

        if (!(key in vm)) {
            // 初始化计算属性
            defineComputed(vm, key, userDef);
        }
    }
};

initComputed函数主要就是将用户传递的计算属性遍历,依次增加到watchers中并调用defineComputed初始化,内部节省了一些非生产环境的健壮性、容错判断:检测定义的computed是否与datapropsmethods存在key的冲突

计算属性支持两种定义方式,在defineComputed中需要对两者分别处理,最终转换为一种格式进行数据劫持

const defineComputed = (target, key, userDef) => {
    const shouldCache = !isServerRendering();
        
    if (typeof userDef === "function") {
        // 仅读取设置方式
        sharedPropertyDefinition.get = shouldCache
            ? createComputedGetter(key)
            : createGetterInvoker(userDef);
        
        sharedPropertyDefinition.set = noop;
    } else {
        // 可读取、配置设置方式
        sharedPropertyDefinition.get = userDef.get
            ? shouldCache && userDef.cache !== false
                ? createComputedGetter(key)
                : createGetterInvoker(userDef.get)
            : noop;
        
        sharedPropertyDefinition.set = userDef.set || noop;
    }
  
    Object.defineProperty(target, key, sharedPropertyDefinition);
};

两种定义方式最终都转换为getset的形式,便于数据劫持处理