Function call or definition inside object scope? - javascript

While going through VueJS sources I've come across this piece:
const injectedComp = {
inject: ['foo', 'bar'],
render () {},
created () {
injected = [this.foo, this.bar]
}
}
My question is - what are render () and created () - function calls? definitions? something else? What exactly is happening in there?

Both render and created are properties of the injectedComp object with function values. The syntax is for method shorthand is fairly new, introduced by the ES2015 specification. You can think of your example like this:
const injectedComp = {
inject: ['foo', 'bar'],
render: function render() {},
created: function created() {
injected = [this.foo, this.bar]
}
}

render () and created () are method definitions.
They are almost a shorthand for named functions:
const injectedComp = {
inject: ['foo', 'bar'],
render : function render() {},
created: function created () {
injected = [this.foo, this.bar]
}
}
Method definitions are nearly identical to functions, except that they are not constructable.
So you could not write new injectedComp.render() for your example.

Related

Accessing "this" in a javascript forEach function

I have a particular scenario where I want to access data from "this" while looking through an array that is also defined on my Vue component. Example:
data () {
return {
question: [],
inputList: [],
form: {}
}
},
methods: {
onSubmit: function () {
let predictionParams = {}
this.inputList.forEach(function (element) {
predictionParams[element.detail] = this.form[element.detail]
})
}
Error:
this.form is not defined in the context of the forEach loop
Question:
What is the idiomatic JS way of handling a case like this? I run into this quite often and I always feel like I come up with sketchy solutions, or at the very least something easier could be done. Any help on this would be great.
Many built-ins, including forEach include an optional 'this' binder:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach
use that to your advantage:
this.inputList.forEach(function (element) {
predictionParams[element.detail] = this.form[element.detail]
},this)
supported since ie9
arrow function syntax avoids rebinding this
this.inputList.forEach(element => {
predictionParams[element.detail] = this.form[element.detail]
})
You can use Arrow function https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions
It will binding this into the function
data () {
return {
question: [],
inputList: [],
form: {}
}
},
methods: {
onSubmit: () => {
let predictionParams = {}
this.inputList.forEach((element) => {
predictionParams[element.detail] = this.form[element.detail]
})
}

Vuejs ajax call not mapping changes to underlying html table

I am making simple ajax call with vuejs and axios:
var app1 = new Vue({
el: '#app1',
data: {
test: []
},
methods: {
setAJAX: function () {
axios.get('/Departments/GetDepartments/').then(response => this.test = response.data.listBACAET);
}
}
});
Why is this working:
setAJAX: function () {
axios.get('/Departments/GetDepartments/').then(response => this.test = response.data.listBACAET);
}
But this is not working, changes are not mapped into table (this.test is undefined):
setAJAX: function () {
axios.get('/Departments/GetDepartments/').then(function(response){this.test = response.data.listBACAET});
}
This is because of the way arrow functions work: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions#No_separate_this
When using an arrow function, this is implicitly bound to this of the enclosing scope, which is, in your case, the vue instance on which the method is called.
So you're setting the data of your view model, which works.
When using a std. function, there is no this in the scope, hence the error. To use a std. function, you need to define a closure for your view model like this:
setAJAX: function () {
let vm = this
axios.get('...').then( function(response) {
vm.test = response.data.listBACAET
});
}

Unit Testing ES6 Class with External Dependency

I'm trying to set a unit testing boilerplate for my company. Our front end projects are built with ES6 classes and have a dependency to our core product. The front end code gets wrapped through a build process in a whole other block of code that is basically a closure and captures the dependency. So we don't have to manually import it in order to use it.
Let's say the dependency is called productScope and it's an object that has some DOM models, internal APIs and parameters among many other things necessary for each project. At the moment, Mocha throws ReferenceError: productScope is not defined. How can I mock this object? Or should I just use the actual object?
Example:
class someClass {
constructor() {
const id = productScope.items[0].id
const item = productScope.domModel.querySelector('.some-div')
item.classList.add(`added-${id}`)
}
}
This get wrapped in core code like below:
(function(productScope) {
// front end code goes here
}(productScope)
Testing file:
import someClass from '../../js/someClass'
describe('someClass', function() {
const someClass = new someClass()
it('should be a class', function() {
console.log(someClass)
});
});
You can try something like this
describe('#someClass', () => {
let someClass;
beforeEach(() => {
global.productScope = {
// mocking productScope object
};
});
it('should be a class', () => {
someClass = new SomeClass;
console.log(someClass);
});
afterEach(() => {
delete global.productScope;
});
});
or alternatively if you want more specific mock logic for each test case
describe('#someClass', () => {
let someClass;
it('should be a class', () => {
global.productScope = {
// mocking productScope object
};
// Test logic start
someClass = new SomeClass;
console.log(someClass);
// Test logic end
delete global.productScope;
});
});
Looks like productScope is a global variable.
Something like this should work for you.
import someClass from '../../js/someClass';
describe('someClass', function() {
let someClass;
beforeEach(() => {
global.productScope = {
// you mock definition
someClass = new someClass();
};
});
it('should be a class', function() {
console.log(someClass)
});
});
I'm with other answers as well, as managing global variables seems to be the simplest and most straightforward solution.
However, you can use toString to get class's string representation, and eval it to bind to closure's scope:
class someClass {
constructor() {
this.id = scopedId
}
}
// pass class as an argument
function scopeFactory(classDef) {
// define scoped data
let scopedId = 2;
// eval is used to bind class to the local closure
// so `scopedId` will be in charge
return eval("(" + classDef + ")");
}
const scopedSomeClass = scopeFactory(someClass);
console.log(new scopedSomeClass)
Note that eval(someCLass.toString()) doesn't work without parentheses.
You can add it as a helper function, into your project.

expressjs server.js this is undefined, how can I refer to server

In an ExpressJS setup, I have server.js where I do the following:
import { call_method } from '../hereIam.mjs';
const process_db = async () => {
console.log(this); // undefined
call_method(this);
};
console.log(this) // undefined
process_db();
And then, from hereIam.mjs I want to call a parent method, but this is undefined
export const call_method = parent_this => console.log(parent_this); // undefined
I tried to include classes in server.js, in an attempt to force having a this
class AppServer {
constructor() {
console.log(this)
}
const process_db = async () => call_method(this);
}
But it seems that the arrow functions inside classes doesn't compile in (experimental) NodeJS (this should be another question)
EDITED
How I can do this is by avoiding the arrow notation to be able to use classes inside Express, and then instantiate a class that provides a this.
class AppServer {
async process_db() {call_method(this)};
}
let server = new AppServer();
server.process_db();
The question would be, the only way of getting a this reference is by using objects/classes?
You could use the the bind method and pass through any object to be used as the this context.
However, arrow functions receive the context from that which they are called from, function() {} function syntax use the context that was bound to them either implicitly by the context they were defined in or explicitly using this bind method.
So, an alternative to using classes would be to bind a simple object to the method, something like:
const call_method = require('../hereIam.mjs');
const process_db = async function() {
console.log(this);
call_method(this);
};
console.log(this);
const context = {
name: 'bound context',
parent_method: async function() {
console.log('Good evening');
}
}
process_db.bind(context)();
Presuming hereIam.mjs contains:
module.exports = parent_this => console.log(parent_this);
then the script will output:
{}
{ name: 'bound context',
parent_method: [AsyncFunction: parent_method] }
{ name: 'bound context',
parent_method: [AsyncFunction: parent_method] }

Lodash's _.debounce() not working in Vue.js

I am trying to run a method called query() when a component property called q in Vue.js is modified.
This fails because this.query() is undefined. This is referring to my component's instance but somehow does not contain the methods.
Here's the relevant code part where I'm trying to watch the component property q and run the query() function:
methods: {
async query() {
const url = `https://example.com`;
const results = await axios({
url,
method: 'GET',
});
this.results = results.items;
},
debouncedQuery: _.debounce(() => { this.query(); }, 300),
},
watch: {
q() {
this.debouncedQuery();
},
},
Error:
TypeError: _this2.query is not a function
If I write the debounce() call as below, the TypeError: expected a function error appears even earlier, at the page load.
debouncedQuery: _.debounce(this.query, 300),
The issue comes from the lexical scope of the arrow function you define within _.debounce. this is bound to the object you are defining it in, not the instantiated Vue instance.
If you switch out your arrow function for a regular function the scope is bound correctly:
methods: {
// ...
debouncedQuery: _.debounce(function () { this.query(); }, 300)
}
We can do it by plain JS (ES6) with few lines of code:
function update() {
if(typeof window.LIT !== 'undefined') {
clearTimeout(window.LIT);
}
window.LIT = setTimeout(() => {
// do something...
}, 1000);
}
As answered in another post This is undefined in Vue, using debounce method the best way to add debouncing IMO is to create the method normally in methods as eg:
setHover() {
if (this.hoverStatus === 'entered') {
this.hoverStatus = 'active'
}
},
But then replace it in your created block eg:
created() {
this.setHover = debounce(this.setHover, 250)
},

Categories

Resources