๐Ÿ“œ โฌ†๏ธ โฌ‡๏ธ

Getters and Setters in Javascript

Javascript is a very elegant language with lots of interesting features. Most of these features are hidden by one unpleasant factor โ€” Internet Explorer and the other crap we have to work with. However, with the advent of mobile phones with current browsers and server-side JavaScript with normal engines, these features can and should be used right now. But out of habit, even when programming for node.js, we try to write so that it works in IE6 +.

In this article I will talk about an interesting and not secret way to specify elegant getters and setters and dig a little bit in the Mootools source code. Partly this information is taken from the article by John Resig , partly personally my experience and experiments.
function Foo(bar){ this._bar = bar; } Foo.prototype = { get bar () { return this._bar; }, set bar (bar) { this._bar = bar; } }; 



Standard getters


What are the Getters and Setters, I hope everyone knows. Usually cross-browser it looks like this:
 function Foo(bar) { this._bar = bar; }; Foo.prototype = { setBar : function (bar) { this._bar = bar; }, getBar : function () { return this._bar; } }; var foo = new Foo; foo.setBar(123); alert(foo.getBar()); 

')
You can go further and write a more elegant version:
 function Foo(bar) { var _bar = bar; this.bar = function (bar) { if (arguments.length) _bar = bar; else return _bar; } }; var foo = new Foo; foo.bar(123); alert(foo.bar()); 


Native getters / setters


But there is a more convenient way that works in all server engines and modern browsers, namely Firefox, Chrome, Safari3 +, Opera9.5 + - setting the setter and getter for the property so as to continue to access the property as a property. There are several advantages to this approach:
1. More graceful record. Imagine ORM:
 for (var i in topics.comments); // vs for (var i in topics.loadComments()); 

2. If api, which is based on properties, already exists and cannot be changed (and is very necessary).

There are two ways to set such a getter / setter:

Through the object:


 function Foo(bar) { this._bar = bar; }; Foo.prototype = { set bar (bar) { this._bar = bar; }, get bar () { return this._bar; } }; var foo = new Foo; foo.bar = 123; alert(foo.bar); 


Through the __defineGetter__ and __defineSetter__ methods:


 function Foo(bar) { var _bar = bar; this.__defineGetter__("bar", function(){ return _bar; }); this.__defineSetter__("bar", function(val){ _bar = bar; }); }; var foo = new Foo; foo.bar = 123; alert(foo.bar); 


We define browser support


From this you can get an easy way to determine whether the browser supports getters or does not support:
 return (typeof {}.__defineGetter__ == 'function'); 


How to deal with inheritance?


You can get the getter or setter function through the .__lookupGetter__ and .__lookupSetter__ .
 function extend(target, source) { for ( var prop in source ) { var getter = source.__lookupGetter__(prop), setter = source.__lookupSetter__(prop); if ( getter || setter ) { if ( getter ) target.__defineGetter__(prop, getter); if ( setter ) target.__defineSetter__(prop, setter); } else a[i] = b[i]; } return target; } 

Thus, our target will not be passed the values โ€‹โ€‹of the parent source, but the getter / setter functions.

What to remember


Some comments from John Resig:
* For each property, you can set only one getter and / or setter. There can be two getters or setters
* The only way to remove a getter or setter is to call delete object[name]; . This command deletes both the getter and the setter, so if you want to delete one thing and leave the other, you must first save it, and after deleting it, assign it again
* When you use __defineGetter__ or __defineSetter__, it just silently overwrites the previous getter or setter, and even deletes the property itself with that name.
* You can check if your browser supports getters and setters using a simple snippet:
 javascript:foo={get test(){ return "foo"; }};alert(foo.test); 



MooTools


Mutuls does not support this feature by default. And, although I have already proposed a patch , we can easily (slightly changing the source code) make him understand the getters and setters.
So what is our goal?
 var Foo = new Class({ set test : function () { console.log('test is set'); }, get test : function () { console.log('test is got'); return 'test'; }, }); foo.test = 1234; // test is set alert(foo.test); // test is get 

Moreover, in the classes inherited through Implements and Extends, the getters and setters of the parent class must also work. All our actions will take place in the [name: Class] file inside the anonymous function.
First, inside the function, at the very top, we define a function that overwrites only the getters and setters. And although we abandoned outdated browsers - it is worth insuring.
 var implementGettersSetters = (typeof {}.__lookupGetter__ == 'function') ? function (target, source) { for (var key in source) { var g = source.__lookupGetter__(key), s = source.__lookupSetter__(key); if ( g || s ) { if (g) target.__defineGetter__(key, g); if (s) target.__defineSetter__(key, s); } } return target; } : function (target, source) { return target; }; 


Of course, if our script with such getters gets into an outdated browser, then it will simply fall, but it is insurance against someone accidentally taking this file and not linking it to their site, and then wondering what the thing is with the donkey .
We see that if __lookupGetter__ is not supported, then the function simply does nothing.

Now we make getters and setters work during class creation and inheritance (Extends). For this:
 //  var Class = this.Class = new Type('Class', function(params){ //   newClass.$constructor = Class; newClass.prototype.$constructor = newClass; newClass.prototype.parent = parent; //   ,    (, ,   !)  : implementGettersSetters(newClass.prototype, params); 


Separate movement is necessary to implement the inheritance of getters and setters from impurities (Implements). To do this, find the built-in Mutators and add just one line:
 Class.Mutators = { // ... Implements: function(items){ Array.from(items).each(function(item){ var instance = new item; for (var key in instance) implement.call(this, key, instance[key], true); //  : implementGettersSetters(this.prototype, instance); }, this); } }; 


Everything, now setters and getters are implemented and we can easily inherit and use them. Enjoy)

 var Foo = new Class({ initialize : function (name) { this.name = name; }, set test : function () { console.log(this.name + ' test is set'); }, get test : function () { console.log(this.name + ' test is got'); return 'test'; }, }); var Bar = new Class({ Extends : Foo }); var Qux = new Class({ Implements : [ Foo ] }); var foo = new Foo('foo'); foo.test = 1234; // foo test is set alert(foo.test); // foo test is got var bar = new Bar('bar'); bar.test = 1234; // bar test is set alert(bar.test); // bar test is got var qux = new Qux('qux'); qux.test = 1234; // qux test is set alert(qux.test); // qux test is got 


Interesting links:


Object.defineProperty - a tool for creating properties with very wide settings, such as writable, get, set, configurable, enumerable
Object.create - it is convenient to quickly create the necessary objects.

Source: https://habr.com/ru/post/108295/


All Articles