ES6 `static get length()` is not inherited as expected - javascript

I'm having an issue with providing a static getter function for the length property of my ES6 class extends.
As it turns out the actual Function.length getter always takes precedence over my own implementation.
class Foo {
static get value() {
return 'Foo';
}
static get length() {
return this.value.length;
}
}
class Bar extends Foo {
static get value() {
return `${super.value}Bar`;
}
}
console.log(Foo.value, Foo.length); // 'Foo', 3
console.log(Bar.value, Bar.length); // 'FooBar', 0
In the example above, Foo does exactly what I expected it to do, Bar not so much. Bar.value does indeed return 'FooBar', but Bar.length being 0 surprised me.
It took me a while to realize where the 0 came from, as I fully expected it to be 6 (and would have understood 3 to some degree).
As it turns out the 0 value provided by Bar.length is in fact the length of the constructor function of Bar, I realised this when wrote the same example in ES5 notation, there is a quick way to prove this though; simply add a constructor to Bar.
class Bar extends Foo {
constructor(a, b, c, d) {
// four configured parameters
}
static get value() {
return `${super.value}Bar`;
}
}
console.log(Foo.value, Foo.length); // 'Foo', 3
console.log(Bar.value, Bar.length); // 'FooBar', 4
There are ways around this:
add the static get length() to all extends (not my idea of
inheritance)
use a different property name (e.g. static get size() works as intended, but is not a generally used property in JS)
extend the base from a built-in class which has a working length (e.g. class Foo extends Array {...}) -
None of these are what I want to do if there's a more appropriate way to do this.
So my question is; does anyone know a proper way to have a custom property override which is inherited as expected, or am I being too stubborn?
As mentioned, I figured out what went wrong by writing the class syntax to (what I believe) would be the ES5 equivalent, as it may be beneficial to other developers and may shed some light on how I think ES6 classes work I'll leave it here.
(If anyone has a tip on how to make this bit collapsable on Stackoverflow, feel free to edit/suggest)
What I suppose is happening in ES5 syntax
I am aware ES6 classes are mostly syntactic sugar around the prototypal inheritance JS has, so what seems to happen for Bar is something like;
function Foo() {}
Object.defineProperties(Foo, {
value: {
configurable: true,
get: function() {
return 'Foo';
}
},
length: {
configurable: true,
get: function() {
return this.value.length;
}
}
});
function Bar() {}
Bar.prototype = Object.create(Object.getPrototypeOf(Foo));
Object.defineProperties(Bar, {
value: {
configurable: true,
get: function() {
return 'Bar' + Foo.value;
}
}
});
console.log(Foo.value, Foo.length); // 'Foo', 3
console.log(Bar.value, Bar.length); // 'FooBar', 0
I would've expected the property descriptors of Foo to be taken into account, like:
function Bar() {}
Bar.prototype = Object.create(Object.getPrototypeOf(Foo));
Object.defineProperties(Bar, Object.assign(
// inherit any custom descriptors
Object.getOwnPropertyDescriptors(Foo),
{
configurable: true,
value: {
get: function() {
return 'Bar' + Foo.value;
}
}
}
));
console.log(Foo.value, Foo.length); // 'foo', 3
console.log(Bar.value, Bar.length); // 'bar', 6

About static members
Static members of an ES6 class are in fact members of the function object rather than its prototype object. Consider the following example, where I'll use regular methods instead of a getter, but the mechanics are identical to getters:
class Foo {
static staticMethod() {
return 'Foo static';
}
nonStaticMethod() {
return 'Foo non-static';
}
}
staticMethod will become a member of the constructor function object, whereas nonStaticMethod will become a member of that function object's prototype:
function Foo() {}
Foo.staticMethod = function() {
return 'Foo static';
}
Foo.prototype.nonStaticMethod = function() {
return 'Foo non-static';
}
If you want to run staticMethod from a Foo 'instance' you'll have to navigate to its constructor first, which is the function object where staticMethod is a member of:
let foo = new Foo();
foo.staticMethod(); // Uncaught TypeError: foo.staticMethod is not a function
// Get the static member either on the class directly
// (this works IF you know that Foo is foo's constructor)
Foo.staticMethod(); // > 'Foo static'
// this is the same, provided that neither 'prototype' nor
// 'prototype.constructor' has been overridden afterwards:
Foo.prototype.constructor.staticMethod(); // > 'Foo static'
// ...or by getting the prototype of foo
// (If you have to perform a computed lookup of an object's constructor)
// You'll want to perform such statements in a try catch though...
Object.getPrototypeOf(foo).constructor.staticMethod(); // > 'Foo static'
function.length
All functions have a length property that tells you how many arguments that function accepts:
function Foo(a, b) {}
Foo.length; // > 2
So in your example, a prototype lookup for Bar.length to Foo.length will indeed never occur, since length is already found directly on Bar. A simple override will not work:
Foo.length = 3;
Foo.length; // still 2
That is because the property is non-writable. Let's verify with getOwnPropertyDescriptor:
Object.getOwnPropertyDescriptor(Foo, 'length');
/*
{
value: 0,
writable: false,
enumerable: false,
configurable: true
}
*/
Also, instead of the value getter you define, you can instead just use function.name to get the name of a function / class constructor:
Foo.name; // > 'Foo'
Let's use this to override the length property on Foo. We are still able to override Foo.length because the property is configurable:
Object.defineProperty(Foo, 'length', {
get() {
return this.name.length;
}
});
Foo.length; // 3
This is code bloat
It is highly undesirable to have to do this for each extending class, or define a static getter for each, which is equivalent to the code above. It is not possible to entirely override the behaviour without any decoration of the function objects of some sort. But since we know that classes are just syntactic sugar, and we are actually just dealing with objects and functions, writing a decorator is easy!
function decorateClasses(...subjects) {
subjects.forEach(function(subject) {
Object.defineProperty(subject, 'value', {
get() {
const superValue = Object.getPrototypeOf(this).value || '';
return superValue + this.name;
},
enumerable: true,
configurable: true,
});
Object.defineProperty(subject, 'length', {
get() {
return this.value.length;
},
enumerable: true,
configurable: true,
});
})
}
This function accepts one or multiple objects, on which it will override the length property and set a value property. Both are accessors with a get method. get value explicitly performs prototype lookups, and then combines the results with the name of the function it belongs to. So, if we have 3 classes:
class Foo {
}
class Bar extends Foo {
}
class Baz extends Bar {
}
We can decorate all classes at once:
decorateClasses(Foo, Bar, Baz);
If we access value and length on all three classes (functions) we get the desired results:
Foo.value; // 'Foo'
Foo.length; // 3
Bar.value; // 'FooBar'
Bar.length; // 6
Baz.value; // 'FooBarBaz'
Baz.length; // 9

Related

Set the prototype constructor correctly on an object

I have a DTO type that really is a map of key/value pairs. I would typically use an object literal for this for terseness, but this preculdes the resulting object having the [[prototype]].constructor set to a meaningful value.
e.g.
function MyDto() {
var o = {
myProperty: null
};
return o; //[[prototype]].constructor is meaningless
}
Is there a way to do something similar, but have the [[prototype]].constructor property set to the more meaningful MyDto (instead of Object)?
Not very much sure what you want to do. But this may help..
function MyDto() {
var o = {
myProperty: null
};
Object.setPrototypeOf(o,MyDto.prototype);
return o;
}
a = MyDto();
console.log(a);
To make obj instanceof Foo work, the prototype of obj has to point to the value of the prototype property of the function (Foo). A couple of ways have already been suggested, here is another one:
Call the function with new and return this (implicitly or explicitly). If you want to be able to call the function without new (not really clear from your question), check inside the function whether it was called with new or not:
function MyDto() {
if (!(this instanceof MyDto)) {
return new MyDto();
}
Object.assign(this, {myProperty: null});
}
Note: The constructor property has no meaning internally, only to the developers using your code.
I don't think I understand your question, but you might try this:
o.constructor = MyDto;
This will set o's constructor as MyDto, but will have no effect when doing o instanceof MyDto.
If this is what you want, my suggest is that you instantiate MyDto instead:
function MyDto() {
this.myProperty = null;
}
var o = new MyDto();
console.log(o instanceof MyDto); // true
console.log(o.constructor === MyDto); // true
EDIT: If you return within a function, then you will lost the reference to your new instance. In your case MyDto works as a factory to instances of Object that have an own property called myPropert.
EDIT 2: I still prefer the other way, but using Object.create also works:
function MyDto() {
return Object.create(MyDto.prototype, {
myProperty: {
writable: true,
configurable: true,
value: null
}
});
}
new MyDto() instanceof MyDto; // true
new MyDto().constructor === MyDto; // true
You're approaching this from the wrong direction. If you want the result of new Class() be a real instanceof Class, just extend the default instance object, instead of creating and returning a new one.
function Class () {
_.extend(this, {
property: 'value',
// ...
});
}
(The above code uses Lo-Dash function _.extend() to keep your code short & sweet. Similar implementations may be found in almost all utility libraries or bigger JavaScript frameworks).

Object doesn't support property or method '__defineGetter__'

I am using arbor js . But when i run ie8,9 and 10 browser it show on error Object doesn't support property or method __defineGetter__ . how can i fix this problem.
__defineGetter__ is an old, proprietary way of defining property "getter" functions. If the library you're using relies on it, you can't use it on JavaScript engines that don't support it.
The modern, standard way of defining getter functions for properties doesn't use that syntax. Perhaps there's a more up-to-date version of the library you could use.
There are two/three current standard ways, and another coming:
Using get and set functions on an object initializer:
var obj = {
get foo() {
return /*...foo value...*/;
},
set foo(v) {
// Set foo value
}
};
...or using Object.defineProperty:
var obj = {};
Object.defineProperty(obj, "foo", {
get: function() {
return /*...foo value...*/;
},
set: function(v) {
// Set foo value
}
};
...or using Object.defineProperties (or the second argument to Object.create, which accepts the same thing):
var obj = {};
Object.defineProperties(obj, {
foo: {
get: function() {
return /*...foo value...*/;
},
set: function(v) {
// Set foo value
}
}
});
For objects created via constructors, ES6 will also add class:
class ThingWithFoo {
get foo() {
return /*...foo value...*/;
}
set foo(v) {
// Set foo value
}
}

JS non-enumerable function

I'm trying to define a non-enumerable toJSON function on a prototype object without much luck. I'm hoping for something similar to ECMAScript 5 toJSON:
Object.defineProperty(obj, prop, { enumerable: false });
However this defines it as a property which cannot be accessed as a method.
[EDIT: Nick is wrong; it can be accessed as a method. His mistake was in code that is not shown in this question - see his comments on answers below, for details.]
I was hoping to be able to define the function in a non-enumerable fashion, as I was planning to define in the prototypes of all primitive types (String, Number, Boolean, Array, and Object), so that I can recursively apply the function through complex objects.
The end goal here is to be able JSONify a Backbone model/collection with nested collections recursively.
I guess in total I have two main questions:
Is it possible to define a non-enumerable function on a prototype? If so how?
Is there a better way to JSONify nested Backbone models?
I don't get it, why can't you access it as a method?
var foo = {};
Object.defineProperty(foo, 'bar', {
enumerable: false,
value: function () {console.log('foo.bar\'d!');}
});
foo.bar(); // foo.bar'd!
If you wanted it on the prototype, it's as easy as
Object.defineProperty(foo.prototype, /* etc */);
or even directly in Object.create
foo.prototype = Object.create(null, {
'bar': {value: function () {/* ... */}}
});
However, unless you're creating instances of foo, it won't show up if you try to foo.bar, and only be visible as foo.prototype.bar.
If foo has it's own prototype (e.g. foo = Object.create({})), you can get it with Object.getPrototypeOf, add the property to that and then foo.bar would work even if it is not an instance.
var proto = Object.getPrototypeOf(foo); // get prototype
Object.defineProperty(proto, /* etc */);
You can see visibility of enumerable vs non-enumerable properties here.
Paul S. is right about needing to set the property definition's value instead of a get, but I wanted to add that you don't need to pass enumerable: false, because false is the default for that option in Object.defineProperty() The answer can be simplified to:
var foo = {};
Object.defineProperty(foo, 'bar', {
value: function(){ console.log('calling bar!'); }
});
foo.bar();
Always you can avoid enumerable functions properties in object when you looping through it. And instead of define property in each object and set enumerable to false , you can create function which will call to any object with the property you want and put a condition to not take the property in the looping list. here is the example :
const obj = {
name: "myName",
title: "developer"
}
function prop() {
this.loop = function(i) {
for (i in this) {
if (typeof(this[i]) == "function") {
continue;
} else {
console.log(this[i]);
}
}
}
}
prop.call(obj);
obj.loop();
output >> myName, developer

JavaScript property & field reflection

I came across this little snippet of code for property reflection in JavaScript:
function GetProperties(obj) {
var result = [];
for (var prop in obj) {
if (typeof obj[prop] !== "function") {
result.push(prop);
}
}
return result;
}
I've tested it using the following "CustomObject":
var CustomObject = (function () {
function CustomObject() {
this.message = "Hello World";
this.id = 1234;
}
Object.defineProperty(CustomObject.prototype, "Foo", {
get: function () {
return "foo";
},
enumerable: true,
configurable: true
});
Object.defineProperty(CustomObject.prototype, "Bar", {
get: function () {
return "bar";
},
enumerable: true,
configurable: true
});
return CustomObject;
})();
Here is a little test using jQuery:
$(document).ready(function () {
console.log(GetProperties(new CustomObject()));
});
Here are the results:
["message", "id", "Foo", "Bar"]
I understand that the GetProperties function just returns an array of anything in the input object that is not a function, but I want to filter the results to get only the "real" properties, so my output should be:
["Foo", "Bar"]
Is this possible?
Also, can I do the opposite and just return the fields?
There are two things you could do (and possibly more, it depends on your exact situation):
Name "private" properties differently, e.g. with a trailing underscore and check whether the property name ends with an underscore when you are iterating over the properties (and exclude them).
If by "real properties" you mean the properties defined on the prototype and you want to ignore all properties defined on the object itself, you can use .hasOwnPrototype to check where it is defined. Alternatively, you could use Object.getPrototypeOf and iterate over the properties of the prototype only.
Bad code. I'm leaving (with a comment) it 'cause the subsequent discussion
might help somebody else.
If you always use defineProperty() to get non enumerable properties, then this would work:
function GetProperties(obj) {
var result = [];
for (var prop in obj) {
// propertyIsEnumerable() returns false just because the properties
// are inherited thru the prototype chain. It was just a coincidence
// that it got the desired result. Don't do this.
if (typeof obj[prop] !== "function" && !obj.propertyIsEnumerable(prop)) {
result.push(prop);
}
}
return result;
}
Otherwise, I'd be curious to know a general solution to the problem.
EDIT: I see that the code has enumerable: true and still my code does exactly what was asked. Double you tee ef?

Why is Object.create() so verbose?

Object.create is a great addition to JavaScript, because it adheres more to the prototypical nature of JS. However, I can't help but find the syntax of the 2nd parameter to the function to be too verbose, and a step back.
For example, if I want to create an object, and specify a new property in the derived object, I need to include that property value within a property object, regardless if I'm interested in the extra features or not.
So, something as simple as this:
o = Object.create({}, { p: 42 })
Now becomes:
o = Object.create({}, { p: { value: 42 } })
Obviously this is a simple example, but to me the verbosity is unnecessary, and should be optional.
Does anyone understand the decision to require a properties object? What is your opinion of the requirement of the new syntax?
Note: I understand there are easy solutions to overcome this requirement.
The syntax is done this way so that you can add parameters that control each property:
So, when you do this:
o = Object.create({}, { p: { value: 42 } })
you are saying that you want a property named p with a value of 42. The key here is that there are other parameters you can set for each property and if there wasn't this extra level of object hierarchy, you wouldn't have any way to pass those extra parameters.
So, for example, you could also do this:
o2 = Object.create({}, { p: { value: 42, writable: true, enumerable: true, configurable: true } });
Here's you are not just specifying the value of 42, but also some options for that property. If there wasn't the extra level of object hierarchy here, then you wouldn't have a place to put those extra options.
Yes, it does seem inconvenient when you only want the simple case. But, you could easily write yourself a helper function that made the simpler syntax work:
function myCreate(proto, props, enumerable, writable, configurable) {
// last three arguments are optional - default them to true if not present
if (typeof enumerable === "undefined") {enumerable = true;}
if (typeof writable === "undefined") {writable = true;}
if (typeof configurable === "undefined") {configurable = true;}
var wrapper = {};
for (var i in props) {
wrapper[i] = {
value: props[i],
enumerable: enumerable,
configurable: configurable,
writable: writable
};
}
return(Object.create(proto, wrapper));
}
Demo here: http://jsfiddle.net/jfriend00/vVjRA/

Categories

Resources