Backbone.js 的技巧和模式 已翻译 100%

yale8848 投递于 2013/08/21 21:37 (共 26 段, 翻译完成于 09-29)
阅读 12047
收藏 75
7
加载中

自从3年前Backbone.js发布第一版以来,Backbone.js就成为一个流行的开源JavaScript “MV*”框架,并获得人们的青睐。尽管Backbone.js给JavaScript应用提供了框架,但是它仍然给开发者留有很多设计模式供选择,不管怎样,当开发者第一次使用Backbone.js时还会产生很多普遍的问题的。

因此,在这篇文章中,我们将介绍很多不同的设计模式供你在Backbone.js应用中使用,而且我们也会一同来看看对于开发者来说会产生很多普遍的有关性能伸缩的问题。


应用就和建筑一样,都是构建于熟知的模式。(图片提供:Matthew Rutledge)

yale8848
翻译于 2013/09/15 13:49
1

对象深度拷贝

JavaScript对待所有原生类型变量是传值。所以,当变量被引用时就传递了变量的值。

var helloWorld = “Hello World”;
var helloWorldCopy = helloWorld;
举个例子,上面的代码将变量helloWorldCopy的值设置为变量helloWorld的值。这样, 自从它的值被复制之后,所有修改helloWorldCopy的值不会修改helloWorld的值。JavaScript对待所有非原始类型的变量时传引用,这就意味着当变量传递的时候将会传递内存地址引用。


var helloWorld = {
    ‘hello’: ‘world’
}
var helloWorldCopy = helloWorld;
yale8848
翻译于 2013/09/15 14:27
1

举个例子,上面的代码将设置helloWorldCopy为helloWorld的引用,而且,也许你会猜到任何修改helooWorldCopy的值都会直接导致helloWorld值的变化。如果你想要helloWorld的拷贝,你可以创建一个拷贝对象即可。

也许你会想到“为什么Backbone.js可以解释为所有的工作都是通过传递引用?”事实上,Backbone.js不会拷贝对象,这将意味着如果你从模型里调用.get()方法获得一个对象,任何给这个对象的修改都会直接修改原来的对象。让我们一起来看一个例子来阐明哪里会发生这样的情况。如果你有个如下的Person模型:

var Person = Backbone.Model.extend({
   defaults: {
        'name': 'John Doe',
        'address': {
            'street': '1st Street'
            'city': 'Austin',
            'state': 'TX'
            'zipCode': 78701
        }
   }
});
这样你就创建了一个新的person对象:
var person = new Person({
    'name': 'Phillip W'
});
现在我们来对新对象的一些属性进行操作操作:
person.set('name', 'Phillip W.', { validate: true }); 
上面的代码成功的给person对象的name属性赋了值。现在我们在来操作person对象的地址属性。当然,在我们这样做之前先验证一下地址属性。
var Person = Backbone.Model.extend({
    validate: function(attributes) {

        if(isNaN(attributes.address.zipCode)) return "Address ZIP code must be a number!";
    },

    defaults: {
        'name': 'John Doe',
        'address': {
            'street': '1st Street'
            'city': 'Austin',
            'state': 'TX'
            'zipCode': 78701
        }
    } 
});
现在,让我们试图给地址属性设置一个不正确的ZIP代码。
var address = person.get('address');
address.zipCode = 'Hello World';
// Raises an error since the ZIP code is invalid
person.set('address', address, { validate: true });
console.log(person.get('address'));
/* Prints an object with these properties.
{
    'street': '1st Street'
    'city': 'Austin',
    'state': 'TX'
    'zipCode': 'Hello World'
}
*/
yale8848
翻译于 2013/09/17 16:45
1

这将会怎样呢?我们的验证出现了错误!为什么属性依旧被改变了?前边我们说过,Backbone.js不会拷贝模型属性;它会返回你所请求的一切。这样,你也许会猜到,如果你需要一个对象,你将得到这个对象的引用,对这个对象的任何操作都会直接改变模型里的对象。如果你要debug,这可能将把你带入到无底的兔子黑洞。

一个很深的模式对象引用可能让你在debug时调入兔子洞中。(图:David Orban)

这个问题对于新的Backbone.js使用要引起注意,甚至对于老练的JavaScript程序员有时也会没有提防。这个问题在GitHub的Backbone.js讨论组中有很激烈的讨论。正如Jeremy Ashkenas指出,执行一个深的对象引用是个很难解决的难题,一个很深的对象引用是要花费很大代价的。

yale8848
翻译于 2013/09/18 08:41
1

幸运的,jQuery 提供了一个深度拷贝功能来实现,$.extend. 如同, Underscore.js ,一个Backbone.js的依靠,提供_.extend 方法,但是我必须避免使用它,因为它没有执行一份是个深度的复制,Lo-Dash, Underscore.js的一个分叉版本,提供了对象一个深度克隆的_.clone 方法的选项。然而,我使用 $.extend 方法的模型使用的语法规则去执行一个任意对象的深度克隆。记得通过后,结果它执行的是一个深度的克隆方法

var address = $.extend(true, {}, person.address);

我们现在快速准确的复制一个theaddressobject?,并且我们能够更改它对于我们要点没有包括在内的我们不用担心会更改它原有的模型。你必须要意思到这个父工厂对于上面所有的事例因为它所有的地址对象成员都是不可变的(numbers, strings, etc.),与此同时这上面所有的事例工厂当你要深度复制对象里面包含的对象时你都必须小心的使用。你必须也要知道一个小小的性能影响都来自于执行一个深度的克隆,但是我从来没有看到过很明显的问题。然而,如果你要深度的克隆一个大对象或者成千上万的对象所有的立即复制,你将有可能做大量的性能分析。这将领导我们直接到下一个模式。

翻译于 2013/09/21 10:08
1

为对象创建外观

在现实世界中,需求经常变化,JavaScript对象符号(或者说JSON)也是一样,这些对象是由模型和集合所在的端点返回的。这或许会成为你的基础代码中的一个真正的大麻烦,如果你的视图与底层的数据模型是紧耦合的话。因此,我为所有对象创建了getters和setters

支持这个模式的人非常多。如果任何底层的数据结构改变了,那么视图层并不需要更新许多;你将有一个数据的访问点,所以你不太可能忘记做一个深度拷贝,你的代码将会更易于维护更易于调试。负面因素在于这个模式可能导致模型或集合的一点点膨胀。

super0555
翻译于 2013/09/17 17:20
1

我们看一个例子来阐明这个模式。想像我们有一个Hotel模型,包含有rooms和目前可获得的rooms,而且我们希望可以通过床位大小来获得rooms。

var Hotel = Backbone.Model.extend({
    defaults: {
        "availableRooms": ["a"],
        "rooms": {
            "a": {
                "size": 1200,
                "bed": "queen"
            },
            "b": {
                "size": 900,
                "bed": "twin"
            },
            "c": {
                "size": 1100,
                "bed": "twin"
            }
        },

        getRooms: function() {
            $.extend(true, {}, this.get("rooms"));
        },

        getRoomsByBed: function(bed) {
            return _.where(this.getRooms(), { "bed": bed });
        }
    }
});

现在我们假设明天你就要发布你的代码,而你又发现端点开发者忘记告诉你rooms的数据结构改变了,由一个对象变为一个数组。你的代码现在看起来会像下面这样。

var Hotel = Backbone.Model.extend({
    defaults: {
        "availableRooms": ["a"],
        "rooms": [
            {
                "name": "a",
                "size": 1200,
                "bed": "queen"
            },
            {
                "name": "b",
                "size": 900,
                "bed": "twin"
            },
            {
                "name": "c",
                "size": 1100,
                "bed": "twin"
            }
        ],

        getRooms: function() {
            var rooms = $.extend(true, {}, this.get("rooms")),
             newRooms = {};

            // transform rooms from an array back into an object
            _.each(rooms, function(room) {
                newRooms[room.name] = {
                    "size": room.size,
                    "bed": room.bed
                }
            });
        },

        getRoomsByBed: function(bed) {
            return _.where(this.getRooms(), { "bed": bed });
        }
    }
});

我们仅仅更新了一个函数,以便将Hotel的结构转变为这个应用的其余部分所期望的结构,同时整个应用仍然像我们所期待的一样运作。如果这里没有一个getter,我们很可能不得不为rooms更新每个访问点。理想情况下,你会希望更新所有的函数,以适应新的数据结构,但如果你在时间方面有压力急于发布的话,这个模式将可以拯救你。

离题说一句,这个模式既可以被认为是装饰模式,因为它隐藏了创建对象拷贝的复杂性,也可以认为是桥接模式,因为它可以用来将数据转换为所期望的形式。一个好的经验是对任何对象元素使用getters 和setters

super0555
翻译于 2013/09/17 17:41
1

存储数据不是通过服务器保存

尽管Backbone.js有模型和集合映射的规定去具象状态的传输(or REST-ful)的端点,你将花大量的时间去找你想要的存储数据在你的模型或者不是在服务器上的连接。另外一些关于Backbone.js的文章,例如“Backbone.js Tips: Lessons From the Trenches” 是通过SupportBee的Prateek Dayal ,这个模式还有其他的描述。让我们一起来快速的看一个小例子来帮助我们说明它可能会派上用场。假设你有一个集合。

<ul>
	<li><a href="#" data-id="1">One</a></li>
	<li><a href="#" data-id="2">Two</a></li>
    . . .
	<li><a href="#" data-id="n">n</a></li>
</ul>
当使用者点击其中一个项目时,这个项目成为了被选中状态并且对于使用者作为选中项目是通过 aselectedclass 添加的是可视化的。以下这是一种方式:


var Model = Backbone.Model.extend({
    defaults: {
        items: [
            {
                "name": "One",
                "id": 1           
            },
            {
                "name": "Two",
                "id": 2           
            },
            {
                "name": "Three",
                "id": 3           
            }
        ]
    }
});

var View = Backbone.View.extend({
    template: _.template($('#list-template').html()),

    events: {
        "#items li a": "setSelectedItem"
    },

    render: function() {
        $(this.el).html(this.template(this.model.toJSON()));
    },

    setSelectedItem: function(event) {
        var selectedItem = $(event.currentTarget);
        // Set all of the items to not have the selected class
        $('#items li a').removeClass('selected');
        selectedItem.addClass('selected');
        return false;
    }
});
<script id="list-template" type="template">
<ul id="items">
        <% for(i = items.length - 1; i >= 0; i--) { %>
	<li>
                <a href="#" data-id="<%= item[i].id %>"><%= item[i].name %></a></li>
<% } %></ul>
</script>

现在我们能够很容易的判断被选中的项目,并且我们没有必要通过对象模型去判断。这种模式对于存储无用的数据是非常有用的以至于 你可能非常想要去跟踪;请记住你能够创建一个模型并且没有必要去关联于他们存储的一些无用的图像数据。

var View = Backbone.View.extend({
    initialize: function(options) {
        // Re-render when the model changes
        this.model.on('change:items', this.render, this);
    },

    template: _.template($('#list-template').html()),

    events: {
        "#items li a": "setSelectedItem"
    },

    render: function() {
        $(this.el).html(this.template(this.model.toJSON()));
    },

    setSelectedItem: function(event) {
        var selectedItem = $(event.currentTarget);
        // Set all of the items to not have the selected class
        $('#items li a').removeClass('selected');
        selectedItem.addClass('selected');
        // Store a reference to what item was selected
        this.selectedItemId = selectedItem.data('id'));
        return false;
    }
});

现在我们可以很容易的确定哪些项已经被选中,并且我们没有必要通过这些对象模型来了解。这个模式对于存储无用的数据是非常有用的,请记住,您可以创建不一定有端点相关联的存储无关的视图数据的模型和集合。

翻译于 2013/09/22 20:01
1

这种模式的缺点是你存储了无用的数据在你的模型或者集合中,它们不能真正意义上的追随一个平静的架构是因为它们不会完美的去映射在web资源上;另外,这个模式会引起一些很膨胀的在你的模型中;;并且当你保存你的模型的时候如果你的端点严格的只接受JSON数据它会引起一个很大的烦恼。

你可能会问你自己,“我如何确定我是否应该讲把额外的数据放进视图或者是模型中?”。如果额外的属性你将要增加的是围绕性的呈现,例如一个容器的高度,我们应该要添加它的图形。如果这个属性跟底层的数据模型有一些关系,然后你想要将它放进这个模型中。例如,如果上面的例子更多的显露出,因为某些原因我仅仅只希望用户通过从模型返回的项目列表中选择一个特殊的项,我可能会增加这种逻辑模型。总而言之,大多数的事情,它实际上取决于这种依赖。你能够为保持你的模型而辩论并且你可以认为保持你的观点是可能的并且把尽可能多的逻辑放进你的模型中。

翻译于 2013/09/22 21:07
1

渲染部分视图,而不是整个视图

当你第一次开始开发Backbone.js应用时,典型的视图结构是像这样的:

var View = Backbone.View.extend({
    initialize: function(options) {
        this.model.on('change', this.render, this);
    },

    template: _.template($(‘#template’).html()),

    render: function() {
        this.$el.html(template(this.model.toJSON());
        $(‘#a’, this.$el).html(this.model.get(‘a’));
        $(‘#b’, this.$el).html(this.model.get(‘b’));
    }
});

在这里,任何对模型的改变都会触发对视图的一个全面的重新渲染。我第一次用Backbone.js开发时,我是这个模式的实践者。但随着视图代码的增长,我迅速的意识到,这种方法不利于维护或优化,因为当模型的任何一个属性发生变化时,视图将会完全的重新渲染。

super0555
翻译于 2013/09/21 12:41
1
本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接。
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
加载中

评论(8)

小小黄鸡
小小黄鸡
点赞
ishengsheng
ishengsheng

引用来自“nickliule”的评论

写的不错!学些了!
me,too!
n
nickliule
写的不错!学些了!
yale8848
yale8848

引用来自“super0555”的评论

引用来自“yale8848”的评论

引用来自“galeo”的评论

文中好多代码都有问题啊,包括显而易见的字符串的*引号*……

缺乏严谨的态度啊

奇怪了,http://coding.smashingmagazine.com/2013/08/09/backbone-js-tips-patterns/ 這個是原文,裡面也有問題,作者Austin應該是用英語的。
感謝您的指正!請 @红薯 能否開一下權限,第2,10,11,19,20,22段,我修改一下,謝謝!麻煩 @super0555 也看一下,謝謝!

确实是检查不够仔细啊,只顾着理解转换英文意思了,没有看源码部分,没想到源码引号还有这么多种,真的很奇怪啊,怀疑可能是作者复制文章的时候或者页面编码转换时候的问题。

是啊,我也是,代码部分没有仔细看。等红薯给咱开权限后再修改吧!
super0555
super0555

引用来自“yale8848”的评论

引用来自“galeo”的评论

文中好多代码都有问题啊,包括显而易见的字符串的*引号*……

缺乏严谨的态度啊

奇怪了,http://coding.smashingmagazine.com/2013/08/09/backbone-js-tips-patterns/ 這個是原文,裡面也有問題,作者Austin應該是用英語的。
感謝您的指正!請 @红薯 能否開一下權限,第2,10,11,19,20,22段,我修改一下,謝謝!麻煩 @super0555 也看一下,謝謝!

确实是检查不够仔细啊,只顾着理解转换英文意思了,没有看源码部分,没想到源码引号还有这么多种,真的很奇怪啊,怀疑可能是作者复制文章的时候或者页面编码转换时候的问题。
yale8848
yale8848

引用来自“galeo”的评论

文中好多代码都有问题啊,包括显而易见的字符串的*引号*……

缺乏严谨的态度啊

奇怪了,http://coding.smashingmagazine.com/2013/08/09/backbone-js-tips-patterns/ 這個是原文,裡面也有問題,作者Austin應該是用英語的。
感謝您的指正!請 @红薯 能否開一下權限,第2,10,11,19,20,22段,我修改一下,謝謝!麻煩 @super0555 也看一下,謝謝!
g
galeo
文中好多代码都有问题啊,包括显而易见的字符串的*引号*……

缺乏严谨的态度啊
yfwz100
yfwz100
深度复制。。。这篇文章翻译得有点机械。。。
返回顶部
顶部