11

Limiting side effects when programming in the browser with javascript is quite tricky.

I can do things like not accessing member variables like in this silly example:

let number = 0;

const inc = (n) => {
  number = number + n;

  return number;
};

console.log(inc(1));  //=> 1
console.log(inc(1));  //=> 2

But what other things can I do to reduce side effects in my javascript?

dagda1
  • 26,856
  • 59
  • 237
  • 450
  • 1
    What's a "member variable" in javascript? – Bergi Jul 05 '15 at 12:15
  • I don't understand your question. Are you asking how to completely prevent yourself from writing code that uses side effects, or how to program effectively without using side effects in js? – Bergi Jul 05 '15 at 12:17
  • I mean how can I minimise side effects. Eliminating side effects seems impractical but what steps can I do to eliminate most of them – dagda1 Jul 05 '15 at 16:01
  • Well, just don't use them (except where you have to: DOM, network, events)? – Bergi Jul 05 '15 at 16:25
  • yes, and not using member variables is one thing I can avoid, what else. You seem to be suggesting the question is a waste of time, ignore it then? – dagda1 Jul 05 '15 at 19:49
  • I don't see a reason to avoid properties (if that's what you mean by "member variables"). You just shouldn't mutate objects you don't own. I don't consider your question a waste of time, I just don't understand what you are looking for. A tutorial on "How to do functional programming in JS?"? That would be off-topic. A generic list of dos and donts? Practical help with your code that has too many side effects? – Bergi Jul 05 '15 at 20:32
  • 1
    a generic list of dos and donts would be great – dagda1 Jul 06 '15 at 06:54
  • your question is too vague. Side affects could be OO issues like your example. They could be DOM related. Honestly, the question is too open ended for a good answer. – Dave Alperovich Jul 08 '15 at 12:20
  • You should rather looks for some good books/articles on “JavaScript best practices” or something like that … – CBroe Jul 11 '15 at 07:29
  • Not an answer to your question, but I wonder why you don't write your function more succinctly as `const inc = n => number += n;`. –  Jul 11 '15 at 07:48
  • You can delay them in a lot of cases and have a single place where there executed. I wrote intention.js as a way of seeing how far I could take this idea with API requests. https://github.com/CrowdHailer/intention.js – Peter Saxton Aug 11 '16 at 15:25

3 Answers3

9

Of course you can avoid side effects by careful programming. I assume your question is about how to prevent them. Your ability to do so is severely limited by the nature of the language. Here are some approaches:

  1. Use web workers. See MDN. Web workers run in another global context that is different from the current window.:

  2. Isolate certain kinds of logic inside iframes. Use cross-window messaging to communicate with the iframe.

  3. Immutability libraries. See https://github.com/facebook/immutable-js. Also http://bahmutov.calepin.co/avoid-side-effects-with-immutable-data-structures.html.

  4. Lock down your objects with Object.freeze, Object.seal, or Object.preventExtensions. In the same vein, create read-only properties on objects using Object.defineProperty with getters but no setters, or with the writeable property set to false.

  5. Use Object.observe to get asynchronous reports on various types of changes to objects and their properties, upon which you could throw an error or take other action.

  6. If available, use Proxy for complete control over access to an object.

For considerations on preventing access to window, see javascript sandbox a module to prevent reference to Window. Also http://dean.edwards.name/weblog/2006/11/sandbox/. Finally, Semi-sandboxing Javascript eval.

It is useful to distinguish between inward side effects and outward side effects. Inward side effects are where some other code intrudes on the state of my component. Outward side effects are where my code intrudes on the state of some other component. Inward side effects can be prevented via the IIFEs mentioned in other answers, or by ES6 modules. But the more serious concern is outward side effects, which are going to require one of the approaches mentioned above.

Community
  • 1
  • 1
4

Just what jumps to my mind thinking about your question:

  • Don't pollute the global namespace. Use 'var' or 'let', those keywords limit your variables to the local scope.

"By reducing your global footprint to a single name, you significantly reduce the chance of bad interactions with other applications, widgets, or libraries." - Douglas Crockford

  • Use semicolons
    The comment section of this article provides some good (real life) reasons to always use semicolons.

  • Don't declare String, Number or Boolean Objects
    (in case you were ever tempted to)

    var m = new Number(2);
    var n = 2;
    m === n; // will be false because n is a Number and m is an object
    
  • "use strict"; is your friend. Enabling strict mode is a good idea, but please don't add it to existing code since it might break something and you can not really declare strict only on lexical scopes or individual scripts as stated here

  • Declare variables first. One common side effect is that people are not aware about JavaScript's Hoisting. Hoisting searches your block for variable declaration and moves them all together to the top of your block.

    function(){
        var x = 3;
        // visible variables at runtime at this point: x,y,i !
    
        // some code....
    
        var y = 1; // y will be moved to top!
        for( var i = 0; i < 10; i++ ){ // i will be moved to top!
            // some code...
        }
    }
    

    Here is discussed what hoisting actually means and to what kind of 'unexpected behaviour' it may lead.

  • use '===' instead of '=='. There are many good reasons for this and this is one of the most common 'side effects' or 'errors' in JavaScript. For more details see this great answer on SO, but let me give you a quick demonstration:

    '' == '0'           // false
    0 == ''             // true
    
    // can be avoided by using '==='
    '' === '0'          // false
    0 === ''            // false
    
  • Make use of IIFE. An IIFE (Immediately Invoked Function Expression) lets you declare an anonymus function which will call itself. This way you can create a new lexical scope and don't have to worry about the global namespace.

  • Be careful with prototypes. Keep in mind that JavaScript objects of the same class share the same prototype. Changing a prototype means changing the behaviour of all instances of the class. (Even those which are used by other scripts/frameworks) like it happened here

    Object.prototype.foo = function(){...} // careful!
    

Those are the 'side effects' that came to my mind. Of course there is way more to take care of (meaningful variable names, consistent code style etc...) but I don't consider those things as 'side effects' since they make your code hard to maintain, but won't break it immediately.

Community
  • 1
  • 1
Tim Hallyburton
  • 2,741
  • 1
  • 21
  • 29
  • 1
    Although these all all worthy programming rules, I fail to see how they relate to the OP's question about side-effects, which he meant in the formal CS/functional programming sense of the word. –  Jul 11 '15 at 07:08
  • As he stated in the comments: " a generic list of dos and donts would be great – dagda1 Jul 6 at 6:54 " I tried to provide a list of things to do/avoid in order to omit unintented behaviour and conflicts. I think this is what he wanted, however i could be wrong - question is kinda vague. Thank you for your feedback :) – Tim Hallyburton Jul 11 '15 at 07:21
2

My favorite trick is to just use a language that compiles to javascript, instead of using javascript.

However, two important tricks you can do :

  • start your file with "use strict";. This will turn on validation of your code and prevent usage of undeclared variables. Yes, that's a special string that the browser will know how to deal with.
  • use functions when needed. Javascript cannot do normal scopes, but for functions it works fine, so get (function() { })(); in your muscle memory.

Normal CS fundamentals also apply: separate your code into logical pieces, name your variables properly, always explicitly initialize variables when you need them, etc.

Tom van der Woerdt
  • 29,532
  • 7
  • 72
  • 105