Before we dig further into working with Vue.js, let’s pause to highlight a couple of key points that are helpful to understand when working with Vue.
When a web page is parsed by a browser, a tree-like data structure called a Document Object Model (DOM) is created.
For example, consider the following HTML:
<!doctype html>
<html lang='en'>
<head></head>
<body>
<ul class='list'>
<li class='list-item'>ABC</li>
</ul>
</body>
</html>
Visualized as a DOM tree, it would look like this:
Using the DOM API we can dynamically manipulate the DOM via JavaScript.
For example, to add a new list item, we could do something like this:
let newListItem = '<li class="list-item">XYZ</li>';
document.getElementsByClassName("list")[0].innerHTML = newListItem;
Complex web apps can contain thousands of nodes; finding and updating these nodes has a performance cost.
JS frameworks like Vue.js addresses this by mapping the DOM to a JavaScript object (aka, the Virtual DOM).
For example, the above DOM mapped to a JavaScript object would look something like this:
const virtualDom = {
tagName: "html",
children: [
{ tagName: "head" },
{
tagName: "body",
children: [
{
tagName: "ul",
attributes: { "class": "list" },
children: [
{
tagName: "li",
attributes: { "class": "list__item" },
textContent: "List item"
}
]
}
]
}
]
}
Instead of updating the DOM directly, we update the Virtual DOM (using the functionality that our framework provides).
These updates are optimized and batched together, and eventually propagated to the real DOM in a way that is more efficient than if we updated the DOM directly.
As you saw in the example built in the introduction, when data properties of our Vue instance are changed, those changes are propagated wherever that data is being used.
This may include places where the data is text-interpolated to the page, bound to an input via v-model, or even evaluated as part of a JavaScript expression.
The magic of this reactivity system is all handled by Vue behind the scenes. If you want the full technical explanation of how this happens, you can read Reactivity in Depth. For a high-level explanation, though, we can boil it down to these two ingredients:
Dependency mapping When a Vue instance is set up, a map of dependencies for all of its data properties is created. For example, when we render a property in the template, that’s a dependency, and Vue keeps track of that so that when the property updates, so does the template.
Change notification Via a system of JavaScript proxies and getters/setters, whenever a property is changed, that change is propagated throughout the dependency map.
Having a high-level understanding of this reactivity system is important, because it’s the driving force of a shift in programming style from imperative to declarative programming.
Imperative
Declarative
Imperative JS example:
<label>What’s your name? <input type='text' id='nameInput'></label>
<p>Player: <span id='nameOutput'></span></p>
let nameInput = document.querySelector('#nameInput');
let nameOutput = document.querySelector('#nameOutput');
nameInput.addEventListener('input', function (evt) {
nameOutput.innerHTML = this.value;
});
This programming is very explicit in its instructions.
Declarative JS example:
<div id='app'>
<label>What’s your name? <input type='text' v-model='playerName'></label>
<p>Player: {{ playerName }}</p>
</div>
const Game = {
data() {
return {
playerName: '',
}
}
}
With declarative programming, the underlying mechanics of how the program works is obscured, so we can focus on the goals/outcomes of the program.
playerName
.In summary: Declarative programming is really just an abstraction. The details of how tasks are accomplished are abstracted by the language/framework we’re using, providing a more convenient pathway for us to accomplish our goals.
Vue abstracts the process of working with the DOM. Because of this, you should not see DOM-specific code within your Vue applications.