In a Vue 3 Options API component, why do arrow functions break methods and lifecycle hooks, and what does method auto-binding give you?
answer
- lexical this
- bind cannot rebind an arrow
- each method bound to the instance
- data and computed get vm as argument
basics
~20 sVue 3 binds every method and hook to the component instance, but an arrow function's this is fixed where it was written, so this is not the instance. Auto-binding means a method passed as a callback keeps its this.
solid answer
~50 sAn arrow function has no `this` of its own: it uses the `this` of the scope where it was written, and `bind()` cannot change that. In an SFC's `<script>`, module scope `this` is `undefined`, so `methods: { save: () => this.post() }` throws when called. Vue 3 binds every entry of `methods`, every lifecycle hook and every `watch` handler to the instance proxy with `bind`, which is exactly what an arrow defeats — hence the docs' advice to avoid arrow functions there. The upside of that binding is that a method keeps its `this` wherever it goes: `setTimeout(this.tick, 1000)` or `addEventListener('resize', this.onResize)` work without an extra wrapper. Arrow functions *inside* a method are fine and useful, because they inherit the method's `this`. `data` and computed getters also receive the instance as their first argument, so an arrow form can use that.
code
js · 28 linesexport default {
data() {
return { width: 0, ticks: 0, items: [] }
},
mounted() {
// bound methods keep this: no wrapper needed, same reference for removal
window.addEventListener('resize', this.onResize)
this.timer = setInterval(this.tick, 1000) // timer id kept off data on purpose: not reactive
},
beforeUnmount() {
window.removeEventListener('resize', this.onResize)
clearInterval(this.timer)
},
methods: {
onResize() {
this.width = window.innerWidth
},
tick() {
this.ticks++
},
load() {
// an arrow INSIDE a method inherits the method's this
return fetch('/api/items').then(res => res.json()).then(items => {
this.items = items
})
}
}
}go deeper
Recall that methods, hooks and watch handlers in the Options API must be regular functions because arrow functions ignore the this Vue binds.
Explain lexical this versus bind, which options Vue binds, and why data and computed can use the instance argument instead of this.
Diagnose a legacy component failing on this at call time, and use stable bound methods for listener add and remove pairs.
Treat top-level arrow functions in options objects as a lint-enforced rule in a legacy codebase, so the failure is caught in review instead of at the first click.
## Two kinds of this JavaScript functions get `this` in two different ways: - A **regular function** (`function () {}` or the method shorthand `save() {}`) receives `this` **when it is called**, and `fn.bind(obj)` can fix it permanently. - An **arrow function** (`() => {}`) has **no `this` of its own**. It uses the `this` of the surrounding scope at the place it was written — **lexical** `this` — and neither `bind`, `call` nor `apply` can change it. In a Vue single-file component, the `<script>` block is an ES module. At module top level, `this` is `undefined`, and inside an object literal it is still the module's `this`, not the object. ## What Vue 3 binds, and how When an Options API instance is created, Vue binds the component's functions to the **instance proxy** — the object you know as `this`: | Option | How Vue passes the instance | Arrow function works? | |---|---|---| | `methods` | Each function is `bind`-ed to the instance once | No — `this` stays lexical | | Lifecycle hooks (`created`, `mounted`, …) | Each hook is bound to the instance | No | | `watch` handlers | Called with the instance as `this` | No | | `data` | Called with `this` **and** the instance as first argument | Yes, via the argument | | `computed` getters | Bound, **and** given the instance as first argument | Yes, via the argument | The Vue docs state it for each: avoid arrow functions when declaring methods, watch callbacks and lifecycle hooks, because they will not have access to the instance via `this`. For `data` and `computed`, the docs show the argument form: `data: (vm) => ({ a: vm.myProp })`. ## What the failure looks like ```js export default { data: () => ({ saving: false }), // fine: uses no this methods: { save: () => { this.saving = true } // TypeError: this is undefined }, mounted: () => { this.save() } // same TypeError } ``` The error appears only when the function is called, not when the component compiles, so the component may render and fail on the first click. ## Why auto-binding is useful Because each method is bound once at creation, **a method carries its instance wherever it is passed**: 1. `setTimeout(this.tick, 1000)` — `tick` still sees the component. 2. `window.addEventListener('resize', this.onResize)` — and the same reference can be passed to `removeEventListener` later, because it is one stable bound function per instance. 3. Passing `this.validate` as a callback into a helper or a child component's prop. Without binding, each of these would need a wrapper such as `() => this.tick()`. ## Where arrow functions are right - **Inside** a method or hook: `fetch(url).then(res => { this.items = res })` — the arrow inherits the method's `this`, which is the instance. - Inline template handlers: `@click="() => select(item)"` — template expressions are compiled against the instance, so no `this` is involved. - The `data` and computed argument form, where the instance arrives as a parameter. ## Handlers named by string The `watch` option also accepts a **method name as a string**: `watch: { query: 'search' }` runs `this.search` when `query` changes. Vue looks the name up among the instance's bound methods, so the handler gets the correct `this` without any function literal in the `watch` block. If the name does not match a method, development builds warn `Invalid watch handler specified by key "search"`. In legacy code this form is a useful way to sidestep the arrow problem entirely: the logic lives in a normal method, and the watcher just points at it. ## Reading an older component When you inherit an options component that fails with `Cannot read properties of undefined` on `this`, look for arrow functions at the top level of `methods`, hooks and `watch`. Convert them to the method shorthand; leave arrows nested inside method bodies as they are. A related dev warning — `Method "save" has type "undefined" in the component definition. Did you reference the function correctly?` — means a `methods` entry is not a function at all, typically a misspelled import.
- Why can this.onResize be passed to both addEventListener and removeEventListener in a Vue 3 options component?Vue binds each method once, when the instance is created, and stores that bound function on the instance. Every read of `this.onResize` returns the same function object, so `removeEventListener` receives the exact reference that was added. Creating a fresh wrapper such as `() => this.onResize()` in each call would break removal.
- A Vue 3 computed option is written as fullName: () => this.first + ' ' + this.last. How do you fix it without changing it to a regular function?Use the argument Vue passes: computed getters receive the instance as their first parameter, so `fullName: (vm) => vm.first + ' ' + vm.last` works. The same applies to `data`, as in `data: (vm) => ({ query: vm.initialQuery })`. Methods, hooks and watch handlers get no such argument, so they must use regular functions.
saying these in an interview costs you the question
- Arrow functions are fine for methods because Vue rebinds this anyway.
- An arrow method fails at compile time, so it can never reach production.
- Arrow functions must also be avoided inside method bodies.
- Passing this.tick to setTimeout loses this, so a wrapper is always required.
- Vue binds methods lazily, the first time a template calls them.