Why doesn't an unassigned Vue instance get garbage collected? - javascript

Here's the standard way to use VueJS on the HTML page (without bundles). No assignment.
<script>
new Vue({
el: '#root',
data: {
title: 'Hello'
}
});
</script>
Why Garbage Collector doesn't collect this Vue object?

When you instantiate a Vue object, it actually mounts itself to the DOM element, here #root element, as briefly hinted in this documentation page The Vue Instance > Instance Lifecycle Hooks.
By using Developer Tools in your browser, like in Chrome, you can open the console tab and prompt, type console.log(window.$vm0); and hit enter. And you get access to your Vue runtime instance even it was not assigned to a variable:
> Vue {_uid: 2, _isVue: true, $options: {…}, _renderProxy: Proxy, _self: Vue, …}
I've opened another question on how to properly access the Vue instance if it wasn't assigned to a variable during instantiation.
The main point, as an answer to this current question, is that there is actually variable assignment / DOM mounting happening behind the scenes by Vue itself, so that is why garbage collection is not triggering.
PS. There is a detailed documentation article Avoiding Memory Leaks in relation to handling Garbage Collection in a Vue application.

A Vue application consists of a Vue instance created with new Vue and mounted in DOM element with id '#root'. Vue is running all this magic behind the scene that's why garbage collector will not collect Vue object.
In addition to data properties, Vue instances expose a number of instance properties and methods. These are prefixed with $ to differentiate them from user-defined properties. For example:
var data = { title: 'Hello' }
var vm = new Vue({
el: '#root',
data: data
});
// If you check below code
vm.$data === data // => true
vm.$el === document.getElementById('root') // => true

Related

How to call Vue instance in chrome console, if he created in a separated file

How to call Vue instance in chrome console, if it created in a separated file new Vue({ ... }). Like console.log(vm.user.id)
new Vue({
el: '#app',
data: {
message: {
id: 1,
title: 'TEST VUE'
}
}
});
If you have the Vue DevTools installed it will create aliases for your Vue instances when you click on them:
Note the light text to the right of the component name. It may be hard to see on some screens.
In the picture above, $vm1 and $vm2 are accessible in the console and will refer to the corresponding Vue instances.
As you click around in the DevTools these aliases will change. $vm0 will refer to the last component you clicked on.
The best way to do this is by using the Vue Chrome extension as described by #skritle. You also get nice UI with bells and whistles to look at the data, computed properties etc if that's what you need.
However, I've had to do this in environments which didn't have the extension. In those scenarios, you can just add the instance to the global object window (browser) or global (nodejs).
const app = new Vue({...});
window.$appRef = app; // Remove this line for release
Then in load the app in the browser and you can access it in the console :
console.log($appRef)
This should only be used as an emergency escape hatch because it pollutes the global object (potentially causing name collisions and memory leaks) and should be cleaned up after use. You can also wrap it in an if condition to ensure it is used only during development
if (
process.env.NODE_ENV !== 'production' &&
process.env.NODE_ENV !== 'test' &&
typeof console !== 'undefined'
){
window.$appRef = app;
}
Question already has it's accepted answer, but it's worth mentioning the recommended pattern to capture different instance for vue - https://v2.vuejs.org/v2/guide/instance.html#Creating-a-Vue-Instance
Just make your own custom variable for different vue instances
// Use var to mark this variable a global, make sure it's wrapped directly to global execution context, so that you can get it at window scope
var vueInstance1 = new Vue({
el: '#app1',
data: { title: 'Vue Instance 1' }
})
var vueInstance2 = new Vue({
el: '#app2',
data: { title: 'Vue Instance 2' }
})
console.log('Instance 1- ', vueInstance1.title ) // Easily access to instance 1
console.log('Instance 2- ', vueInstance2.title ) // Easily access to instance 2
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app1">{{ title }}</div>
<div id="app2">{{ title }}</div>

Declaring data in Vue with components

According to the docs, this is how you declare data in Vue:
data: {
name: 'Vue.js'
}
However, when I do that it doesn't work and an error shows in the console:
The "data" option should be a function that returns a per-instance value in component definitions.
I change it to the following and then it works fine:
data() {
return {
name: 'Vue.js',
}
}
Why do the Vue docs show the top bit of code when it doesn't work? Is there something wrong on my end?
Edit: This only happens when using components.
In a root Vue instance (which is constructed via new Vue({ . . . }), you can simply use data: { . . . } without any problems.
When you are planing to reuse Vue components using Vue.component(...) or using "template" tag, Use data attribute as a function.
Please review the corresponding section of the Vue.js documentation for more information regarding this problem
https://v2.vuejs.org/v2/guide/components.html#data-Must-Be-a-Function
You should declare data in Vue.js by doing
var app = new Vue({
el: '#app', //This is the container in which Vue will be in. The #app means the id of the container is app
data: {
}
});
It turns out you need to declare data in components different than when you set it on a Vue object.
Instead, a component’s data option must be a function, so that each instance can maintain an independent copy of the returned data object:
More: Vue docs

Where to use the main variable of a new vue instance

I'm obviously missing the point somewhere here, but where does one use the main variable of a new vue instance?
I'm new to vue.js (obviously) and whilst reading various documentation I can't help notice that each new vue instance starts with something like var app = new Vue({ but then in the examples I've read this app variable doesn't get referenced again in the js or html. The code works fine without referencing it anywhere.
Could someone please kindly advise on where or why I would use the app variable?
Many thanks
It's completely not required to capture the result of new Vue() if you don't need or want to.
The reason it's done is primarily for testing (ala from the console) or for interaction with external libraries. Because all of the methods and data of the Vue are available on the variable it's possible to call those methods and use that data from outside Vue.
For example, let's say I have some logic on my page completely outside Vue that has some data I want to use inside my Vue.
const externalData = {message:"I'm some interesting data."}
const myVueApp = new Vue({
el: "#app",
data:{
message: null
}
})
myVueApp.message = externalData.message
Here the code is setting the message property of Vue from outside Vue.
This is useful primarily when you have existing code, and you are integrating Vue into that existing environment.
Another scenario is just plain testing. Open your console and run the snippet below. Change the context to the snippet's javascript:
And then type
app.message = "Hey, this is nifty!"
And the new message will be reflected in the Vue.
console.clear()
const app = new Vue({
el: "#testing",
data:{
message: "Change me from the console!"
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.3.4/vue.min.js"></script>
<div id="testing">
{{message}}
</div>

Angular UI router - child of abstract state not inheriting data in $stateChangeStart

According to the top answer to this question...
Acess parameters of parent state from child state in stateChangeStart
When you inject toState into $stateChageStart it's supposed to inherit/merge data from parent states.
Unfortunately this isn't working in my app where the parent is an abstract state. Here are my routes
// Settings
.state('wfSettings', {
url: '/settings',
abstract: true,
template: '<ui-view/>',
data:{
customData1: "Hello",
customData2: "World!"
}
})
.state('wfSettings.siteSettings', {
url: "/site-settings",
templateUrl: "/templates/siteSettings.tpl.html",
controller: "SiteSettingsCtrl as siteSettings",
data:{
customData2: "UI-Router!"
}
})
When I console.log(toState) in my $atateChangeStart function and visit the child page it outputs this.
So you can see that the data from the parent isn't there.
The only strange things is that in the screenshot above you can see that Chrome dev tools has put "V" next to data, instead of the usual "Object" which appears there if I don't put any data on the parent state. Very strange behavior.
Edit:
I've made some progress. There seems to be a difference between version 0.2.11 and 0.2.18 of ui-router. This screenshot shows two lines of output. The first one is the data from the child route in $stateChageStart as calculated by version 0.2.11.
When I switch the library to 0.2.18 it outputs the second line instead!
Edit 2:
I've tracked it down to this change (which is not classed as a breaking change in the changelog)
https://github.com/angular-ui/ui-router/commit/c4fec8c7998113902af4152d716c42dada6eb465
So, I know what has cause it, but I still don't know how to fix this. Presumably I need to somehow go up the prototype chain and merge these values manually? That's a bit beyond my knowledge of javascript prototypical inheritance though.
This is due to a breaking change in 0.2.16. The properties are still accessible by child states, but needs to be accessed directly.
In 0.2.15 and prior, properties from the parent state’s data object
were copied to the child state’s data object when the state was
registered. If the parent state’s data object changed at runtime, the
children didn’t relect the change.
In 0.2.16+, we switched to prototypal inheritance. Changes to the
parent data object are reflected in the child.
Source: https://ui-router.github.io/guide/ng1/migrate-to-1_0#prototypal-inherited-data
What is not mentioned however, is that even though the previous example still works (customData1 is available in childe states), the inherited properties won't appear in if you enumerate over $state.current.data.property - for instance the "json" angular filter will not display the inherited properties. Check out this plunkr to see these two effects: https://plnkr.co/edit/8ufkoluh1z6qmZT7VySd?p=preview
Source: Experiments resulting in the above plunkr
If you want the old behavior back, you can achieve this as well:
The ship has sailed on the decision between copying vs prototypal inheritance. Switching to deep copy would be a breaking change for many apps which expect a child state's data property to clobber a parent state's data property.
If you want non-standard (deep copy) behavior you can certainly
implement it for your app:
$stateProvider.decorator('data', function(state, parentDecoratorFn){
var parentData = state.parent && state.parent.data || {};
var selfData = state.self.data || {};
return state.self.data = angular.merge({}, parentData, selfData);
});
example: http://plnkr.co/edit/Wm9p13QRkxCjRKOwVvbQ?p=preview
Source: https://github.com/angular-ui/ui-router/issues/3045#issuecomment-249991317

Javascript object data binding with Vue

I have a JavaScript object that I am attempting to bind to a Vue view.
I am running a function to update the JavaScript object using AJAX and I was expecting Vue to bind to the JS object and update the view when the object is updated though that isn't happening.
Research suggests making the AJAX call within the Vue declaration but due other constraits I would rather not do that.
I've created a fiddle to illustrate what the issue is since it's reproducable without the AJAX portion as well as pasted the code below.
https://jsfiddle.net/g6u2tph7/5/
Thanks in advance for your time and wisdom.
Thanks,
vmitchell85
JavaScript
window.changeTheData = function (){
externalJSSystems = [{description: 'Baz'}, {description: 'Car'}];
document.getElementById("log").innerHTML = 'function has ran...';
// This doesn't update the Vue data
}
var externalJSSystems = [{description: 'Foo'}, {description: 'Bar'}];
Vue.component('systable', {
template: '#sysTable-template',
data() {
return {
systems: externalJSSystems
};
}
});
new Vue({
el: 'body'
});
HTML
<systable :systems="systems"></systable>
<button type="button" onclick="changeTheData()">Change</button>
<br><br>
<div id="log"></div>
<template id="sysTable-template">
<table>
<thead>
<tr>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr v-for="sys in systems">
<td>{{ sys.description }}</td>
</tr>
</tbody>
</table>
</template>
Try this out :
externalJSSystems.push({description: 'Baz'}, {description: 'Car'});
It will append the new objects to externalJSSystems and the view will be updated. Why doesn't your example work ? Because you are assigning a new Array reference to externalJSSystems but Vue is still watching the old one.
To achieve what you want, don't assign a new Array instance but clear it. For example :
window.changeTheData = function (){
externalJSSystems.length = 0
externalJSSystems.push({description: 'Baz'}, {description: 'Car'});
}
When that instance of the systable Component is instantiated, Vue adds an "Observer" class to the initial externalJSSystems Array — extending the Array's prototype, adding getter/setters for each of the properties, and maintaining the two-way binding between the Component's data and the original Array. The changeTheData() method is overwriting that Vue-modified externalJSSystems Array with a completely new Array (that lacks the Observer), thus breaking the two-way binding.
In this way, externalJSSystems.push( … ) works because the default Array methods ('push', 'pop', 'shift', 'unshift', 'splice', 'sort', and 'reverse') have been mutated such that they are handled by the Observer.
I think the key to the behavior you're looking for lies in the Vue Component "props" — http://vuejs.org/guide/components.html#Props. In fact, it looks like your component markup — <systable :systems="systems"></systable> — is already set up to pass dynamic data to the Component instance. Right now, that :systems="systems" isn't doing anything. By defining systems in the Parent Vue scope, and defining systems as a prop(s) within the Component registration, you can pass dynamic data to Components within that Parent's scope.
Component
Vue.component('systable', {
template: '#sysTable-template',
props: {
systems: Array
}
});
Vue Instance
var vm = new Vue({
el: 'body',
data: {
systems: externalJSSystems
}
});
You can see it in action in this fiddle: https://jsfiddle.net/itopizarro/ycr12dgw/
I cached the Vue instance — var vm = new Vue({ … }) — so the changeTheData method had access to its systems data. This gives your external changeTheData() method a reference to the Vue instance where you defined system — thus giving it access to modify (without replacing, or iteratively adding/removing items from…) the Array of data.
Rather than making systems a data property, you can make it a computed property. Like the other answer said, the reference is to the old object. But if you make systems a computed property, it will automatically watch any variable used in the calculation (like externalJSSystems) and re-calculate the computed property.
Vue.component('systable', {
template: '#sysTable-template',
computed: {
systems() {
return externalJSSystems;
}
}
});

Categories

Resources