Difference between get/set and Object.defineProperty in the Module Pattern - javascript

I've seen two different ways of implementing getters/setters in a module pattern. One uses "defineProperty", while the other doesn't. What are the advantages/disadvantages of one versus the other?
var MyModule = (function() {
var _val;
var api = {
get value1() {
return _val
},
set value1(value) {
_val = value
}
};
Object.defineProperty(api, 'value2', {
get: function() {
return _val;
},
set: function(value) {
_val = value
}
});
return api;
}());
https://plnkr.co/edit/TbJSD4noZTew8II83eTH?p=preview

Using getter syntax you create a property which, prior to ES2015, you had to know the name of at the time that you were writing the code.
Object.defineProperty allows you to perform the same as the above but, even before ES2015, does not require you to know the name of the property in advanced. It also allows you to decide if the property is configurable, enumerable, or writable which is not possible using the get/set syntax.
To answer your exact question: neither is better. They're for different situations. The get/set syntax is simpler to read and write but isn't as powerful as Object.defineProperty.

As mentioned in documentation.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty
Normal property addition through assignment creates properties which show up during property enumeration (for...in loop or Object.keys method), whose values may be changed, and which may be deleted. This method allows these extra details to be changed from their defaults. By default, values added using Object.defineProperty() are immutable.

One "advantage" of Object.defineProperty vs get/set notation is that defineProperty can be used at any time, even on objects that have already been created (on which you cannot use the shorthand 'get' notation). From the mdn:
To append a getter to an existing object later at any time, use Object.defineProperty().

Related

Is it safe to use "get" and "set" as property names in javascript?

Is it generally safe to use get and set as the names of properties for an object literal, like this:
var myObject={
get: 'value1',
set: 'value2'
}
Get and set do not appear to be reserved words in javascript, but I'm worried this could cause clashes with some built-in behaviors. In particular get and set seem to have some pretty unique behavior in the Object.defineProperties method:
Object.defineProperties(someObject,{
'Property1':{
get:function(){
return property1;
},
set:function(val){
property1=val;
},
enumerable:true;
}
});
In this built-in behavior, the get and set aren't treated as normal properties but actually execute their respective functions when someObject.Property1 is accessed or assigned to.
Because of how they're used in Object.defineProperties I've always sort of treated get and set as special reserved words. So my question is, is that a good rule of thumb or should I just learn to stop worrying and just use get and set whenever it seems semantically appropriate?
Yes, it's safe. get and set are always treated as normal properties. The fact that they are used to define getters and setters in property descriptors is completely irrelevant.
An example of an unsafe property would be valueOf, because it actually changes how an object behaves:
console.log(+{}); // NaN
console.log(+{ valueOf: () => 5 }); // 5
The fact that get and set are valid properties in the object you pass to Object.defineProperties is proof that it's safe to use get and set as property names.
The only time get and set have a special meaning in JavaScript is when you're creating a getter or setter, but the syntax is different, so there isn't a clash with property names.
the setter getter mechanism in javascript described , I don't see any conflict or reason to consider it unsafe except maybe others do it too, so careful about overwriting by other libraries so beside that it's good to go.

How to get object itself in custom Object.prototype.xxx function?

Object.prototype.getB = function() {
// how to get the current value a
return a.b;
};
const a = {b: 'c'};
a.getB();
As you can see, I want to make a function to all Object value. And I need to get the object value in this function then do something.
Monkey Patching
What you want to do is called monkey patching — you mutate a built-in prototype.
There are many wrong ways to do it, and it should be avoided entirely, because it has negative Web compatibility impacts.
I will, however, demonstrate how this can be done in a way that matches existing prototype features most closely.
In your case, the function body should return this.b.
In functions called as methods, you can get the object itself with the this keyword.
See How does the "this" keyword work? (section 4: “Entering function code”, subsection “Function properties”) for more details.
You correctly added the method to Object.prototype.
See Inheritance and the prototype chain for more details.
The tools
There is a number of tools involved when reasoning about monkey-patching:
Checking own property existence
Using property descriptors
Using the correct function kind
Getters and setters
Let’s assume you’re trying to implement theMethod on TheClass.
1. Checking own property existence
Depending on your use case you may want to check if the method you want to introduce already exists.
You can do that with Object.hasOwn; this is quite a new method, but in older environments it can simply be replaced by Object.prototype.hasOwnProperty.call.
Alternatively, use hasOwnProperty normally, but be aware that if you or someone else monkey-patched the hasOwnProperty method itself, this may lead to incorrect results.
Note that in does not check for own properties, exclusively, but for inherited properties as well, which isn’t (necessarily) what you want when you’re about to create an own property on an object.
Also, note that if(TheClass.prototype.theMethod) is not a property existence check; it’s a truthiness check.
Code samples
if(Object.hasOwn(TheClass.prototype, "theMethod")){
// Define the method.
}
if(Object.prototype.hasOwnProperty.call(TheClass.prototype, "theMethod")){
// Define the method.
}
if(TheClass.prototype.hasOwnProperty("theMethod")){
// Define the method.
}
2. Using property descriptors
You can choose the property descriptor however you like, but existing methods are writable, configurable, and non-enumerable (the last of which is the default when using defineProperty).
defineProperties can be used to define multiple properties in one go.
When simply assigning a property using =, the property becomes writable, configurable, and enumerable.
Code samples
// Define the method:
Object.defineProperty(TheClass.prototype, "theMethod", {
writable: true,
configurable: true,
value: function(){}
});
// Define the method:
Object.defineProperties(TheClass.prototype, {
theMethod: {
writable: true,
configurable: true,
value: function(){}
}
});
3. Using the correct function kind
JavaScript has four major kinds of functions which have different use cases.
“Is invokable” means that it can be called without new and “Is constructable” means that it can be called with new.
Function kind
Example
Is invokable
Is constructable
Has this binding
Arrow function
() => {}
Yes
No
No
Method
({ method(){} }).method
Yes
No
Yes
Class
(class{})
No
Yes
Yes
function function
(function(){})
Yes
Yes
Yes
What we’re looking for is a method that can be called (without new) and has its own this binding.
We could use functions, but, looking at existing methods, not only is new "HELLO".charAt(); strange, it also doesn’t work!
So the method should also not be constructable.
Therefore proper Methods are what we’re looking for.
Note that this obviously depends on your use case.
For example, if you want a constructable function, by all means, use a class instead.
Code sample
We go back to the previous code sample and instead of function(){} use a method definition.
// Define the method:
Object.defineProperty(TheClass.prototype, "theMethod", {
writable: true,
configurable: true,
value: {
theMethod(){
// Do the thing.
}
}.theMethod
});
Why bother with 2. and 3.?
The goal of the enumerability and constructability considerations is to create something that has the same “look and feel” as existing, built-in methods.
The difference between those and a naive implementation can be demonstrated using this snippet:
class TheClass{}
TheClass.prototype.theMethod = function(){};
Let’s compare this to a different, built-in method, like String.prototype.charAt:
Code snippet
Naive example
Built-in example
thePrototype
Is TheClass.prototype
Is String.prototype
theMethod
Is TheClass.prototype.theMethod
Is String.prototype.charAt
for(const p in thePrototype){ console.log(p); }
"theMethod" will be logged at some point.
"charAt" will never be logged.
new theMethod
Creates an instance of theMethod.
TypeError: theMethod is not a constructor.
Using the tools from subsections 2 and 3 make it possible to create methods that behave more like built-in methods.
4. Getters and setters
An alternative is to use a getter. Consider this:
const arr = [
"a",
"b",
"c",
];
console.log(arr.indexOfB); // 1
How would an indexOfB getter on the Array prototype look like?
We can’t use the above approach and replace value by get, or else we’ll get:
TypeError: property descriptors must not specify a value or be writable when a getter or setter has been specified
The property writable needs to be removed entirely from the descriptor.
Now value can be replaced by get:
Object.defineProperty(Array.prototype, "indexOfB", {
configurable: true,
get: {
indexOfB(){
return this.indexOf("b");
}
}.indexOfB
});
A setter can also be specified by adding a set property to the descriptor:
Object.defineProperty(Array.prototype, "indexOfB", {
configurable: true,
get: {
indexOfB(){
return this.indexOf("b");
}
}.indexOfB,
set: {
indexOfB(newValue){
// `newValue` is the assigned value.
// Use `this` for the current Array instance.
// No `return` necessary.
}
}.indexOfB
});
Web compatibility impact
There are a few reasons why anyone would want to extend built-in prototypes:
You got a brilliant idea yourself for a new feature to be added to all instances of whatever class you’re extending, or
You want to backport an existing, specified feature to older browsers.
If you extend the target object, there are two options to consider:
If the property doesn’t exist, supply it, otherwise, leave the existing property in place, or
Always replace the property with your own implementation, no matter if it exists or not.
All approaches mostly have disadvantages:
If you or someone else invented their own feature, but a standard method comes along which has the same name, then these features are almost guaranteed to be incompatible.
If you or someone else try to implement a standard feature in order to backport it to browsers that don’t support it, but don’t read the specification and “guess” how it works, then the polyfilled feature is almost guaranteed to be incompatible with the standard implementation.
Even if the spec is followed closely, who will make sure to keep up with spec changes?
Who will account for possible errors in the implementation?
If you or someone else choose to check if the feature exists before overriding it, then there’s a chance that as soon as someone with a browser which supports the feature visits your page, suddenly everything breaks because the implementations turn out to be incompatible.
If you or someone else choose to override the feature regardless, then at least the implementations are consistent, but then migration to the standard feature may be difficult.
If you write a library that is used a lot in other software, then the migration cost becomes so large that the standard itself has to change; this is why Array.prototype.contains had to be renamed to Array.prototype.includes[Reddit] [ESDiscuss] [Bugzilla] [MooTools], and Array.prototype.flatten could not be used and had to be named Array.prototype.flat instead[Pull request 1] [Pull request 2] [Bugzilla].
In other words, this is the reason we can’t have nice things.
Also, your library may not be interoperable with other libraries.
Alternatives
The simplest alternative is to define your own plain function:
const theMethod = (object) => object.theProperty; // Or whatever.
const theProperty = theMethod(theObject);
You could also consider a Proxy.
This way you can dynamically query the property and respond to it.
Let’s say you have an object with properties a through z and you want to implement methods getA through getZ:
const theProxiedObject = new Proxy(theObject, {
get(target, property, receiver){
const letter = property.match(/^get(?<letter>[A-Z])$/)?.groups?.letter.toLowerCase();
if(letter){
return () => target[letter];
}
return Reflect.get(target, property, receiver);
}
});
console.assert(theProxiedObject.getB() === theProxiedObject.b);
You could also extend your object’s prototype using another class, and use this instead:
class GetterOfB extends Object{
b;
constructor(init){
super();
Object.assign(this, init);
}
getB(){
return this.b;
}
}
const theObject = new GetterOfB({
b: "c"
});
const theB = theObject.getB();
All you have to keep in mind is to not modify things you didn’t define yourself.

JavaScript, unknown syntax

I'm using the library handsontable and I'd like to get my application running in IE8-IE9 (even though it's not IE < 10 compatible...). When my code use the minify version I get an error in the JS console : "';' expected".
Here is the code.
, {
get DEFAULT_WIDTH() {
return 50;
}
}
I just don't know this syntax. What does "get DEFAULT_WIDTH()" do ?
MDN has documentation for get, including a list of supporting browsers. What get does is invoke a function when the property is looked up. See Defining getters and setters for a more general explanation.
The get syntax binds an object property to a function that will be called when that property is looked up.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get
The more general and exhausting explanation can be found here:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Working_with_Objects#Defining_getters_and_setters
Basically, it allows you to define what happens when a certain object property is read by code. In an analogous fashion, you can also define what should happen when code writes to that property with a set definition. In both cases you overwrite the standard behaviour for that object property.
This is all part of ECMAScript 5.1, and thus, not available in IE < 9.
What does your example code do?
In your example code, you can see that whenever the property DEFAULT_WIDTH is read, a constant value will be returned. I guess the intention of this is to make sure DEFAULT_WIDTH cannot be redefined as some other value (which it in fact can, but reading it will still return 50).
Defining a getter on existing objects using defineProperty
To append a getter to an existing object later at any time, use Object.defineProperty().
var o = { a:0 }
Object.defineProperty(o, "b", { get: function () { return this.a + 1; } });
console.log(o.b) // Runs the getter, which yields a + 1 (which is 1)
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get#Defining_a_getter_on_existing_objects_using_defineProperty

Closure compiler, Backbone and meta programming

I am trying to use Backbone.js and Closure Compiler in advanced mode. I wrote a convenient function that creates getters/setter for my Backbone.Models using Backbone.Model.defaults, it looks like this:
some.defaultProperties = function(ctor) {
if (!ctor.prototype.defaults)
return;
var defattr = function(name) {
return {
get: function() {
return this.get(name);
},
set: function(val) {
var diff = {};
diff[name] = val;
return this.set(diff);
}
};
};
var props = {};
for (var attr in ctor.prototype.defaults) {
if (ctor.prototype.defaults.hasOwnProperty(attr))
props[attr] = defattr(attr);
}
Object.defineProperties(ctor.prototype, props);
};
Equivalent in CoffeeScript here http://srackham.wordpress.com/2011/10/16/getters-and-setters-for-backbone-model-attributes/
Now back to Closure Compiler in advanced mode. It doesn't seem to like it because I'm accessing those using normal property access syntax, ie. model.color instead of model.get('color'), that's the point. But Closure is mangling those, and therefore I get undefined instead of my value. Any workaround for this? (Except rewriting everything to use get('attrname'))?
UPDATE And of course, how would this work with Backbone.Model.hasChanged and other methods that take a string literal for attribute name.
You are defining your properties using quoted syntax:
props[attr]
But your problem is occurring when you try to access them via dotted syntax:
model.color
In ADVANCED_OPTIMAZATIONS, a cardinal rule is that you must reference a property consistently. See https://developers.google.com/closure/compiler/docs/api-tutorial3#propnames
It sounds like the easiest answer may be for you to use quoted syntax for these properties:
model['color']
However, you'll lose all renaming and dead code elimination for such properties.
Any properties you create dynamically are "external" in advanced mode and you have three choices: (1) use quoted access (as Chad suggests) or (2) create externs or (3) use Simple mode and give up property rename and global dead code removal.

Is there a way to define symbolic constants in Javascript?

I searched if JavaScript offers a mean to define symbolic constants, but didn't find anything. Did I miss something ?
Is it a common practices to use const var instead ?
var const MAXIMUM_VALUE = 100;
Thanx.
const is not supported by IE, so if you want to support IE that is out of the question.
As far as I know, and the best way of doing this to keep it simple is to just have a naming convention for your constants like the ever-popular ALL UPPERCASE. There are some examples out there to force constants but they are not worth it for the most part. Alternatively, you could use a function:
function myConst() { return 'myValue'; }
This can of course still be overridden but I've seen it used.
Also see:
Are there constants in Javascript?
Is it possible to simulate constants in Javascript using closures?
Javascript: final / immutable global variables?
Yes. But you remove the var. const replaces var.
const MAXIMUM_VALUE = 100;
Object.defineProperty(window, 'CONSTANT_NAME', {value: CONSTANT_VALUE});
// usage
console.log(CONSTANT_NAME);
Object.defineProperty() creates a property with the following default attributes:
configurable true if and only if the type of this property descriptor may be changed and if the property may be deleted from the corresponding object.
Defaults to false.
enumerable true if and only if this property shows up during enumeration of the properties on the corresponding object. Defaults to false.
writable true if and only if the value associated with the property may be changed with an assignment operator. Defaults to false.
if the "constant" is an object you might additionally want to make it immutable by freezing it. obj =Object.freeze(obj). have in mind that child-property-objects are not automatically frozen.

Categories