Last active
December 19, 2015 17:48
-
-
Save crossai-2033/5993952 to your computer and use it in GitHub Desktop.
适配器和插件架构的设计模式
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var Lawnchair = function(options, callback) { | |
// 确保Lawnchair被作为contructor调用 | |
if (!(this instanceof Lawnchair)) return new Lawnchair(options, callback); | |
// lawnchair依赖JSON | |
if (!JSON) throw 'JSON unavailable! Include http://www.json.org/json2.js to fix.'; | |
// options是可选的,但callback是必须的 | |
if (arguments.length <= 2 && arguments.length > 0) { | |
callback = (typeof arguments[0] === 'function') ? arguments[0] : arguments[1]; | |
options = (typeof arguments[0] === 'function') ? {} : arguments[0]; | |
} else { | |
throw 'Incorrect # of ctor args!' | |
} | |
// TODO 或许使用pub/sub模式替代? | |
if (typeof callback !== 'function') throw 'No callback was provided'; | |
// 默认配置 | |
this.record = options.record || 'record' // default for record | |
this.name = options.name || 'records' // 存储结构默认的名字 | |
// mixin第一个有效的adapter(适配器) | |
var adapter; | |
// 如果我们想要使用的adapter已经传入进来 | |
if (options.adapter) { | |
for (var i = 0, l = Lawnchair.adapters.length; i < l; i++) { | |
if (Lawnchair.adapters[i].adapter === options.adapter) { | |
adapter = Lawnchair.adapters[i].valid() ? Lawnchair.adapters[i] : undefined; | |
break; | |
} | |
} | |
} | |
// 否则就找到当前环境中的第一个有效的适配器 | |
else { | |
for (var i = 0, l = Lawnchair.adapters.length; i < l; i++) { | |
adapter = Lawnchair.adapters[i].valid() ? Lawnchair.adapters[i] : undefined; | |
if (adapter) break; | |
} | |
} | |
// 加载适配器失败,异常 | |
if (!adapter) throw 'No valid adapter.'; | |
// mixin the adapter | |
for (var j in adapter) | |
this[j] = adapter[j] | |
// 初始化mixin进来的plugin | |
for (var i = 0, l = Lawnchair.plugins.length; i < l; i++) { | |
Lawnchair.plugins[i].call(this); | |
} | |
// 初始化适配器 | |
this.init(options, callback); | |
} | |
Lawnchair.adapters = []; | |
/** | |
* 做一个适配器的队列 | |
* === | |
* 确保适配器拥有统一的接口 | |
*/ | |
Lawnchair.adapter = function(id, obj) { | |
// 添加适配器的id给适配器对象obj | |
// 为了拥有清晰的SDL所以使用了比较丑陋的设计 | |
obj['adapter'] = id; | |
// 作为一个lawnchair适配器应该具备的方法 | |
var implementing = 'adapter valid init keys save batch get exists all remove nuke'.split(' '), | |
indexOf = this.prototype.indexOf; | |
// mix in the adapter | |
for (var i in obj) { | |
if (indexOf(implementing, i) === -1) throw 'Invalid adapter! Nostandard method: ' + i; | |
} | |
// 如果我们确保这个适配器是有效的,那么我们就添加到有线队列中 | |
Lawnchair.adapters.splice(0, 0, obj); | |
}; | |
/** | |
* 给插件机制的简单扩展 | |
* === | |
* 如果能够找到init方法被注册了,那么就直接调用init方法启动插件 | |
* - 遍历熟悉可以使用hasOwnProp,但是应该没人去破坏pro | |
*/ | |
Lawnchair.plugin = function(obj) { | |
for (var i in obj) { | |
i === 'init' ? Lawnchair.plugins.push(obj[i]) : this.prototype[i] = obj[i]; | |
} | |
}; | |
/** | |
* helpers辅助 | |
*/ | |
Lawnchair.prototype = { | |
isArray: Array.isArray || function(o) { return Object.prototype.toString.call(o) === '[object Array]'; } | |
// 低版本的shim | |
indexOf: function(ary, item, i, l) { | |
if (ary.indexOf) return ary.indexOf(item); | |
for (i = 0, l = ary.length; i < l; i++) if (ary[i] === item) return i; | |
return -1; | |
}, | |
// 强大的简洁字符串形式函数回调,无耻的从dojo抄袭的 | |
lambda: function(callback) { | |
return this.fn(this.record, callback); | |
}, | |
fn: function(name, callback) { | |
return typeof callback === 'string' ? new Function(name, callback) : callback; | |
}, | |
// 唯一的标识符 | |
// TODO 研究一个更短小的UUID | |
uuid: function() { | |
var S4 = function() { | |
return (((1+Math.random())*0x10000)|0).toString(16).substring(1); | |
} | |
return (S4()+S4()+"-"+S4()+"-"+S4()+"-"+S4()+"-"+S4()+S4()+S4()); | |
}, | |
// 一个经典的遍历器 | |
each: function(callback) { | |
var cb = this.lambda(callback); | |
if (this.__results) { | |
for (var i = 0, l = this.__results.length; i < l; i++) cb.call(this, this.__results[i], i); | |
} | |
else { | |
this.all(function(r) { | |
for(var i = 0, l = r.length; i < l; i++) cb.call(this, r[i], i); | |
}); | |
} | |
return this; | |
} | |
}; | |
/** | |
* dom storage adapter | |
* === | |
* 链式操作 valid, keys除外 | |
*/ | |
Lawnchair.adapter('dom', (function() { | |
var storage = window.localStorage; | |
// indexer是一个需要保持顺序索引的keys的包装 | |
var indexer = function(name) { | |
return { | |
key: name + '._index_', | |
// return the index | |
all: function() { | |
var a = storage.getItem(this.key); | |
if (a) { | |
a = JSON.parse(a) | |
} | |
if (a === null) storage.setItem(this.key, JSON.stringify([])); // lazy init | |
return JSON.parse(storage.getItem(this.key)); | |
}, | |
// 添加一个key到索引中 | |
add: function(key) { | |
var a = this.all(); | |
a.push(key); | |
storage.setItem(this.key, JSON.stringify(a)); | |
}, | |
// 从索引中删除一个key | |
del: function(key) { | |
var a = this.all(), r = []; | |
// 使用低效的排除法 | |
for (var i = 0, l = a.length; i < l; i++) { | |
if (a[i] != key) r.push(a[i]); | |
} | |
storage.setItem(this.key, JSON.stringify(r)); | |
}, | |
// return index for a key | |
find: function(key) { | |
var a = this.all(); | |
for (var i = 0, l = a.length; i < l; i++) { | |
if (key === a[i]) return i; | |
} | |
return false; | |
} | |
} | |
}; | |
// adapter api | |
return { | |
// 确保当前环境由localStorage | |
valid: function() { | |
return !!storage; | |
}, | |
init: function(options, callback) { | |
this.indexer = indexer(this.name); | |
if (callback) this.fn(this.name, callback).call(this, this); | |
}, | |
save: function(obj, callback) { | |
var key = obj.key ? this.name + '.' + obj.key : this.name + '.' + this.uuid(); | |
// 如果目前的key在索引中还不存在,那么就添加进去 | |
if (this.indexer.find(key) === false) this.indexer.add(key); | |
delete obj.key; | |
storage.setItem(key, JSON.stringify(obj)); | |
obj.key = key.slice(this.name.length + 1); | |
if (callback) { | |
this.lambda(callback).call(this, obj); | |
} | |
return this; | |
}, | |
batch: function(ary, callback) { | |
var saved = []; | |
for (var i = 0, l = ary.length; i < l; i++) { | |
this.save(ary[i], function(r) { | |
saved.push(r); | |
}); | |
} | |
if (callback) this.lambda(callback).call(this, saved); | |
return this; | |
}, | |
keys: function(callback) { | |
if (callback) { | |
var name = this.name, | |
keys = this.indexer.all().map(function(r) { return r.replace(name + '.', '')}); | |
this.fn('keys', callback).call(this, keys); | |
} | |
}, | |
get: function(key, callback) { | |
if (this.isArray(key)) { | |
var r = []; | |
for (var i = 0, l = keys.length; i < l; i++) { | |
var k = this.name + '.' + key[i]; | |
var obj = storage.getItem(k); | |
if (obj) { | |
obj.key = key[i]; | |
r.push(obj); | |
} | |
} | |
if (callback) this.lambda(callback).call(this, r); | |
} else { | |
var key = this.name + '.' + key; | |
var obj = storage.getItem(key); | |
if (obj) { | |
obj = JSON.parse(obj); | |
obj.key = key; | |
} | |
if (callback) this.lambda(callback).call(this, obj); | |
} | |
return this; | |
}, | |
exists: function(key, cb) { | |
var exists = this.indexer.find(this.name + '.' + key) === false ? false : true; | |
this.lambda(cb).call(this, exists); | |
return this; | |
}, | |
all: function(callback) { | |
var idx = this.indexer.all(), | |
r = [], | |
o, | |
k; | |
for(var i = 0, l = idx.length; i < l; i++) { | |
k = idx[i]; | |
o = JSON.parse(storage.getItem(k)); | |
o.key = k.replace(this.name + '.', ''); | |
r.push(o); | |
} | |
if (callback) this.fn(this.name, callback).call(this, r); | |
return this; | |
}, | |
remove: function(keyOrObj, callback) { | |
var key = this.name + '.' + ((keyOrObj.key) ? keyOrObj.key : keyOrObj); | |
this.indexer.del(key); | |
storage.removeItem(key); | |
if (callback) this.lambda(callback).call(this); | |
return this; | |
}, | |
nuke: function(callback) { | |
this.all(function(r) { | |
for(var i = 0, l = r.length; i < l; i++) { | |
this.remove(r[i]); | |
} | |
if (callback) this.lambda(callback).call(this); | |
}); | |
return this; | |
} | |
} | |
})()); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment