源码调试
pnpm install
pnpm run dev
目录下生产 vue 文件夹
vue/dist 下新建 index.html
<html>
<head></head>
<body>
<div id="app">
{{ c }}
</div>
</body>
<script src="vue.global.js"></script>
<script>
const { createApp, ref } = Vue
createApp({
props: {
i: {
type: String,
default: 'inject'
}
},
data() {
return {
a: 'data'
}
},
setup(props) {
let a = ref('setup')
return {
a
}
},
computed: {
c() {
return 'computed'
}
},
mounted() {
console.log(this.a) => setup
},
methods: {
m() { }
},
}).mount('#app')
</script>
</html>
即可开始调试,源码在 vue/dist/vue.global.js 中
获取与写入
先看两个关键实参 instance 和 ctx,在实例中 this 指向的就是 ctx
instance
function createComponentInstance(vnode, parent, suspense) {
const type = vnode.type;
const appContext = (parent ? parent.appContext : vnode.appContext) || emptyAppContext;
const instance = {
uid: uid2++,
vnode,
type,
parent,
appContext,
root: null,
next: null,
subTree: null,
effect: null,
update: null,
scope: new EffectScope(true),
render: null,
proxy: null,
exposed: null,
exposeProxy: null,
withProxy: null,
provides: parent ? parent.provides : Object.create(appContext.provides),
accessCache: null,
renderCache: [],
components: null,
directives: null,
propsOptions: normalizePropsOptions(type, appContext),
emitsOptions: normalizeEmitsOptions(type, appContext),
emit: null,
emitted: null,
propsDefaults: EMPTY_OBJ,
inheritAttrs: type.inheritAttrs,
ctx: EMPTY_OBJ,
data: EMPTY_OBJ,
props: EMPTY_OBJ,
attrs: EMPTY_OBJ,
slots: EMPTY_OBJ,
refs: EMPTY_OBJ,
setupState: EMPTY_OBJ,
setupContext: null,
suspense,
suspenseId: suspense ? suspense.pendingId : 0,
asyncDep: null,
asyncResolved: false,
isMounted: false,
isUnmounted: false,
isDeactivated: false,
bc: null,
c: null,
bm: null,
m: null,
bu: null,
u: null,
um: null,
bum: null,
da: null,
a: null,
rtg: null,
rtc: null,
ec: null,
sp: null
};
instance.ctx = createDevRenderContext(instance);
instance.root = parent ? parent.root : instance;
instance.emit = emit2.bind(null, instance);
if (vnode.ce) {
vnode.ce(instance);
}
return instance;
}
当前实例的所有参数、生命周期函数都在 instance 中
ctx
function createDevRenderContext(instance) {
const target = {};
Object.defineProperty(target, `_`, {
configurable: true,
enumerable: false,
get: () => instance
});
Object.keys(publicPropertiesMap).forEach((key) => {
Object.defineProperty(target, key, {
configurable: true,
enumerable: false,
get: () => publicPropertiesMap[key](instance),
set: NOOP
});
});
return target;
}
var publicPropertiesMap = /* @__PURE__ */ extend(/* @__PURE__ */ Object.create(null), {
$: (i) => i,
$el: (i) => i.vnode.el,
$data: (i) => i.data,
$props: (i) => true ? shallowReadonly(i.props) : i.props,
$attrs: (i) => true ? shallowReadonly(i.attrs) : i.attrs,
$slots: (i) => true ? shallowReadonly(i.slots) : i.slots,
$refs: (i) => true ? shallowReadonly(i.refs) : i.refs,
$parent: (i) => getPublicInstance(i.parent),
$root: (i) => getPublicInstance(i.root),
$emit: (i) => i.emit,
$options: (i) => true ? resolveMergedOptions(i) : i.type,
$forceUpdate: (i) => i.f || (i.f = () => queueJob(i.update)),
$nextTick: (i) => i.n || (i.n = nextTick.bind(i.proxy)),
$watch: (i) => true ? instanceWatch.bind(i) : NOOP
});
- $ 指向 instance
- $el 指向 instance虚拟DOM节点信息
- $data 指向 instance 下 data
- attrs、refs 也指向 instance 下对应位置,但是是只读的
instance 与 ctx 也就有了循环引用。ctx._ 和 ctx.$ 指向 instance,instance.ctx 指向 ctx
Get
非 $ 开头数据
get({ _: instance }, key) {
const { ctx, setupState, data, props, accessCache, type, appContext } = instance;
if (key === "__isVue") {
return true;
}
// <script setup> 语法糖中执行代码, __isScriptSetup 为 true。
if (setupState !== EMPTY_OBJ && setupState.__isScriptSetup && hasOwn(setupState, key)) {
return setupState[key];
}
let normalizedProps;
if (key[0] !== "$") {
// accessCache 缓存,记录 变量 所在位置。
const n = accessCache[key];
if (n !== void 0) {
switch (n) {
case 1 /* SETUP */:
return setupState[key];
case 2 /* DATA */:
return data[key];
case 4 /* CONTEXT */:
return ctx[key];
case 3 /* PROPS */:
return props[key];
}
} else if (setupState !== EMPTY_OBJ && hasOwn(setupState, key)) {
accessCache[key] = 1 /* SETUP */;
return setupState[key];
} else if (data !== EMPTY_OBJ && hasOwn(data, key)) {
accessCache[key] = 2 /* DATA */;
return data[key];
} else if ((normalizedProps = instance.propsOptions[0]) && hasOwn(normalizedProps, key)) {
accessCache[key] = 3 /* PROPS */;
return props[key];
} else if (ctx !== EMPTY_OBJ && hasOwn(ctx, key)) {
// methods、inject、computed等参数都在 ctx 中获取
accessCache[key] = 4 /* CONTEXT */;
return ctx[key];
} else if (shouldCacheAccess) {
accessCache[key] = 0 /* OTHER */;
}
}
...
}
- setupState
- data
- props
- ctx
参数的获取顺序,所以同名参数都会被保存,但是有优先级问题
propsOptions
将 props 按照 全局mixins、extends、当前实例mixins和props 组合成 props数据。详情可见 normalizePropsOptions 初始化props
props: {
i: {
type: String,
default: 'inject'
}
}
normalized : { i: {0: false, 1: true, default: 'inject', type: ƒ}, ... }
needCastKeys : [ 'i' ]
instance.propsOptions = [normalized, needCastKeys]
所有数据
// 获取 publicPropertiesMap 中的参数
const publicGetter = publicPropertiesMap[key];
let cssModule, globalProperties;
if (publicGetter) {
// instance、el、data、props、attrs、slots等
return publicGetter(instance);
} else if ((cssModule = type.__cssModules) && (cssModule = cssModule[key])) {
// css模块(由vue loader注入)
return cssModule;
} else if (ctx !== EMPTY_OBJ && hasOwn(ctx, key)) {
accessCache[key] = 4 /* CONTEXT */;
return ctx[key];
} else if (globalProperties = appContext.config.globalProperties, hasOwn(globalProperties, key)) {
// app.config.globalProperties 一个用于注册能够被应用内所有组件实例访问到的全局属性的对象。
return globalProperties[key];
} else if (currentRenderingInstance && (!isString(key) || key.indexOf("__v") !== 0)) {
if (data !== EMPTY_OBJ && isReservedPrefix(key[0]) && hasOwn(data, key)) {
// data 中的数据不能以 $和_开头
warn2(`Property ${JSON.stringify(key)} must be accessed via $data because it starts with a reserved character ("$" or "_") and is not proxied on the render context.`);
} else if (instance === currentRenderingInstance) {
// 未定义
warn2(`Property ${JSON.stringify(key)} was accessed during render but is not defined on instance.`);
}
}
优先级
- instance、el、data、props、attrs、slots、emit等数据获取
- css模块
- ctx:methods、inject、computed等参数
- config.globalProperties 全局属性
- data 中的数据不能以 $和_开头,其他的可以
Set
set({ _: instance }, key, value) {
const { data, setupState, ctx } = instance;
if (setupState !== EMPTY_OBJ && hasOwn(setupState, key)) {
setupState[key] = value;
return true;
} else if (data !== EMPTY_OBJ && hasOwn(data, key)) {
data[key] = value;
return true;
} else if (hasOwn(instance.props, key)) {
warn2(`Attempting to mutate prop "${key}". Props are readonly.`, instance);
return false;
}
if (key[0] === "$" && key.slice(1) in instance) {
warn2(`Attempting to mutate public property "${key}". Properties starting with $ are reserved and readonly.`, instance);
return false;
} else {
if (key in instance.appContext.config.globalProperties) {
Object.defineProperty(ctx, key, {
enumerable: true,
configurable: true,
value
});
} else {
ctx[key] = value;
}
}
return true;
}
优先级
- setupState
- data
- props,数据不能修改
- instance 中 $ 开头的数据不能修改
- appContext.config.globalProperties
- ctx
Get | Set |
---|---|
非$开头数据 | setupState |
setupState | data |
data | props,数据不能修改 |
props | instance , $ 开头的数据不能修改 |
ctx | config.globalProperties |
$开头数据 | ctx |
publicPropertiesMap | |
css模块 | |
ctx | |
config.globalProperties |