zoukankan      html  css  js  c++  java
  • 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的原理。

  • 相关阅读:
    C++之用程序理解浅拷贝
    es6 | 新增语法 | 总结
    http协议 | http缓存
    Mobx | 强大的状态管理工具 | 可以用Mobx来替代掉redux
    nohup和&后台运行,进程查看及终止
    MIME Type介绍 Content-Type 各种定义
    Meta http-equiv属性详解(转)
    sublme text 3 快捷键
    【坑】【数组的坑】1、对象assign复制的假深度,2、数组slice复制的坑,3、还有数组map复制的坑
    Proxy监听对象的数据变化,处理绑定数据很有用
  • 原文地址:https://www.cnblogs.com/dojo-lzz/p/4557149.html
Copyright © 2011-2022 走看看