本篇文章带大家解读vue源码,来介绍一下vue2中为什么可以使用 this 访问各种选项中的属性,希望对大家有所帮助!
下方的如何阅读源码不感兴趣可以不用看,可以通过这个直接定位到【源码分析】
如何阅读源码网上有很多关于源码阅读的文章,每个人都有自己的方式,但是网上的文章都是精炼之后的,告诉你哪个文件、那个函数、那个变量是干什么的;【相关推荐: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这两个脚本;
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模块化或者commonjs模块化的,这些都会有一个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 typeinitmixin(vue)//@ts-expect-error vue has function typestatemixin(vue)//@ts-expect-error vue has function typeeventsmixin(vue)//@ts-expect-error vue has function typelifecyclemixin(vue)//@ts-expect-error vue has function typerendermixin(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 typeinitmixin(vue)//@ts-expect-error vue has function typestatemixin(vue)//@ts-expect-error vue has function typeeventsmixin(vue)//@ts-expect-error vue has function typelifecyclemixin(vue)//@ts-expect-error vue has function typerendermixin(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/propsinitstate(vm)initprovide(vm) // resolve provide after data/propscallhook(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根据代码结构可以看到,initstate主要做了以下几件事:
初始化props初始化setup初始化methods初始化data初始化computed初始化watch我们可以用this来访问的属性是props、methods、data、computed;
看到这里也明白了,为什么在props中定义了一个属性,在data、methods、computed中就不能再定义了,因为props是最先初始化的,后面的也是同理。
initpropsinitprops的作用是初始化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实现的,我这里直接是空的,这些超出了本文的范围。
initmethodsinitmethods的代码如下:
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 : polyfillbindconst vm = new vue(options)vm.say()
initdatainitdata的代码如下:
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的获取值的地方是其他的不相同,这里只做提醒,不做详细分析。
initcomputedinitcomputed的代码如下:
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 : polyfillbindfunction 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() // 1console.log('computed d: 2', vm.d)
注意:上面的代码对比于文章中写的示例有改动,主要是为了实现最后打印结果正确,增加了赋值操作。
总结通过上面的分析,让我们对构造函数的this有了更深的理解,同时对于this指向的问题也有了更深的理解。
(学习视频分享:vuejs入门教程、编程基础视频)
以上就是聊聊vue2为什么能通过this访问各种选项中属性的详细内容。
