Backbone.js自定义同步 #

一、自定义sync概述 #

1.1 为什么要自定义sync #

text
自定义sync场景
├── 自定义存储:localStorage、IndexedDB
├── 自定义API:GraphQL、WebSocket
├── 添加认证:统一添加Token
└── 数据转换:请求/响应数据转换

1.2 sync方法签名 #

javascript
Backbone.sync = function(method, model, options) {
    // method: 'read', 'create', 'update', 'patch', 'delete'
    // model: 模型或集合实例
    // options: 配置选项
    
    // 返回一个Promise或jqXHR
};

二、覆盖sync #

2.1 全局覆盖 #

javascript
var originalSync = Backbone.sync;

Backbone.sync = function(method, model, options) {
    console.log('Sync:', method, model);
    
    return originalSync.call(this, method, model, options);
};

2.2 模型级别覆盖 #

javascript
var User = Backbone.Model.extend({
    urlRoot: '/api/users',
    
    sync: function(method, model, options) {
        console.log('User sync:', method);
        return Backbone.sync(method, model, options);
    }
});

2.3 集合级别覆盖 #

javascript
var Users = Backbone.Collection.extend({
    url: '/api/users',
    
    sync: function(method, collection, options) {
        console.log('Users sync:', method);
        return Backbone.sync(method, collection, options);
    }
});

三、添加认证 #

3.1 统一添加Token #

javascript
var AuthSync = {
    sync: function(method, model, options) {
        options = options || {};
        
        options.headers = _.extend({
            'Authorization': 'Bearer ' + localStorage.getItem('token'),
            'Content-Type': 'application/json'
        }, options.headers);
        
        return Backbone.sync(method, model, options);
    }
};

var User = Backbone.Model.extend({
    urlRoot: '/api/users'
});

_.extend(User.prototype, AuthSync);

3.2 自动刷新Token #

javascript
var RefreshTokenSync = {
    sync: function(method, model, options) {
        var token = localStorage.getItem('token');
        var refreshToken = localStorage.getItem('refreshToken');
        
        options = options || {};
        options.headers = options.headers || {};
        options.headers['Authorization'] = 'Bearer ' + token;
        
        var originalError = options.error;
        options.error = function(xhr) {
            if (xhr.status === 401 && refreshToken) {
                this.refreshToken().then(function() {
                    options.headers['Authorization'] = 'Bearer ' + localStorage.getItem('token');
                    Backbone.sync(method, model, options);
                });
            } else if (originalError) {
                originalError.apply(this, arguments);
            }
        }.bind(this);
        
        return Backbone.sync(method, model, options);
    },
    
    refreshToken: function() {
        return $.ajax({
            url: '/api/auth/refresh',
            method: 'POST',
            data: JSON.stringify({
                refreshToken: localStorage.getItem('refreshToken')
            }),
            contentType: 'application/json'
        }).done(function(response) {
            localStorage.setItem('token', response.token);
        });
    }
};

四、LocalStorage适配器 #

4.1 基本实现 #

javascript
var LocalStorageSync = {
    sync: function(method, model, options) {
        var store = localStorage.getItem(model.urlRoot || model.url);
        var data = store ? JSON.parse(store) : [];
        
        var resp, status;
        
        switch (method) {
            case 'read':
                resp = model.id ? _.find(data, { id: model.id }) : data;
                status = resp ? 'success' : 'error';
                break;
                
            case 'create':
                model.set({ id: Date.now() });
                data.push(model.toJSON());
                resp = model.toJSON();
                status = 'success';
                break;
                
            case 'update':
                var index = _.findIndex(data, { id: model.id });
                if (index !== -1) {
                    data[index] = model.toJSON();
                }
                resp = model.toJSON();
                status = 'success';
                break;
                
            case 'patch':
                var index = _.findIndex(data, { id: model.id });
                if (index !== -1) {
                    _.extend(data[index], model.changedAttributes());
                }
                resp = model.toJSON();
                status = 'success';
                break;
                
            case 'delete':
                data = _.reject(data, { id: model.id });
                resp = model.toJSON();
                status = 'success';
                break;
        }
        
        localStorage.setItem(model.urlRoot || model.url, JSON.stringify(data));
        
        if (status === 'success') {
            if (options.success) options.success(resp);
            return $.Deferred().resolve(resp).promise();
        } else {
            if (options.error) options.error('Not found');
            return $.Deferred().reject('Not found').promise();
        }
    }
};

4.2 使用LocalStorage #

javascript
var Todo = Backbone.Model.extend({
    urlRoot: 'todos',
    
    sync: LocalStorageSync.sync
});

var Todos = Backbone.Collection.extend({
    model: Todo,
    url: 'todos',
    
    sync: LocalStorageSync.sync
});

五、自定义API适配器 #

5.1 GraphQL适配器 #

javascript
var GraphQLSync = {
    endpoint: '/graphql',
    
    sync: function(method, model, options) {
        var query, variables;
        
        switch (method) {
            case 'read':
                if (model.id) {
                    query = 'query($id: ID!) { user(id: $id) { id name email } }';
                    variables = { id: model.id };
                } else {
                    query = 'query { users { id name email } }';
                    variables = {};
                }
                break;
                
            case 'create':
                query = 'mutation($input: UserInput!) { createUser(input: $input) { id name email } }';
                variables = { input: model.toJSON() };
                break;
                
            case 'update':
                query = 'mutation($id: ID!, $input: UserInput!) { updateUser(id: $id, input: $input) { id name email } }';
                variables = { id: model.id, input: model.toJSON() };
                break;
                
            case 'delete':
                query = 'mutation($id: ID!) { deleteUser(id: $id) }';
                variables = { id: model.id };
                break;
        }
        
        return $.ajax({
            url: this.endpoint,
            method: 'POST',
            data: JSON.stringify({ query: query, variables: variables }),
            contentType: 'application/json',
            success: function(response) {
                var data = response.data;
                if (options.success) options.success(data[Object.keys(data)[0]]);
            },
            error: options.error
        });
    }
};

5.2 WebSocket适配器 #

javascript
var WebSocketSync = {
    socket: null,
    callbacks: {},
    
    connect: function(url) {
        var self = this;
        
        this.socket = new WebSocket(url);
        
        this.socket.onmessage = function(event) {
            var response = JSON.parse(event.data);
            var callback = self.callbacks[response.requestId];
            
            if (callback) {
                delete self.callbacks[response.requestId];
                
                if (response.success) {
                    callback.success(response.data);
                } else {
                    callback.error(response.error);
                }
            }
        };
    },
    
    sync: function(method, model, options) {
        var requestId = _.uniqueId('req_');
        
        this.callbacks[requestId] = {
            success: options.success,
            error: options.error
        };
        
        this.socket.send(JSON.stringify({
            requestId: requestId,
            method: method,
            model: model.urlRoot || model.url,
            id: model.id,
            data: model.toJSON()
        }));
        
        return $.Deferred().promise();
    }
};

六、数据转换 #

6.1 请求转换 #

javascript
var TransformSync = {
    sync: function(method, model, options) {
        options = options || {};
        
        if (method === 'create' || method === 'update') {
            var data = model.toJSON();
            
            data = this.transformRequest(data);
            
            options.data = JSON.stringify(data);
            options.contentType = 'application/json';
        }
        
        return Backbone.sync(method, model, options);
    },
    
    transformRequest: function(data) {
        var transformed = {};
        
        for (var key in data) {
            transformed[this.toSnakeCase(key)] = data[key];
        }
        
        return transformed;
    },
    
    toSnakeCase: function(str) {
        return str.replace(/([A-Z])/g, '_$1').toLowerCase();
    }
};

6.2 响应转换 #

javascript
var ResponseTransformSync = {
    sync: function(method, model, options) {
        options = options || {};
        var originalSuccess = options.success;
        
        options.success = function(response) {
            response = this.transformResponse(response);
            if (originalSuccess) originalSuccess(response);
        }.bind(this);
        
        return Backbone.sync(method, model, options);
    },
    
    transformResponse: function(data) {
        var transformed = {};
        
        for (var key in data) {
            transformed[this.toCamelCase(key)] = data[key];
        }
        
        return transformed;
    },
    
    toCamelCase: function(str) {
        return str.replace(/_([a-z])/g, function(g) {
            return g[1].toUpperCase();
        });
    }
};

七、实用示例 #

7.1 缓存同步 #

javascript
var CachedSync = {
    cache: {},
    cacheTTL: 5 * 60 * 1000,
    
    sync: function(method, model, options) {
        if (method !== 'read') {
            return Backbone.sync(method, model, options);
        }
        
        var cacheKey = model.url();
        var cached = this.cache[cacheKey];
        
        if (cached && Date.now() - cached.timestamp < this.cacheTTL) {
            if (options.success) {
                options.success(cached.data);
            }
            return $.Deferred().resolve(cached.data).promise();
        }
        
        var originalSuccess = options.success;
        options.success = function(response) {
            this.cache[cacheKey] = {
                data: response,
                timestamp: Date.now()
            };
            if (originalSuccess) originalSuccess(response);
        }.bind(this);
        
        return Backbone.sync(method, model, options);
    },
    
    clearCache: function(key) {
        if (key) {
            delete this.cache[key];
        } else {
            this.cache = {};
        }
    }
};

7.2 批量操作同步 #

javascript
var BatchSync = {
    pending: [],
    timer: null,
    delay: 100,
    
    sync: function(method, model, options) {
        if (method === 'create' || method === 'update') {
            this.pending.push({
                method: method,
                model: model,
                options: options
            });
            
            if (this.timer) {
                clearTimeout(this.timer);
            }
            
            this.timer = setTimeout(this.flush.bind(this), this.delay);
            
            return $.Deferred().promise();
        }
        
        return Backbone.sync(method, model, options);
    },
    
    flush: function() {
        var batch = this.pending;
        this.pending = [];
        
        if (batch.length === 0) return;
        
        $.ajax({
            url: '/api/batch',
            method: 'POST',
            data: JSON.stringify(batch.map(function(item) {
                return {
                    method: item.method,
                    url: item.model.url(),
                    data: item.model.toJSON()
                };
            })),
            contentType: 'application/json',
            success: function(response) {
                batch.forEach(function(item, index) {
                    if (item.options.success) {
                        item.options.success(response[index]);
                    }
                });
            }
        });
    }
};

八、总结 #

8.1 自定义sync要点 #

要点 说明
方法签名 sync(method, model, options)
返回值 Promise或jqXHR
回调调用 success/error

8.2 最佳实践 #

  1. 保留原始sync作为后备
  2. 统一处理认证
  3. 实现缓存机制
  4. 处理数据转换
  5. 添加错误处理
最后更新:2026-03-28