When declaring a class (using the ECMAScript 2015 class) syntax, where are the methods that are defined as part of the class stored? I was expecting to find them on the prototype-object, but for some reason I could not access them in the following sample:
class MyClass {
constructor() {
this.a = function() {return ("a");};
}
b() { return ("b"); }
}
MyClass.prototype.c = function() { return ("c"); }
const m = new MyClass();
// Sanity check: All three functions are callable and return correct results
console.log(m.a()); // "a"
console.log(m.b()); // "b"
console.log(m.c()); // "c"
// Expected: The only property of the object m itself is a
console.log(Object.keys(m)); // ["a"]
// Unexpected: Where is b?
console.log(Object.keys(Object.getPrototypeOf(m))); // ["c"]
console.log(Object.keys(MyClass.prototype)); // ["c"]
The b-function must be somewhere (it is clearly callable), but where? I tested this behavior node 9.5.0 and Firefox 58.0.1
Object.keys only shows enumerable properties. The properties defined on the prototype by class aren’t enumerable.
console.log(Object.getOwnPropertyNames(Object.getPrototypeOf(m)));
You can use getOwnPropertyNames() method to return all the properties:
class MyClass {
constructor() {
this.a = function() {
return ("a");
};
}
b() {
return ("b");
}
}
MyClass.prototype.c = function() {
return ("c");
}
const m = new MyClass();
// Sanity check: All three functions are callable and return correct results
console.log(m.a()); // "a"
console.log(m.b()); // "b"
console.log(m.c()); // "c"
// Expected: The only property of the object m itself is a
console.log(Object.keys(m)); // ["a"]
// Unexpected: Where is b?
console.log(Object.keys(Object.getPrototypeOf(m))); // ["c"]
console.log(Object.keys(MyClass.prototype)); // ["c"]
console.log(Object.getOwnPropertyNames(MyClass.prototype));
The Object.getOwnPropertyNames() method returns an array of all properties (including non-enumerable properties except for those which use Symbol) found directly upon a given object.
Class methods defined within the class syntax are non-enumerable, which is why b doesn't appear in the output of Object.keys:
> Object.getOwnPropertyDescriptor(MyClass.prototype, "b")
{value: ƒ, writable: true, enumerable: false, configurable: true}
> Object.getOwnPropertyDescriptor(MyClass.prototype, "c")
{value: ƒ, writable: true, enumerable: true, configurable: true}
Related
I'm trying to understand why using Object.defineProperty makes a property not visible when the object is logged in the console.
For instance:
let foo = {};
Object.defineProperty(foo, "a", {
get() { return "a"; }
});
Object.defineProperty(foo, "b", {
get() { return "b"; },
enumerable: true
});
console.log( foo ); // returns {}
I do understand that enumerable will affect the outcome of a for...in loop or the Object.keys() static method.
But I don't get why console.log(foo) returns {}, regardless of the use of enumerable, and even if I start by declaring foo as let foo = { a: 'bar' };
Thanks!
I am trying to get my class to behave like a "normal" object, in that when it is called in object.entries it returns an array of key value pairs. After quite a bit of searching around I have been able to make my class iterable. But I cannot find where to start in terms of implementing object.entries.
Here is where I have got up to,
'use strict'
class Person {
#name
constructor(name) {
this.#name = name
}
get name () {
return this.#name
}
*iterator () {
var props = Object.getOwnPropertyNames(Object.getPrototypeOf(this))
for (const prop of props) {
if (typeof this[prop] !== 'function') {
const o = {}
o[prop] = this[prop]
yield o
}
}
}
[Symbol.iterator] () {
return this.iterator()
}
}
const person = new Person ('bill')
//works - produces { name: 'bill' }
for (const prop of person){
console.log (prop)
}
// doesn't work. Prints an empty array
console.log (Object.entries(person))
The issue is that your instances have no public "own" (not inherited) properties, which is what Object.entries includes in its array.
I am trying to get my class to behave like a "normal" object, in that when it is called in object.entries it returns an array of key value pairs.
That's the default behavior, whether you create the object via a class constructor or some other way.
After quite a bit of searching around I have been able to make my class iterable.
Object.entries has nothing to do with whether an object is iterable.
I don't think you can make Object.entries return [[name, "value"]] without making name an "own" data property. But you can make it a read-only own data property via Object.defineProperty:
class Person {
#name;
constructor(name) {
this.#name = name;
Object.defineProperty(this, "name", {
value: name,
writable: false,
enumerable: true,
configurable: true
});
}
}
const bill = new Person("Bill");
console.log(`bill.name:`, bill.name);
console.log(`Object.entries(bill):`, Object.entries(bill));
I've kept #name there, but there's probably no reason for it, so:
class Person {
constructor(name) {
Object.defineProperty(this, "name", {
value: name,
writable: false,
enumerable: true,
configurable: true
});
}
}
const bill = new Person("Bill");
console.log(`bill.name:`, bill.name);
console.log(`Object.entries(bill):`, Object.entries(bill));
If you want to be able to set name internally, just give yourself a private method that repeates the defineProperty:
class Person {
constructor(name) {
this.#setName(name);
}
#setName(name) {
Object.defineProperty(this, "name", {
value: name,
writable: false,
enumerable: true,
configurable: true
});
}
}
const bill = new Person("Bill");
console.log(`bill.name:`, bill.name);
console.log(`Object.entries(bill):`, Object.entries(bill));
Unfortunately, because the property is configurable, your code isn't the only code that can use defineProperty to change it. (This is rather like the name property on functions, which is read-only by default but can be changed via defineProperty.) So it's not exactly the same as having #name and get name, it's just close while providing support for Object.entries.
You actually don't have to implement the iterator. The reason Object.entries wouldnt work is that due to the # the property is treated as private and therefore is not returned. If you remove the # name is returned by Object.entries like in the example below.
class Person {
name
constructor(name) {
this.name = name
}
}
const person = new Person ('bill')
// doesn't work. Prints an empty array
console.log(Object.entries(person))
I am using rest parameter to do some operations, but got some properties disappeared in result.
The data format of this.props looks like this:
```
this.props = {
data: [{...}, ...],
...
}
```
And I tried to rest it in this way:
```
let {showWaveAnimation, ...otherProps} = this.props;
console.log('data' in otherProps); // false
console.log('data' in this.props); //true
```
Why would 'data' property lost after I tried rest operation?
According to MDN, I found the description:
rest parameters are only the ones that haven't been given a separate name (i.e. formally defined in function expression), while the arguments object contains all arguments passed to the function;
What does separate name here means? Is it means properties that extends from its prototype would not be rest? But after I tried the following statements, I got the same result, I am confusing.
```
class Parent {
constructor() {
this.props = {
data: '123',
};
}
}
class Child extends Parent {
constructor() {
super();
this.props.childProp = 'test';
let {test, ...otherProps} = this.props;
console.log('data' in this.props, 'data' in otherProps);
// true true
}
}
new Child();
```
The former code behaviors abnormal after Babel's transpiling, could it be Babel's plugin problem?
ADD: I found this concept may be more precise in describing my rest operations. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#Rest_in_Object_Destructuring
I figured out why 'data' disappeared in my rest result, it's the enumerable property of 'data' field!
Once, the property is set as enumerable: false, we can't get it with the operation of rest in object destructuring.
According to MDN
The Rest/Spread Properties for ECMAScript proposal (stage 3) adds the rest syntax to destructuring. Rest properties collect the remaining own enumerable property keys that are not already picked off by the destructuring pattern.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#Rest_in_Object_Destructuring
I think this example can explain my ideas more precisely:
const person = {
name: 'Dave',
surname: 'Bowman'
};
Object.defineProperty(person, 'age', {
enumerable: false, // Make the property non-enumerable
value: 25
});
console.log(person['age']); // => 25
const clone = {
...person
};
console.log(clone); // => { name: 'Dave', surname: 'Bowman' }
The rest parameter allows you to get all the remaining attributes not already selected by identifier preceding it in the assignment.
Let's take a simple example:
const obj = {
a: 1,
b: 2,
c: 3
}
const { a, ...rest } = obj;
console.log(a);
console.log(rest);
Above obj has a property named a so it is destructured from obj and assigned to the variable a, then all other properties are assigned in an object to rest.
However, what you have is more like:
const obj = {
a: 1,
b: 2,
c: 3
}
const { d, ...rest } = obj;
console.log(d);
console.log(rest);
Here obj doesn't have a property named d. So it is destructured from obj as undefined and assgined to the variable a. Then all other properties not already assigned, i.e. a, b and c are given to rest.
In your code otherProps has the same properties as this.props, because object this.props doesn't have a property named test. In other words, in order to destructure an object you have to know its structure.
rest parameters are only the ones that haven't been given a separate name (i.e. formally defined in function expression), while the arguments object contains all arguments passed to the function;
What does separate name here means? Is it means properties that extends from its prototype would not be rest? But after I tried the following statements, I got the same result, I am confusing.
Given a function definition function foo(a, b, ...c) {}, arguments a, and b are the named parameters, whereas the rest of the passed arguments are spread into the last variable c. Now, c is a named variable, but any parameters passed from the third on will just be accessible in c, i.e. c[0].
Now, your console.log error is stemming from your use of the in operator. You defined an object const foo = {'data': 'bar'}. 'data' in foo is true, but 'blarg' in foo is false since foo doesn't contain a key/property named 'blarg'.
You're actually using the spread operator to destructure your props object into several named variables.
const object = {
data: 'foo',
other: 'bar',
moreData: [1,2,3],
};
// Example usage of the spread operator
const { data, ...allTheRest} = object; // data is destructured, other and moreData into allTheRest
console.log(data);
console.log(allTheRest);
// in operator
console.log('data' in object); // true
console.log('data' in allTheRest); // false
console.log('moreData' in allTheRest); // true
// This is an example of the rest operator
const foo = (...allTheArgs) => {
allTheArgs.forEach(e => console.log(e));
}
foo(1,2,3); // all the args are collected into a single array variable in the function
const bar = (a, b, ...c) => {
console.log(`a: ${a}`);
console.log(`b: ${b}`);
console.log(`c: ${c}`);
};
bar('this is first param', 42, 'foo', 'bar', '1', 2, 'stackoverflow');
I am trying to create an object. But I dont understand why my property getters setters cannot simply call this.bar. As such my foo object seems to end up with two properties.
Is this correct or am I:
using defineProperties wrong
missing the point
creating foo with a bar property
var foo = function ()
{
Object.defineProperties(this, {
bar : {
get : function () {return this.barVal},
set : function(value) { this.barVal = value},
enumerable: true,
configurable: true
}
})
};
var o = new foo();
o.bar = "Hello";
console.log(JSON.stringify(o));
//output {"bar":"Hello","barVal":"Hello"}
JSON.stringify activates the getter to resolve the property. Your setter sets a second property, so you end up seeing both. What you need is a way to store the "internal" value for foo.bar that isn't ON foo itself.
function Foo(){
var secret = {};
Object.defineProperties(this, {
bar: {
get: function( ){ return secret.bar },
set: function(v){ secret.bar = v },
enumerable: true,
configurable: true
}
});
}
var foo = new Foo;
foo.bar = 'Hello';
console.log(JSON.stringify(foo));
You are creating two properties, one named "bar" and the other named "barVal". "bar" is created by the defineProperties call and "barVal" is created by the this.barVal assignment in the set function. They both have a true value for their enumerable attribute (you explicitly set it for varVal, the assignment implicitly set it for barVal) so they are both listed by JSON.stringify.
If you intent is for barVal to be treated as a private value that does not show up in a JSON or for-in enumeration you can explicitly set its enumerable attribute to false:
var foo = function ()
{
Object.defineProperties(this, {
bar : {
get : function () {return this.barVal},
set : function(value) { this.barVal = value},
enumerable: true,
configurable: true
},
barVal : {
value: undefined,
enumerable: false, writable: true, configurable: true
}
})
};
How come constants cannot be set as properties of objects which are variables themselves?
const a = 'constant' // all is well
// set constant property of variable object
const window.b = 'constant' // throws Exception
// OR
var App = {}; // want to be able to extend
const App.goldenRatio= 1.6180339887 // throws Exception
And how come constants passed by reference suddenly become variable?
EDIT: I know App won't (or rather... SHOULDN'T) be mutable; this is just an observation...
(function() {
const App;
// bunch of code
window.com_namespace = App;
}());
window.com_namespace; // App
window.com_namespace = 'something else';
window.com_namespace; // 'something else'
How can a nicely organized, extensible, object-oriented, singly namespaced library containing constants be made with these limitations?
EDIT: I believe zi42, but I just have to ask why
You cannot do it with constants. The only possible way to do something that behaves like you want, but is not using constants, is to define a non-writable property:
var obj = {};
Object.defineProperty( obj, "MY_FAKE_CONSTANT", {
value: "MY_FAKE_CONSTANT_VALUE",
writable: false,
enumerable: true,
configurable: true
});
Regarding your question as to why a const passed to a function becomes variable, the answer is because it's passed by value and not by reference. The function is getting a new variable that has the same value as your constant.
edit: thanks to #pst for noting that objects literals in javascript are not actually "passed by reference", but using call-by-sharing:
Although this term has widespread usage in the Python community, identical semantics in other languages such as Java and Visual Basic are often described as call-by-value, where the value is implied to be a reference to the object.
const person = {
name: "Nicholas"
};
// works
person.name = "Greg";
console.log(person) //Greg
That's why use Object.defineProperty
There is a far simpler way to do this. I like this pattern. Simple Objects.
window.Thingy = (function() {
const staticthing = "immutable";
function Thingy() {
let privateStuff = "something";
function get() {
return privateStuff;
}
function set(_) {
privateStuff = _;
}
return Object.freeze({
get,
set,
staticthing
});
}
Thingy.staticthing = staticthing;
return Object.freeze(Thingy);
})();
let myThingy = new Thingy();
Thingy.staticthing = "fluid";
myThingy.staticthing = "fluid";
console.log(Thingy.staticthing); // "immutable"
console.log(myThingy.staticthing); // "immutable"
Object.freeze is doing the work here
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze
if you want you can leave the static property off the instance by leaving it off the object literal return on the constructor function.
const will only make it a read-only reference. As soon as you assign it, like here in a object literal it becomes a property of the constructed object.
var obj = {};
Object.defineProperty( obj, "MY_FAKE_CONSTANT", {
value: "MY_FAKE_CONSTANT_VALUE",
writable: false,
enumerable: true,
configurable: false // instead of true
});
We should set also configurable to be false so that it will prevent the property from being deleted from the obj
delete obj.MY_FAKE_CONSTANT;
With configurable to be true, after the line, we don't have the MY_FAKE_CONSTANT anymore.
Reference
You should not forgot that the const declaration "creates a read-only reference to a value. It does not mean the value it holds is immutable, just that the variable identifier cannot be reassigned"
The const keyword work in a similar way than 'let', so you can redeclare it in an other block
const MyConst = 5;
console.log('global MyConst =', MyConst); //global MyConst = 5
if(true){
const MyConst = 99
console.log('in if block, MyConst =', MyConst); //in if block, MyConst = 99
}
console.log('global MyConst still 5 ?', MyConst===5); //global MyConst still 5 ? true
Just like #ziad-saab mantioned if you want an object property than act like a constant, you have to define it as a non-writable property.
if your constant is an object and is property should not change, use Object.freeze() to make the object immutable.
(function(){
var App = { };
// create a "constant" object property for App
Object.defineProperty(App , "fixedStuff", {
value: Object.freeze({ prop:6 }),
writable: false,
enumerable: true,
configurable: true
});
Object.defineProperty(window, "com_namespace", {
value: App,
writable: false,
enumerable: true,
configurable: true
});
})()
com_namespace.newStuff = 'An extension';
com_namespace.fixedStuff.prop = 'new value'; // do nothing!
console.log(com_namespace.fixedStuff.prop); //6
I thinks that you should define a constant in property making configurable:false and writable:false in Object.defineProperty, if you leave configurable:true then you can still making changes to the property
for example: when you set configurable:true in Object.defineProperty
const obj = {name: 'some name'};
Object.defineProperty(obj, 'name', {
enumerable: true,
configurable: true,
writable: false
});
With this you are making obj.name 'constant' but after you could do
Object.defineProperty(obj, 'name', {
enumerable: true,
configurable: true,
writable: true // <-- you can set to true because configurable is true
});
But if you set configurable to false then you could never edit writable property.
Object.defineProperty(obj, 'name', {
enumerable: true,
configurable: false, // <-- with this you never could edit writable property
writable: false
});
And for all properties you could use Object.freeze
Object.freeze(obj);
Object.freeze will iterate over enumerable properties only