Why does the Proxy object reflect changes beyond the target object? - javascript

I wanted to experiment a bit with the Proxy object, and got some unexpected results, as follows:
Test script
function Person(first, last, age) {
this.first = first;
this.last = last;
this.age = age;
}
Person.prototype.greeting = function () {
return `Hello my name is ${this.first} and I am ${this.age} years old`;
};
So in tracking how the prototype object is being modified, I added this wrapper:
let validator = {
set: function(target, key, value) {
console.log(`The property ${key} has been updated with ${value}`);
target[key] = value;
return true;
}
};
Person.prototype = new Proxy(Person.prototype, validator);
let george = new Person('George', 'Clooney', 55);
Person.prototype.farewell = function () {
return `Hello my name is ${this.first} and I will see you later`;
};
What I expected
The property: "farewell" has been updated with: "function () {
return `Hello my name is ${this.first} and I will see you later`;
}"
and nothing else.
And of course, each time I added or removed something from the prototype, i.e Person.prototype or instance.constructor.prototype I expected to see the console.log() message.
However I did not expect to see anything when setting something on the instance, like:
george.someProp = 'another value'; // did NOT expect to see the console.log()
Output
The property: "first" has been updated with: "george"
The property: "last" has been updated with: "clooney"
The property: "age" has been updated with: "55"
The property: "farewell" has been updated with: "function () {
return `Hello my name is ${this.first} and I will see you later`;
}"
Person.prototype
Proxy {greeting: ƒ, first: "George", last: "Clooney", age: 55, farewell: ƒ, constructor: ƒ}
It set all the properties on the prototype and nothing on the instance, and each time I set something on the instance it sets it straight on the prototype.
Evidently this is not the default behaviour, as if I remove that Proxy, every property set with this will be set on the instance itself and the prototype will start up empty (or in our case with just the greeting function).
What am I missing here ? A point in the right direction would be appreciated.

What you are missing is the fact that when you have a Proxy object in the prototype chain, the set handler will be called when you modify the child object.
In your example, when you set a property on the new instance, the set trap will be executed, the target will be the wrapped Person.prototype object, but there's a fourth argument, the receiver. This argument points to the object that the property has been accessed on.
To properly do the property assignment, you can use the Reflect.set API to set it:
Reflect.set(target, key, value, receiver);
That's why the Reflect API matches the proxy traps arguments.
So, in your example, we could use the Reflect API and you will see that Person.prototype doesn't get "polluted".
function Person(first, last, age) {
this.first = first;
this.last = last;
this.age = age;
}
Person.prototype.greeting = function () {
return `Hello my name is ${this.first} and I am ${this.age} years old`;
};
const validator = {
set: function(target, key, value, receiver) {
console.log(`The property ${key} has been updated with ${value}`);
Reflect.set(target, key, value, receiver)
return true;
}
};
Person.prototype = new Proxy(Person.prototype, validator);
const george = new Person('George', 'Clooney', 55);
Person.prototype.farewell = function () {
return `Hello my name is ${this.first} and I will see you later`;
};
console.log(george.hasOwnProperty('first')); // true
console.log(Person.prototype.hasOwnProperty('first')); // false

Related

Call a getter function with its name in JavaScript?

When I create a class in JavaScript like this one:
class Person {
_firstName = "";
_lastName = "";
constructor(firstName, lastName) {
this._firstName = firstName;
this._lastName = lastName;
}
get firstName() {
return this._firstName;
}
get lastName() {
return this._lastName;
}
fullName() {
return `${this.firstName} ${this.lastName}`;
}
}
let me = new Person("John", "Doe");
It creates a variable me that is an instance of Person. Its prototype has some functions whose names are “fullName” and “get firstName” (plus other functions)
I can call the function fullName by its name using:
me[“fullName”]();
John Doe
However, calling the getter function by its name throws an error:
me[“get firstName”]();
Uncaught TypeError: me.get firstName is not a function
Why does this behavior happen?
EDIT: Thanks for the replies. I know I can and should access it as a property. But I'd like to understand why accessing it by its name with the brackets notation does not work. Does that mean JavaScript has different kinds of functions since a behavior that works for one function does not work for another function (when it has a space in its name / is a getter or setter)?
When a function is assigned to a variable or as a property value (maybe a getter), there are two names involved:
The name of the function
The name of the variable/property
These names are different entities and don't necessarily have to be the same.
Here is a simple object with property "a" that has as value a function whose name is "b":
let obj = {
a: function b() { }
};
console.log("property: ", Object.keys(obj)[0]); // "a"
console.log("function: ", obj.a.name); // "b"
Now when the function does not get an explicit name, it is given one:
let obj = {
a() { }
};
console.log("property: ", Object.keys(obj)[0]); // "a"
console.log("function: ", obj.a.name); // "a"
This may give the false impression that both names are one and the same string, but this cannot be true as the same function object might be assigned to different properties:
let obj = {
a() { }
};
obj.c = obj.a;
console.log("property: ", Object.keys(obj)[1]); // "c"
console.log("function: ", obj.c.name); // "a"
Getters
The situation with a getter can be confusing, because the expression obj.a.name will not retrieve the name of the function, but will invoke the getter and then try to access the name property of the returned value.
To still get the name of the getter function, you can use Object.getOwnPropertyDescriptor as follows:
let obj = {
get a() { }
};
console.log("property: ", Object.keys(obj)[0]); // "a"
console.log("function: ", Object.getOwnPropertyDescriptor(obj, "a").get.name); // "get a"
This is how a getter's function name is set by default. You can however set it explicitly to your desire using Object.defineProperty as follows:
let obj = {};
Object.defineProperty(obj, "a", {
get: function b() {},
enumerable: true
});
console.log("property: ", Object.keys(obj)[0]); // "a"
console.log("function: ", Object.getOwnPropertyDescriptor(obj, "a").get.name); // "b"
Conclusions
A property name does not necessarily match with the name of the function that might be the property's value.
A property is identified by property name, not by the name of the function that might be associated with it.
The function object, that serves as a getter property, can be accessed with Object.getOwnPropertyDescriptor(object, propertyName).get
You should access it as a normal property like this:
me["firstName"];
But, if you know already the name, you could access the firstName value like this:
me.firstName
Typically, you use the first form if you want to access a property of an object using a variable like this:
const person = new Person("John", "Doe");
const propertyName = "firstName";
const firstName = person[propertyName];
If you want to know more about getter and setters check out this and this.
Follows a fully working example:
class Person {
_firstName = "";
_lastName = "";
constructor(firstName, lastName) {
this._firstName = firstName;
this._lastName = lastName;
}
get firstName() {
return this._firstName;
}
get lastName() {
return this._lastName;
}
fullName() {
return `${this.firstName} ${this.lastName}`;
}
}
const me = new Person("John", "Doe");
const propertyName = "firstName";
console.log(me["firstName"])
console.log(me.firstName)
console.log(me[propertyName])
When doing this:
get firstName() {
return this._firstName;
}
it´s defining a property and the you have to call it over:
me.firstName;
doing it like this:
getFirstName() {
return this._firstName;
}
is really defining a getter function and you can call it like this:
me.getFirstName();
Maybe this is helpfull: https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Functions/get

Log what getter gets using proxy

I have the following proxy handler that logs get events.
const proxyHandler = {
get: function(target, prop) {
console.log("get", prop);
return Reflect.get(target, prop);
}
};
const obj = new Proxy(
{
value: 4,
text: "hi",
get textVal() {
return this.text.repeat(this.value);
},
getTextVal() {
return this.text.repeat(this.value);
}
},
proxyHandler
);
console.log("------- normal func -------")
console.log(obj.getTextVal());
console.log("------- getter func -------")
console.log(obj.textVal);
When I log console.log(obj.getTextVal()) I get:
get getTextVal
get text
get value
hihihihi
But when I log the getter console.log(obj.textVal), I only get the following:
get textVal
hihihihi
How can I make obj.textVal log the get text and get get value events using proxy? ie. When running console.log(obj.textVal) I would like the following result.
get getTextVal
get text
get value
hihihihi
The above answer works but there is a bit more elegant solution. You are missing the receiver in your Proxy trap and Reflect arguments. Simply change the Proxy to this:
const proxyHandler = {
get: function(target, prop, receiver) {
console.log("get", prop);
return Reflect.get(target, prop, receiver);
}
};
Notice the new receiver in the trap and Reflect arguments.
There is an important distinction between a Proxy trap target and receiver. In this case, the target is the underlying raw object while the receiver is the Proxy wrapper. If you do not pass the receiver to the Reflect call everything inside the get operation will be run against the raw object and won't trigger the Proxy traps.
If you have the time I suggest you read the relevant parts of the ES6 spec to fully grasp the difference between these two. Otherwise, just make sure that you forward all Proxy trap args to the matching Reflect call if you are aiming for a transparent wrap.
You can set the Proxy instance to proxyHandler object and access the properties through it (instead of this).
const proxyHandler = {
get: function(target, prop) {
console.log("get", prop);
return Reflect.get(target, prop);
}
};
const proxifiedObj = {
value: 4,
text: "hi",
get textVal() {
return this.proxyInstance.text.repeat(this.proxyInstance.value);
},
getTextVal() {
return this.text.repeat(this.value);
}
}
obj = proxifiedObj.proxyInstance = new Proxy(proxifiedObj, proxyHandler);
console.log("------- normal func -------")
console.log(obj.getTextVal());
console.log("------- getter func -------")
console.log(obj.textVal);
console.log(obj.textVal);
get getTextVal
get text
get value
hihihihi
Update:
Or you could do the same thing by creating a custom Proxy that does the assignment for you
(Note: Proxy class cannot be extended but we can use the constructor return value pattern):
class InstanceAwareProxy {
constructor(proxifiedObject, proxyHandler) {
return proxifiedObject.proxyInstance
= new Proxy(proxifiedObject, proxyHandler);
}
}
obj = new InstanceAwareProxy(proxifiedObj, proxyHandler);

console.log() an object does not log the method added via prototype in node js console. Or how to print the prototypes also?

function Person(name) {
this.name = name;
}
Person.prototype.getName = function() {
return this.name
}
var tinu = new Person('Tinu');
console.log(tinu.getName()) //Prints the name 'Tinu' - Expected, means the function is added to protoype
console.log(tinu);
The last console.log() does not print the newly added method named 'getName' via dot prototype, prints only the property 'name', Here I would expect to print both property 'name' and also method 'getName' inside the Person object. Below is the actual output and desired output for the above code:
Actual output
Tinu
Person { name: 'Tinu' }
Desired output
Tinu
Person { name: 'Tinu', getName: [Function] }
The image below shows another example where the method 'getFullName' added via prototype is correctly shown while printing to console the object to which it is added. And was expecting the same with my example
console.log is a provided API by your js-environment (in your case Node.js). There's no standard spec. So in your case console.log prints a simple string representation of your Javascript-object.
{ propName: propValue }
In Node.js there's a util-module (util-documentation). Furthermore I found a method, which returns all properties of an object including all properties of the prototype-chain.
const util = require('util')
function Person(name) {
this.name = name;
}
Person.prototype.getName = function() {
return this.name
}
var tinu = new Person('Tinu');
console.log(util.inspect(tinu, {showHidden: false, depth: null}))
function getAllPropertyNames(obj) {
var props = [];
do {
Object.getOwnPropertyNames(obj).forEach(function (prop) {
if (props.indexOf(prop) === -1 ) {
props.push( prop );
}
});
} while (obj = Object.getPrototypeOf(obj));
return props;
}
console.log(getAllPropertyNames(tinu));
/*
[ 'name',
'constructor',
'getName',
'__defineGetter__',
'__defineSetter__',
'hasOwnProperty',
'__lookupGetter__',
'__lookupSetter__',
'isPrototypeOf',
'propertyIsEnumerable',
'toString',
'valueOf',
'__proto__',
'toLocaleString' ]
*/
If you are on a Browser and want to see defined methods and other infos, you can use your browser's developer tools. Press F12 and you can do a lot of investigation.
In chrome dev tools, if you click the unfold icon you can see the prototype properties in __proto__:
You can see that getName() is defined there. That's the proper place for it since it's a property of the prototype, not the person object itself.

function.apply( argument1, array) not taking all values from array

function.apply does not register the first argument (index 0) in my array.
I have tried the rest/spread syntax, and
the code below seems to work fine:
let sayName = function (object, ...languages) {
console.log(`My name is ${this.name} and I speak ${languages.join(', ')}`);
};
let stacy = {
name: 'Stacy',
age: 34
};
let languages = ['Javascript', 'CSS', 'German','English', 'Spanish'];
sayName(stacy, ...languages);
But the code below does not work, with explicit binding function.apply:
let languages1 = ['Javascript', 'CSS', 'German','English', 'Spanish'];
sayName.apply(stacy, languages1)
The result always leaves out the 0 index from the array, i.e. "Javascript" is not included in the output.
What am I doing wrong?
Your function seems to expect an object parameter, but that is incorrect seeing how you call it with apply. When you call apply or call, the first argument to that method is used to set the this value for your function's execution context. It does not get passed as argument.
As your function references this, it is important that you set this correctly, and so the use of apply or call is the good choice.
So change:
let sayName = function (object, ...languages) {
to:
let sayName = function (...languages) {
Note that you didn't use object anyway in your function's code.
In the version where you don't call apply, but call the function directly, you do:
sayName(stacy, ...languages);
...but here the this object will not be set correctly. You should have used call to ensure that this will refer to stacy:
sayName.call(stacy, ...languages);
Check out the documentation of apply:
Syntax
function.apply(thisArg, [argsArray])
Parameters
thisArg
Optional. The value of this provided for the call to func. [...]
argsArray
Optional. An array-like object, specifying the arguments with which func should be called. [...]
Object oriented programming, a better way
Now, taking a step back you should really have defined sayName as a prototype method, and construct stacy like so:
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
sayName(...languages) {
console.log(`My name is ${this.name} and I speak ${languages.join(', ')}`);
}
}
let stacy = new Person('Stacy', 34);
let languages = ['Javascript', 'CSS', 'German','English', 'Spanish'];
stacy.sayName(...languages);
... and why not actually store the languages a person speaks as a property as well, and remove the console.log from the method, as it should be the caller who decides to where the output should be made (maybe to a file or to a HTML element):
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
this.languages = [];
}
speaks(...languages) {
this.languages = languages;
}
introduction() {
return `My name is ${this.name} and I speak ${this.languages.join(', ')}`;
}
}
let stacy = new Person('Stacy', 34);
stacy.speaks('Javascript', 'CSS', 'German','English', 'Spanish');
console.log(stacy.introduction());

Why are getter/setter no longer working after copying an object with the spread syntax?

In the following snippet, the spread syntax works in a manner that I don't quite understand:
let obj = {
set setName(name){
obj.name = name
},
get myName() {
return obj.name
}
}
obj.setName = 'Jon Doe'
let spread_obj = {...obj}
spread_obj.setName = 'Marion Luke'
console.log('spread_obj name', spread_obj.myName) // spread_obj name Jon Doe
let create_obj = Object.create(obj)
create_obj.setName = 'Marion Luke'
console.log('create_obj name', create_obj.myName) // create_obj name Marion Luke
Can you explain why the reassignment of the name does not work in such a case?
Spreading an object does not copy getter and setter methods - rather, the spread operation only gets the property (that is, it calls the getter, if there is one), and assigns the result to the resulting object (the spread_obj, here), just like a normal object, without any getters or setters. You can see this if you put log statements inside the setter and getter. The resulting spread_obj has a setName property of undefined because setName is only a setter method, which doesn't return anything.
let obj = {
set setName(name){
console.log('setting ' + name);
this.name = name
},
get myName() {
console.log('getting');
return this.name
}
}
obj.setName = 'Jon Doe'
console.log('About to spread');
let spread_obj = {...obj}
console.log('Done spreading. spread_obj contents is:');
console.log(spread_obj);
spread_obj.setName = 'Marion Luke' // No "setting" log statement
Also note that if you want to use the second part of your code, you should probably change the setter and getter methods to refer to this rather than to obj, otherwise the results could be unintuitive; your getter and setter methods area always referring to the .name property on obj, rather than the .name property on the standard calling context (that is, to spread_obj or to create_obj). When you try to assign to spread_obj.setName, you actually change the original obj's .name property:
let obj = {
set setName(name){
obj.name = name
},
get myName() {
return obj.name
}
}
obj.setName = 'Jon Doe'
let create_obj1 = Object.create(obj)
create_obj1.setName = 'Marion Luke1'
let create_obj2 = Object.create(obj)
create_obj2.setName = 'Marion Luke2'
// "Marion Luke2", but we're logging the property from the "first" object!
console.log('create_obj name', create_obj1.name)
Fix by referring to this instead of obj:
let obj = {
set setName(name){
this.name = name
},
get myName() {
return this.name
}
}
obj.setName = 'Jon Doe'
let create_obj1 = Object.create(obj)
create_obj1.setName = 'Marion Luke1'
let create_obj2 = Object.create(obj)
create_obj2.setName = 'Marion Luke2'
console.log(create_obj1.name)
In addition to #CertainPerformances explanation, I would add that the behavior seems more sensible when using the getters/setters a more traditionally by having one name for both get and set.
For example when your object looks like this, everything works a little better (at least on the surface):
let obj = {
set name(name){ // setter and getter are accessed with the same property
this._name = name
},
get name() {
return this._name
}
}
obj.name = 'Jon Doe'
let spread_obj = {...obj}
spread_obj.name = 'Marion Luke'
// this seems more expected
console.log('spread_obj name', spread_obj.name)
// but this is still a little strange because
// Jon Doe is still there
console.log(spread_obj)
It's seems like a lot of work, but since object spread only takes enumerable properties, you can make _name non-enumerable. Then everything seems a little more sensible:
let obj = {
set name(name){
this._name = name
},
get name() {
return this._name
}
}
Object.defineProperty(obj, '_name', {
enumerable: false,
writable: true,
configurable: true
});
obj.name = 'Jon Doe'
let spread_obj = {...obj}
spread_obj.name = 'Marion Luke'
// now spread_obj only has a name prop:
console.log(spread_obj)
// and obj too:
console.log(obj)
the select answer is wrong.
getter/setter is not method, it is special properties.
...spread and object.assign will not work, just because they treat getter/setter like normal enumerable property, they
copy the 'value' of it, so getter/setter will fail.
if you want to copy it, you can refer undercore.js the extend method:
this is my assign code:
const assign = (ob, ...o) => {
o.forEach(obj=>{if (typeof obj !== 'undefined')
Object.defineProperties(ob, Object.getOwnPropertyDescriptors(obj));})
return ob;
};

Categories

Resources