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 最佳实践 #
- 保留原始sync作为后备
- 统一处理认证
- 实现缓存机制
- 处理数据转换
- 添加错误处理
最后更新:2026-03-28