JavaScript FAQ: Part 1

    image

    A few days ago, TheShock and I created a topic in which we collected your questions regarding JavaScript (architecture, frameworks, problems). It is time to answer them. We received a lot of questions, both in the comments and by email. This first part of the answers is the questions that I got.

    1. Prototype inheritance. How does it work?

    I have a problem understanding the prototype model, I’m used to the “classical” class :) but I decided to study JS. Please, if possible, write an article where, in the form of patterns, explain the possible options for constructing “classes”, different levels of visibility of methods and variables. I understand that such articles can be found in a huge number, I understand that in JS visibility levels are somehow “not needed”.

    The answer was very long, so I created a separate topic: Basics and Misconceptions About JavaScript

    2. What is the most convenient model for creating objects?

    If with new, then how to protect yourself from errors:
    1. I always write constructor functions with a capital letter;
    2. Check the validity of the creation through this instanceof Function_name (I avoid this instanceof arguments.callee for performance reasons)
    3. Similar to the second, but I check with window, because I don’t want to hardcode the name and do not write scripts for out-of-browser environments.

    It’s better, more familiar and ideological to create objects through new. Designers should be capitalized.
    I prefer to rely on conventions and don’t check this inside the constructor - I called the constructor without new and therefore has flown to the globals - which means "the fool himself." And in no case do I encourage an error with new - some people check if this is a global one, then the user called the constructor without new and create an object inside the constructor and return it - this is an encouragement to the error and an ideologically incorrect approach.
    var Obj = function () {
        "use strict";
        this.pew = 100;
    };
    // Правильно
    new Obj.pew++;
    // пользователь словит ошибку
    Obj(); // TypeError: this is undefined
    

    new is not acceptable for factory methods, and short constructors - jQuery I

    summarize the code:
    // Хорошо: меньше кода, не поощряется ошибка, use strict
    var Obj = function () {
        "use strict";
        this.pew = 100;
    };
    // Не очень хорошо: лишний и совершенно ненужный код
    var Obj = function () {
        if (!(this instanceof Obj)) {
            return new Obj();
        }
        this.pew = 100;
    };

    3. How to determine which mouse button is pressed on JS?


    The event is mousedown mouseuptriggered by all the mouse buttons, clickonly the left one. In the event handler, you need to check the button code event.buttonto find out which one was pressed:
    (0 - Left, 1 - Middle, 2 - Right). In IE8- everything is wrong, see the code:
    var button = document.getElementById('button'),
                  //  0       1         2
        buttonMap = ['Left', 'Middle', 'Right'],
        handler = function (event) {
            event = event || window.event;
            alert(buttonMap[event.button] + ' id: ' + event.button);
        };
    if (button.addEventListener) {
         button.addEventListener('mousedown',  handler, false);     
    } else {
         // IE         0      1       2        3      4
         buttonMap = ['???', 'Left', 'Right', '???', 'Middle'];
         button.attachEvent('onmousedown',  handler);
    }

    jQuery fixes this IE flaw, it should be checked event.whichinstead of magic withevent.button
    $('button').mousedown(function (event) {
        alert(['Left', 'Middle', 'Right'][event.which]);
    });

    Example: jsfiddle.net/azproduction/W2XgH
    More: www.quirksmode.org/js/events_properties.html Paragraph “Which mouse button has been clicked?”
    jQuery event.which: api.jquery.com/event.which

    4. Is it possible to intercept keyboard keystroke events?

    Is it possible to intercept keyboard keystroke events (down, up arrow) in javascript so that the browser does not scroll the window after that? Are there any features among browsers in this behavior, if at all possible? For example, there is a table that does not fit the entire screen, while moving along the lines is implemented using the keyboard arrows. It is necessary that the browser does not scroll through the page.

    To do this, it is necessary to cancel the so-called default action: the down arrow and the mouse wheel scroll the window, the right mouse button brings up the context menu, clicks on sumbit to execute form.submit(), when you click on input it will get focus, when you click on anchor the browser will go to link etc.

    Using jQuery this can be done like this:
    // ИЕ и сафари не отслеживает стрелки на keypress, а Опера глючит на keyup
    $(window).bind($.browser.opera ? 'keypress' : 'keyup', function (event) {
        event.preventDefault();
        // или
        return false;
    });

    There is an important point. Must be completed preventDefault()before defaultAction is executed. For example, when we click on input we don’t want to give it focus, then we need to hang the handler on an event in the chain until defaultAction - mousedown is executed.
    $('input').bind('mousedown', function (event) {
        event.preventDefault();
        // или
        return false;
    });

    The chain of events itself is as follows:
    1. mousedown
    2. focus (blur will work on another object in front of the focus)
    3. mouseup
    4. click
    If we hang the event on focus and lower, nothing will happen because defaultAction will work after mousedown.

    5. How to solve the problem of stopping gif-animation when pressing ESC, if this key is bind?


    See answer above. Some browsers, when you press Esc, stop the gif animation, stop loading the page - this is their default action.
    You must undo the default action event.preventDefault():
    $(document).bind($.browser.webkit ? 'keydown' : 'keypress', function (event) { 
        // Нажали Esc
        if ((event.which || event.keyCode) === 27) {
            event.preventDefault();
            // или
            return false;
        }
    });

    6. And what is the operator () with which the closure was created?


    The brackets allow the parser to understand what the brackets are after the function: the grouping or the operator of the function call.

    If you do it like this:
    function () {
      // source
    }()

    In this case, we get a SyntaxError due to the absence of a function name (a declaration function must always have a name).

    If you add a name:
    function foo() {
      // source
    }()

    In the second case, the name is set (foo), and in theory, the function declaration should pass normally. However, we still have a syntax error, but already, with regards to the grouping operator without an expression inside. Please note that in this case it is the grouping operator that follows the function declaration, not the function call brackets!

    There are other ways to prevent ParseError - put the function in the expression state i.e. show the parser that this is exactly Function Expression:
    From TheShock :
    !function () {
      // source
    }();
    +function () {
      // source
    }();
    [function() {
      // source
    }()];
    var a = function () {
      // source
    }();

    It is used incl. in jQuery. Allows you to select all the code in one block with a local scope. This speeds up access to internal variables, allows you to clutter up the global namespace, and, most importantly, is better compressed by minifiers. habrahabr.ru/blogs/jquery/118564


    Based on dsCode Subtleties ECMA-262-3. Part 5. Functions. - The question "about brackets" Read
    more: kangax.github.com/nfe

    7. Forwarding code in XHR


    The server sends a response “alert ('Boom !!!');” to the user’s actions in the ajax system. On the client, the received response is run through eval () and executed. What is the name of this data transfer? This is not JSON, not XML, not HTML.

    There is no name for this. Actually, this is a very bad approach, as bad as storing PHP code in a database and eval it each time. In addition to conditional non-secrecy, such an architecture carries with it a strong coherence, and in the future something will be difficult to redo. It turns out a mess: data + code + presentation, in such a model, in order to redo something, we will have to unravel this tangle, make changes, given the numerous connections, to confuse back. I'm not talking about tearing a piece of functionality out of such a mess ...
    To simplify code support, it is necessary to maximally separate parts of the system and reduce the number of connections (dependencies). To obtain a weak connection (when a piece of an application can be torn off or replaced as painlessly as possible), events and, for example, the MVC application architecture are introduced.

    Read:
    And again about MVC.
    Application of Event-driven models in a web application.
    Writing complex interfaces with Backbone.js

    8. How to correctly organize the queue of command execution with a delay without hanging the entire script?


    JavaScript has one thread in which the code itself is executed, the DOM tree is redrawn, timers work. Each time you perform a chain of operations (cycles, heavy functions), user interaction with the interface is blocked (if the chain is not heavy, then the user does not notice the changes). To prevent UI blocking, Threed introduced Web Workers threads in JavaScript.
    If the use of workers is not possible, it is necessary to optimize the cycles and heavy functions. As Nicholas C. Zakas writes in his book OReilly High Performance JavaScript: the user will not notice lags if the UI Threed is blocked for 100 ms or less. Those. we can calculate 100 ms, then it’s worth unlocking the UI Threed so that the user does not notice lags.

    Here is the original code optimized for all processors from his book:
    function timedProcessArray(items, process, callback) {
        var todo = items.concat();   //create a clone of the original
        setTimeout(function () {
            var start = +new Date();
            do {
                process(todo.shift());
            } while (todo.length > 0 && (+new Date() - start < 50));
            if (todo.length > 0){
                setTimeout(arguments.callee, 25);
            } else {
                callback(items);
            }
        }, 25);
    }
    function saveDocument(id) {
        var tasks = [openDocument, writeText, closeDocument, updateUI];
        timedProcessArray(tasks, [id], function(){
            alert("Save completed!");
        });
    }

    The function timedProcessArrayblocks the UI Threed for 25 ms, executing a chain of actions, then releases the UI Threed for 25 ms and so on.

    Read:
    Nicholas C. Zakas - OReilly High Performance JavaScript
    Comix Web Workers
    Calculate with Web Workers
    WXHR: good old XHR with taste

    9. Is it possible to somehow find out that the user has finished resizing the window?


    There is no such event, but you can find out if the user resized the window for some time, which roughly corresponds to onresizeend

    .
    var time = 0,
        timerId,
        TIME_ADMISSION = 100; // 0.1s
    function onresizeend () {
        console.log('onresizeend');
    };
    function resizeWatcher () {
        if (+new Date - time >= TIME_ADMISSION) {
            onresizeend();
            if (timerId) {
                window.clearInterval(timerId);
                timerId = null;
            }
        }
    };
    $(window).resize(function () {
        if (!timerId) {
            timerId = window.setInterval(resizeWatcher, 25);
        }
        time = +new Date;
    });

    Live example: jsfiddle.net/azproduction/2Yt6T

    10. How to use window.open () to open a new window, and not a tab?


    This behavior is browser-specific. Opera always opens a tab (but it appears as a window), Safari always open a window (Safari behavior can be bypassed). Chrome, FF and IE are controllable.

    If you pass additional parameters - the position of the window, a new window will open:
    window.open('http://www.google.com', '_blank', 'toolbar=0,location=0,menubar=0');

    If you don’t transmit anything, the tab will open:
    window.open('http://www.google.com');

    More often you need to open a new tab, there may be a problem with the safari: by default (depending on the settings), the safari opens a new window whenever the window.open function is called. But when you click on a link with those pressed, it Ctrl+Shift/Meta+Shiftalways opens a new tab (regardless of settings). To open a new tab, we will emulate the click event with the pressed Ctrl+Shift/Meta+Shift:
    function safariOpenWindowInNewTab(href) {
        var event = document.createEvent('MouseEvents'),
            mac = (navigator.userAgent.indexOf('Macintosh') >= 0);
        // выполняем Ctrl+Shift+LeftClick/Meta+Shift+LeftClick (фокус)
        // создаем собственное событие
        event.initMouseEvent(
            /* type */          "click",
            /* canBubble */     true,
            /* cancelable */    true,
            /* view */          window,
            /* detail */        0,
            /* screenX, screenY, clientX, clientY */ 0, 0, 0, 0,
            /* ctrlKey */       !mac,
            /* altKey */        false,
            /* shiftKey */      true,
            /* metaKey */       mac,
            /* button */        0,
            /* relatedTarget */ null
        );
        // создаем ссылку в памяти и кликеам этм событием по ссылке - откроется новый таб
        $('', {'href': href, 'target': '_blank'})[0].dispatchEvent(event);
    }

    11. How to effectively make deep copy?


    If oldObject does not change, then it will be more efficient to clone through the prototype (it will be very fast):
    function object(o) {
        function F() {}
        F.prototype = o;
        return new F();
    }
    var newObject = object(oldObject);

    If you need honest cloning, it will be faster to recursively walk through the object tree + do some optimizations (this is the fastest honest cloning algorithm so far):
    var cloner = {
        _clone: function _clone(obj) {
            if (obj instanceof Array) {
                var out = [];
                for (var i = 0, len = obj.length; i < len; i++) {
                    var value = obj[i];
                    out[i] = (value !== null && typeof value === "object") ? _clone(value) : value;
                }
            } else {
                var out = {};
                for (var key in obj) {
                    if (obj.hasOwnProperty(key)) {
                        var value = obj[key];
                        out[key] = (value !== null && typeof value === "object") ? _clone(value) : value;
                    }
                }
            }
            return out;
        },
        clone: function(it) {
            return this._clone({
            it: it
            }).it;
        }
    };
    var newObject = cloner.clone(oldObject);


    For jQuery, you can use the following:
    // Мелкое копирование
    var newObject = jQuery.extend({}, oldObject);
    // Глубокое копирование
    var newObject = jQuery.extend(true, {}, oldObject);


    Read:
    Benchmark for honest cloning methods.
    Very long discussion of the subject on stackoverflow.

    12. How to make an analog destructor / finalizer? And in general, how to manage the lifetime of objects?


    In JavaScript, the object will be deleted when the last link to it disappears:

    var a = {z: 'z'};
    var b = a;
    var c = a;
    delete a.z;
    delete a; // Мы всего лишь удаляем убиваем ссылку "а"
    console.log(b, c); // Объект по факту существует: Object {} Object {}, но он пустой
    

    Those. Using the "destructor", you cannot completely delete an object - you can only clean out the contents.

    13. Is it possible to process binary data? If so, how?


    In JavaScript, all numbers are presented for use in string form and there are no built-in tools for working with binary data. There is a BinaryParser JavaScript library for working with binary numbers: encoding, decoding (its code is hell!)

    ECMAScript 6+ (strawman) has a draft StructType (this is a struct familiar to us from C ++ and others). It is needed to simplify working with binary files. Here's what it might look like in the future:
    const Point2D = new StructType({ x: uint32, y: uint32 });
    const Color = new StructType({ r: uint8, g: uint8, b: uint8 });
    const Pixel = new StructType({ point: Point2D, color: Color });
    const Triangle = new ArrayType(Pixel, 3);
    let t = new Triangle([{ point: { x:  0, y: 0 }, color: { r: 255, g: 255, b: 255 } },
                          { point: { x:  5, y: 5 }, color: { r: 128, g: 0,   b: 0   } },
                          { point: { x: 10, y: 0 }, color: { r: 0,   g: 0,   b: 128 } }]);
    


    donnerjack13589 ArtemS You can use JavaScript typed arrays to read from buffers , but you cannot get the number in binary form.

    XMLHttpRequest Level 2 allows you to send and receive binary files:

    14. How to change context variables of another function from one function?


    1. You can pass a reference to the primer context object in smth
    2. Pass the function generated in the primer context to smth
    var primer = function (){
        var a, b, c, d, e = {};
        smth(function () {
            a = 1;
            b = 2;
            c = 3;
            d = 4;
        }, e);
        alert([a, b, c, d, e.pewpew]);
    },
    smth = function (callback, e) {
        callback();
        e.pewpew = "pewpew";
    };
    primer();


    3. Previously (FireFox 3.6-), it was possible to reach the context through __parent__, but in version 4 this feature was cut.

    15. Regarding the article “Five Ways to Call a Function”. Which of these N methods (in heading 5, in article 4, several in the comments) is better when to apply and why?


    I will not consider the global call / method call and constructor, their scope is already clear.
    I will dwell separately on call and apply. They do the same thing - they call a function with an explicit this context.
    1. Call and apply for constructor override:
    // Вспомогательная функция
    function extend(newObj, oldObj) {function F() {}F.prototype = oldObj.prototype;newObj.prototype = new F();return newObj}
    var Obj = function () {
       this.obj_var = 100;
    };
    Obj.prototype.obj_proto_var = 101;
    var NewObj = function () {
       Obj.call(this); // Вызываем конструктор Obj и получаем Own property obj_var
       this.new_obj_var = 102;
    };
    extend(NewObj, Obj)
    NewObj.prototype.new_obj_proto_var = 103; 
    new NewObj(); // {new_obj_proto_var: 103, new_obj_var: 102, obj_proto_var: 101, obj_var: 100}
    

    2. Converting arguments NodeList and other array-like objects to an array, turning a live list (getElementsByTagName) into an array
    // document.getElementsByTagName("div") возвращает не массив (хотя похож), поэтому мы не можем выполнять методы массива  
    document.getElementsByTagName("div").forEach(function (elem) {
        // ...
    }); // TypeError: document.getElementsByTagName("div").forEach is not a function
    // Приводим к массиву: мы подсовываем фукнции slice this, который поход на массив
    Array.prototype.slice.call(document.getElementsByTagName("div")).forEach(function (elem) {
        // OK
    });
    // Аналогично можно сделать со строками
    Array.prototype.slice.call('pewpew') // ["p", "e", "w", "p", "e", "w"]
    // В ИЕ8- будет будет массив из undefined
    

    3. Tricks with Function.call.apply to create wrappers:
    We need to write a wrapper foo () that calls bar () in the specified context with an arbitrary number of arguments
    From hyborg
    In a traditional form, it would look like this:
    function bar() {}
    // foo(context, arg1, arg2, ...)
    function foo() {
        var context = arguments[0]; 
        var args = Array.prototype.slice.call(arguments, 1); //делаем массив аргументов для bar
        bar.apply(context, args);
    }

    Instead of this salad use the call.apply trick:
    function foo() { 
        Function.call.apply(bar, arguments);
    }

    It works like this: aplly calls Function.call on the bar object with parameters passed to foo. That is, we get the following for the very first example with context and arg1, arg2:
    bar.call(context, arg1, arg2)

    4. Bind emulation

    16. How to transfer scope of execution of one function to another?


    No way. Previously (FireFox 3.6-), it was possible to reach the context through __parent__, but in version 4 this feature was cut.

    17. How to get a global object correctly without its direct reference, without eval and with 'use strict'?


    No way, if you omit one of the conditions, or execute only in the global scope, then you can:
    // 1: eval - on
    (function(){
        "use strict";
        var globalObject = (0, eval)("this"); // Магия :)
        return globalObject;
    }());
    // 2: указание имени - on
    (function(global){
        // ...
    }(window));
    // 3: "use strict" - off
    (function(){
        return this;
    }());
    // 4: Если выполнить вот этот код в глобалах, то мы получим ссылку на глобальную переменную, но в остальных случаях он не будет работать.
    // Это самый оптимальный вариант
    "use strict";
    (function(global){
        // global
    })(this);
    

    18. Is it possible in javascript after intercepting the event to restart it later?


    event does not carry any load; it is just an event descriptor. But you can explicitly pass a link to the event handler, like this:
    $('#smth').click(function onSmthClick(event){
        if (smth) {
            // Прописать обработчик
            event.handlerFunction = onSmthClick;
            event.handlerContext = this;
            // передать левому объекту
            // теперь otherObjectSetSomeEvent может использовать event.handlerFunction и вызывать обработчик
            otherObjectSetSomeEvent(event);
        } else {
            // делаем что-то другое
        }
    });

    But this is not a good solution, because there’s still a lot to do. And the logic is very confusing.
    It is better to redo the logic and divide the general handler into 2 parts:
    $('#smth').click(function handler1(event) {
        if (smth) {
            // передать левому объекту
            leftObjectSetSomeEvent(event, function handler2(e) {
                // делаем что-нибудь с event или e
            });
        } else {
            // делаем что-то другое
        }
    });
    function leftObjectSetSomeEvent(event, callback) {
        callback(event);
        // делаем что-нибудь с event
    }

    19. And you did not think to write your reference book on js? Tell me where to learn JavaScript in depth? Books, tutorials?


    There are many sites and many books , some of them translated into Russian .

    20. How on JS to intercept all clicks on the page on any elements? That is, make a single click handler


    It is necessary to hang the click event handler on the lowest object in the DOM tree, all clicks on the elements will “pop up” (if traffic cops forbid surfacing on the road ) to it.
    // jQuery
    $(window).bind('click', function (e) {
        console.log('Clicked on ', e.target);
    });
    // Можно также ограничить какой-то областью, используя jQuery delegate
    $('#pewpew').delegate('*', 'click', function (e) {
        console.log('Clicked on ', e.target);
    });
    // Можно ограничить и цели
    $('#pewpew').delegate('.pewpew', 'click', function (e) {
        console.log('Clicked on element with .pewpew class name');
    });

    21. How to execute XHR without jQuery?


    Non cross browser function:
    function xhr(m,u,c,x){with(new XMLHttpRequest)onreadystatechange=function(x){readyState^4||c(x.target)},open(m,u),send(с)}

    Cross-browser, slightly longer:
    function xhr(m,u,c,x){with(new(this.XMLHttpRequest||ActiveXObject)("Microsoft.XMLHTTP"))onreadystatechange=function(x){readyState^4||c(x)},open(m,u),send(с)}

    Using:
    xhr('get', '//ya.ru/favicon.ico', function(xhr){console.dir(xhr)});

    22. Work with the file system


    I wanted to learn about working with the file system via JavaScript, for example, reading and writing to files. Most textbooks describe JavaScript for browsers, where working with files is not necessary, but for example in a server application or for example in XUL, this is a necessity, but there is very little documentation, if any.

    There are a bunch of articles about client work with the file system, including file uploads on the hub:
    HTML5 File API: multiple file uploads to the server
    Uploading files using the html5 File API, with preference and dancers
    FileSystem API & File API: we understand and use the
    shortest image uploader !

    There is a good article on working with files on the Node.js server. Reading and writing files in Node.js.

    For XUL, you can read here in the MDC File_I / O article.

    23. Reflow, repaint and methods for minimizing them


    1. If the browser supports the function requestAnimationFrame, then you should use it insteadsetInterval/setTimeout
    Browsers can optimize animations running at the same time, reducing the number of reflow and repaint to one, which in turn will increase the accuracy of the animation. For example, animations in JavaScript synchronized with CSS transitions or SVG SMIL. Plus, if the animation is performed in a tab that is invisible, browsers will not continue to redraw, which will lead to less use of CPU, GPU, memory and as a result will reduce battery consumption in mobile devices.
    2. Avoid a large number of float elements (reflow will decrease)
    4. Modify the DOM tree as little as possible - write to memory, and then insert it into the DOM 1 time (reflow will decrease)
    5. Change the properties of the object with a pack (reflow, redraw will decrease) (this is not true for modern browsers)
    // Вместо
    element.style.left="150px;";
    //...
    element.style.color="green";
    // Измените все сразу
    element.setAttribute('style', 'color:green;left:150px');

    6. Perform animations only with absolutely positioned objects (reflow will decrease)
    7. Before changing the group of elements hide them style.display = "none"(reflow will decrease) (this is not true for modern browsers)

    Off-topic, but also about optimization:
    8. Use event delegation to reduce them quantities
    9. Cache references to DOM elements (calling the selector is the most expensive operation)
    10. Use the quick selector functions querySelectorAll () firstElementChild
    11. Remember that document.getElementsByTagName returns a “live” collection of elements (if an element is added to the DOM tree, then lecture will receive it automatically)

    In many modern browsers, these methods will not give such a visible advantage (browser developers optimize everything for us).

    Read:
    Nicholas C. Zakas - OReilly High Performance JavaScript
    Advanced Animations with requestAnimationFrame

    24. Is it worth using childProcesses in node.js for every request in highly loaded projects?


    For each request, in no case should you use childProcesses because we get too much overhead (this is like PHP with Apache): allocating extra memory, fork time, initialization time (jid compilation), CPU load, etc. Node.js very well distributes the load and loads a single processor core in its “evented processing loop” - the main thread of the application. The ideal download for Node.js is 1 fork per kernel, it is best to fork using Cluster . The cluster will act as a balancer (masters), and forks - slaves. Use of childProcesses for heavy requests is justified.
    You can still read here: stackoverflow.com/questions/3491811/node-js-and-cpu-intensive-requests

    25. Using runInNewContext in node.js


    What is runInNewContext? - node-js.ru/12-control-context-using-runinnewcontext
    The only application of this technology I see to run someone else's, potentially dangerous code (this is how Node.js hosting is nodester). If there is no critical need for this, then I am categorically against this - this is an absolutely unnecessary wrapper that you can not use if you select the correct application architecture and use the conventions during development. What is bad: creating / deleting contexts - memory allocation as a result of frequent GC (which blocks the entire application). I think there will be problems with the support of such code.

    Conclusion


    TheShock will answer all questions that are not in this article . There is also an article about the architecture of heavy interfaces (gmail, and others).

    If something is not clear - ask questions.

    Also popular now: