关闭 x
IT技术网
    技 采 号
    ITJS.cn - 技术改变世界
    • 实用工具
    • 菜鸟教程
    IT采购网 中国存储网 科技号 CIO智库

    IT技术网

    IT采购网
    • 首页
    • 行业资讯
    • 系统运维
      • 操作系统
        • Windows
        • Linux
        • Mac OS
      • 数据库
        • MySQL
        • Oracle
        • SQL Server
      • 网站建设
    • 人工智能
    • 半导体芯片
    • 笔记本电脑
    • 智能手机
    • 智能汽车
    • 编程语言
    IT技术网 - ITJS.CN
    首页 » JavaScript »JavaScript中getter/setter的实现

    JavaScript中getter/setter的实现

    2015-06-08 00:00:00 出处:OshynSong的博客
    分享

    虽然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。

    第一版实现代码如下:

    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);
            //假如对象拥有_nameSetter方法则调用该方法,否则直接在对象上赋值。
            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方法则调用该方法,否则直接从对象中获取。
            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){
            //每次调用set方法时都将name存储到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数组中
                }
    
                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函数存放于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);
                    }
                }
            };
        };

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

    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);
                            }
                        }
                    }
                    //在函数作用域链中可以访问到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);
                    }
                }
            };
        };
    
        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的原理。

    上一篇返回首页 下一篇

    声明: 此文观点不代表本站立场;转载务必保留本文链接;版权疑问请联系我们。

    别人在看

    hiberfil.sys文件可以删除吗?了解该文件并手把手教你删除C盘的hiberfil.sys文件

    Window 10和 Windows 11哪个好?答案是:看你自己的需求

    盗版软件成公司里的“隐形炸弹”?老板们的“法务噩梦” 有救了!

    帝国CMS7.5编辑器上传图片取消宽高的三种方法

    帝国cms如何自动生成缩略图的实现方法

    Windows 12即将到来,将彻底改变人机交互

    帝国CMS 7.5忘记登陆账号密码怎么办?可以phpmyadmin中重置管理员密码

    帝国CMS 7.5 后台编辑器换行,修改回车键br换行为p标签

    Windows 11 版本与 Windows 10比较,新功能一览

    Windows 11激活产品密钥收集及专业版激活方法

    IT头条

    智能手机市场风云:iPhone领跑销量榜,华为缺席引争议

    15:43

    大数据算法和“老师傅”经验叠加 智慧化收储粮食尽显“科技范”

    15:17

    严重缩水!NVIDIA将推中国特供RTX 5090 DD:只剩24GB显存

    00:17

    无线路由大厂 TP-Link突然大裁员:补偿N+3

    02:39

    Meta 千万美金招募AI高级人才

    00:22

    技术热点

    微软已修复windows 7/windows 8.1媒体中心严重漏洞 用户可下载安

    卸载MySQL数据库,用rpm如何实现

    windows 7中使用网上银行或支付宝支付时总是打不开支付页面

    一致性哈希算法原理设计

    MySQL数字类型中的三种常用种类

    如何解决SQL Server中传入select语句in范围参数

      友情链接:
    • IT采购网
    • 科技号
    • 中国存储网
    • 存储网
    • 半导体联盟
    • 医疗软件网
    • 软件中国
    • ITbrand
    • 采购中国
    • CIO智库
    • 考研题库
    • 法务网
    • AI工具网
    • 电子芯片网
    • 安全库
    • 隐私保护
    • 版权申明
    • 联系我们
    IT技术网 版权所有 © 2020-2025,京ICP备14047533号-20,Power by OK设计网

    在上方输入关键词后,回车键 开始搜索。Esc键 取消该搜索窗口。