视频1 视频21 视频41 视频61 视频文章1 视频文章21 视频文章41 视频文章61 推荐1 推荐3 推荐5 推荐7 推荐9 推荐11 推荐13 推荐15 推荐17 推荐19 推荐21 推荐23 推荐25 推荐27 推荐29 推荐31 推荐33 推荐35 推荐37 推荐39 推荐41 推荐43 推荐45 推荐47 推荐49 关键词1 关键词101 关键词201 关键词301 关键词401 关键词501 关键词601 关键词701 关键词801 关键词901 关键词1001 关键词1101 关键词1201 关键词1301 关键词1401 关键词1501 关键词1601 关键词1701 关键词1801 关键词1901 视频扩展1 视频扩展6 视频扩展11 视频扩展16 文章1 文章201 文章401 文章601 文章801 文章1001 资讯1 资讯501 资讯1001 资讯1501 标签1 标签501 标签1001 关键词1 关键词501 关键词1001 关键词1501 专题2001
JavaScript中getter/setter实现的示例代码分享
2020-11-27 20:24:06 责编:小采
文档

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

下载本文
显示全文
专题