const vs let vs var in Vue: Scope, Hoisting, and Vue 3 Best Practices

This article focuses on variable declaration rules in Vue development, explaining the fundamental differences between const, let, and var in terms of scope, hoisting, and mutability. It addresses common pain points such as variable pollution, poor maintainability, and bug-prone code. The conclusion is simple: use const by default in Vue projects, use let only when reassignment is required, and avoid var entirely. Keywords: Vue, JavaScript, const.

Technical Specification Snapshot

Parameter Description
Language JavaScript / Vue 3
License Originally marked as CC 4.0 BY-SA
GitHub Stars Not provided
Core Dependencies Vue, ref, reactive, ES6

Variable declarations in Vue projects should follow modern JavaScript conventions

In both Vue 2 and Vue 3, var, let, and const can all declare variables, but they are not equivalent in engineering practice. Modern frontend code emphasizes clear scope boundaries, controlled state, and consistency across teams, so these three keywords must be used according to their semantics.

The most important takeaway is one sentence: use const by default, use let only when you need to reassign a value, and stop using var. This is not a stylistic preference. It is a practical rule for reducing variable pollution, improving readability, and lowering the risk of runtime errors.

The core differences between the three declaration forms can be summarized across four dimensions

Feature var let const
Scope Function / global scope Block scope Block scope
Reassignment Supported Supported Rebinding not allowed
Redeclaration Supported Not supported Not supported
Hoisting Yes, initialized as undefined Temporal Dead Zone Temporal Dead Zone

This table directly shapes day-to-day coding strategy. Because var has overly broad function scope, it can easily leak variables from blocks such as if and for; let and const provide block scope, which makes them better suited to component-based development.

function testVar() {
  var a = 10

  if (true) {
    var a = 20 // Overrides the outer variable with the same name
  }

  console.log(a) // Outputs 20, which shows variable pollution
}

This code demonstrates the typical risk of var: modifying a variable inside a block can affect the outer variable with the same name.

var amplifies scope pollution in Vue scenarios

Vue components often contain reactive state, event handlers, asynchronous logic, and lifecycle code. If you continue using var, variables can be accidentally overwritten within function scope, which is especially dangerous in loops, timers, and conditional branches.

Another problem is hoisting. A var declaration is hoisted even though its assignment is not. That behavior creates misleading execution expectations during reading and debugging, which increases cognitive overhead.

console.log(msg) // Outputs undefined instead of throwing an error immediately
var msg = 'hello'

This code shows that var hoists the declaration, making the variable accessible before initialization even though its value is not yet usable.

let is suitable for local variables that need reassignment

The value of let is simple: mutable without leaking scope. It is commonly used for loop indices, counters, temporary state, timer handles, and similar scenarios. Because it has block scope, it keeps variables within the smallest possible visible range.

In Vue components, any variable that needs to be rebound later should generally use let. For example: timer handles, pagination offsets, or procedural temporary flags.

let count = 0
count = 1 // Reassignment is allowed

for (let i = 0; i < 3; i++) {
  console.log(i) // Accessible inside the loop
}

// console.log(i) // Error: i is not in the current scope

This example shows that let supports change while still limiting variables to block scope.

const is the most recommended default choice in Vue components

const does not mean a value is absolutely immutable. It means the variable binding cannot be reassigned. This distinction matters in Vue, because many reactive objects need their internal properties updated without replacing the binding itself.

For that reason, ref, reactive, functions, arrays, objects, and module imports should usually use const. This clearly communicates to the team that the identifier itself will not be reassigned.

const PI = 3.14
// PI = 3.1415 // Error: primitive values cannot be reassigned

const user = { name: 'Xiaoming' }
user.name = 'Xiaohong' // Internal object properties can still be modified

// user = {} // Error: the entire object cannot be rebound

This code shows that const restricts the binding, not modifications to the contents of an object.

Vue 3 Composition API naturally fits a const-first approach

In script setup, most declarations are initialized only once. Reactive references are updated through .value or through changes to object properties, so you usually do not need to assign a new value to the variable name itself. That makes const the ideal choice.

Here is a version that better reflects Vue 3 best practices:

<script setup>
import { ref, reactive } from 'vue' // Import modules with const semantics

const count = ref(0) // The reactive reference itself is not rebound
const user = reactive({ name: 'Zhang San' }) // Internal properties of the reactive object can change
const add = () => {
  count.value++ // This updates the inner value, not the count binding itself
}

let timer = null // The timer handle needs reassignment later
timer = setTimeout(() => {
  user.name = 'Li Si' // Modify an internal property of the reactive object
}, 1000)
</script>

This example shows the standard Vue 3 division of responsibility: use const for reactive state and functions, and use let for handles that need reassignment.

The most stable team rule is const by default and let only as an exception

If a team does not adopt a shared convention, variable declaration styles can quickly become inconsistent, which eventually reduces code readability. The most practical approach is to establish a default policy instead of deciding case by case.

A recommended rule set you can apply immediately

  1. Use const by default.
  2. Use let only when the variable name itself needs reassignment.
  3. Do not use var in modern Vue projects.
  4. Prefer const for imports, functions, configuration objects, arrays, ref, and reactive.
  5. Use let for loop indices, timer handles, and temporary procedural variables.
// Recommended
const config = { api: '/user' }
const loadUser = async () => {
  // Core logic: send the request and handle the result
}

let currentPage = 1
currentPage = 2 // Pagination state requires reassignment

This snippet reflects the engineering habit of using const by default and let only when necessary.

FAQ

1. Why do ref and reactive usually use const in Vue?

Because reactive updates rely on .value changes or mutations to internal object properties, not on rebinding the variable name itself. const clearly communicates that the reference is stable and improves maintainability.

2. Why can an object declared with const still have mutable properties?

Because const only prevents the variable from pointing to a different value. It does not make the object contents read-only. Objects and arrays are reference types, so their internal members can still change.

3. Should var really never be used in modern Vue projects?

Yes. Vue 3, Vite, Webpack, and modern browser environments fully support let and const. Continuing to use var only introduces scope pollution and hoisting pitfalls without any practical benefit.

[AI Readability Summary]

This article gives Vue developers a clear explanation of the core differences between const, let, and var: scope, hoisting, redeclaration, and mutability. It also provides standard Vue 3 usage patterns and practical engineering rules. The conclusion is unambiguous: prefer const, use let when reassignment is necessary, and avoid var.

AI Visual Insight: In Vue development, choose const for stable bindings such as ref, reactive, imports, and functions; choose let only for values that must be reassigned later, such as loop counters or timer handles; and avoid var because its function scope and hoisting behavior make bugs more likely.