What are these patterns in this Backbone TodoMVC e

2020-02-14 07:26发布

问题:

Looking into the todomvc backbone codes example. The structure in the js/ fold:

├── app.js
├── collections
│   └── todos.js
├── models
│   └── todo.js
├── routers
│   └── router.js
└── views
    ├── app-view.js
    └── todo-view.js

app.js

var app = app || {};
$(function () {
    'use strict';
    // kick things off by creating the `App`
    new app.AppView();
});

collections/todos.js

var app = app || {};

(function () {
    'use strict';
    var Todos = Backbone.Collection.extend({
    model: app.Todo,
    app.todos = new Todos();
})();

models/todo.js

var app = app || {};

(function () {
    'use strict';
    app.Todo = Backbone.Model.extend({
    });
})();

views/app-view.js

var app = app || {};
(function ($) {
    'use strict';
    app.AppView = Backbone.View.extend({
})(jQuery);

I have two questions:

  1. why var app = app || {} in each file?

  2. What are the differences between $(function(){}), (function(){})(), and (function($))(jQuery)?

回答1:

  1. app variable is global and encapsulates entire Backbone application to minimize global namespace pollution. Here you can find more details about Namespacing Patterns.

    var app = app || {} initializes global app variable with new empty object if it is not initialized yet. Otherwise it will be untouched.

  2. The functions:

    • $(function(){}) is a shortcut for jQuery's $(document).ready(function(){}). Docs
    • (function(){})() is an Immediately-invoked function expression (IIFE) without parameters
    • (function($){ /* here $ is safe jQuery object */ })(jQuery) is IIFE with parameter - jQuery object will be passed as $ into that anonymous function

$(function() {
  console.log("Document ready event");
});

$(document).ready(function() {
  console.log("Document ready event");
});

(function() {
  console.log("Immediately-invoked function expression without parameters");
})();

(function($) {
  console.log("Immediately-invoked function expression with parameter. $ is a jQuery object here:");
  console.log($.fn.jquery);
})(jQuery);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>



回答2:

While Yurii explained the difference between all the patterns, it's missing the "why" you would need these.

Namespacing and scoping

The overrall goal of the following patterns is mostly namespacing and scoping, with different benefits. It's a good practice to avoid polluting the global namespace, and since JavaScript doesn't have namespace as a core feature, other patterns have emmerged to solve that.

See How do I declare a namespace.

Global namespace

var app = app || {}; // if it doesn't exist yet, make it an new object.

In order to avoid polluting the global namespace (AKA making everything a global variable), you create only one variable, inside of which you insert every other modules of your app.

Then, each file exports its module into that sole global variable.

Note that the order of the files is still important if a module depends on another.

If we look at the TodoMVC example, they included the files in a specific order:

<script src="js/models/todo.js"></script>
<script src="js/collections/todos.js"></script>
<script src="js/views/todo-view.js"></script>
<script src="js/views/app-view.js"></script>
<script src="js/routers/router.js"></script>
<script src="js/app.js"></script>

Scoping

Imagine you declared var test = 2; in a file and it's a critical variable that is used throughout that module. Then, in another file, you copy the good pattern you were using inside the first module. You've just overriden the test variable and now, it is involontary shared between two modules.

In order to have local functions and variables private to a module, you can scope them with an Immediately-invoked function expression (IIFE). Block scoping is relatively new and not well-supported yet, so the safest way is to use the function scope.

var app = app || {}; // global

(function () {
    // private to this scope
    var Todos = Backbone.Collection.extend({});

    // export the Todos constructor to the global app namespace
    app.Todos = Todos;

    function localFunction(param) { /** snip **/ }
})();