Function declarations or expressions for class methods? - methods

A coworker of mine and I have had this discussion several times. There are two ways to define class methods. The first way is with a function declaration:
class Action {
public execute(): void {
this.doSomething();
}
...
}
Function declarations tend to be easier to read. Only one function object is used for each instance of Action, so they're also more memory-friendly.
The second is with a function expression:
class Action {
public execute: () => void = () => {
this.doSomething();
};
...
}
Function expressions need more typing (especially with type definitions), are harder to read, and generate a new function object for every instance of Action. And if you're generating lots and lots of objects, that's bad.
However, function expressions have one small benefit: they preserve the context of this (i.e. the Action instance) no matter who calls them:
var instance = new Action();
setTimeout(instance.execute);
A method declared as a function expression works as expected in this case. Function declarations fail miserably, but they can easily be fixed by doing this instead:
var instance = new Action();
setTimeout(() => instance.execute());
// or
setTimeout(instance.execute.bind(instance));
So, is one considered better practice over the other, or is this purely situational/preferential?

In my opinion, arrow functions should be used as class methods only when you know for sure that the function might be called with a different context for this (if it's passed as an event handler, for example) and you prefer to avoid using Function.prototype.bind.
There are several reasons for that, including, as you wrote, for code readability, but the main reason is for inheritance.
If you use an arrow function then you simply assign a function to the instance as a member, but the function won't be added to the prototype:
// ts
class A {
fn1() {}
fn2 = () => {}
}
// js
var A = (function () {
function A() {
this.fn2 = function () { };
}
A.prototype.fn1 = function () { };
return A;
}());
So what happens if you want to extend this class and override the fn2 method?
Because it's a property and not part of the prototype, you'll need to do something like:
class B extends A {
private oldFn2 = this.fn2;
fn2 = () => {
this.fn2();
}
}
Which just looks terrible when compared to:
class A {
fn1() {}
fn2() {}
}
class B extends A {
fn2() {
super.fn2();
}
}
There are a few reasons to prefer using the bind method over an anonymous function. I find it to be more implicit, because it's the exact same function but bound to a specific this. On the other hand, in the anonymous function, you can add more code other than calling the actual function.
Another thing is that the bind function lets you not only bind which object will be considered as this, but also to bind parameters:
function fn(one, two, three) {}
fn.bind(null, 1, 2)(3);
fn(1, 2, 3);
The two invocations of fn here are the same.
You can do that with an anonymous functions, but not always:
var a = ["zero", "one", "two", "three", "four", "five"];
function fn(value, index) {
console.log(value, index);
}
// works
a.forEach((item, index) => {
setTimeout(() => {
fn(item, index);
}, 45);
});
// works
for (let i = 0; i < a.length; i++) {
setTimeout(() => {
fn(a[i], i);
}, 45);
}
// doesn't work as i is undefined when the function is invoked
for (var i = 0; i < a.length; i++) {
setTimeout(() => {
fn(a[i], i);
}, 45);
}
// works because the value of i and the value of a[i] are bound
for (var i = 0; i < a.length; i++) {
setTimeout(fn.bind(null, a[i], i), 45);
}

Related

Cypress automation: multiple cy.get elements in one function

I have a function:
checkWebElemAndAssert(...elements) {
for (const element of elements) {
element.should('be.visible').click().should('be.checked');
}
}
and i use it within another function:
checkRegisterValues = () => {
let maleCheckBox = cy.get('input[value=Male]');
let femaleCheckBox = cy.get('input[value=FeMale]');
let cricketCheckBox = cy.get('#checkbox1');
let registerElemList = [maleCheckBox, femaleCheckBox, cricketCheckBox];
this.browserUtils.checkWebElemAndAssert(...registerElemList);
return this;
}
The problem is that when i use checkRegisterValues() it uses for each action the last element: cricketCheckBox. Any hints on what is wrong? i would expect that the action is made for each element and not the last one.
Have you tried passing in the array like this?
this.browserUtils.checkWebElemAndAssert(registerElemList);
You can also print out
checkWebElemAndAssert(...elements) {
console.log(elements);
for (const element of elements) {
element.should('be.visible').click().should('be.checked');
}
}
and see what you are passing in
ok so i read a bit more and made this:
checkWebElemAndAssert2(elements) {
cy.get(elements).each(($list) => {
cy.get($list).click({ multiple: true }).should('be.checked')
})
}
Basically in elements when i call checkWebElemAndAssert2 i will give the list identifier. Seems to work but not sure meets the standard.
checkRegisterValues = () => {
let myList = 'input[type=radio]';
this.browserUtils.checkWebElemAndAssert2(myList);
return this;
}

How to write a function to chain elements together in ProtractorJS/Jasmine

I am trying to build a function that can accept an array of tag name, by passing in the array of tag names, such as ['span', 'input', 'strong'] I want it to return a chain to search for the elements. For example, I want to find...
element(by.tagName('span'))
.element(by.tagName('input'))
.element(by.tagName('strong'));
By using my a function like...
public static getNestedElements = (arrayOfElementTags) => {
const temporaryElementArr = [];
for (let i = 0; i < arrayOfElementTags.length; i++ ) {
temporaryElementArr.push(element(by.tagName(arrayOfElementTags[i])));
}
for (let j = 0; j < temporaryElementArr.length; j++) {
if (j !== temporaryElementArr.length) {
temporaryElementArr[j] = temporaryElementArr[j] + '.';
}
}
return temporaryElementArr
};
The above function obviously sucks and doesn't work.
element(by.tagName('span')).element(by.tagName('input')).element(by.tagName('strong'));
// equivalent to element(by.css('span input strong'))
Therefor you can join all tags with space to generate a css selector. And use the css selector to find element. As following done.
public static getNestedElements = (arrayOfElementTags) => {
return element(by.css(arrayOfElementTags.join(' ')));
}
I would suggest to keep your logic simple instead of using a function.
element(by.tagName('span')).element(by.tagName('input')).element(by.tagName('strong'));
//It is a simple way to get the chained element provided by protractor api
documentation.
Creating a function is cumbersome and it won't give you desired results all the time.

Does Jasmine have an after-advice spy?

When spying on a method, we can either callThrough (use original implementation) or callFake (use a custom implementation).
What I want is a behaviour similar to callThrough but inspect/modify its return value before returning it to the caller.
So I can do something like this:
spyOn(foo, "fetch").and.afterCall(function(result) {
expect(result).toBeDefined();
result.bar = "baz";
return result;
});
Right now the simplest way is doing something like this:
var original = foo.fetch;
foo.fetch = function() {
var result = original.apply(this, arguments);
expect(result).toBeDefined();
result.bar = "baz";
return result;
}
Which is somewhat annoying because now I have to manually restore the spy instead of having the framework automatically does it for me.
Does Jasmine have an after-advice spy?
Generally: no.
You could extend the SpyStrategy object with such a function though:
this.callThroughAndModify = function(resultModifier) {
var result;
plan = function() {
result = originalFn.apply(this, arguments);
return resultModifier(result);
};
return getSpy();
};
You've to clone the above SpyStrategy file and insert that method.
Usage:
var obj = {
fn: function(a) { return a * 2; }
};
spyOn(obj, "fn").and.callThroughAndModify(function(result) {
console.log("Original result: ", result);
return 1;
});
expect(obj.fn(2)).toBe(1);
Drawbacks:
You've to replace the whole SpyStrategy.js
You've to load that script before Jasmine initializes the original SpyStrategy at boot

How to set context to rxjs subscribe callback function?

I have a subscribe like this:
this.test.subscribe(params => {
...some code
});
If I pass a callback function instead of arrow function, the context is missing.
I want to bind the context to the subscribe function but I never see that.
is it possible to do without make something like
that = this
I will attempt an answer but I'm still not quite sure what you mean.
When you write:
const v = 42;
observable.subscribe(x => {
// here you have access to `v`
});
But when you write:
{
const v = 42;
observable.subscribe(f);
}
function f(x) {
// here you do not have access to `v`
}
And that's how it should be. If you want f to see variables that are not in its declaration scope, then you must make them arguments and pass them appropriately. For instance:
{
const v = 42;
observable.subscribe(x => f(x, v));
}
function f(x, v) {
// here you **do** have access to `v`
}
Or if you can define the callback in the context of the variable you want to capture:
{
const v = 42;
observable.subscribe(x => f(x));
function f(x) {
// here you **do** have access to `v` because it is in scope
}
}
Does this answer your question? It has nothing to do with RxJS though, those are pure JavaScript (and programming languages) concepts.

Nothing happens with my event listener on click in javascript

I have these functions :
createTreeItem: function (num, val)
{
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
var i = document.createElementNS(XUL_NS, "treeitem");
var r = document.createElementNS(XUL_NS, "treerow");
var c1 = document.createElementNS(XUL_NS, 'treecell');
var c2 = document.createElementNS(XUL_NS, 'treecell');
var c3 = document.createElementNS(XUL_NS, 'treecell');
i.setAttribute("container", true);
i.setAttribute("open", true);
c1.setAttribute("label", num);
c2.setAttribute("label", val);
c3.setAttribute("value", false);
r.appendChild(c1);
r.appendChild(c2);
r.appendChild(c3);
i.appendChild(r);
i.addEventListener("click", test, false);
return i;
}
test: function ()
{
alert("zero");
}
func: function (liste)
{
try
{
root = document.getElementById("treeRoot");
var current;
for(o in liste)
{
current = createTreeItem(liste[o].id, liste[o].nom_scenario);
root.appendChild(current);
}
}
catch(e)
{
alert(e);
}
}
I am creating elements in a tree and I would like to add event listeners on each element created. The problem is that nothing happens.
In the code, Liste is the response of a json request. It contains all the elements I want to create in my xul file.
I'm not super familiar with this syntax, but my bet is that the test function isn't being 'hoisted' because of how it's being defined. try moving the 'test' function above the 'createTreeItem' function or just defining test like so:
function test() {
...
}
That way when it gets evaluated it will be 'hoisted' to the top so that when you try to add it as the action for the click event, it'll be defined. Not 100% sure this is correct but if I had to bet...

Resources