Vue源码解读之InitState

前面我们讲到了_init函数的执行流程,简单回顾下:

  • 初始化生命周期-initLifecycle
  • 初始化事件-initEvents
  • 初始化渲染函数-initRender
  • 调用钩子函数-beforeCreate
  • 初始化依赖注入-initInjections
  • 初始化状态信息-initState
  • 初始化依赖提供-initProvide
  • 调用钩子函数-created
    一共经过上面8步,init函数执行完成,开始mount渲染。

初始化状态信息

本章咱们主要讲解initState函数的处理过程,咱们先看下init的主函数

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)
    }
}

看上面代码,先声明了一个_watchers的空数组;然后依次判断传递进来的options是否包含系列参数;依次执行initProps、initMethods、initData、initComputed、initWatch。

initProps

initProps函数主要是处理传进来的props对象,但是这个props对象是在上一篇文章中讲到的normalizeProps函数处理后的对象,不是传递进来的原对象。来看下initProps的代码:

function initProps(vm: Component, propsOptions: Object) {
    const propsData = vm.$options.propsData || {}
    const props = vm._props = {}
    const keys = vm.$options._propKeys = []
    const isRoot = !vm.$parent
    if (!isRoot) {
        toggleObserving(false)
    }
    for (const key in propsOptions) {
        keys.push(key)
        const value = validateProp(key, propsOptions, propsData, vm)
        defineReactive(props, key, value)
        if (!(key in vm)) {
            proxy(vm, `_props`, key)
        }
    }
    toggleObserving(true)
}

上面代码解读:

  • 第一步获取了propsData;
  • 第二步给当前实例添加了_props属性,新增了一个props引用,指向了_props属性;
  • 第三步给当前实例增加了_propKeys属性,新增了一个keys的引用,指向了_propKeys属性;
  • 第四步判断了是否需要进行监听;
  • 遍历normalizeProps函数处理后的对象propsOptions;
    • 存储key
    • 校验props格式
    • 为当前key定义响应式的属性:defineReactive
    • 把当前key的访问方式提高到实例上面:proxy,即可以vm.name来访问vm._props.name
function proxy(target: Object, sourceKey: string, key: string) {
    sharedPropertyDefinition.get = function proxyGetter() {
        return this[sourceKey][key]
    }
    sharedPropertyDefinition.set = function proxySetter(val) {
        this[sourceKey][key] = val
    }
    Object.defineProperty(target, key, sharedPropertyDefinition)
}
  • 打开监听。

initMethods

initMethods方法是用来处理传递进来的methods参数,把methods绑定到当前实例上面

function initMethods(vm: Component, methods: Object) {
    const props = vm.$options.props
    for (const key in methods) {
        if (process.env.NODE_ENV !== 'production') {
            if (typeof methods[key] !== 'function') {
                warn(`Method "${key}" has type "${typeof methods[key]}" in the component definition. ` + `Did you reference the function correctly?`, vm)
            }
            if (props && hasOwn(props, key)) {
                warn(`Method "${key}" has already been defined as a prop.`, vm)
            }
            if ((key in vm) && isReserved(key)) {
                warn(`Method "${key}" conflicts with an existing Vue instance method. ` + `Avoid defining component methods that start with _ or $.`)
            }
        }
        vm[key] = typeof methods[key] !== 'function' ? noop : bind(methods[key], vm)
    }
}

上面代码解读:

  • 第一步获取了props;

  • 第二步遍历methods;

    • 判断当前method是否是函数,不是函数则在开发环境下报警
    • 判断props是否已经有了当前method的key,如有则在开发环境下报警
    • 判断当前method是否已经在vm上面了,并且以$或_开头,如是,则在开发环境下报警
    • 为当前实例添加方法;

    参考 Vue面试题详细解答


initData

initData方法是用来处理传递进来的data参数,添加监听

function initData(vm: Component) {
    let data = vm.$options.data
    data = vm._data = typeof data === 'function'
        ? getData(data, vm)
        : data || {}
    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.`, vm)
            }
        }
        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.`, vm)
        } else if (!isReserved(key)) {
            // 实现代理,可以this.massage 来进行访问this._data.message
            proxy(vm, `_data`, key)
        }
    }
    observe(data, true /* asRootData */)
}

上面代码解读:

  • 第一步获取传递进来的data,判断data是否为函数,是函数则执行函数获取当前对象,否则直接读取当前对象;
  • 第二步,获取上一步的data所有的key,赋值给keys;
  • 第三步获取props;
  • 第四步获取methods;
  • 第五步,循环keys;
    • 判断是否和methods里面是否重复,重复则开发环境进行报警
    • 判断是否和props里面是否重复,重复则开发环境进行报警
    • 判断如不是以_或$开头的key,则进行代理处理,把当前key的访问方式提高到实例上面:proxy,即可以vm.name来访问vm._datas.name
  • 对当前data对象进行observe处理,暂时先不用关注observe,后面会讲到是做什么的。

initComputed

initComputed是用来处理传进来的computed参数

function initComputed(vm: Component, computed: Object) {
    const watchers = vm._computedWatchers = Object.create(null)
    const isSSR = isServerRendering()

    for (const key in computed) {
        const userDef = computed[key]
        const getter = typeof userDef === 'function' ? userDef : userDef.get
        if (!isSSR) {
            watchers[key] = new Watcher(
                vm,
                getter || noop,
                noop,
                {
                    lazy: true
                }
            )
        }
        if (!(key in vm)) {
            defineComputed(vm, key, userDef)
        }
    }
}

initComputed方法解读:

  • 第一步为实例增加了_computedWatchers属性,声明引用watchers;
  • 获取是否是服务端渲染-isSSR;
  • 遍历computed;
    • 获取用户定义的内容-userDef
    • 根据用户定义的内容来获取当前属性key的getter函数
    • 为当前key增加Watcher,暂时不用关注Watcher后面会讲到
    • 调用defineComputed,参数为当前实例,当前属性key和userDef
      下面来看下defineComputed的实现:
function defineComputed(target: any, key: string, userDef: Object | Function) {
    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)
}

defineComputed

defineComputed方法解读:

  • 判断是否需要使用cache,非server端渲染,使用cache,即浏览器情况下都是true;
  • 分情况讨论:
    • userDef为函数时,调用createComputedGetter函数生成get函数,set函数为空函数
    • userDef不为函数时,get函数为createComputedGetter或者createGetterInvoker生成的函数;
  • 调用Object.defineProperty为当前实例添加定义属性;

createGetterInvoker

下面来看下createGetterInvoker:

function createGetterInvoker(fn) {
    return function computedGetter() {
        return fn.call(this, this)
    }
}

上面代码直接返回了一个函数,函数内部调用的是传递进来的fn函数,fn函数是从defineComputed传进来的,值为userDef或者userDef.get。

createComputedGetter

下面来看下createComputedGetter:

function createComputedGetter(key) {
    return function computedGetter() {
        const watcher = this._computedWatchers && this._computedWatchers[key]
        if (watcher) {
            if (watcher.dirty) {
                watcher.evaluate()
            }
            if (Dep.target) {
                watcher.depend()
            }
            return watcher.value
        }
    }
}

上面代码返回了一个computedGetter的函数,函数内部分析:

  • 获取了在initComputed函数里面声明的_computedWatchers,
  • watcher肯定是有值的,dirty属性的值在此处也相当于lazy属性,因为创建watcher的时候传的是true,所以此处也是true;
  • 执行watcher.evaluate,该方法会获取当前watcher的value,并且把dirty属性变为false;
  • 判断Dep.target,然后调用watcher的收集依赖;
  • 返回watcher.value;

initWatch

initWatch是用来处理传进来的watch参数。

function initWatch(vm: Component, watch: Object) {
    for (const key in watch) {
        const handler = watch[key]
        if (Array.isArray(handler)) {
            for (let i = 0; i < handler.length; i++) {
                createWatcher(vm, key, handler[i])
            }
        } else {
            createWatcher(vm, key, handler)
        }
    }
}

initWatch函数解读:
遍历watch,根据key获取handler,handler为数组遍历执行createWatcher,不为数组直接执行createWatcher;
来看下createWatcher:

createWatcher

function createWatcher(vm: Component, expOrFn: string | Function, handler: any, options?: Object) {
    if (isPlainObject(handler)) {
        options = handler
        handler = handler.handler
    }
    if (typeof handler === 'string') {
        handler = vm[handler]
    }
    return vm.$watch(expOrFn, handler, options)
}

createWatcher代码解读:

  • 判断handler是否为对象,如果为对象,则把当前handler作为options,options.handler作为handler;
  • 判断handler是否为字符串,字符串的话,则直接获取实例的handler方法;
  • 调用$watch返回;
    综上分析,watch的传参可以分为以下几种:
watch: {
    telephone: function (newValue, oldValue) {
      console.log('telephone')
    },
    name: 'printName',
    message: ['printName', 'printValue'],
    address: [{
      handler: function (newValue, oldValue) {
        console.log('address')
      }
    }]
  },
  methods: {
    printName(newValue, oldValue) {
      console.log('printName')
    },
    printValue(newValue, oldValue) {
      console.log('printValue')
    }
  }
  • 第一种:直接传方法;
  • 第二种:传递方法的字符串名称;
  • 第三种:传递方法的字符串名称数组;
  • 第四种:传递一个包含handler属性的对象数组;
    接下来咱们看下$watch方法的实现

$watch

现在咱们来看下watch的实现,watch是Vue原型上的方法,主流程篇简单提了一下,流程图上面看到$watch是在statesMixin函数里面给Vue挂载到原型对象上的。

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) {
        try {
            cb.call(vm, watcher.value)
        } catch (error) {
            handleError(error, vm, `callback for immediate watcher "${watcher.expression}"`)
        }
    }
    return function unwatchFn() {
        watcher.teardown()
    }
}

上面代码就是$watch函数的实现,咱们一步步来看下。

  • 参数,包含3个,第一个就是需要watch的key,比如上面例子代码的name;第二个就是回调函数,当name属性改变的时候会调用此回调函数;第三个参数为options,顾名思义,就是配置信息;
  • 第一步:实例vm的声明;
  • 第二步:判断cb是否为对象,如果是则调用上面的createWatcher;
  • 第三步:options检测是否有值,无值则赋值为空对象;
  • 第四步:设置options.user为true,即这是用户所定义和调用触发的;
  • 第五步:创建Watcher;
  • 第六步:如果是立即调用,则调用cb,即回调函数;
  • 返回一个函数,此函数为watcher的销毁函数。
    上面就是整个initWatch的调用过程。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Cesium Vue源码是一个开源项目的源代码,它结合了Cesium和Vue.js两个框架的优势,用于在Web上构建高性能的3D地球应用程序。 Cesium是一个强大的基于Web的地理空间可视化引擎,可以实现全球范围内的地图展示和3D场景的渲染。而Vue.js是一个流行的JavaScript框架,用于构建用户界面。通过将这两个框架结合在一起,Cesium Vue能够提供一个更灵活和易于使用的工具,用于开发各种各样的地球应用程序。 Cesium Vue源码包含了各种组件和工具,用于与Cesium引擎进行交互和集成。例如,它提供了CesiumViewer组件,用于创建一个全功能的Cesium场景,并且能够与Vue组件进行交互。它还提供了各种用于加载地理数据、设置相机位置、渲染3D对象等功能的工具函数。 使用Cesium Vue源码,开发人员可以方便地创建一个具有交互性和可视化效果的地球应用程序。他们可以轻松地创建地图、添加地标和矢量数据,实现相机控制、动画效果等功能。同时,由于使用了Vue.js框架,开发人员还能够轻松管理和更新应用程序的状态,并且可以方便地与其他Vue组件进行集成。 总的来说,Cesium Vue源码提供了一个强大的工具,用于开发具有高性能和可交互性的3D地球应用程序。通过结合Cesium和Vue.js的优势,它能够为开发人员提供更好的开发体验和更多的灵活性。无论是构建地图应用程序、数据可视化还是虚拟现实应用程序,Cesium Vue都是一个非常有价值的工具。 ### 回答2: Cesium Vue源码是一个基于Vue框架封装的对Cesium进行集成的库。Cesium是一个用于创建各种地理和地球空间应用的开源JavaScript库,而Vue是一个用于构建用户界面的渐进式JavaScript框架。 Vue的优势在于其虚拟DOM和响应式数据绑定的特性,使得开发者可以更加高效地构建可复用的组件和灵活的界面。Cesium则提供了强大的地理可视化功能,包括3D地球的展示、地理信息的呈现和地图操作等。 Cesium Vue源码的主要目的是将Cesium和Vue无缝集成,使得开发者可以更加便捷地在Vue项目中使用Cesium的功能。通过Cesium Vue,我们可以轻松地将Cesium的3D地球嵌入到Vue的组件中,使用Vue的数据绑定和组件化技术来管理Cesium的状态和交互。 Cesium Vue源码实现了Cesium和Vue之间的双向数据绑定,使得我们可以通过Vue的数据驱动Cesium的展示和交互。同时,Cesium Vue提供了一系列的Vue组件,用于简化和封装Cesium的常用功能,比如地图控制、实体渲染和相机操作等。 通过Cesium Vue,开发者可以利用Vue的生态系统和丰富的插件,以及Cesium的地理可视化功能,快速构建高质量的地理和地球空间应用。无论是创建交互式地图应用,还是构建具有地球模型的数据可视化工具,Cesium Vue都提供了简洁、灵活和高效的开发方式。 总之,Cesium Vue源码是一个基于Vue框架封装的Cesium集成库,通过它我们可以更加便捷地在Vue项目中使用Cesium的功能,享受Vue的开发便捷性和Cesium的地理可视化能力。 ### 回答3: Cesium-Vue源码是一个基于Vue.js框架开发的Cesium地球引擎的封装库,用于在Vue项目中使用Cesium进行地球可视化开发。该库主要提供了一系列在Vue组件中使用Cesium功能的API和组件。 源码的结构主要包括了基于Vue的组件目录、样式文件目录和存放Cesium相关代码的目录。在组件目录中,可以看到各个Vue组件用于实现不同的地球可视化功能,如地图视图、图层控制、导航控制等。样式文件目录主要包括了用于美化地球视图的CSS样式。 Cesium-Vue源码的主要功能包括: 1. 地图视图:通过Vue组件提供的功能,可以在项目中创建一个地球视图,并进行交互操作,如移动、旋转等。 2. 图层控制:该库提供了图层控制的API和组件,使得开发者可以方便地添加不同类型的图层,如地形图、影像图等,并进行切换和控制。 3. 数据可视化:通过Cesium的API和Vue的数据绑定机制,可以实现将数据可视化展示在地球上,如点、线、面等实体的展示和交互。 4. 导航控制:Cesium-Vue提供了一系列的API和组件,用于实现地球视图的导航控制,如缩放、旋转、定位等操作。 总体而言,Cesium-Vue源码的设计和实现通过将Cesium地球引擎与Vue.js框架相结合,提供了一种便捷的方式在Vue项目中开发地球可视化应用。通过使用该库,可以方便地在Vue项目中创建地球视图、添加图层、展示数据,并实现交互和导航控制等功能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值