【转】Vue2,2024最新版

module.exports = genConfig({

entry: path.resolve(__dirname, ‘…/src/entries/web-runtime-with-compiler.js’),

dest: path.resolve(__dirname, ‘…/dist/vue.js’),

format: ‘umd’,

env: ‘development’,

alias: { he: ‘./entity-decoder’ },

banner

})

最终,genConfig 函数返回一个 config 对象,这个 config 对象就是 Rollup 的配置对象。那么我们就不难看到,入口文件是:

src/entries/web-runtime-with-compiler.js

我们打开这个文件,不要忘了我们的主题,我们在寻找 Vue 构造函数,所以当我们看到这个文件的第一行代码是:

import Vue from ‘./web-runtime’

这个时候,你就应该知道,这个文件暂时与你无缘,你应该打开 web-runtime.js 文件,不过当你打开这个文件时,你发现第一行是这样的:

import Vue from ‘core/index’

依照此思路,最终我们寻找到 Vue 构造函数的位置应该是在 src/core/instance/index.js 文件中,其实我们猜也猜得到,上面介绍目录的时候说过:instance 是存放 Vue 构造函数设计相关代码的目录。总结一下,我们寻找的过程是这样的:

我们回头看一看 src/core/instance/index.js 文件,很简单:

import { initMixin } from ‘./init’

import { stateMixin } from ‘./state’

import { renderMixin } from ‘./render’

import { eventsMixin } from ‘./events’

import { lifecycleMixin } from ‘./lifecycle’

import { warn } from ‘…/util/index’

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)

}

initMixin(Vue)

stateMixin(Vue)

eventsMixin(Vue)

lifecycleMixin(Vue)

renderMixin(Vue)

export default Vue

引入依赖,定义 Vue 构造函数,然后以Vue构造函数为参数,调用了五个方法,最后导出 Vue。这五个方法分别来自五个文件:init.js state.js render.js events.js 以及 lifecycle.js

打开这五个文件,找到相应的方法,你会发现,这些方法的作用,就是在 Vue 的原型 prototype 上挂载方法或属性,经历了这五个方法后的 Vue会变成这样:

// initMixin(Vue) src/core/instance/init.js **************************************************

Vue.prototype._init = function (options?: Object) {}

// stateMixin(Vue) src/core/instance/state.js **************************************************

Vue.prototype.$data

Vue.prototype.$set = set

Vue.prototype.$delete = del

Vue.prototype.$watch = function(){}

// renderMixin(Vue) src/core/instance/render.js **************************************************

Vue.prototype.$nextTick = function (fn: Function) {}

Vue.prototype._render = function (): VNode {}

Vue.prototype._s = _toString

Vue.prototype._v = createTextVNode

Vue.prototype._n = toNumber

Vue.prototype._e = createEmptyVNode

Vue.prototype._q = looseEqual

Vue.prototype._i = looseIndexOf

Vue.prototype._m = function(){}

Vue.prototype._o = function(){}

Vue.prototype._f = function resolveFilter (id) {}

Vue.prototype._l = function(){}

Vue.prototype._t = function(){}

Vue.prototype._b = function(){}

Vue.prototype._k = function(){}

// eventsMixin(Vue) src/core/instance/events.js **************************************************

Vue.prototype.$on = function (event: string, fn: Function): Component {}

Vue.prototype.$once = function (event: string, fn: Function): Component {}

Vue.prototype.$off = function (event?: string, fn?: Function): Component {}

Vue.prototype.$emit = function (event: string): Component {}

// lifecycleMixin(Vue) src/core/instance/lifecycle.js **************************************************

Vue.prototype._mount = function(){}

Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {}

Vue.prototype._updateFromParent = function(){}

Vue.prototype.$forceUpdate = function () {}

Vue.prototype.$destroy = function () {}

这样就结束了吗?并没有,根据我们之前寻找 Vue 的路线,这只是刚刚开始,我们追溯路线往回走,那么下一个处理 Vue 构造函数的应该是 src/core/index.js 文件,我们打开它:

import Vue from ‘./instance/index’

import { initGlobalAPI } from ‘./global-api/index’

import { isServerRendering } from ‘core/util/env’

initGlobalAPI(Vue)

Object.defineProperty(Vue.prototype, ‘$isServer’, {

get: isServerRendering

})

Vue.version = ‘VERSION

export default Vue

这个文件也很简单,从 instance/index 中导入已经在原型上挂载了方法和属性后的 Vue,然后导入 initGlobalAPI 和 isServerRendering,之后将Vue作为参数传给 initGlobalAPI ,最后又在 Vue.prototype 上挂载了 $isServer ,在 Vue 上挂载了 version 属性。

initGlobalAPI 的作用是在 Vue 构造函数上挂载静态属性和方法,Vue 在经过 initGlobalAPI 之后,会变成这样:

// src/core/index.js / src/core/global-api/index.js

Vue.config

Vue.util = util

Vue.set = set

Vue.delete = del

Vue.nextTick = util.nextTick

Vue.options = {

components: {

KeepAlive

},

directives: {},

filters: {},

_base: Vue

}

Vue.use

Vue.mixin

Vue.cid = 0

Vue.extend

Vue.component = function(){}

Vue.directive = function(){}

Vue.filter = function(){}

Vue.prototype.$isServer

Vue.version = ‘VERSION

其中,稍微复杂一点的就是 Vue.options,大家稍微分析分析就会知道他的确长成那个样子。下一个就是 web-runtime.js 文件了,web-runtime.js 文件主要做了三件事儿:

1、覆盖 Vue.config 的属性,将其设置为平台特有的一些方法

2、Vue.options.directives 和 Vue.options.components 安装平台特有的指令和组件

3、在 Vue.prototype 上定义 __patch__ 和 $mount

经过 web-runtime.js 文件之后,Vue 变成下面这个样子:

// 安装平台特定的utils

Vue.config.isUnknownElement = isUnknownElement

Vue.config.isReservedTag = isReservedTag

Vue.config.getTagNamespace = getTagNamespace

Vue.config.mustUseProp = mustUseProp

// 安装平台特定的 指令 和 组件

Vue.options = {

components: {

KeepAlive,

Transition,

TransitionGroup

},

directives: {

model,

show

},

filters: {},

_base: Vue

}

Vue.prototype.patch

Vue.prototype.$mount

这里大家要注意的是 Vue.options 的变化。另外这里的 $mount 方法很简单:

Vue.prototype.$mount = function (

el?: string | Element,

hydrating?: boolean

): Component {

el = el && inBrowser ? query(el) : undefined

return this._mount(el, hydrating)

}

首先根据是否是浏览器环境决定要不要 query(el) 获取元素,然后将 el 作为参数传递给 this._mount()。

最后一个处理 Vue 的文件就是入口文件 web-runtime-with-compiler.js 了,该文件做了两件事:

1、缓存来自 web-runtime.js 文件的 $mount 函数

const mount = Vue.prototype.$mount

然后覆盖覆盖了 Vue.prototype.$mount

2、在 Vue 上挂载 compile

Vue.compile = compileToFunctions

compileToFunctions 函数的作用,就是将模板 template 编译为 render 函数。

至此,我们算是还原了 Vue 构造函数,总结一下:

1、Vue.prototype 下的属性和方法的挂载主要是在 src/core/instance 目录中的代码处理的

2、Vue 下的静态属性和方法的挂载主要是在 src/core/global-api 目录下的代码处理的

3、web-runtime.js 主要是添加 web 平台特有的配置、组件和指令,web-runtime-with-compiler.js 给 Vue 的 $mount 方法添加 compiler 编译器,支持 template。

四、一个贯穿始终的例子

===========

在了解了 Vue 构造函数的设计之后,接下来,我们一个贯穿始终的例子就要登场了,掌声有请:

let v = new Vue({

el: ‘#app’,

data: {

a: 1,

b: [1, 2, 3]

}

})

好吧,我承认这段代码你家没满月的孩子都会写了。这段代码就是我们贯穿始终的例子,它就是这篇文章的主线,在后续的讲解中,都会以这段代码为例,当讲到必要的地方,会为其添加选项,比如讲计算属性的时候当然要加上一个 computed 属性了。不过在最开始,我只传递了两个选项 el 以及 data,“我们看看接下来会发生什么,让我们拭目以待“ —- NBA 球星在接受采访时最喜欢说这句话。

当我们按照例子那样编码使用Vue的时候,Vue都做了什么?

想要知道 Vue 都干了什么,我们就要找到 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)

}

我们发现,_init() 方法就是 Vue 调用的第一个方法,然后将我们的参数 options 透传了过去。在调用 _init() 之前,还做了一个安全模式的处理,告诉开发者必须使用 new 操作符调用 Vue。根据之前我们的整理,_init() 方法应该是在 src/core/instance/init.js 文件中定义的,我们打开这个文件查看 _init() 方法:

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

const vm: Component = this

// a uid

vm._uid = uid++

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

callHook(vm, ‘beforeCreate’)

initState(vm)

callHook(vm, ‘created’)

initRender(vm)

}

_init() 方法在一开始的时候,在 this 对象上定义了两个属性:_uid 和 _isVue,然后判断有没有定义 options._isComponent,在使用 Vue 开发项目的时候,我们是不会使用 _isComponent 选项的,这个选项是 Vue 内部使用的,按照本节开头的例子,这里会走 else 分支,也就是这段代码:

vm.$options = mergeOptions(

resolveConstructorOptions(vm.constructor),

options || {},

vm

)

这样 Vue 第一步所做的事情就来了:使用策略对象合并参数选项

可以发现,Vue 使用 mergeOptions 来处理我们调用 Vue 时传入的参数选项(options),然后将返回值赋值给 this.$options (vm === this),传给 mergeOptions 方法三个参数,我们分别来看一看,首先是:resolveConstructorOptions(vm.constructor),我们查看一下这个方法:

export function resolveConstructorOptions (Ctor: Class) {

let options = Ctor.options

if (Ctor.super) {

const superOptions = Ctor.super.options

const cachedSuperOptions = Ctor.superOptions

const extendOptions = Ctor.extendOptions

if (superOptions !== cachedSuperOptions) {

// super option changed

Ctor.superOptions = superOptions

extendOptions.render = options.render

extendOptions.staticRenderFns = options.staticRenderFns

extendOptions._scopeId = options._scopeId

options = Ctor.options = mergeOptions(superOptions, extendOptions)

if (options.name) {

options.components[options.name] = Ctor

}

}

}

return options

}

这个方法接收一个参数 Ctor,通过传入的 vm.constructor 我们可以知道,其实就是 Vue 构造函数本身。所以下面这句代码:

let options = Ctor.options

相当于:

let options = Vue.options

大家还记得 Vue.options 吗?在寻找 Vue 构造函数一节里,我们整理了 Vue.options 应该长成下面这个样子:

Vue.options = {

components: {

KeepAlive,

Transition,

TransitionGroup

},

directives: {

model,

show

},

filters: {},

_base: Vue

}

之后判断是否定义了 Vue.super ,这个是用来处理继承的,我们后续再讲,在本例中,resolveConstructorOptions 方法直接返回了 Vue.options。也就是说,传递给 mergeOptions 方法的第一个参数就是 Vue.options。

传给 mergeOptions 方法的第二个参数是我们调用 Vue 构造函数时的参数选项,第三个参数是 vm 也就是 this 对象,按照本节开头的例子那样使用 Vue,最终运行的代码应该如下:

vm.$options = mergeOptions(

// Vue.options

{

components: {

KeepAlive,

Transition,

TransitionGroup

},

directives: {

model,

show

},

filters: {},

_base: Vue

},

// 调用Vue构造函数时传入的参数选项 options

{

el: ‘#app’,

data: {

a: 1,

b: [1, 2, 3]

}

},

// this

vm

)

了解了这些,我们就可以看看 mergeOptions 到底做了些什么了,根据引用寻找到 mergeOptions 应该是在 src/core/util/options.js 文件中定义的。这个文件第一次看可能会头大,下面是我处理后的简略展示,大家看上去应该更容易理解了:

// 1、引用依赖

import Vue from ‘…/instance/index’

其他引用…

// 2、合并父子选项值为最终值的策略对象,此时 strats 是一个空对象,因为 config.optionMergeStrategies = Object.create(null)

const strats = config.optionMergeStrategies

// 3、在 strats 对象上定义与参数选项名称相同的方法

strats.el =

strats.propsData = function (parent, child, vm, key){}

strats.data = function (parentVal, childVal, vm)

config._lifecycleHooks.forEach(hook => {

strats[hook] = mergeHook

})

config._assetTypes.forEach(function (type) {

strats[type + ‘s’] = mergeAssets

})

strats.watch = function (parentVal, childVal)

strats.props =

strats.methods =

strats.computed = function (parentVal: ?Object, childVal: ?Object)

// 默认的合并策略,如果有 childVal 则返回 childVal 没有则返回 parentVal

const defaultStrat = function (parentVal: any, childVal: any): any {

return childVal === undefined

? parentVal
childVal

}

// 4、mergeOptions 中根据参数选项调用同名的策略方法进行合并处理

export function mergeOptions (

parent: Object,

child: Object,

vm?: Component

): Object {

// 其他代码

const options = {}

let key

for (key in parent) {

mergeField(key)

}

for (key in child) {

if (!hasOwn(parent, key)) {

mergeField(key)

}

}

function mergeField (key) {

const strat = strats[key] || defaultStrat

options[key] = strat(parent[key], child[key], vm, key)

}

return options

}

上面的代码中,我省略了一些工具函数,例如 mergeHook 和 mergeAssets 等等,唯一需要注意的是这段代码:

config._lifecycleHooks.forEach(hook => {

strats[hook] = mergeHook

})

config._assetTypes.forEach(function (type) {

strats[type + ‘s’] = mergeAssets

})

config 对象引用自 src/core/config.js 文件,最终的结果就是在 strats 下添加了相应的生命周期选项的合并策略函数为 mergeHook,添加指令(directives)、组件(components)、过滤器(filters)等选项的合并策略函数为 mergeAssets。

这样看来就清晰多了,拿我们贯穿本文的例子来说:

let v = new Vue({

el: ‘#app’,

data: {

a: 1,

b: [1, 2, 3]

}

})

其中 el 选项会使用 defaultStrat 默认策略函数处理,data 选项则会使用 strats.data 策略函数处理,并且根据 strats.data 中的逻辑,strats.data 方法最终会返回一个函数:mergedInstanceDataFn。

这里就不详细的讲解每一个策略函数的内容了,后续都会讲到,这里我们还是抓住主线理清思路为主,只需要知道Vue在处理选项的时候,使用了一个策略对象对父子选项进行合并。并将最终的值赋值给实例下的 o p t i o n s 属性即: t h i s . options 属性即:this. options属性即:this.options,那么我们继续查看 _init() 方法在合并完选项之后,又做了什么:

合并完选项之后,Vue 第二部做的事情就来了:初始化工作与Vue实例对象的设计

前面讲了 Vue 构造函数的设计,并且整理了 Vue 原型属性与方法 和 Vue 静态属性与方法,而 Vue 实例对象就是通过构造函数创造出来的,让我们来看一看 Vue 实例对象是如何设计的,下面的代码是 _init() 方法合并完选项之后的代码:

/* 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)

callHook(vm, ‘beforeCreate’)

initState(vm)

callHook(vm, ‘created’)

initRender(vm)

根据上面的代码,在生产环境下会为实例添加两个属性,并且属性值都为实例本身:

vm._renderProxy = vm

vm._self = vm

然后,调用了四个 init* 方法分别为:initLifecycle、initEvents、initState、initRender,且在 initState 前后分别回调了生命周期钩子 beforeCreate 和 created,而 initRender 是在 created 钩子执行之后执行的,看到这里,也就明白了为什么 created 的时候不能操作 DOM了。因为这个时候还没有渲染真正的 DOM 元素到文档中。created 仅仅代表数据状态的初始化完成。

根据四个 init* 方法的引用关系打开对应的文件查看对应的方法,我们发现,这些方法是在处理 Vue 实例对象,以及做一些初始化的工作,类似整理 Vue 构造函数一样,我同样针对 Vue 实例做了属性和方法的整理,如下:

// 在 Vue.prototype._init 中添加的属性 **********************************************************

this._uid = uid++

this._isVue = true

this.$options = {

components,

directives,

filters,

_base,

el,

data: mergedInstanceDataFn()

}

this._renderProxy = this

this._self = this

// 在 initLifecycle 中添加的属性 **********************************************************

this.$parent = parent

this. r o o t = p a r e n t ? p a r e n t . root = parent ? parent. root=parent?parent.root : this

this.$children = []

this.$refs = {}

this._watcher = null

this._inactive = false

this._isMounted = false

this._isDestroyed = false

this._isBeingDestroyed = false

// 在 initEvents 中添加的属性 **********************************************************

this._events = {}

this._updateListeners = function(){}

// 在 initState 中添加的属性 **********************************************************

this._watchers = []

// initData

this._data

// 在 initRender 中添加的属性 **********************************************************

this.$vnode = null // the placeholder node in parent tree

this._vnode = null // the root of the child tree

this._staticTrees = null

this.$slots

this.$scopedSlots

this._c

this.$createElement

以上就是一个 Vue 实例所包含的属性和方法,除此之外要注意的是,在 initEvents 中除了添加属性之外,如果有 vm.$options._parentListeners 还要调用 vm._updateListeners() 方法,在 initState 中又调用了一些其他 init 方法,如下:

export function initState (vm: Component) {

vm._watchers = []

initProps(vm)

initMethods(vm)

initData(vm)

initComputed(vm)

initWatch(vm)

}

最后在 initRender 中如果有 vm. o p t i o n s . e l 还要调用 v m . options.el 还要调用 vm. options.el还要调用vm.mount(vm.$options.el),如下:

if (vm.$options.el) {

vm. m o u n t ( v m . mount(vm. mount(vm.options.el)

}

这就是为什么如果不传递 el 选项就需要手动 mount 的原因了。

那么我们依照我们本节开头的的例子,以及初始化的先后顺序来逐一看一看都发生了什么。我们将 initState 中的 init* 方法展开来看,执行顺序应该是这样的(从上到下的顺序执行):

initLifecycle(vm)

initEvents(vm)

callHook(vm, ‘beforeCreate’)

initProps(vm)

initMethods(vm)

initData(vm)

initComputed(vm)

initWatch(vm)

callHook(vm, ‘created’)

initRender(vm)

首先是 initLifecycle,这个函数的作用就是在实例上添加一些属性,然后是 initEvents,由于 vm. o p t i o n s . _ p a r e n t L i s t e n e r s 的值为 u n d e f i n e d 所以也仅仅是在实例上添加属性, v m . _ u p d a t e L i s t e n e r s ( l i s t e n e r s ) 并不会执行,由于我们只传递了 e l 和 d a t a ,所以 i n i t P r o p s 、 i n i t M e t h o d s 、 i n i t C o m p u t e d 、 i n i t W a t c h 这四个方法什么都不会做,只有 i n i t D a t a 会执行。最后是 i n i t R e n d e r ,除了在实例上添加一些属性外,由于我们传递了 e l 选项,所以会执行 v m . options.\_parentListeners 的值为 undefined 所以也仅仅是在实例上添加属性, vm.\_updateListeners(listeners) 并不会执行,由于我们只传递了 el 和 data,所以 initProps、initMethods、initComputed、initWatch 这四个方法什么都不会做,只有 initData 会执行。最后是 initRender,除了在实例上添加一些属性外,由于我们传递了 el 选项,所以会执行 vm. options._parentListeners的值为undefined所以也仅仅是在实例上添加属性,vm._updateListeners(listeners)并不会执行,由于我们只传递了eldata,所以initPropsinitMethodsinitComputedinitWatch这四个方法什么都不会做,只有initData会执行。最后是initRender,除了在实例上添加一些属性外,由于我们传递了el选项,所以会执行vm.mount(vm.$options.el)。

综上所述:按照我们的例子那样写,初始化工作只包含两个主要内容即:initData 和 initRender。

五、通过initData看Vue的数据响应系统

=======================

Vue 的数据响应系统包含三个部分:Observer、Dep、Watcher。关于数据响应系统的内容真的已经被文章讲烂了,所以我就简单的说一下,力求大家能理解就 ok,我们还是先看一下 initData 中的代码:

function initData (vm: Component) {

let data = vm.$options.data

data = vm._data = typeof data === ‘function’

? data.call(vm)
data || {}

if (!isPlainObject(data)) {

data = {}

process.env.NODE_ENV !== ‘production’ && warn(

‘data functions should return an object:\n’ +

‘https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function’,

vm

)

}

// proxy data on instance

const keys = Object.keys(data)

const props = vm.$options.props

let i = keys.length

while (i–) {

if (props && hasOwn(props, keys[i])) {

process.env.NODE_ENV !== ‘production’ && warn(

The data property "${keys[i]}" is already declared as a prop. +

Use prop default value instead.,

vm

)

} else {

proxy(vm, keys[i])

}

}

// observe data

observe(data)

data.ob && data.ob.vmCount++

}

首先,先拿到 data 数据:let data = vm. o p t i o n s . d a t a ,大家还记得此时 v m . options.data,大家还记得此时 vm. options.data,大家还记得此时vm.options.data 的值应该是通过 mergeOptions 合并处理后的 mergedInstanceDataFn 函数吗?所以在得到 data 后,它又判断了 data 的数据类型是不是 ‘function’,最终的结果是:data 还是我们传入的数据选项的 data,即:

data: {

a: 1,

b: [1, 2, 3]

}

然后在实例对象上定义 _data 属性,该属性与 data 是相同的引用。

然后是一个 while 循环,循环的目的是在实例对象上对数据进行代理,这样我们就能通过 this.a 来访问 data.a 了,代码的处理是在 proxy 函数中,该函数非常简单,仅仅是在实例对象上设置与 data 属性同名的访问器属性,然后使用 _data 做数据劫持,如下:

function proxy (vm: Component, key: string) {

if (!isReserved(key)) {

Object.defineProperty(vm, key, {

configurable: true,

enumerable: true,

get: function proxyGetter () {

return vm._data[key]

},

set: function proxySetter (val) {

vm._data[key] = val

}

})

}

}

做完数据的代理,就正式进入响应系统。

observe(data)

我们说过,数据响应系统主要包含三部分:Observer、Dep、Watcher,代码分别存放在:observer/index.js、observer/dep.js 以及 observer/watcher.js 文件中,这回我们换一种方式,我们先不看其源码,大家先跟着我的思路来思考,最后回头再去看代码,你会有一种:”奥,不过如此“的感觉。

假如,我们有如下代码:

var data = {

a: 1,

b: {

c: 2

小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Web前端开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img
img
img
img

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频

如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注:前端)
img

data: {

a: 1,

b: [1, 2, 3]

}

然后在实例对象上定义 _data 属性,该属性与 data 是相同的引用。

然后是一个 while 循环,循环的目的是在实例对象上对数据进行代理,这样我们就能通过 this.a 来访问 data.a 了,代码的处理是在 proxy 函数中,该函数非常简单,仅仅是在实例对象上设置与 data 属性同名的访问器属性,然后使用 _data 做数据劫持,如下:

function proxy (vm: Component, key: string) {

if (!isReserved(key)) {

Object.defineProperty(vm, key, {

configurable: true,

enumerable: true,

get: function proxyGetter () {

return vm._data[key]

},

set: function proxySetter (val) {

vm._data[key] = val

}

})

}

}

做完数据的代理,就正式进入响应系统。

observe(data)

我们说过,数据响应系统主要包含三部分:Observer、Dep、Watcher,代码分别存放在:observer/index.js、observer/dep.js 以及 observer/watcher.js 文件中,这回我们换一种方式,我们先不看其源码,大家先跟着我的思路来思考,最后回头再去看代码,你会有一种:”奥,不过如此“的感觉。

假如,我们有如下代码:

var data = {

a: 1,

b: {

c: 2

小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Web前端开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

[外链图片转存中…(img-sjKUZ2Pe-1710904018055)]
[外链图片转存中…(img-3ayU7W4D-1710904018056)]
[外链图片转存中…(img-1KEfHGSY-1710904018057)]
[外链图片转存中…(img-5TiN4ULp-1710904018057)]

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频

如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注:前端)
[外链图片转存中…(img-bm0TPTzU-1710904018057)]

  • 15
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值