この記事は、vue ソース コードを解釈するのに役立ち、これを使用して Vue2 のさまざまなオプションのプロパティにアクセスできる理由を紹介します。皆さんのお役に立てれば幸いです。
以下のソース コードの読み方に興味がない場合は、読む必要はありません。これを使用して [ に直接移動できます。 ソースコード解析]
インターネット上にはソースコードの読み方に関する記事がたくさんあります。独自の方法ですが、インターネット上の記事はすべて洗練されており、どのファイル、その関数、その変数が何をするのかを示しています。[関連する推奨事項: vuejs ビデオ チュートリアル 、Web フロントエンド開発]
しかし、これらを見つける方法、これらをどのように理解し、どのように検証し、どのように記憶し、どのように適用するのかについては説明しませんでした。
私は偉大なマスターではありませんが、探求の過程で徐々に自分自身の道を見つけました。
最初は何事も難しいものです。開始点を見つけるのが最も難しいです。フロントエンド プロジェクトの場合、エントリ ファイル (通常は から) package.json
の main
フィールド、
package.json の
main フィールドから検索を開始します。
はこのパッケージを表します。エントリ ファイル。通常、このフィールドの値を通じて、読み取りたい開始点を見つけることができます。
ただし、Vue
の場合、このフィールドは dist/vue.runtime.common.js
です。このファイルはコンパイルされたファイルであり、理解できません。ソース コードのエントリ ファイルを見つける必要があります。
現時点では、package.json
の scripts
フィールドを確認する必要があります:
{ "scripts": { "dev": "rollup -w -c scripts/config.js --environment TARGET:full-dev", "dev:cjs": "rollup -w -c scripts/config.js --environment TARGET:runtime-cjs-dev", "dev:esm": "rollup -w -c scripts/config.js --environment TARGET:runtime-esm", "dev:ssr": "rollup -w -c scripts/config.js --environment TARGET:server-renderer", "dev:compiler": "rollup -w -c scripts/config.js --environment TARGET:compiler ", "build": "node scripts/build.js", "build:ssr": "npm run build -- runtime-cjs,server-renderer", "build:types": "rimraf temp && tsc --declaration --emitDeclarationOnly --outDir temp && api-extractor run && api-extractor run -c packages/compiler-sfc/api-extractor.json", "test": "npm run ts-check && npm run test:types && npm run test:unit && npm run test:e2e && npm run test:ssr && npm run test:sfc", "test:unit": "vitest run test/unit", "test:ssr": "npm run build:ssr && vitest run server-renderer", "test:sfc": "vitest run compiler-sfc", "test:e2e": "npm run build -- full-prod,server-renderer-basic && vitest run test/e2e", "test:transition": "karma start test/transition/karma.conf.js", "test:types": "npm run build:types && tsc -p ./types/tsconfig.json", "format": "prettier --write --parser typescript "(src|test|packages|types)/**/*.ts"", "ts-check": "tsc -p tsconfig.json --noEmit", "ts-check:test": "tsc -p test/tsconfig.json --noEmit", "bench:ssr": "npm run build:ssr && node benchmarks/ssr/renderToString.js && node benchmarks/ssr/renderToStream.js", "release": "node scripts/release.js", "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s" } }
わかりました。Vue
の package.json
には、たくさんの scripts
があることがわかりました。これらは誰でも理解できると思います。ここでは、次の点にのみ焦点を当てます。 dev
と build
は 2 つのスクリプトです。
dev
スクリプトは開発に使用され、build
スクリプトはパッケージ化に使用されます。 dev
スクリプトに TARGET
環境変数があることがわかります。この環境変数の値は full-dev
です。設定できます。 scripts/config でこの値を js
で見つけます;
scripts/config.js
で full-dev
を直接検索します:
このようにして、この値に対応する設定を見つけることができます。
var config = { 'full-dev': { entry: resolve('web/entry-runtime-with-compiler.ts'), dest: resolve('dist/vue.js'), format: 'umd', env: 'development', alias: { he: './entity-decoder' }, banner } }
entry
フィールドは、探しているエントリ ファイルです。このファイルは Vue
エントリー ファイルのソース コードです。次の値は web/entry-runtime-with-compiler.ts
です。このファイルは にあります。 web
ディレクトリ;
ですが、ルート ディレクトリに web
ディレクトリがありません。このとき、エイリアス設定があるかどうかを大胆に推測します。 scripts
. ファイルの下に
alias.js も見つかり、このファイルを開くと、web
;
module.exports = { vue: resolve('src/platforms/web/entry-runtime-with-compiler'), compiler: resolve('src/compiler'), core: resolve('src/core'), web: resolve('src/platforms/web'), weex: resolve('src/platforms/weex'), shared: resolve('src/shared') }
config.js で
alias を検索すると、次のものが見つかると思います。このファイルが実際に導入されていることを確認します:
const aliases = require('./alias') const resolve = p => { const base = p.split('/')[0] if (aliases[base]) { return path.resolve(aliases[base], p.slice(base.length + 1)) } else { return path.resolve(__dirname, '../', p) } }
aliases を検索すると、構成エイリアス:
// 省略部分代码 const config = { plugins: [ alias({ entries: Object.assign({}, aliases, opts.alias) }), ].concat(opts.plugins || []), }
であることを確認できます。 web はディレクトリ
src/platforms/web であり、# このディレクトリ ##entry-runtime-with-compiler.ts
このファイル;## にあります。
#このようにして、 Vue
のソース コード エントリ ファイルを見つけることができました。その後、ソース コードの読み取りを開始できます。方法ソースコードを読むには
esm
モジュール性またはcommonjsModular を使用しており、これらには
export があります。 または
module.exports、これを使用してエクスポートされた内容を確認できます。
エクスポートされたコンテンツのみが表示され、その他は当面無視して、最終的にエクスポートされたコンテンツを直接検索します
Vue
のソース コードなど
entry-runtime-with-compiler.ts
的导出内容:
import Vue from './runtime-with-compiler' export default Vue
这个时候就去找runtime-with-compiler.ts
的导出内容:
runtime-with-compiler.ts
的导出内容:
import Vue from './runtime/index' export default Vue as GlobalAPI
这个时候就去找runtime/index.ts
的导出内容:
runtime/index.ts
的导出内容:
import Vue from 'core/index' export default Vue
这个时候就去找core/index.ts
的导出内容:
core/index.ts
的导出内容:
import Vue from './instance/index' export default Vue
这个时候就去找instance/index.ts
的导出内容:
instance/index.ts
的导出内容:
function Vue(options) { if (__DEV__ && !(this instanceof Vue)) { warn('Vue is a constructor and should be called with the `new` keyword') } this._init(options) } export default Vue as unknown as GlobalAPI
这样我们就找到Vue
的构造函数了,这个时候我们就可以开始阅读源码了;
阅读源码的目的一定要清晰,当然你可以说目的就是了解Vue
的实现原理,但是这个目的太宽泛了,我们可以把目的细化一下,例如:
Vue
的生命周期是怎么实现的
Vue
的数据响应式是怎么实现的
Vue
的模板编译是怎么实现的
Vue
的组件化是怎么实现的
Vue
的插槽是怎么实现的
等等...
例如我们的这次阅读计划就是了解Vue
的this
为什么可以访问到选项中的各种属性,这里再细分为:
Vue
的this
是怎么访问到data
的
Vue
的this
是怎么访问到methods
的
Vue
的this
是怎么访问到computed
的
Vue
的this
是怎么访问到props
的
上面顺序不分先后,但是答案一定是在源码中。
上面已经找到了Vue
的入口文件,接下来我们就可以开始阅读源码了,这里我就以Vue
的this
为什么可以访问到选项中的各种属性为例,来分析Vue
的源码;
首先看一下instance/index.ts
的源码:
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' import type { GlobalAPI } from 'types/global-api' function Vue(options) { if (__DEV__ && !(this instanceof Vue)) { warn('Vue is a constructor and should be called with the `new` keyword') } this._init(options) } //@ts-expect-error Vue has function type initMixin(Vue) //@ts-expect-error Vue has function type stateMixin(Vue) //@ts-expect-error Vue has function type eventsMixin(Vue) //@ts-expect-error Vue has function type lifecycleMixin(Vue) //@ts-expect-error Vue has function type renderMixin(Vue) export default Vue as unknown as GlobalAPI
有这么多东西,我们不用管,要清晰目的,我们在使用Vue
的时候,通常是下面这样的:
const vm = new Vue({ data() { return { msg: 'hello world' } }, methods: { say() { console.log(this.msg) } } }); vm.say();
也就是Vue
的构造函数接收一个选项对象,这个选项对象中有data
和methods
;
我们要知道Vue
的this
为什么可以访问到data
和methods
,那么我们就要找到Vue
的构造函数中是怎么把data
和methods
挂载到this
上的;
很明显构造函数只做了一件事,就是调用了this._init(options)
:
this._init(options)
那么我们就去找_init
方法,这个方法在哪我们不知道,但是继续分析源码,我们可以看到下面会执行很多xxxMixin
的函数,并且Vue
作为参数传入:
//@ts-expect-error Vue has function type initMixin(Vue) //@ts-expect-error Vue has function type stateMixin(Vue) //@ts-expect-error Vue has function type eventsMixin(Vue) //@ts-expect-error Vue has function type lifecycleMixin(Vue) //@ts-expect-error Vue has function type renderMixin(Vue)
盲猜一波,见名知意:
initMixin
:初始化混入
stateMixin
:状态混入
eventsMixin
:事件混入
lifecycleMixin
:生命周期混入
renderMixin
:渲染混入
我们就去找这些混入的方法,一个一个的找,找到initMixin
,直接就找了_init
方法:
export function initMixin(Vue: typeof Component) { Vue.prototype._init = function (options?: Record<string, any>) { const vm: Component = this // a uid vm._uid = uid++ let startTag, endTag /* istanbul ignore if */ if (__DEV__ && config.performance && mark) { startTag = `vue-perf-start:${vm._uid}` endTag = `vue-perf-end:${vm._uid}` mark(startTag) } // a flag to mark this as a Vue instance without having to do instanceof // check vm._isVue = true // avoid instances from being observed vm.__v_skip = true // effect scope vm._scope = new EffectScope(true /* detached */) vm._scope._vm = 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 as any) } else { vm.$options = mergeOptions( resolveConstructorOptions(vm.constructor as any), options || {}, vm ) } /* istanbul ignore else */ if (__DEV__) { initProxy(vm) } else { vm._renderProxy = vm } // expose real self vm._self = vm initLifecycle(vm) initEvents(vm) initRender(vm) callHook(vm, 'beforeCreate', undefined, false /* setContext */) initInjections(vm) // resolve injections before data/props initState(vm) initProvide(vm) // resolve provide after data/props callHook(vm, 'created') /* istanbul ignore if */ if (__DEV__ && config.performance && mark) { vm._name = formatComponentName(vm, false) mark(endTag) measure(`vue ${vm._name} init`, startTag, endTag) } if (vm.$options.el) { vm.$mount(vm.$options.el) } } }
代码这么多没必要全都看,记住我们的目的是找到data
和methods
是怎么挂载到this
上的;
先简化代码,不看没有意义的代码:
export function initMixin(Vue) { Vue.prototype._init = function (options) { const vm = this } }
传递过来的Vue
并没有做太多事情,只是把_init
方法挂载到了Vue.prototype
上;
在_init
方法中,vm
被赋值为this
,这里的this
就是Vue
的实例,也就是我们的vm
;
继续往下看,我们有目的的看代码,只需要看有vm
和options
组合出现的代码,于是就看到了:
if (options && options._isComponent) { initInternalComponent(vm, options) } else { vm.$options = mergeOptions( resolveConstructorOptions(vm.constructor), options || {}, vm ) }
_isComponent
前面带有_
,说明是私有属性,我们通过new Vue
创建的实例时走到现在是没有这个属性的,所以走到else
分支;
resolveConstructorOptions(vm.constructor)
中没有传递options
,所以不看这个方法,直接看mergeOptions
:
export function mergeOptions(parent, child, vm) { if (__DEV__) { checkComponents(child) } if (isFunction(child)) { // @ts-expect-error child = child.options } normalizeProps(child, vm) normalizeInject(child, vm) normalizeDirectives(child) // Apply extends and mixins on the child options, // but only if it is a raw options object that isn't // the result of another mergeOptions call. // Only merged options has the _base property. if (!child._base) { if (child.extends) { parent = mergeOptions(parent, child.extends, vm) } if (child.mixins) { for (let i = 0, l = child.mixins.length; i < l; i++) { parent = mergeOptions(parent, child.mixins[i], vm) } } } 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 }
记住我们的目的,只需要关心vm
和options
组合出现的代码,child
就是options
,vm
就是vm
,简化之后:
export function mergeOptions(parent, child, vm) { normalizeProps(child, vm) normalizeInject(child, vm) normalizeDirectives(child) return options }
可以看到只剩下了normalizeProps
、normalizeInject
、normalizeDirectives
这三个方法,值得我们关注,但是见名知意,这三个方法可能并不是我们想要的,跟进去看一眼也确实不是;
虽然没有得到我们想要的,但是从这里我们也得到了一个重要信息,mergeOptions
最后会返回一个options
对象,这个对象就是我们的options
,最后被vm.$options
接收;
vm.$options = mergeOptions( resolveConstructorOptions(vm.constructor), options || {}, vm )
现在我们分析要多一步了,参数只有vm
的函数也是需要引起我们的注意的,继续往下看:
if (__DEV__) { initProxy(vm) } else { vm._renderProxy = vm }
操作了vm
,但是内部没有操作$options
,跳过,继续往下看:
initLifecycle(vm) initEvents(vm) initRender(vm) callHook(vm, 'beforeCreate', undefined, false /* setContext */) initInjections(vm) // resolve injections before data/props initState(vm) initProvide(vm) // resolve provide after data/props callHook(vm, 'created')
initLifecycle
、initEvents
、initRender
、initInjections
、initState
、initProvide
这些方法都是操作vm
的;
盲猜一波:
initLifecycle
:初始化生命周期initEvents
:初始化事件initRender
:初始化渲染initInjections
:初始化注入initState
:初始化状态initProvide
:初始化依赖注入callHook
:调用钩子这里面最有可能是我们想要的是initState
,跟进去看一下:
export function initState(vm) { const opts = vm.$options if (opts.props) initProps(vm, opts.props) // Composition API initSetup(vm) if (opts.methods) initMethods(vm, opts.methods) if (opts.data) { initData(vm) } else { const ob = observe((vm._data = {})) ob && ob.vmCount++ } if (opts.computed) initComputed(vm, opts.computed) if (opts.watch && opts.watch !== nativeWatch) { initWatch(vm, opts.watch) } }
已经找到我们想要的了,现在开始正式分析initState
。
根据代码结构可以看到,initState
主要做了以下几件事:
props
setup
methods
data
computed
watch
我们可以用this
来访问的属性是props
、methods
、data
、computed
;
看到这里也明白了,为什么在props
中定义了一个属性,在data
、methods
、computed
中就不能再定义了,因为props
是最先初始化的,后面的也是同理。
initProps
的作用是初始化props
,跟进去看一下:
function initProps(vm, propsOptions) { const propsData = vm.$options.propsData || {} const props = (vm._props = shallowReactive({})) // cache prop keys so that future props updates can iterate using Array // instead of dynamic object key enumeration. const keys = (vm.$options._propKeys = []) const isRoot = !vm.$parent // root instance props should be converted if (!isRoot) { toggleObserving(false) } for (const key in propsOptions) { keys.push(key) const value = validateProp(key, propsOptions, propsData, vm) /* istanbul ignore else */ if (__DEV__) { const hyphenatedKey = hyphenate(key) if ( isReservedAttribute(hyphenatedKey) || config.isReservedAttr(hyphenatedKey) ) { warn( `"${hyphenatedKey}" is a reserved attribute and cannot be used as component prop.`, vm ) } defineReactive(props, key, value, () => { if (!isRoot && !isUpdatingChildComponent) { warn( `Avoid mutating a prop directly since the value will be ` + `overwritten whenever the parent component re-renders. ` + `Instead, use a data or computed property based on the prop's ` + `value. Prop being mutated: "${key}"`, vm ) } }) } else { defineReactive(props, key, value) } // static props are already proxied on the component's prototype // during Vue.extend(). We only need to proxy props defined at // instantiation here. if (!(key in vm)) { proxy(vm, `_props`, key) } } toggleObserving(true) }
代码很多,我们依然不用关心其他的代码,只关心props
是怎么挂载到vm
上的,根据我上面的方法,简化后的代码如下:
function initProps(vm, propsOptions) { vm._props = shallowReactive({}) for (const key in propsOptions) { const value = validateProp(key, propsOptions, propsData, vm) if (!(key in vm)) { proxy(vm, `_props`, key) } } }
这里真正有关的就两个地方:
validateProp
:看名字就知道是验证props
,跳过
proxy
:代理,很可疑,跟进去看一下:
export function proxy(target, sourceKey, key) { sharedPropertyDefinition.get = function proxyGetter() { return this[sourceKey][key] } sharedPropertyDefinition.set = function proxySetter(val) { this[sourceKey][key] = val } Object.defineProperty(target, key, sharedPropertyDefinition) }
这里的target
就是vm
,sourceKey
就是_props
,key
就是props
的属性名;
这里通过Object.defineProperty
把vm
的属性代理到_props
上,这样就可以通过this
访问到props
了。
不是很好理解,那我们来自己就用这些代码实现一下:
var options = { props: { name: { type: String, default: 'default name' } } } function Vue(options) { const vm = this initProps(vm, options.props) } function initProps(vm, propsOptions) { vm._props = {} for (const key in propsOptions) { proxy(vm, `_props`, key) } } function proxy(target, sourceKey, key) { Object.defineProperty(target, key, { get() { return this[sourceKey][key] }, set(val) { this[sourceKey][key] = val } }) } const vm = new Vue(options) console.log(vm.name); console.log(vm._props.name); vm.name = 'name' console.log(vm.name); console.log(vm._props.name);
上面的代码只是为了方便理解,所以会忽略一些细节,比如
props
的验证等等,真实挂载在_props
上的props
是通过defineReactive
实现的,我这里直接是空的,这些超出了本文的范围。
initMethods
的代码如下:
function initMethods(vm, methods) { const props = vm.$options.props for (const key in methods) { if (__DEV__) { 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) } }
跟着之前的思路,我们忽略无关代码,简化后的代码如下:
function initMethods(vm, methods) { for (const key in methods) { vm[key] = typeof methods[key] !== 'function' ? noop : bind(methods[key], vm) } }
这里的
noop
和bind
在之前的文章中有出现过,可以去看一下:【源码共读】Vue2源码 shared 模块中的36个实用工具函数分析
这里的vm[key]
就是methods
的方法,这样就可以通过this
访问到methods
中定义的方法了。
bind
的作用是把methods
中定义的函数的this
指向vm
,这样就可以在methods
中使用this
就是vm
了。
简单的实现一下:
var options = { methods: { say() { console.log('say'); } } } function Vue(options) { const vm = this initMethods(vm, options.methods) } function initMethods(vm, methods) { for (const key in methods) { vm[key] = typeof methods[key] !== 'function' ? noop : bind(methods[key], vm) } } function noop() {} function polyfillBind(fn, ctx) { function boundFn(a) { const l = arguments.length return l ? l > 1 ? fn.apply(ctx, arguments) : fn.call(ctx, a) : fn.call(ctx) } boundFn._length = fn.length return boundFn } function nativeBind(fn, ctx) { return fn.bind(ctx) } const bind = Function.prototype.bind ? nativeBind : polyfillBind const vm = new Vue(options) vm.say()
initData
的代码如下:
function initData(vm) { let data = vm.$options.data data = vm._data = isFunction(data) ? getData(data, vm) : data || {} if (!isPlainObject(data)) { data = {} __DEV__ && warn( 'data functions should return an object:\n' + 'https://v2.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 const methods = vm.$options.methods let i = keys.length while (i--) { const key = keys[i] if (__DEV__) { if (methods && hasOwn(methods, key)) { warn(`Method "${key}" has already been defined as a data property.`, vm) } } if (props && hasOwn(props, key)) { __DEV__ && warn( `The data property "${key}" is already declared as a prop. ` + `Use prop default value instead.`, vm ) } else if (!isReserved(key)) { proxy(vm, `_data`, key) } } // observe data const ob = observe(data) ob && ob.vmCount++ }
简化之后的代码如下:
function initData(vm) { let data = vm.$options.data // proxy data on instance const keys = Object.keys(data) let i = keys.length while (i--) { const key = keys[i] proxy(vm, `_data`, key) } }
这里的实现方式和initProps
是一样的,都是通过proxy
把data
中的属性代理到vm
上。
注意:
initData
的获取值的地方是其他的不相同,这里只做提醒,不做详细分析。
initComputed
的代码如下:
function initComputed(vm, computed) { // $flow-disable-line const watchers = (vm._computedWatchers = Object.create(null)) // computed properties are just getters during SSR const isSSR = isServerRendering() for (const key in computed) { const userDef = computed[key] const getter = isFunction(userDef) ? userDef : userDef.get if (__DEV__ && getter == null) { warn(`Getter is missing for computed property "${key}".`, vm) } if (!isSSR) { // create internal watcher for the computed property. watchers[key] = new Watcher( vm, getter || noop, noop, computedWatcherOptions ) } // component-defined computed properties are already defined on the // component prototype. We only need to define computed properties defined // at instantiation here. if (!(key in vm)) { defineComputed(vm, key, userDef) } else if (__DEV__) { if (key in vm.$data) { warn(`The computed property "${key}" is already defined in data.`, vm) } else if (vm.$options.props && key in vm.$options.props) { warn(`The computed property "${key}" is already defined as a prop.`, vm) } else if (vm.$options.methods && key in vm.$options.methods) { warn( `The computed property "${key}" is already defined as a method.`, vm ) } } } }
简化之后的代码如下:
function initComputed(vm, computed) { for (const key in computed) { const userDef = computed[key] const getter = userDef defineComputed(vm, key, userDef) } }
这里的实现主要是通过defineComputed
来定义computed
属性,进去瞅瞅:
export function defineComputed(target, key, userDef) { const shouldCache = !isServerRendering() if (isFunction(userDef)) { 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 } if (__DEV__ && sharedPropertyDefinition.set === noop) { sharedPropertyDefinition.set = function () { warn( `Computed property "${key}" was assigned to but it has no setter.`, this ) } } Object.defineProperty(target, key, sharedPropertyDefinition) }
仔细看下来,其实实现方式还是和initProps
和initData
一样,都是通过Object.defineProperty
来定义属性;
不过里面的getter
和setter
是通过createComputedGetter
和createGetterInvoker
来创建的,这里不做过多分析。
上面我们已经分析了props
、methods
、data
、computed
的属性为什么可以直接通过this
来访问,那么我们现在就来实现一下这个功能。
上面已经简单了实现了initProps
、initMethods
,而initData
和initComputed
的实现方式和initProps
的方式一样,所以我们直接复用就好了:
function Vue(options) { this._init(options) } Vue.prototype._init = function (options) { const vm = this vm.$options = options initState(vm) } function initState(vm) { const opts = vm.$options if (opts.props) initProps(vm, opts.props) if (opts.methods) initMethods(vm, opts.methods) if (opts.data) initData(vm) if (opts.computed) initComputed(vm, opts.computed) } function initProps(vm, propsOptions) { vm._props = {} for (const key in propsOptions) { vm._props[key] = propsOptions[key].default proxy(vm, `_props`, key) } } function proxy(target, sourceKey, key) { Object.defineProperty(target, key, { get() { return this[sourceKey][key] }, set(val) { this[sourceKey][key] = val } }) } function initMethods(vm, methods) { for (const key in methods) { vm[key] = typeof methods[key] !== 'function' ? noop : bind(methods[key], vm) } } function noop() {} function polyfillBind(fn, ctx) { function boundFn(a) { const l = arguments.length return l ? l > 1 ? fn.apply(ctx, arguments) : fn.call(ctx, a) : fn.call(ctx) } boundFn._length = fn.length return boundFn } function nativeBind(fn, ctx) { return fn.bind(ctx) } const bind = Function.prototype.bind ? nativeBind : polyfillBind function initData(vm) { vm._data = {} for (const key in vm.$options.data) { vm._data[key] = vm.$options.data[key] proxy(vm, `_data`, key) } } function initComputed(vm, computed) { for (const key in computed) { const userDef = computed[key] const getter = userDef defineComputed(vm, key, bind(userDef, vm)) } } function defineComputed(target, key, userDef) { Object.defineProperty(target, key, { get() { return userDef() }, }) } const vm = new Vue({ props: { a: { type: String, default: 'default' } }, data: { b: 1 }, methods: { c() { console.log(this.b) } }, computed: { d() { return this.b + 1 } } }) console.log('props a: default',vm.a) console.log('data b: 1', vm.b) vm.c() // 1 console.log('computed d: 2', vm.d)
注意:上面的代码对比于文章中写的示例有改动,主要是为了实现最后打印结果正确,增加了赋值操作。
通过上面的分析,让我们对构造函数的this
有了更深的理解,同时对于this
指向的问题也有了更深的理解。
以上がVue2 がこれを通じてさまざまなオプションのプロパティにアクセスできる理由について話しましょうの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。