📜 ⬆️ ⬇️

We study Javascript by going through the bones of Backbone.js

In this post, a javascript fan will shake his bones, getting something useful and interesting from the source Backbone.

There will not be considered the issue of using the library, this was done a long time ago on Habré, but there will be a simple js-crib with examples, in the role of examples - Backbone itself.


Lyrical digression


The idea to do something similar arises after reading a bunch of literature on the basics, patterns, algorithms, good practices. There is a feeling that there is a lot of knowledge, but they cannot be properly applied for their own purposes, moreover, the material from different books sometimes even, barely perceptible, contradicts each other. Begin to torment vague doubts.
Then I found out that good programmers, it turns out, do not use all of the above in full, but use some kind of subset and feel great (everything else can be obtained from reference books or connected as needed). Next, it was necessary to calculate this very “optimal subset”.

The professional knows exactly the answer to this question: "The best and most correct subset is the one that I use." But lovers without mentors will have to think about brains.
Although, you can not waste time, but immediately believe that the source code of the Underscore + Backbone bundle contains exactly the necessary optimally adjusted minimum of “basic javascript”, “algorithms”, “patterns” and “best practices”.
')
Most do not agree, especially Douglas Crockford, besides there they forgot to add Dijkstra's algorithm for circumventing the graphs and, what a mess, there are no promises!

For those who still believed me, I decided to create a synopsis that would capture more unique tricks from Backbone, allowed javascript to be repeated by those who know it and understand where to dig - those who do not know it. Those who do not know, should immediately open the source code of the backbone - underscore and the reading campaign to do a task like: “Find ten cases where this technique occurs, try to understand what exactly is happening in each case, follow Firebug, and then write your outline with the moments that the author missed the post.

Module creation
(function(root, factory) { //      if (typeof define === 'function' && define.amd) { //  ,     define  amd, define(['underscore', 'jquery', 'exports'], function(_, $, exports) { //      //   -        root.Backbone = factory(root, exports, _, $); }); //   Node,  jQuery   } else if (typeof exports !== 'undefined') { var _ = require('underscore'); factory(root, exports, _); //    } else { root.Backbone = factory(root, {}, root._, (root.jQuery || root.Zepto || root.ender || root.$)); } }(this, function(root, Backbone, _, $) { })); 



Factory structure
 function(root, Backbone, _, $) { //  ,       Backbone var previousBackbone = root.Backbone; //          var array = []; var push = array.push; var slice = array.slice; var splice = array.splice; //  Backbone.VERSION = '1.1.2'; //       Backbone.$ = $; //  , ,     Backbone.noConflict = function() { root.Backbone = previousBackbone; return this; }; //     Backbone.emulateHTTP = false; Backbone.emulateJSON = false; //  ,       //   ,   ,    - var Events = Backbone.Events = { //  ,  ,    obj.trigger(' '); on: function(name/* */, callback/**/, context/* ,   this  */) { }, // ,    once: function(name, callback, context) { }, //    off: function(name, callback, context) { }, //    trigger: function(name) { }, //   -     stopListening: function(obj, name, callback) {}; //    ,  -  this     var listenMethods = {listenTo: 'on', listenToOnce: 'once'}; //     ,     Events.bind = Events.on; Events.unbind = Events.off; //  Backbone     _.extend(Backbone, Events); //  . //       - var Model = Backbone.Model = function(attributes, options) {}; //   ,    _.extend(Model.prototype, Events/*      */, {/*  */}); //   //     ,    var Collection = Backbone.Collection = function(models, options) { }; // ,   ,   _.extend(Collection.prototype, Events, {}); //   //  -   ,            jQuery var View = Backbone.View = function(options) {}; //  _.extend(View.prototype, Events, {}); //   /    // -   jQuery.ajax Backbone.sync = function(method, model, options) {}; //         //      ,      //     history var Router = Backbone.Router = function(options) {}; _.extend(Router.prototype, Events, {}); //     ,   //    var History = Backbone.History = function() {}; _.extend(History.prototype, Events, {}); // C  Backbone.history = new History; //    return Backbone; } 



Prototype chain
 var extend = function(protoProps/*   */, staticProps/*   */) { var parent = this; //  ,    var child; //  ,   if (protoProps && _.has(protoProps, 'constructor')) { //       child = protoProps.constructor; //       } else { //        child = function(){ return parent.apply(this, arguments); }; //  this    child } //    child ,      _.extend(child, parent, staticProps); //  ,      //  : child.staticProp(); //  child  ,   var Surrogate = function(){ this.constructor = child; }; //    Surrogate.prototype = parent.prototype; //     parent child.prototype = new Surrogate; //     parent //     ,  : var x = new child; x.protoProp(); if (protoProps) _.extend(child.prototype, protoProps); // ,   , ,       child.__super__ = parent.prototype; return child; }; //           Model.extend = Collection.extend = Router.extend = View.extend = History.extend = extend; 



Working with this, arguments, prototype, constructor
  //           var events = this._events[name] || (this._events[name] = []); //   this -  Events //    ,      _.extend(Model.prototype, Events); //  ,          (function(root, factory) {}(this, function(root, Backbone, _, $) {})) //    // ,  once: function(name, callback, context) { var self = this; //  ,    var once = _.once(function() { self.off(name, once); //  ,          callback.apply(this, arguments); }); once._callback = callback; //         ,          return this.on(name, once, context); // -     }, trigger: function(name) { var args = slice.call(arguments, 1); //     if (events) triggerEvents(events, args); // ,     ,    if (allEvents) triggerEvents(allEvents, arguments); // ,     ,       return this; } //          var triggerEvents = function(events, args) { var ev, i = -1, l = events.length, a1 = args[0], a2 = args[1], a3 = args[2]; switch (args.length) { case 0: while (++i < l) (ev = events[i]).callback.call(ev.ctx); return; //   this  ev.ctx case 1: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1); return; case 2: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2); return; case 3: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2, a3); return; //   ,     : ev.ctx.callback(args[0], args[1], args[2]  ..) default: while (++i < l) (ev = events[i]).callback.apply(ev.ctx, args); return; } }; // ,      ,   ,     this.initialize.apply(this, arguments); has: function(attr) { //    return this.get(attr) != null; //       } // ,    ,     if (!diff) return this.hasChanged() ? _.clone(this.changed) : false; fetch: function(options) { options = options ? _.clone(options) : {}; //         if (options.parse === void 0) options.parse = true; //    -    var model = this; //    ,     var success = options.success; //    options.success = function(resp) { //  ,        ,    ,   if (!model.set(model.parse(resp, options), options)) return false; if (success) success(model, resp, options); //  ,    model.trigger('sync', model, resp, options); }; wrapError(this, options); // error    ,     return this.sync('read', this, options); //           }, //   return new this.constructor(this.attributes); //    var modelMethods = ['keys', 'values', 'pairs', 'invert', 'pick', 'omit']; _.each(modelMethods, function(method) { Model.prototype[method] = function() { //     var args = slice.call(arguments); //     args.unshift(this.attributes); // c     return _[method].apply(_, args); //     Underscore }; }); attrs instanceof Model //      //       this[first ? 'find' : 'filter'](function(model) { for (var key in attrs) { if (attrs[key] !== model.get(key)) return false; } return true; }); this.models.sort(_.bind(this.comparator, this)/*  ,        comparator.call(this)*/); 



Regular Expression Examples
  //     () var eventSplitter = /\s+/; //     //         var names = name.split(eventSplitter) //      ,      //    ,      id c    base.replace(/([^\/])$/, '$1/') + encodeURIComponent(this.id) //       _routeToRegExp: function(route) { route = route.replace(escapeRegExp, '\\$&') .replace(optionalParam, '(?:$1)?') .replace(namedParam, function(match, optional) { return optional ? match : '([^/?]+)'; }) .replace(splatParam, '([^?]*?)'); return new RegExp('^' + route + '(?:\\?([\\s\\S]*))?$'); } //      var params = route.exec(fragment).slice(1); //       var match = (window || this).location.href.match(/#(.*)$/); 



Use $
  //     $: function(selector) { return this.$el.find(selector); }, this.el = this.$el[0]; //     method = _.bind(method, this); //  , ,        eventName += '.delegateEvents' + this.cid; //      if (selector === '') { this.$el.on(eventName, method); //   } else { // .. jQuery  , ,   -     this.$el.on(eventName, selector, method); } this.$el.off('.delegateEvents' + this.cid); //   - //       var $el = Backbone.$('<' + _.result(this, 'tagName') + '>').attr(attrs); //    var xhr = options.xhr = Backbone.ajax(_.extend(params, options)); Backbone.ajax = function() { return Backbone.$.ajax.apply(Backbone.$, arguments); }; 



Browser Conversion Processing
  //        if (typeof window !== 'undefined') { this.location = window.location; this.history = window.history; } //   fragment = decodeURI(this.location.pathname + this.location.search) Backbone.$(window).on('popstate', this.checkUrl); //     Backbone.$(window).on('hashchange', this.checkUrl); this.location.replace(this.root + '#' + this.fragment); //   //      Backbone.$(window).off('popstate', this.checkUrl).off('hashchange', this.checkUrl); 



Different stuff
  //       Backbone       root.Backbone = factory(root, {}/*  */, root._, (root.jQuery || root.Zepto || root.ender || root.$)) // ,   ,  on: function(name, callback, context) { //          if (!eventsApi(this, 'on', name, [callback, context]) || !callback) return this; //      } off: function(name, callback, context) { var retain, ev, events, names, i, l, j, k; //     ,   -      return this; }, //         if (obj) (listeningTo = {})[obj._listenId] = obj; //     delete this._listeningTo[id] //       var remove = !name && !callback; //      //   eventsApi, ,   x.on({     }) for (var key in name) { obj[action].apply(obj, [key, name[key]].concat(rest)); //       x.on(''); } //    ( ) var listenMethods = {listenTo: 'on', listenToOnce: 'once'}; _.each(listenMethods, function(implementation, method) { Events[method] = function(obj, name, callback) { //      obj[implementation](name, callback, this); return this; }; }); //   ,    var attrs = attributes || {}; options || (options = {}); unset: function(attr, options) { return this.set(attr, void 0/* undefined*/, _.extend({}, options, {unset: true})/*     */); } //         if (key == null || typeof key === 'object') { attrs = key; options = val; } else { (attrs = {})[key] = val; } //       . method = this.isNew() ? 'create' : (options.patch ? 'patch' : 'update'); //   ,   ,     , ,  var iterator = _.isFunction(value) ? value : function(model) { return model.get(value); }; //        var methodMap = { 'create': 'POST', 'update': 'PUT', 'patch': 'PATCH', 'delete': 'DELETE', 'read': 'GET' }; //    throw new Error('A "url" property or function must be specified'); //    ,     var wrapError = function(model, options) { var error = options.error; options.error = function(resp) { if (error) error(model, resp, options); model.trigger('error', model, resp, options); }; }; 

Source: https://habr.com/ru/post/240969/


All Articles