1

我刚刚开始使用 Backbone.js,我的测试用例正在搅动一些非常奇怪的东西。

简而言之,我所经历的是——在我调用主干模型的构造函数之后,我的对象中的一些字段似乎来自以前的项目。例如,如果我打电话:

var playlist = new Playlist({
    title: playlistTitle,
    position: playlists.length,
    userId: user.id
});

playlist.get('items').length; //1

但是,如果我这样做:

var playlist = new Playlist({
    title: playlistTitle,
    position: playlists.length,
    userId: user.id,
    items: []
});

playlist.get('items').length; //0

这是代码:

define(['ytHelper', 'songManager', 'playlistItem'], function (ytHelper, songManager, PlaylistItem) {
    'use strict';
    var Playlist = Backbone.Model.extend({
        defaults: {
            id: null,
            userId: null,
            title: 'New Playlist',
            selected: false,
            position: 0,
            shuffledItems: [],
            history: [],
            items: []
        },
        initialize: function () {
            //Our playlistItem data was fetched from the server with the playlist. Need to convert the collection to Backbone Model entities.
            if (this.get('items').length > 0) {
                console.log("Initializing a Playlist object with an item count of:", this.get('items').length);
                console.log("items[0]", this.get('items')[0]);
                this.set('items', _.map(this.get('items'), function (playlistItemData) {
                    var returnValue;
                    //This is a bit more robust. If any items in our playlist weren't Backbone.Models (could be loaded from server data), auto-convert during init.
                    if (playlistItemData instanceof Backbone.Model) {
                        returnValue = playlistItemData;
                    } else {
                        returnValue = new PlaylistItem(playlistItemData);
                    }
                    return returnValue;
                }));

                //Playlists will remember their length via localStorage w/ their ID.
                var savedItemPosition = JSON.parse(localStorage.getItem(this.get('id') + '_selectedItemPosition'));
                this.selectItemByPosition(savedItemPosition != null ? parseInt(savedItemPosition) : 0);

                var songIds = _.map(this.get('items'), function(item) {
                    return item.get('songId');
                });

                songManager.loadSongs(songIds);
                this.set('shuffledItems', _.shuffle(this.get('items')));
            }
        },
        //TODO: Reimplemnt using Backbone.sync w/ CRUD operations on backend.
        save: function(callback) {
            if (this.get('items').length > 0) {
                var selectedItem = this.getSelectedItem();
                localStorage.setItem(this.get('id') + '_selectedItemPosition', selectedItem.get('position'));
            }

            var self = this;
            console.log("Calling save with:", self);
            console.log("my position is:", self.get('position'));
            $.ajax({
                url: 'http://localhost:61975/Playlist/SavePlaylist',
                type: 'POST',
                dataType: 'json',
                contentType: 'application/json; charset=utf-8',
                data: JSON.stringify(self),
                success: function (data) {
                    console.log('Saving playlist was successful.', data);
                    self.set('id', data.id);
                    if (callback) {
                        callback();
                    }
                },
                error: function (error) {
                    console.error("Saving playlist was unsuccessful", error);
                }
            });
        },
        selectItemByPosition: function(position) {
            //Deselect the currently selected item, then select the new item to have selected.
            var currentlySelected = this.getSelectedItem();
            //currentlySelected is not defined for a brand new playlist since we have no items yet selected.
            if (currentlySelected != null && currentlySelected.position != position) {
                currentlySelected.set('selected', false);
            }

            var item = this.getItemByPosition(position);
            if (item != null && item.position != position) {
                item.set('selected', true);
                localStorage.setItem(this.get('id') + '_selectedItemPosition', item.get('position'));
            }

            return item;
        },
        getItemByPosition: function (position) {
            return _.find(this.get('items'), function(item) {
                return item.get('position') == position;
            });
        },
        addItem: function (song, selected) {
            console.log("this:", this.get('title'));
            var playlistId = this.get('id');
            var itemCount = this.get('items').length;

            var playlistItem = new PlaylistItem({
                playlistId: playlistId,
                position: itemCount,
                videoId: song.videoId,
                title: song.title,
                relatedVideos: [],
                selected: selected || false
            });

            this.get('items').push(playlistItem);
            this.get('shuffledItems').push(playlistItem);
            this.set('shuffledItems', _.shuffle(this.get('shuffledItems')));
            console.log("this has finished calling");

            //Call save to give it an ID from the server before adding to playlist.
            songManager.saveSong(song, function (savedSong) {
                song.id = savedSong.id;
                playlistItem.set('songId', song.id);
                console.log("calling save item");

                $.ajax({
                    type: 'POST',
                    url: 'http://localhost:61975/Playlist/SaveItem',
                    dataType: 'json',
                    data: {
                        id: playlistItem.get('id'),
                        playlistId: playlistItem.get('playlistId'),
                        position: playlistItem.get('position'),
                        songId: playlistItem.get('songId'),
                        title: playlistItem.get('title'),
                        videoId: playlistItem.get('videoId')
                    },
                    success: function (data) {
                        playlistItem.set('id', data.id);
                    },
                    error: function (error) {
                        console.error(error);
                    }
                });
            });

            return playlistItem;
        },
        addItemByVideoId: function (videoId, callback) {
            var self = this;
            ytHelper.getVideoInformation(videoId, function (videoInformation) {
                var song = songManager.createSong(videoInformation, self.get('id'));
                var addedItem = self.addItem(song);

                if (callback) {
                    callback(addedItem);
                }
            });
        },
        //Returns the currently selected playlistItem or null if no item was found.
        getSelectedItem: function() {
            var selectedItem = _.find(this.get('items'), function (item) {
                return item.get('selected');
            });

            return selectedItem;
        }
    });

    return function (config) {
        var playlist = new Playlist(config);
        playlist.on('change:title', function () {
            this.save();
        });
        return playlist;
    };
});

基本上,当我传入一个根本没有指定项目的配置对象时,我看到属性“项目”填充在初始化内部。如果我在我的配置对象中指定一个空白项目数组,那么初始化中没有项目,但这似乎违反直觉。难道我做错了什么?

4

1 回答 1

1

问题在于在对象中使用引用类型(数组)defaultsPlaylist在未指定值的情况下创建新模型时items,将应用默认值。在数组和对象的情况下,这是有问题的,因为基本上发生的是:

newModel.items = defaults.items

所以所有以这种方式初始化的模型都引用同一个数组。为了验证这一点,您可以测试:

var a = new Playlist();
var b = new Playlist();
var c = new Playlist({items:[]});

//add an item to a
a.get('items').push('over the rainbow');

console.log(b.get('items')); // -> ['over the rainbow'];
console.log(c.get('items')); // -> []

为了解决这个问题,Backbone 支持定义Model.defaults为函数:

var Playlist = Backbone.Model.extend({
    defaults: function() {
        return {
            id: null,
            userId: null,
            title: 'New Playlist',
            selected: false,
            position: 0,
            shuffledItems: [],
            history: [],
            items: []
        };
    }
});
于 2013-01-02T14:37:27.260 回答