javascript
JavaScript中getter/setter的实现
雖然ES5中為我們提供了Object.defineProperty方法來設(shè)置getter與setter,但此原生方法使用起來并不方便,我們何不自己來實(shí)現(xiàn)一個(gè)類,只要繼承該類并遵循一定的規(guī)范就可以擁有媲美原生的getter與setter。
現(xiàn)在我們定義以下規(guī)范:
取值器跟設(shè)值器遵循格式:_xxxGetter/_xxxSetter,xxx代表需要被控制的屬性。例如,如果要控制foo屬性,則對(duì)象需要提供 _fooGetter/_fooSetter方法來作為實(shí)際的取值器與控制器,這樣我們可以帶代碼中調(diào)用obj.get(‘foo’)和 obj.set(‘foo’, value)來進(jìn)行取值與設(shè)值;否則調(diào)用get與set方法相當(dāng)于代碼:obj.foo和obj.foo = value;
提供watch函數(shù):obj.watch(attr, function(name, oldValue, newValue){});每次調(diào)用set方法時(shí),便會(huì)觸發(fā)fucntion參數(shù)。 function中name代表被改變的屬性,oldValue是上一次該屬性的值,newValue代表該屬性的最新值。該方法返回一個(gè)handle對(duì) 象,擁有remove方法,調(diào)用remove將function參數(shù)從函數(shù)鏈中移除。
首先使用閉包模式,使用attributes變量作為私有屬性存放所有屬性的getter與setter:
var Stateful = (function(){'use strict';var attributes = {Name: {s: '_NameSetter',g: '_NameGetter',wcbs: []}};var ST = function(){};return ST; })()其中wcbs用來存儲(chǔ)調(diào)用watch(name, callback)時(shí)所有的callback。
第一版實(shí)現(xiàn)代碼如下:
var Stateful = (function(){'use strict';var attributes = {};function _getNameAttrs(name){return attributes[name] || {};}function _setNameAttrs(name) {if (!attributes[name]) {attributes[name] = {s: '_' + name + 'Setter',g: '_' + name + 'Getter',wcbs: [] }}}function _setNameValue(name, value){_setNameAttrs(name);var attrs = _getNameAttrs(name);var oldValue = _getNameValue.call(this, name);//如果對(duì)象擁有_nameSetter方法則調(diào)用該方法,否則直接在對(duì)象上賦值。if (this[attrs.s]){this[attrs.s].call(this, value);} else {this[name] = value;}if (attrs.wcbs && attrs.wcbs.length > 0){var wcbs = attrs.wcbs;for (var i = 0, len = wcbs.length; i < len; i++) {wcbs[i](name, oldValue, value);}}};function _getNameValue(name) {_setNameAttrs(name);var attrs = _getNameAttrs(name);var oldValue = null;// 如果擁有_nameGetter方法則調(diào)用該方法,否則直接從對(duì)象中獲取。if (this[attrs.g]) {oldValue = this[attrs.g].call(this, name);} else {oldValue = this[name];}return oldValue;};function ST(){};ST.prototype.set = function(name, value){//每次調(diào)用set方法時(shí)都將name存儲(chǔ)到attributes中if (typeof name === 'string'){_setNameValue.call(this, name, value);} else if (typeof name === object) {for (var p in name) {_setNameValue.call(this, p, name[p]);}}return this;};ST.prototype.get = function(name) {if (typeof name === 'string') {return _getNameValue.call(this, name);}};ST.prototype.watch = function(name, wcb) {var attrs = null;if (typeof name === 'string') {_setNameAttrs(name);attrs = _getNameAttrs(name);attrs.wcbs.push(wcb);return {remove: function(){for (var i = 0, len = attrs.wcbs.length; i < len; i++) {if (attrs.wcbs[i] === wcb) {break;}}attrs.wcbs.splice(i, 1);}}} else if (typeof name === 'function'){for (var p in attributes) {attrs = attributes[p];attrs.wcbs.splice(0,0, wcb); //將所有的callback添加到wcbs數(shù)組中}return {remove: function() {for (var p in attributes) {var attrs = attributes[p];for (var i = 0, len = attrs.wcbs.length; i < len; i++) {if (attrs.wcbs[i] === wcb) {break;}}attrs.wcbs.splice(i, 1);}}}}};return ST; })()測試工作:
console.log(Stateful);var stateful = new Stateful();function A(name){this.name = name;};A.prototype = stateful;A.prototype._NameSetter = function(n) {this.name = n;};A.prototype._NameGetter = function() {return this.name;}function B(name) {this.name = name;};B.prototype = stateful;B.prototype._NameSetter = function(n) {this.name = n;};B.prototype._NameGetter = function() {return this.name;};var a = new A();var handle = a.watch('Name', function(name, oldValue, newValue){console.log(name + 'be changed from ' + oldValue + ' to ' + newValue);});a.set('Name', 'AAA');console.log(a.name);var b = new B();b.set('Name', 'BBB');console.log(b.get('Name'));handle.remove();a.set('Name', 'new AAA');console.log(a.get('Name'), b.get('Name'))輸出:
function ST(){} Namebe changed from undefined to AAA AAA Namebe changed from undefined to BBB BBB new AAA BBB可以看到將所有watch函數(shù)存放于wcbs數(shù)組中,所有子類重名的屬性訪問的都是同一個(gè)wcbs數(shù)組。有什么方法可以既保證每個(gè)實(shí)例擁有自己的 watch函數(shù)鏈又不發(fā)生污染?可以考慮這種方法:為每個(gè)實(shí)例添加一個(gè)_watchCallbacks屬性,該屬性是一個(gè)函數(shù),將所有的watch函數(shù)鏈 都存放到該函數(shù)上,主要代碼如下:
ST.prototype.watch = function(name, wcb) {var attrs = null;var callbacks = this._watchCallbacks;if (!callbacks) {callbacks = this._watchCallbacks = function(n, ov, nv) {var execute = function(cbs){if (cbs && cbs.length > 0) {for (var i = 0, len = cbs.length; i < len; i++) {cbs[i](n, ov, nv);}}}//在函數(shù)作用域鏈中可以訪問到callbacks變量execute(callbacks['_' + n]);execute(callbacks['*']);// 通配符}}var _name = '';if (typeof name === 'string') {var _name = '_' + name;} else if (typeof name === 'function') {//如果name是函數(shù),則所有屬性改變時(shí)都會(huì)調(diào)用該函數(shù)_name = '*';wcb = name;}callbacks[_name] = callbacks[_name] ? callbacks[_name] : [];callbacks[_name].push(wcb);return {remove: function(){var idx = callbacks[_name].indexOf(wcb);if (idx > -1) {callbacks[_name].splice(idx, 1);}}};};經(jīng)過改變后整體代碼如下:
var Stateful = (function(){'use strict';var attributes = {};function _getNameAttrs(name){return attributes[name] || {};}function _setNameAttrs(name) {if (!attributes[name]) {attributes[name] = {s: '_' + name + 'Setter',g: '_' + name + 'Getter'/*,wcbs: []*/}}}function _setNameValue(name, value){if (name === '_watchCallbacks') {return;}_setNameAttrs(name);var attrs = _getNameAttrs(name);var oldValue = _getNameValue.call(this, name);if (this[attrs.s]){this[attrs.s].call(this, value);} else {this[name] = value;}if (this._watchCallbacks){this._watchCallbacks(name, oldValue, value);}};function _getNameValue(name) {_setNameAttrs(name);var attrs = _getNameAttrs(name);var oldValue = null;if (this[attrs.g]) {oldValue = this[attrs.g].call(this, name);} else {oldValue = this[name];}return oldValue;};function ST(obj){for (var p in obj) {_setNameValue.call(this, p, obj[p]);}};ST.prototype.set = function(name, value){if (typeof name === 'string'){_setNameValue.call(this, name, value);} else if (typeof name === 'object') {for (var p in name) {_setNameValue.call(this, p, name[p]);}}return this;};ST.prototype.get = function(name) {if (typeof name === 'string') {return _getNameValue.call(this, name);}};ST.prototype.watch = function(name, wcb) {var attrs = null;var callbacks = this._watchCallbacks;if (!callbacks) {callbacks = this._watchCallbacks = function(n, ov, nv) {var execute = function(cbs){if (cbs && cbs.length > 0) {for (var i = 0, len = cbs.length; i < len; i++) {cbs[i](n, ov, nv);}}}//在函數(shù)作用域鏈中可以訪問到callbacks變量execute(callbacks['_' + n]);execute(callbacks['*']);// 通配符}}var _name = '';if (typeof name === 'string') {var _name = '_' + name;} else if (typeof name === 'function') {//如果name是函數(shù),則所有屬性改變時(shí)都會(huì)調(diào)用該函數(shù)_name = '*';wcb = name;}callbacks[_name] = callbacks[_name] ? callbacks[_name] : [];callbacks[_name].push(wcb);return {remove: function(){var idx = callbacks[_name].indexOf(wcb);if (idx > -1) {callbacks[_name].splice(idx, 1);}}};};return ST; })()測試:
console.log(Stateful);var stateful = new Stateful();function A(name){this.name = name;};A.prototype = stateful;A.prototype._NameSetter = function(n) {this.name = n;};A.prototype._NameGetter = function() {return this.name;}function B(name) {this.name = name;};B.prototype = stateful;B.prototype._NameSetter = function(n) {this.name = n;};B.prototype._NameGetter = function() {return this.name;};var a = new A();var handle = a.watch('Name', function(name, oldValue, newValue){console.log(name + 'be changed from ' + oldValue + ' to ' + newValue);});a.set('Name', 'AAA');console.log(a.name);var b = new B();b.set('Name', 'BBB');console.log(b.get('Name'));a.watch(function(name, ov, nv) {console.log('* ' + name + ' ' + ov + ' ' + nv);});a.set({foo: 'FOO',goo: 'GOO'});console.log(a.get('goo'));a.set('Name', 'AAA+');handle.remove();a.set('Name', 'new AAA');console.log(a.get('Name'), b.get('Name'))輸出:
function ST(obj){for (var p in obj) {_setNameValue.call(this, p, obj[p]);}} Namebe changed from undefined to AAA AAA BBB * foo undefined FOO * goo undefined GOO GOO Namebe changed from AAA to AAA+ * Name AAA AAA+ * Name AAA+ new AAA new AAA BBB以上代碼就是dojo/Stateful的原理。
from:?http://developer.51cto.com/art/201506/479033.htm
總結(jié)
以上是生活随笔為你收集整理的JavaScript中getter/setter的实现的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java 垃圾回收机制概念梳理
- 下一篇: 10个实用的但偏执的Java编程技术