Arrow functions in ES2015 provide a more concise syntax.
Can I replace all my function declarations / expressions with arrow functions now?
What do I have to look out for?
Examples:
Constructor function
function User(name) {
this.name = name;
}
// vs
const User = name => {
this.name = name;
};
Prototype methods
User.prototype.getName = function() {
return this.name;
};
// vs
User.prototype.getName = () => this.name;
Object (literal) methods
const obj = {
getName: function() {
// ...
}
};
// vs
const obj = {
getName: () => {
// ...
}
};
Callbacks
setTimeout(function() {
// ...
}, 500);
// vs
setTimeout(() => {
// ...
}, 500);
Variadic functions
function sum() {
let args = [].slice.call(arguments);
// ...
}
// vs
const sum = (...args) => {
// ...
};
tl;dr: No! Arrow functions and function declarations / expressions are not equivalent and cannot be replaced blindly.
If the function you want to replace does not use this, arguments and is not called with new, then yes.
As so often: it depends. Arrow functions have different behavior than function declarations / expressions, so let's have a look at the differences first:
1. Lexical this and arguments
Arrow functions don't have their own this or arguments binding. Instead, those identifiers are resolved in the lexical scope like any other variable. That means that inside an arrow function, this and arguments refer to the values of this and arguments in the environment the arrow function is defined in (i.e. "outside" the arrow function):
// Example using a function expression
function createObject() {
console.log('Inside `createObject`:', this.foo);
return {
foo: 42,
bar: function() {
console.log('Inside `bar`:', this.foo);
},
};
}
createObject.call({foo: 21}).bar(); // override `this` inside createObject
// Example using a arrow function
function createObject() {
console.log('Inside `createObject`:', this.foo);
return {
foo: 42,
bar: () => console.log('Inside `bar`:', this.foo),
};
}
createObject.call({foo: 21}).bar(); // override `this` inside createObject
In the function expression case, this refers to the object that was created inside the createObject. In the arrow function case, this refers to this of createObject itself.
This makes arrow functions useful if you need to access the this of the current environment:
// currently common pattern
var that = this;
getData(function(data) {
that.data = data;
});
// better alternative with arrow functions
getData(data => {
this.data = data;
});
Note that this also means that is not possible to set an arrow function's this with .bind or .call.
If you are not very familiar with this, consider reading
MDN - this
YDKJS - this & Object prototypes
2. Arrow functions cannot be called with new
ES2015 distinguishes between functions that are callable and functions that are constructable. If a function is constructable, it can be called with new, i.e. new User(). If a function is callable, it can be called without new (i.e. normal function call).
Functions created through function declarations / expressions are both constructable and callable.
Arrow functions (and methods) are only callable.
class constructors are only constructable.
If you are trying to call a non-callable function or to construct a non-constructable function, you will get a runtime error.
Knowing this, we can state the following.
Replaceable:
Functions that don't use this or arguments.
Functions that are used with .bind(this)
Not replaceable:
Constructor functions
Function / methods added to a prototype (because they usually use this)
Variadic functions (if they use arguments (see below))
Generator functions, which require the function* notation
Lets have a closer look at this using your examples:
Constructor function
This won't work because arrow functions cannot be called with new. Keep using a function declaration / expression or use class.
Prototype methods
Most likely not, because prototype methods usually use this to access the instance. If they don't use this, then you can replace it. However, if you primarily care for concise syntax, use class with its concise method syntax:
class User {
constructor(name) {
this.name = name;
}
getName() {
return this.name;
}
}
Object methods
Similarly for methods in an object literal. If the method wants to reference the object itself via this, keep using function expressions, or use the new method syntax:
const obj = {
getName() {
// ...
},
};
Callbacks
It depends. You should definitely replace it if you are aliasing the outer this or are using .bind(this):
// old
setTimeout(function() {
// ...
}.bind(this), 500);
// new
setTimeout(() => {
// ...
}, 500);
But: If the code which calls the callback explicitly sets this to a specific value, as is often the case with event handlers, especially with jQuery, and the callback uses this (or arguments), you cannot use an arrow function!
Variadic functions
Since arrow functions don't have their own arguments, you cannot simply replace them with an arrow function. However, ES2015 introduces an alternative to using arguments: the rest parameter.
// old
function sum() {
let args = [].slice.call(arguments);
// ...
}
// new
const sum = (...args) => {
// ...
};
Related question:
When should I use arrow functions in ECMAScript 6?
Do ES6 arrow functions have their own arguments or not?
What are the differences (if any) between ES6 arrow functions and functions bound with Function.prototype.bind?
How to use arrow functions (public class fields) as class methods?
Further resources:
MDN - Arrow functions
YDKJS - Arrow functions
Arrow functions => best ES6 feature so far. They are a tremendously
powerful addition to ES6, that I use constantly.
Wait, you can't use arrow function everywhere in your code, its not going to work in all cases like this where arrow functions are not usable. Without a doubt, the arrow function is a great addition it brings code simplicity.
But you can’t use an arrow function when a dynamic context is required: defining methods, create objects with constructors, get the target from this when handling events.
Arrow functions should NOT be used because:
They do not have this
It uses “lexical scoping” to figure out what the value of “this”
should be. In simple word lexical scoping it uses “this” from the
inside the function’s body.
They do not have arguments
Arrow functions don’t have an arguments object. But the same
functionality can be achieved using rest parameters.
let sum = (...args) => args.reduce((x, y) => x + y, 0);
sum(3, 3, 1) // output: 7
They cannot be used with new
Arrow functions can't be constructors because they do not have a prototype property.
When to use arrow function and when not:
Don't use to add function as a property in object literal because we
can not access this.
Function expressions are best for object methods. Arrow functions
are best for callbacks or methods like map, reduce, or forEach.
Use function declarations for functions you’d call by name (because
they’re hoisted).
Use arrow functions for callbacks (because they tend to be terser).
To use arrow functions with function.prototype.call, I made a helper function on the object prototype:
// Using
// #func = function() {use this here} or This => {use This here}
using(func) {
return func.call(this, this);
}
usage
var obj = {f:3, a:2}
.using(This => This.f + This.a) // 5
Edit
You don't NEED a helper. You could do:
var obj = {f:3, a:2}
(This => This.f + This.a).call(undefined, obj); // 5
They are not always equivalent. Here's a case where you cannot simply use arrow functions instead of regular functions.
Arrow functions CANNOT be used as constructors
TLDR:
This is because of how Arrow Functions use the this keyword. JS will simply throw an error if it sees an arrow function being invoked as a "constructor". Use regular functions to fix the error.
Longer explanation:
This is because objects "constructors" rely on the this keyword to be able to be modified.
Generally, the this keyword always references the global object. (In the browser it is the window object).
BUT, when you do something like:
function personCreator(name) {
this.name = name;
}
const person1 = new personCreator('John');
The new keyword do some of its magic and makes the this keyword that is inside of personCreator to be initially an empty object instead of referencing the global object. After that, a new property called name is created inside that empty this object, and its value will be 'John'. At the end, the this object is returned.
As we see, the new keyword changed the value of this from referencing the global object to now be an empty object {}.
Arrow functions do not allow their this object to be modified. Their this object is always the one from the scope where they were statically created. This is called Static Lexical Scope. That is why you cannot do operations like bind, apply, or call with arrow functions. Simply, their this is locked to the value of the this of the scope were they were created. This is by design.
And because of this :D, arrow functions cannot be used as "constructors".
Side Note:
A lexical scope is just the area where a function is created. For example:
function personCreator(name) {
this.name = name;
const foo = () => {
const bar = () => {
console.log(this); // Output: { name: 'John' }
}
console.log(this); // Output: { name: 'John' }
bar();
}
foo();
}
const person1 = new personCreator('John');
The lexical scope of bar is everything that is within foo. So, the this value of bar is the one that foo has, which is the one of personCreator.
I'm trying to style the currently active tab of my web project with the class "active". To target my tab elements I am using
onMount(() => {
const links = document.querySelectorAll(".topnav a");
});
I am then using a reactive statement to style the appropriate element like this
$: {
links.forEach((link) => {
if (link.getAttribute("id") === $page.url.pathname) {
link.classList.add("active");
} else {
link.classList.remove("active");
}
});
}
However, I have no way of sharing the links variable to my reactive statement. I also tried putting document.querySelectorAll inside my reactive statement (not using onMount at all), which worked flawlessly until i reloaded the page. What is the conventional approach to this?
You need to declare the variable outside of onMount so it is in scope of the reactive statement. E.g.
let links = null;
onMount(() => {
links = ...;
);
$: if (links != null) {
links.forEach((link) => {
});
Using document.querySelectorAll is not idiomatic Svelte.
Changing class (or other attributes) use the template syntax:
<a class:active={link.id === $page.url.pathname}>
{link.label}
</a>
If you really need access to the DOM api's Svelte has bind:this or action to get access to specific elements.
I have a simple LitElement component like so:
class MyElement extends LitElement {
constructor() {
super();
}
customMethod(data) {
// do something with the passed parameter
}
render() {
return html`<div id="element"></div>`;
}
}
customElements.define('my-element', MyElement);
And I want to be able to call that customMethod from outside of my element.
So for example if I add the element to web page like so:
<my-element></my-element>
I then want to be able to add some JavaScript to the page and call that customMethod.
I tried:
var element = document.getElementById('element');
element.shadowRoot.customMethod('example data');
But it claims it's not available... How can I call a method on an instance of LitElement?
You don't need to use shadowRoot in the call :
var element = document.getElementById('element');
element.customMethod('example data');
but you need to be able to locate your element
<my-element id='element'></my-element>
I had a very similar problem and the existing answers did not seem to fix it. The reason for my issue was caused by the fact that LIT Element scripts are exported as modules, meaning that they are loaded and executed after the initial DOM has been parsed. So if you are using a script to access the public method - make sure that it is also in a module (or you can alternatively place the code into an appropriate timeout).
So when defining an element in LIT Element as follows:
#customElement('my-element')
export class MyElement extends LitElement {
#state()
text = '';
customMethod(data) {
this.text = 'Custom method was called!';
}
render() {
return html`<div id="element">${this.text}</div>`;
}
}
And adding a script in my index.html page:
<my-element id='element'></my-element>
<script type="module">
const element = document.getElementById('element');
element.customMethod();
</script>
Make sure that the script tag contains type="module". Otherwise you will see the following error in the console: Uncaught TypeError: element.customMethod is not a function
Link to LIT Element Playground.
Also, here is a great article that explains how scripts are loaded in detail.
I'm working with Dust.js and Node/Express. Dust.js documents the context helpers functions, where the helper is embedded in the model data as a function. I am adding such a function in my JSON data model at the server, but the JSON response to the browser doesn't have the function property (i.e. from the below model, prop1 and prop2 are returned but the helper property is not.
/* JSON data */
model: {
prop1: "somestring",
prop2: "someotherstring",
helper: function (chunk, context, bodies) {
/* I help, then return a chunk */
}
/* more JSON data */
I see that JSON.stringify (called from response.json()) is removing the function property. Not sure I can avoid using JSON.stringify so will need an alternative method of sharing this helper function between server/client. There probably is a way to add the helper functions to the dust base on both server and client. That's what I'm looking for. Since the Dust docs are sparse, this is not documented. Also, I can't find any code snippets that demonstrate this.
Thanks for any help.
send your helpers in a separate file - define them in a base context in dust like so:
base = dust.makeBase({foo:function(){ code goes here }})
then everytime you call your templates, do something like this:
dust.render("index", base.push({baz: "bar"}), function(err, out) {
console.log(out);
});
what this basically does is it merges your template's context into base, which is like the 'global' context. don't worry too much about mucking up base if you push too much - everytime you push, base recreates a new context with the context you supplied AND the global context - the helpers and whatever variables you defined when you called makeBase.
hope this helps
If you want stringify to preserve functions you can use the following code.
JSON.stringify(model, function (key, value) {
if (typeof(value) === 'function') {
return value.toString();
} else {
return value;
}
});
This probably doesn't do what you want though. You most likely need to redefine the function on the client or use a technology like nowjs.
I have a requirement to extend the YUI Panel with some custom functionality that will be in a new file and shared across multiple views.
I am at a bit of a loss as to how best to go about this, can anyone give me any pointers please?
Let's say you want to extend a Panel to create one that has a list in its body. I usually use Y.Base.create for this. It's a more declarative way of extending YUI classes than using a constructor and Y.extend. But I'll stay closer to your example in the YUI forums.
There are a couple of tricks dealing with WidgetStdMod (one of the components of Y.Panel), but mostly it's just about using Y.extend and following the YUI inheritance patterns. I'll try to answer with an example:
function MyPanel() {
MyPanel.superclass.constructor.apply(this, arguments);
}
// hack: call it the same so you get the same css class names
// this is good for demos and tests. probably not for real life
MyPanel.NAME = 'panel';
MyPanel.ATTRS = {
listItems: {
// YUI now clones this array, so all's right with the world
value: []
},
bodyContent: {
// we want this so that WidgetStdMod creates the body node
// and we can insert our list inside it
value: ''
}
};
Y.extend(MyPanel, Y.Panel, {
// always a nice idea to keep templates in the prototype
LIST_TEMPLATE: '<ul class="yui3-panel-list"></ul>',
initializer: function (config) {
// you'll probably want to use progressive enhancement here
this._listContainer = Y.Node.create(this.LIST_TEMPLATE);
// initializer is also the place where you'll want to instantiate other
// objects that will live inside the panel
},
renderUI: function () {
// you're inheriting from Panel, so you'll want to keep its rendering logic
// renderUI/bindUI/syncUI don't call the superclass automatically like
// initializer and destructor
MyPanel.superclass.renderUI.call(this);
// Normally we would append stuff to the body in the renderUI method
// Unfortunately, as of 3.5.0 YUI still removes all content from the body
// during renderUI, so we either hack it or do everything in syncUI
// Hacking WidgetStdModNode is doable but I don't have the code around
// and I haven't memorized it
//var body = this.getStdModNode('body');
},
syncUI: function () {
// same here
MyPanel.superclass.syncUI.call(this);
// insert stuff in the body node
var listContainer = this._listContainer.appendTo(this.getStdModNode('body'));
Y.Array.each(this.get('listItems'), function (item) {
listContainer.append('<li>' + item + '</li>');
});
}
});