前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >Vue源码解读之InitState

Vue源码解读之InitState

原创
作者头像
yyzzabc123
发布于 2022-09-28 03:21:53
发布于 2022-09-28 03:21:53
30500
代码可运行
举报
运行总次数:0
代码可运行

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

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

初始化状态信息

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

代码语言:scss
AI代码解释
复制
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。


Vue3核心源码视频讲解:进入学习

initProps

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

代码语言:scss
AI代码解释
复制
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
代码语言:text
AI代码解释
复制
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绑定到当前实例上面

代码语言:text
AI代码解释
复制
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上面了,并且以$或_开头,如是,则在开发环境下报警
    • 为当前实例添加方法;

initData

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

代码语言:text
AI代码解释
复制
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参数

代码语言:text
AI代码解释
复制
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的实现:
代码语言:text
AI代码解释
复制
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:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
function createGetterInvoker(fn) {
    return function computedGetter() {
        return fn.call(this, this)
    }
}

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

createComputedGetter

下面来看下createComputedGetter:

代码语言:scss
AI代码解释
复制
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参数。

代码语言:scss
AI代码解释
复制
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

代码语言:text
AI代码解释
复制
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的传参可以分为以下几种:
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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挂载到原型对象上的。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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的调用过程。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Vue——initState【十】
前面我们简单的了解了 vue 初始化时的一些大概的流程,这里我们详细的了解下具体的内容;
思索
2024/08/15
640
【源码学习】你知道data,props,methods初始化的顺序么? (附思维导图)
---- 声明 🔊 本文是开始学习 Vue 源码的第三篇笔记,当前的版本是 2.6.14 。如果对你有一点点帮助,请点赞鼓励一下,如果有错误或者遗漏,请在评论区指出,非常感谢各位大佬。 🔊 代码基本上是逐行注释,由于本人的能力有限,很多基础知识也进行了注释和讲解。由于源码过长,文章不会贴出完整代码,所以基本上都是贴出部分伪代码然后进行分析,建议在阅读时对照源码,效果更佳。 🔊 从本篇文章开始,可能会出现暂时看不懂的地方,是因为还没有学习前置知识,不必惊慌,只需知道存在这样一个知识点,接着向下看,看完了前
一尾流莺
2022/12/10
1.4K0
【源码学习】你知道data,props,methods初始化的顺序么? (附思维导图)
4. 「vue@2.6.11 源码分析」new Vue() 整体流程和组件渲染之前的准备工作
将组件渲染渲染分为两大步骤:组件信息(事件、方法等等)的初始化,以及组件的渲染。 虽然源码中 $mount方法调用放在了_init方法上,但是感觉拿出来好些,毕竟是两个大的步骤。
tinyant
2023/02/24
6940
4.  「vue@2.6.11 源码分析」new Vue() 整体流程和组件渲染之前的准备工作
Vue中computed分析
在Vue中computed是计算属性,其会根据所依赖的数据动态显示新的计算结果,虽然使用{{}}模板内的表达式非常便利,但是设计它们的初衷是用于简单运算的,在模板中放入太多的逻辑会让模板过重且难以维护,所以对于任何复杂逻辑,都应当使用计算属性。计算属性是基于数据的响应式依赖进行缓存的,只在相关响应式依赖发生改变时它们才会重新求值,也就是说只要计算属性依赖的数据还没有发生改变,多次访问计算属性会立即返回之前的计算结果,而不必再次执行函数,当然如果不希望使用缓存可以使用方法属性并返回值即可,computed计算属性非常适用于一个数据受多个数据影响以及需要对数据进行预处理的条件下使用。
WindRunnerMax
2020/09/28
6190
手摸手带你理解Vue的Computed原理
computed 在 Vue 中是很常用的属性配置,它能够随着依赖属性的变化而变化,为我们带来很大便利。那么本文就来带大家全面理解 computed 的内部原理以及工作流程。
WahFung
2020/08/24
1.2K0
「源码级回答」大厂高频Vue面试题(上)
但,收到很多朋友的反馈都是:源码晦涩难懂,时常看着看着就不知道我在看什么了,感觉缺乏一点动力,如果你可以出点面试中会问到的源码相关的面试题,通过面试题去看源码,那就很棒棒。
前端森林
2020/04/26
7950
「源码级回答」大厂高频Vue面试题(上)
Vue2剥丝抽茧-响应式系统之computed
Vue2 源码从零详解系列文章, 还没有看过的同学可能需要看一下之前的,vue.windliang.wang/
windliang
2022/08/20
3820
Vue2剥丝抽茧-响应式系统之computed
vue源码分析-响应式系统(一)
回顾一下之前的内容,我们对Vue源码的分析是从初始化开始,初始化_init会执行一系列的过程,这个过程包括了配置选项的合并,数据的监测代理,最后才是实例的挂载。而在实例挂载前还有意忽略了一个重要的过程,数据的初始化(即initState(vm))。initState的过程,是对数据进行响应式设计的过程,过程会针对props,methods,data,computed和watch做数据的初始化处理,并将他们转换为响应式对象,接下来我们会逐步分析每一个过程。
yyzzabc123
2022/10/17
2980
手摸手带你理解Vue的Watch原理
watch 是由用户定义的数据监听,当监听的属性发生改变就会触发回调,这项配置在业务中是很常用。在面试时,也是必问知识点,一般会用作和 computed 进行比较。
WahFung
2020/08/22
1.7K0
vue源码分析-响应式系统(二)
在构建简易式响应式系统的时候,我们引出了几个重要的概念,他们都是响应式原理设计的核心,我们先简单回顾一下:
yyzzabc123
2022/10/17
3170
【Vue原理】Computed - 源码版
今天要记录 computed 的源码,有时候想,理解不就好了吗,为什么要记录一遍源码。现在终于想通了
神仙朱
2019/08/02
1.1K0
【Vue原理】Computed - 源码版
石桥码农:20 vue计算属性和侦听器
在template里的插值表达式,如果太长,会让模板代码变得难于维护;如果有多处用到了同样的插值表达式,也不便于复用和修改。例如,这样的一个插值表达式:
LIYI
2020/02/13
6850
石桥码农:20 vue计算属性和侦听器
new Vue发生了什么
从入口代码开始分析,我们先来分析 new Vue 背后发生了哪些事情。我们都知道,new 关键字在 Javascript 语言中代表实例化是一个对象,而 Vue 实际上是一个类,类在 Javascript 中是用 Function 来实现的,来看一下源码,在src/core/instance/index.js 中。
19组清风
2021/11/15
5680
【Vue原理】Watch - 源码版
没错,当你调用 Vue 创建实例过程中,会去处理各种选项,其中包括处理 watch
神仙朱
2019/08/02
7490
【Vue原理】Watch - 源码版
从源码解读Vue生命周期,让面试官对你刮目相看
在我们的实际项目中,与Vue的生命周期打交道可以说是家常便饭。掌握Vue的生命周期对开发者来说是特别重要的。那么如果能够从源码角度理解Vue的生命周期,对我们的开发和成长会有进一步的提升。
童欧巴
2020/03/30
5470
从源码解读Vue生命周期,让面试官对你刮目相看
vue源码分析-从new Vue开始
那你觉得是不是很有意思,咱们new Vue之后,就可以使用他那么多的功能,可见Vue是暴出来的一个一个功能类函数,我们进入源码一探究竟:
yyzzabc123
2022/10/19
5870
为什么 Vue2 this 能够直接获取到 data 和 methods ? 源码揭秘!
之前写的《学习源码整体架构系列》 包含jQuery、underscore、lodash、vuex、sentry、axios、redux、koa、vue-devtools、vuex4十余篇源码文章。其中最新的三篇是:
若川
2021/09/27
8070
【Vue 源码解析】Vue实例挂载过程
源码位置:node_modules/vue/src/core/instance/index.js(ps:找不到可以在node_modules目录下搜索,因为懒惰后边就不写node_modules/vue这两级目录了)
CODER-V
2023/03/19
8270
【Vue 源码解析】Vue实例挂载过程
从零手写 Vue 之响应式系统
之前的文章把响应式系统基本讲完了,没看过的同学可以看一下 vue.windliang.wang/。这篇文章主要是按照 Vue2 源码的目录格式和调用过程,把我们之前写的响应式系统移动进去。
windliang
2022/09/23
2930
从零手写 Vue 之响应式系统
2022前端经典vue面试题(持续更新中)
一个SPA应用的路由需要解决的问题是 页面跳转内容改变同时不刷新 ,同时路由还需要以插件形式存在,所以:
bb_xiaxia1998
2022/09/16
1K0
相关推荐
Vue——initState【十】
更多 >
领券
社区富文本编辑器全新改版!诚邀体验~
全新交互,全新视觉,新增快捷键、悬浮工具栏、高亮块等功能并同时优化现有功能,全面提升创作效率和体验
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
查看详情【社区公告】 技术创作特训营有奖征文