skip to content

In a Vue 3 Options API component, how does this resolve a name such as this.total across data, props, computed and methods?

level: middleimportance: should knowfreq 40%

answer

  1. one proxy, several stores
  2. fixed lookup order
  3. data before props
  4. $ properties, then globalProperties

basics

~20 s

Vue 3's this is a proxy that looks a name up in a fixed order: setup() bindings, data, props, then methods, computed and inject, then built-in $ properties, then app globalProperties. Writes follow the same order, and props are read-only.

solid answer

~50 s

`this` in an Options API component is not the options object but a **proxy** over the internal instance. On a read it checks, in order: bindings returned from `setup()`, keys from `data()`, props, then the instance context holding `methods`, `computed` and `inject`, then the public `$` properties such as `$el`, `$refs`, `$emit` and `$nextTick`, and finally `app.config.globalProperties`. Where a key was found is cached, so later reads go straight there. Writes go to a setup binding or data key if one matches; writing a prop warns `Attempting to mutate prop "x". Props are readonly.`; writing a built-in `$` property such as `$refs` warns; anything else is stored on the context without reactivity. In development, the same name declared in two of props, inject, methods, data and computed warns, and a template reading an unknown name warns `Property "x" was accessed during render but is not defined on instance.`

go deeper

for a junior

Recall that data, props, computed and methods are all reachable on this, and that props cannot be reassigned through it.

for a middle

Explain the proxy's read order across setup, data, props, context, $ properties and globalProperties, and what each write path does.

for a senior

Use the lookup order and the dev warnings to trace where a legacy component's this.x comes from and why a clash hides one value.

for a principal

Weigh the flat this namespace as a maintainability cost of options code and set naming rules, such as $-prefixed globals, that keep it traceable.

## What this actually is In a Vue 3 Options API component, `this` inside `methods`, hooks, `computed` and `watch` — and the implicit scope of the template — is the **public instance proxy**. It is a JavaScript `Proxy` placed in front of the component's internal instance, which keeps its state in several separate stores: - **`setupState`** — bindings returned from a `setup()` function, if the component has one; - **`data`** — the reactive object returned by `data()`; - **`props`** — the resolved props; - **`ctx`** — the context object where Vue puts bound `methods`, `computed` getters and `inject`ed values. `this.total` has to be answered from one of these, and the proxy decides which. ## The read order 1. **`setup()` bindings.** 2. **`data`** keys. 3. **props**. 4. **context**: `methods`, `computed`, `inject`. 5. **Public `$` properties**: `$el`, `$data`, `$props`, `$attrs`, `$slots`, `$refs`, `$parent`, `$root`, `$emit`, `$options`, `$forceUpdate`, `$nextTick`, `$watch`. 6. **`app.config.globalProperties`** — values a plugin or the app registered for every component. After the first lookup the proxy remembers which store held the key, so repeated reads of `this.total` in a render do not walk the chain again. | Declared as | Read from | Writable through `this` | |---|---|---| | `data()` key | `data` store | Yes, reactive | | prop | props store | No — dev warning, value unchanged | | `computed` with a setter | context | Yes, calls the setter | | `computed` without a setter | context | No — dev warning `Write operation failed: computed property "x" is readonly.` | | method | context | Replaceable, but never do it | | built-in `$` properties such as `$data`, `$props`, `$refs` | public getters | No — dev warning `Properties starting with $ are reserved and readonly.` | | never declared | nothing, `undefined` | Stored on context, **not reactive** | ## Name clashes Because several stores share one namespace, the same name in two places is ambiguous. In development Vue checks props, `inject`, `methods`, `data` and `computed` against each other and warns on a clash, for example `Data property "title" is already defined in Props.` Given the read order, `this.title` would return the **data** value, silently hiding the prop — which is why the warning matters. Clashes with `globalProperties` are resolved the other way: a component's own property wins, because global properties are checked last. This is also why plugins conventionally prefix global properties with `$`. ## The template sees the same thing Template expressions are compiled into a render function that reads names from the same instance proxy, so `{{ total }}` resolves exactly like `this.total`. When a template reads a name that none of the stores contain, Vue renders it as an empty string and, in development, warns `Property "totl" was accessed during render but is not defined on instance.` — usually a typo or a key missing from `data()`. One more exception is visible here: `data` keys starting with `_` or `$` are **not** proxied, and reading them in a template produces a warning telling you to go through `$data`. ## Inspecting the stores directly The proxy also exposes each store on its own, which helps when a name is ambiguous: - **`this.$data`** — only the keys returned from `data()`, as the reactive object; - **`this.$props`** — only the resolved props (read-only in development); - **`this.$options`** — the resolved component options, including anything merged from mixins, where you can see which `methods` and `computed` exist. Comparing `this.title`, `this.$data.title` and `this.$props.title` in a debugger shows at once which store the proxy is answering from. ## Reading an older component When you inherit a large options component, this order answers most "where does `this.x` come from?" questions: - search `data()`, then `props`, then `computed`, `methods` and `inject`; - if none declare it, check the component's mixins and the app's `globalProperties`; - if it is assigned somewhere but declared nowhere, it is a non-reactive property added to the context — a likely bug if the template shows it. The proxy is the reason Options API code reads fluently — everything is on `this` — and also the reason it is hard to trace: one flat namespace fed from several stores.

  • Why do plugins put global helpers on app.config.globalProperties with a $ prefix, such as $t or $http?
    Global properties are the last place the instance proxy looks, so any component key with the same name silently wins. A `$` prefix keeps them out of the namespace components use for their own data, props and methods, and follows Vue's own convention for public instance properties like `$emit` and `$refs`.
  • What does Vue 3 do in development when a template reads a name that is not declared anywhere on the instance?
    The read returns `undefined`, which an interpolation renders as an empty string, and Vue warns `Property "x" was accessed during render but is not defined on instance.` It does not throw. The usual causes are a typo, a key missing from `data()`, or a value that was assigned to `this` but never declared.

The proxy is a receptionist with a fixed list of directories: asked for a name, they check setup first, then data, then props, then methods and computed, then the building's own services, then the head-office directory. If the same name is listed in two directories, the one checked first is the one you are connected to.

saying these in an interview costs you the question

  • this is the options object passed to export default.
  • Props are read before data, so a prop always wins a name clash.
  • Writing this.someProp updates the prop and notifies the parent.
  • A template reading an undeclared name throws a ReferenceError.
  • globalProperties override a component's own methods of the same name.