Javascript自己动手实现getter/setter

  虽然ES5中为我们提供了Object.defineProperty方法来设置getter与setter,但此原生方法使用起来并不方便,我们何不自己来实现一个类,只要继承该类并遵循一定的规范就可以拥有媲美原生的getter与setter。

  现在我们定义以下规范:

  取值器跟设值器遵循格式:_xxxGetter/_xxxSetter,xxx代表需要被控制的属性。例如,如果要控制foo属性,则对象需要提供_fooGetter/_fooSetter方法来作为实际的取值器与控制器,这样我们可以带代码中调用obj.get('foo')和obj.set('foo', value)来进行取值与设值;否则调用get与set方法相当于代码:obj.foo和obj.foo = value;

  提供watch函数:obj.watch(attr, function(name, oldValue, newValue){});每次调用set方法时,便会触发fucntion参数。 function中name代表被改变的属性,oldValue是上一次该属性的值,newValue代表该属性的最新值。该方法返回一个handle对象,拥有remove方法,调用remove将function参数从函数链中移除。

  首先使用闭包模式,使用attributes变量作为私有属性存放所有属性的getter与setter:

var Stateful = (function(){
    'use strict';

    var attributes = {
        Name: {
            s: '_NameSetter',
            g: '_NameGetter',
            wcbs: []
        }
    };
    
    var ST = function(){};
    
    return ST;
})()

  其中wcbs用来存储调用watch(name, callback)时所有的callback。

  第一版实现代码如下:

  1 var Stateful = (function(){
  2     'use strict';
  3 
  4     var attributes = {};
  5     
  6     function _getNameAttrs(name){
  7         return attributes[name] || {};
  8     }
  9     
 10     function _setNameAttrs(name) {
 11         if (!attributes[name]) {
 12             attributes[name] = {
 13                 s: '_' + name + 'Setter',
 14                 g: '_' + name + 'Getter',
 15                 wcbs: [] 
 16             }
 17         }
 18     }
 19 
 20     
 21     function _setNameValue(name, value){
 22         _setNameAttrs(name);
 23         var attrs = _getNameAttrs(name);
 24         var oldValue = _getNameValue.call(this, name);
 25         //如果对象拥有_nameSetter方法则调用该方法,否则直接在对象上赋值。
 26         if (this[attrs.s]){
 27             this[attrs.s].call(this, value);
 28         } else {
 29             this[name] = value;
 30         }
 31         
 32         if (attrs.wcbs && attrs.wcbs.length > 0){
 33             var wcbs = attrs.wcbs;
 34             for (var i = 0, len = wcbs.length; i < len; i++) {
 35                 wcbs[i](name, oldValue, value);
 36             }
 37         }
 38     };
 39     
 40     function _getNameValue(name) {
 41         _setNameAttrs(name);
 42         var attrs = _getNameAttrs(name);
 43         
 44         var oldValue = null;
 45         // 如果拥有_nameGetter方法则调用该方法,否则直接从对象中获取。
 46         if (this[attrs.g]) {
 47             oldValue = this[attrs.g].call(this, name);
 48         } else {
 49             oldValue = this[name];
 50         }
 51         
 52         return oldValue;
 53     };
 54     
 55     function ST(){};
 56     
 57     ST.prototype.set = function(name, value){
 58         //每次调用set方法时都将name存储到attributes中
 59         if (typeof name === 'string'){
 60             _setNameValue.call(this, name, value);
 61         } else if (typeof name === object) {
 62             for (var p in name) {
 63                 _setNameValue.call(this, p, name[p]);
 64             }
 65         }
 66         
 67         return this;
 68     };
 69     
 70     ST.prototype.get = function(name) {
 71         if (typeof name === 'string') {
 72             return _getNameValue.call(this, name);
 73         }
 74     };
 75     
 76     ST.prototype.watch = function(name, wcb) {
 77         var attrs = null;
 78         if (typeof name === 'string') {
 79             _setNameAttrs(name);
 80             attrs = _getNameAttrs(name);
 81             attrs.wcbs.push(wcb);
 82             
 83             return {
 84                 remove: function(){
 85                     for (var i = 0, len = attrs.wcbs.length; i < len; i++) {
 86                         if (attrs.wcbs[i] === wcb) {
 87                             break;
 88                         }
 89                     }
 90                     
 91                     attrs.wcbs.splice(i, 1);
 92                 }
 93             }
 94         } else if (typeof name === 'function'){
 95             for (var p in attributes) {
 96                 attrs = attributes[p];
 97                 attrs.wcbs.splice(0,0, wcb); //将所有的callback添加到wcbs数组中
 98             }
 99             
100             return {
101                 remove: function() {
102                     for (var p in attributes) {
103                         var attrs = attributes[p];
104                         for (var i = 0, len = attrs.wcbs.length; i < len; i++) {
105                             if (attrs.wcbs[i] === wcb) {
106                                 break;
107                             }
108                         }
109                         
110                         attrs.wcbs.splice(i, 1);
111                     }
112                 }
113             }
114         }
115     };
116     
117     return ST;
118 })()
View Code

  测试工作:

 1 console.log(Stateful);
 2     var stateful = new Stateful();
 3     
 4     function A(name){
 5         this.name = name;
 6     };
 7     A.prototype = stateful;
 8     A.prototype._NameSetter = function(n) {
 9         this.name = n;
10     };
11     A.prototype._NameGetter = function() {
12         return this.name;
13     }
14     
15     function B(name) {
16         this.name = name;
17     };
18     B.prototype = stateful;
19     B.prototype._NameSetter = function(n) {
20         this.name = n;
21     };
22     B.prototype._NameGetter = function() {
23         return this.name;
24     };
25     
26     var a = new A();
27     var handle = a.watch('Name', function(name, oldValue, newValue){
28         console.log(name + 'be changed from ' + oldValue + ' to ' + newValue);
29     });
30     a.set('Name', 'AAA');
31     console.log(a.name);
32     
33     var b = new B();
34     b.set('Name', 'BBB');
35     console.log(b.get('Name'));
36     
37     handle.remove();
38     a.set('Name', 'new AAA');
39     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函数存放于wcbs数组中,所有子类重名的属性访问的都是同一个wcbs数组。有什么方法可以既保证每个实例拥有自己的watch函数链又不发生污染?可以考虑这种方法:为每个实例添加一个_watchCallbacks属性,该属性是一个函数,将所有的watch函数链都存放到该函数上,主要代码如下:

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);
                        }
                    }
                }
                //在函数作用域链中可以访问到callbacks变量
                execute(callbacks['_' + n]);
                execute(callbacks['*']);// 通配符
            }
        }
        
        var _name = '';
        if (typeof name === 'string') {
            var _name = '_' + name;
        } else if (typeof name === 'function') {//如果name是函数,则所有属性改变时都会调用该函数
            _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);
                }
            }
        };
    };

  经过改变后整体代码如下:

  1 var Stateful = (function(){
  2     'use strict';
  3 
  4     var attributes = {};
  5     
  6     function _getNameAttrs(name){
  7         return attributes[name] || {};
  8     }
  9     
 10     function _setNameAttrs(name) {
 11         if (!attributes[name]) {
 12             attributes[name] = {
 13                 s: '_' + name + 'Setter',
 14                 g: '_' + name + 'Getter'/*,
 15                 wcbs: []*/
 16             }
 17         }
 18     }
 19 
 20     
 21     function _setNameValue(name, value){
 22         if (name === '_watchCallbacks') {
 23             return;
 24         }
 25         _setNameAttrs(name);
 26         var attrs = _getNameAttrs(name);
 27         var oldValue = _getNameValue.call(this, name);
 28         
 29         if (this[attrs.s]){
 30             this[attrs.s].call(this, value);
 31         } else {
 32             this[name] = value;
 33         }
 34         
 35         if (this._watchCallbacks){
 36             this._watchCallbacks(name, oldValue, value);
 37         }
 38     };
 39     
 40     function _getNameValue(name) {
 41         _setNameAttrs(name);
 42         var attrs = _getNameAttrs(name);
 43         
 44         var oldValue = null;
 45         if (this[attrs.g]) {
 46             oldValue = this[attrs.g].call(this, name);
 47         } else {
 48             oldValue = this[name];
 49         }
 50         
 51         return oldValue;
 52     };
 53     
 54     function ST(obj){
 55         for (var p in obj) {
 56             _setNameValue.call(this, p, obj[p]);
 57         }
 58     };
 59     
 60     ST.prototype.set = function(name, value){
 61         if (typeof name === 'string'){
 62             _setNameValue.call(this, name, value);
 63         } else if (typeof name === 'object') {
 64             for (var p in name) {
 65                 _setNameValue.call(this, p, name[p]);
 66             }
 67         }
 68         
 69         return this;
 70     };
 71     
 72     ST.prototype.get = function(name) {
 73         if (typeof name === 'string') {
 74             return _getNameValue.call(this, name);
 75         }
 76     };
 77     
 78     ST.prototype.watch = function(name, wcb) {
 79         var attrs = null;
 80         
 81         var callbacks = this._watchCallbacks;
 82         if (!callbacks) {
 83             callbacks = this._watchCallbacks = function(n, ov, nv) {
 84                 var execute = function(cbs){
 85                     if (cbs && cbs.length > 0) {
 86                         for (var i = 0, len = cbs.length; i < len; i++) {
 87                             cbs[i](n, ov, nv);
 88                         }
 89                     }
 90                 }
 91                 //在函数作用域链中可以访问到callbacks变量
 92                 execute(callbacks['_' + n]);
 93                 execute(callbacks['*']);// 通配符
 94             }
 95         }
 96         
 97         var _name = '';
 98         if (typeof name === 'string') {
 99             var _name = '_' + name;
100         } else if (typeof name === 'function') {//如果name是函数,则所有属性改变时都会调用该函数
101             _name = '*';
102             wcb = name;
103         }
104         callbacks[_name] = callbacks[_name] ? callbacks[_name] : [];
105         callbacks[_name].push(wcb);
106         
107         return {
108             remove: function(){
109                 var idx = callbacks[_name].indexOf(wcb);
110                 if (idx > -1) {
111                     callbacks[_name].splice(idx, 1);
112                 }
113             }
114         };
115     };
116     
117     return ST;
118 })()
View Code

  测试:

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的原理。

 

posted @ 2015-06-06 19:24  木的树  阅读(2761)  评论(11编辑  收藏  举报