Javascript Know when object value is modified - javascript

For any variable or property thereof, is there a way to know when its value is set?
For example, say I have:
let x = { 'a': 1, 'b': 2 };
// x.a's set operation is linked to a method
x.a = 3; // the method is automatically called
Is there a way I can call a function when a's value is changed? Lots of code would be changing this value; I don't want to add the method call all over the place.
I'm aware of Proxies, but to use them seems to require a separate variable. Meaning, x can't be a proxy of itself.
Preferably this technique would work with primitive and non-primitives.

x can't be a proxy of itself
Sure it can. You can change the variable to point to a Proxy by simply doing
x = new Proxy(x, handler)
Primitive example:
const handler = {
set: function(obj, prop, value) {
console.log('setting prop: ', prop, ' to ', value)
obj[prop] = value;
return true;
}
};
let x = { 'a': 1, 'b': 2 };
x = new Proxy(x, handler);
x.a = 3; // the method is automatically called

To be honest, use Proxy if you can
If you really can't use Proxy, you could achieve this using using setters and getters
Though it does mean re-declaring your original x object, I assume it's declared inline like the Minimal, Complete and Verifiable Example in your question
let x = {
_a: 1,
_b: 2,
get a() {
return this._a;
},
get b() {
return this._b;
},
set a(value) {
console.log(`changing a from ${this._a} to ${value}`);
this._a = value;
},
set b(value) {
console.log(`changing b from ${this._b} to ${value}`);
this._b = value;
}
};
x.a = 3;

Related

When is a getter function useful in JavaScript?

Specifically when used within objects, when would a getter function be used over a regular function. For example, what is the difference between using
const obj = {
get method() {
return data;
}
};
console.log(obj.method);
and this
conts obj = {
method() {
return data;
}
};
console.log(obj.method());
1 . With a normal property/method, you can change its value.
A getter method cannot have its value changed. Look what happens when we try to change the getter here (it doesn't change):
const obj = {
methodNormal() {
return 5;
},
get methodGetter() {
return 5;
}
};
obj.methodNormal = "red";
obj.methodGetter = "blue";
console.log(obj);
2 . Secondly, with a normal property you have the luxury of either returning the function i.e. obj.methodNormal or returning the function executing i.e. obj.methodNormal(). With getter functions you do not have the luxury of returning the function. You can only do obj.methodGetter which executes that function. The code snippet below demonstrates that.
const obj = {
methodNormal() {
return 5;
},
get methodGetter() {
return 5;
}
};
let captureNormalMethod = obj.methodNormal;
let captureGetterMethod = obj.methodGetter;
console.log(captureNormalMethod);
console.log(captureGetterMethod);
Both these qualities - being unchangeable & unable to be captured in a new variable or property - contribute to getter functions having a sense of 'hiddenness'. Now you can understand what people mean when they say getters are 'read-only' properties!
Further reading:
What I've been referring to as 'normal properties' are called data properties (good article).
Getter methods are an example of what are called accessor properties (good article).
A method can only return data a property can also have a setter
This is useful if you want to expose read-only properties. It isn't just a function to the caller.
In your example, both do the same thing, but the importance is not how they are alike but how they are not. You can, for example, pass the method version as an argument somewhere else to do some lazy execution, but the getter won't work like that.
const obj = {
lazyProp(parameter) {
return parameter + 2;
}
}
function evaluateLazy(fn) {
return fn()
}
evaluateLazy(obj.lazyProp)
One useful scenario is where you want to use regular property access on all your properties, but need to invoke a method to calculate a given value. For example take the following example which doesn't use a getter. It ends up printing the function, not the return value of the function. Without a getter, we would need to handle a specific case where the value is a function, and then invoke it.
const obj = {
x: 10,
y: 20,
total() {
return this.x + this.y;
}
}
const prettyPrintObj = obj => {
for(let key in obj) {
console.log(`${key.toUpperCase()} is ${obj[key]}`);
}
}
prettyPrintObj(obj);
However, with a getter, we can use the same function, and don't need to handle specific cases for where obj[key] is a function, as simply doing obj[key] on a getter will invoke the function for you:
const obj = {
x: 10,
y: 20,
get total() {
return this.x + this.y;
}
}
const prettyPrintObj = obj => {
for(let key in obj) {
console.log(`${key.toUpperCase()} is ${obj[key]}`);
}
}
prettyPrintObj(obj);

Why doesn't JavaScript ES6 support multi-constructor classes?

I want to write my Javascript class like below.
class Option {
constructor() {
this.autoLoad = false;
}
constructor(key, value) {
this[key] = value;
}
constructor(key, value, autoLoad) {
this[key] = value;
this.autoLoad = autoLoad || false;
}
}
I think it would be nice if we can write out class in this way.
Expect to happen:
var option1 = new Option(); // option1 = {autoLoad: false}
var option2 = new Option('foo', 'bar',); // option2 = {foo: 'bar'}
var option3 = new Option('foo', 'bar', false); // option3 = {foo: 'bar', autoLoad: false}
I want to write my Javascript class like below
You can't, in the same way you can't overload standard functions like that. What you can do is use the arguments object to query the number of arguments passed:
class Option {
constructor(key, value, autoLoad) {
// new Option()
if(!arguments.length) {
this.autoLoad = false;
}
// new Option(a, [b, [c]])
else {
this[key] = value;
this.autoLoad = autoLoad || false;
}
}
}
Babel REPL Example
Of course (with your updated example), you could take the approach that you don't care about the number of arguments, rather whether each individual value was passed, in which case you could so something like:
class Option {
constructor(key, value, autoLoad) {
if(!key) { // Could change this to a strict undefined check
this.autoLoad = false;
return;
}
this[key] = value;
this.autoLoad = autoLoad || false;
}
}
What you want is called constructor overloading. This, and the more general case of function overloading, is not supported in ECMAScript.
ECMAScript does not handle missing arguments in the same way as more strict languages. The value of missing arguments is left as undefined instead of raising a error. In this paradigm, it is difficult/impossible to detect which overloaded function you are aiming for.
The idiomatic solution is to have one function and have it handle all the combinations of arguments that you need. For the original example, you can just test for the presence of key and value like this:
class Option {
constructor(key, value, autoLoad = false) {
if (typeof key !== 'undefined') {
this[key] = value;
}
this.autoLoad = autoLoad;
}
}
Another option would be to allow your constructor to take an object that is bound to your class properties:
class Option {
// Assign default values in the constructor object
constructor({key = 'foo', value, autoLoad = true} = {}) {
this.key = key;
// Or on the property with default (not recommended)
this.value = value || 'bar';
this.autoLoad = autoLoad;
console.log('Result:', this);
}
}
var option1 = new Option();
// Logs: {key: "foo", value: "bar", autoLoad: true}
var option2 = new Option({value: 'hello'});
// Logs: {key: "foo", value: "hello", autoLoad: true}
This is even more useful with Typescript as you can ensure type safety with the values passed in (i.e. key could only be a string, autoLoad a boolean etc).
Guessing from your sample code, all you need is to use default values for your parameters:
class Option {
constructor(key = 'foo', value = 'bar', autoLoad = false) {
this[key] = value;
this.autoLoad = autoLoad;
}
}
Having said that, another alternative to constructor overloading is to use static factories. Suppose you would like to be able to instantiate an object from plain parameters, from a hash containing those same parameters or even from a JSON string:
class Thing {
constructor(a, b) {
this.a = a;
this.b = b;
}
static fromHash(hash) {
return new this(hash.a, hash.b);
}
static fromJson(string) {
return this.fromHash(JSON.parse(string));
}
}
let thing = new Thing(1, 2);
// ...
thing = Thing.fromHash({a: 1, b: 2});
// ...
thing = Thing.fromJson('{"a": 1, "b": 2}');
Here's a hack for overloading based on arity (number of arguments). The idea is to create a function from a number of functions with different arities (determined by looking at fn.length).
function overloaded(...inputs) {
var fns = [];
inputs.forEach(f => fns[f.length] = f);
return function() {
return fns[arguments.length].apply(this, arguments);
};
}
var F = overloaded(
function(a) { console.log("function with one argument"); },
function(a, b) { console.log("function with two arguments"); }
);
F(1);
F(2, 3);
Of course this needs a lot of bullet-proofing and cleaning up, but you get the idea. However, I don't think you'll have much luck applying this to ES6 class constructors, because they are a horse of a different color.
you can use static methods,look at my answer to same question
class MyClass {
constructor(a,b,c,d){
this.a = a
this.b = b
this.c = c
this.d = d
}
static BAndCInstance(b,c){
return new MyClass(null,b,c)
}
}
//a Instance that has b and c params
MyClass.BAndCInstance(b,c)
Use object.assigne with arguments with this
This={...this,...arguments}
Its not the overload I wanted, but this is a basic version of how I faked my way through creating an obj1 with some different initialization behavior. I realize I could have expanded the arguments as stated above, but I already had a nasty set of arguments and relatively different data sources to deconstruct that would have really distorted my objectives; this just made it cleaner for my situation...
class obj1{
constructor(v1, v2){
this.a = v1;
this.b = v2;
}
}
class obj1Alt{
constructor(v1, v2){
return new obj1(v1*2,v2*2);
}
}
new obj1(2,4) // returns an obj1
new obj1Alt(2,4) // also returns an obj1
Disclaimer: I've been programming for a long time, but I am fairly new to JS; probably not a best practice.

Variable is instance of Enum

I have the following javascript code:
function testClass() {
this.SaveValue = function (value) {
var isInstance = value instanceof TestEnum;
if (!isInstance) {
return;
}
}
}
TestEnum = {
VALUE_0: 0,
VALUE_1: 1,
VALUE_2: 2
}
I create an instance of this object in the following way:
$(function () {
var a = new testClass();
a.SaveValue(TestEnum.VALUE_1);
});
All I'd like to do is test that the value passed to the SaveValue function is actually the type of TestEnum. However, when I run this code I get the following error: Uncaught TypeError: Expecting a function in instanceof check, but got 1
Am I going about this the right way? I tried typeof but it only returns number which is not particularly useful to me.
You could create the values as instances of the "class":
function TestEnum(value) {
this._value = value;
}
TestEnum.prototype.valueOf = function() {
return this._value;
}
TestEnum.prototype.toString = function() {
return 'TestEnum_' + this._value;
}
TestEnum.VALUE_0 = new TestEnum(0);
TestEnum.VALUE_1 = new TestEnum(1);
The following would work then:
TestEnum.VALUE_0 instanceof TestEnum
But it also means you'd have to explicitly access the numerical value of one value with .valueOf. In some cases JS will do this automatically for you (like in 5 + TestEnum.VALUE_1). Overriding toString so that you can use a value as property might also be necessary.
It really depends on your use case whether this is a viable solution.
Alternatively, if just want to test whether a value is part of the enum, you can have an additional property which holds all possible values:
TestEnum.values = {0: true, 1: true, ...};
And then test it with
value in TestEnum.values
// or more reliable (fails for inherited `Object` properties)
TestEnum.values.hasOwnProperty(value);
You could even automate this:
function collectValues(obj) {
var values = {}; // or Object.create(null) if available
for (var prop in obj) {
if (obj.hasOwnProperty(prop)) {
values[obj[prop]] = true;
}
}
return values;
}
TestEnum.values = collectValues(TestEnum);
This will only reliably work for primitive values though and won't distinguish between the string "1" and the number 1.
You are passing a number to the function in
a.SaveValue(TestEnum.VALUE_1);
Since TestEnum is simply an Object, and you are referencing a number property on that object, you're calling your function with a number. You should instead create a TestEnumValue object and use that for your Object's properties:
JSFiddle link for below
function testClass() {
this.SaveValue = function (value) {
var isInstance = value instanceof TestEnumValue;
if (!isInstance) {
return;
}
}
}
TestEnumValue = function(arg) {
arg = arg ? arg : 0; // sensible default
this.key = 'VALUE_' + arg;
this.val = arg;
}
Level = {
NumSpiders : new TestEnumValue(0),
NumCreepers: new TestEnumValue(1),
NumZombies : new TestEnumValue(2),
NumChickens: new TestEnumValue // uses default enum value
};
$(function() {
var a = new testClass();
a.SaveValue(Level.NumSpiders);
$('#hi').text(Level.NumSpiders.key);
});
Playing around with this, I noticed that you can leverage the fact that an enum compiles into an object that binds the values both ways combined with a hasOwnProperty check.
export enum TEST_ENUM{
ZERO, // 0
ONE, // 1
TWO, // 2
}
let a = 1;
let b = TEST_ENUM.TWO // 2
let c = 5 // incorrect value
TEST_ENUM.hasOwnProperty(a); // TRUE
TEST_ENUM.hasOwnProperty(b); // TRUE
TEST_ENUM.hasOwnProperty(c); // FALSE
This comes with a few caveats though;
// An object's keys are always strings...
// Although this shouldn't not matter usually (e.g. parsed user input)
TEST_ENUM.hasOwnProperty("2"); // TRUE
// And the enum is bound two-way so:
let input = "TWO";
if (TEST_ENUM.hasOwnProperty(input) { // TRUE
let result = input // "TWO"
// result is now the enum's value, instead of the key.
result = TEST_ENUM[input]; // this would be the correct assignment
};
Of course you can fix both of these with a typeof check, in case of a string assign it TEST_ENUM[mystring].
Note that my intellisense didn't autocomplete the hasOwnProperty function on an enum, but it doesn't complain about it either, and it's available on all browsers.
Edit
Here's an example of how you could do it.
function TestEnum(val) {
this.vals = this.vals || [];
if (this.vals.indexOf(val) == -1) console.log('nope: ' + val);
else console.log('ok: ' + val);
}
(function() {
var vals = {
VALUE_0: 0,
VALUE_1: 1,
VALUE_2: 2
};
TestEnum.prototype.vals = [];
for (var key in vals) {
TestEnum[key] = vals[key];
TestEnum.prototype.vals.push(vals[key]);
}
})();
Now new TestEnum(TestEnum.VALUE_0); is OK, but if you try, say, new TestEnum(3), then it throws an exception.
This is a bit backwards -- x instanceof y means that x has been created as x = new y(). Since TestEnum isn't even a function, you can't create an instance of it, so this isn't going to work.
What you could do is maybe something like this:
function MyEnum(enumVal) { this.val = enumVal; }
a.SaveValue( new MyEnum(TestEnum.VALUE_1) );
Then check using isInstance = value instanceof MyEnum.

Set undefined javascript property before read

var tr={};
tr.SomeThing='SomeThingElse';
console.log(tr.SomeThing); // SomeThingElse
console.log(tr.Other); // undefined
tr.get=function(what){
if (tr.hasOwnProperty(what)) return tr[what];
else return what;
};
tr.get('SomeThing') // SomeThingElse
tr.get('Other') // Other
Is there any way to make tr.Other or tr['Other'] and all other undefined properties of the object to return its name instead undefined?
Three solutions:
Implement your object as a Proxy, which is designed to do exactly what you want. Yet, it is only a draft and currently only supported in Firefox' Javascript 1.8.5 It was standardised with ES6, but might not yet be available in all environments.
Always fill your translation object with a complete set of messages. When creating that "dictionary" (serverside or clientside), always include all needed keys. If no translation exists, you can use a fallback language, the message's name or the string representation of undefined - your choice.
But a non-existing property should always mean "there is no such message" instead of "no translation available".
Use a getter function with a string parameter instead of object properties. That function can look the messages up in an internal dictionary object, and handle misses programmatically.
I would recommend a map object which is different from the dictionary, to allow "get" and co as message names:
var translate = (function(){
var dict = {
something: "somethingelse",
...
};
return {
exists: function(name) { return name in dict; },
get: function(name) { return this.exists(name) ? dict[name] : "undefined"; },
set: function(name, msg) { dict[name] = msg; }
};
})();
You could define a getter for your property, either using object initializers:
var o = {
a: 7,
get b() {
return this.a + 1;
},
set c(x) {
this.a = x / 2;
}
};
console.log(o.a); // 7
console.log(o.b); // 8 <-- At this point the get b() method is initiated.
o.c = 50; // <-- At this point the set c(x) method is initiated
console.log(o.a); // 25
or using Object.defineProperties():
var o = { a: 0 };
Object.defineProperties(o, {
'b': { get: function() { return this.a + 1; } },
'c': { set: function(x) { this.a = x / 2; } }
});
o.c = 10; // Runs the setter, which assigns 10 / 2 (5) to the 'a' property
console.log(o.b); // Runs the getter, which yields a + 1 or 6
While this solution isn't exactly what you were looking for, a JavaScript implementation of python's collections.defaultdict class might help:
var collections = require('pycollections');
var dd = new collections.DefaultDict([].constructor);
console.log(dd.get('missing')); // []
dd.get(123).push('yay!');
console.log(dd.items()); // [['missing', []], [123, ['yay!']]]

Defining Setter/Getter for an unparented local variable: impossible?

There's a few previous questions on StackOverflow questioning how one goes about accessing local variables via the scope chain, like if you wanted to reference a local variables using bracket notation and a string, you'd need something like __local__["varName"]. Thus far I haven't found even the hackiest method for accomplishing this, and haven't come up with a method after hours of exploiting every trick I know.
The purpose for it is to implement getters/setters on arbitrary unparented variables. Object.defineProperties or __defineGet/Setter__ require a context to be called on. For properties in the global or window contexts you can accomplish the goal of having a setter/getter for direct references to the object.
Object.defineProperty(this, "glob", {get: function(){return "direct access"})
console.log(glob); //"direct access"
Even in my tests with a custom extension I compiled into a modified Chromium that runs prior to any window creation where the context is the actual global context, and even trying to call this directly in the global context crashes my program, I can pull this off without a hitch:
Object.defineProperty(Object.prototype, "define", {
value: function(name, descriptor){
Object.defineProperty(this, name, descriptor);
}
};
define("REALLYglobal", {get: function(){ return "above window context"; }});
And it is then available in all frames created later as a global routed through the specified getter/setter. The old __defineGet/Setter__ also works in that context without specifying what to call it on (doesn't work in Firefox though, the method above does).
So basically it's possible to define get/set guards for any variable on an object, including the window/global context with direct call to the object (you don't need window.propname, just propname). This is the issue with being unable to reference unparented scoped variables, being the only type that can be in an accessible scope but have no addressable container. Of course they're also the most commonly used too so it's not an edge case. This problem also transcends the current implementation of Proxies in ES6/Harmony since it's a problem specifically with being unable to address a local object's container with the language's syntax.
The reason I want to be able to do this is that it's the only barrier to allow overloading of most math operators for use in complex objects like arrays and hashes and deriving a complex resulting value. I need to be able to hook into the setter in cases where a value is being set on an object type I've set up for overloading. No problem if the object can be global or can be a contained in a parent object, which is probably what I'll just go with. It's still useful with a.myObject, but the goal is to make it as transparently usable as possible.
Not only that, but it'd just be really useful to be able to accomplish something like this:
var point3d = function(){
var x, y, z;
return {
get: function(){ return [x, y, z]; },
set: function(vals){ x=vals[0]; y=vals[1]; z=vals[2]; }
};
};
(That is similar to ES6's destructuring but has more general applications for implementing functionality attached to getting/setting and not just transporting complex values). Even this basic code will completely fail:
var x = {myname: "intercept valueOf and :set: to overload math ops!", index: 5};
x++; //x is now NaN if you don't implement a setter somehow
I don't care how hacky the solution is, at this point it's just an intense curiosity for me as to whether it can be accomplished, even if it requires breaking every best practice that exists. I've crashed Firefox and Chrome a few hundred times in pursuit of this so far by doing things like redefining/intercepting/modifying Object.prototype.valueOf/toString, Function.prototype Function.prototype.constructor, Function.prototype.call/apply, arguments.callee.caller, etc. with infinite recursion errors and whatnot in attempts to jury rig contexts retroactively. The only thing that I've been able to make work is wrapping basically the whole thing with eval and dynamically building code chunks, which is a bridge too far for me to ever actually use. The only other remotely successful route was in using with combined with pre-defining all local variables on a container, but that's obviously very intrusive on top of the issues with using with.
This is currently possible in environments with Proxies. That would be node > 0.6 run as node --harmony_proxies or >0.7 with node --harmony. Chromium Canary (not sure if it's out of that yet) in about:flags at the bottom, experimental javascript. Firefox has had it for a while with no flags.
So this probably won't work when ES6 becomes more official, but it works to an extent now.
var target = (function(){
var handler = Proxy.create(Proxy.create({
get: function(r, trap){
return function(name,val,c,d){
if (trap === 'get' || trap === 'set') {
name = val;
val = c;
}
console.log('"'+trap + '" invoked on property "'+name+'" ' + (val?' with value "'+val+'"':''));
switch (trap) {
case 'get': return target[name];
case 'set': return target[name] = val;
case 'has': return name in target;
case 'delete': return delete target;
case 'keys': return Object.keys(target);
case 'hasOwn': return Object.hasOwnProperty.call(target, name);
case 'getPropertyDescriptor':
case 'getOwnPropertyDescriptor': return Object.getOwnPropertyDescriptor(target, name);
case 'getPropertyNames':
case 'getOwnPropertyNames': return Object.getOwnPropertyNames(target);
case 'defineProperty': return Object.defineProperty(target, name, val);
}
}
}
}))
var target = {
x: 'stuff',
f: { works: 'sure did' },
z: ['overwritten?']
};
with (handler){
var z = 'yes/no';
if (x) {
//x
} else {
x = true;
}
console.log(f.works);
if (f.works) {
f.works = true;
delete f;
}
}
return target
})()
// "getPropertyDescriptor" invoked on property "z"
// "getPropertyDescriptor" invoked on property "z"
// "getPropertyDescriptor" invoked on property "x"
// "get" invoked on property "x"
// "getPropertyDescriptor" invoked on property "console"
// "getPropertyDescriptor" invoked on property "f"
// "get" invoked on property "f"
// sure did
// "getPropertyDescriptor" invoked on property "f"
// "get" invoked on property "f"
// "getPropertyDescriptor" invoked on property "f"
// "get" invoked on property "f"
// "getPropertyDescriptor" invoked on property "f"
target: { x: 'Stuff', f: { works: true }, z: ['overwritten?'] }
Hit or miss and you need to take care not to blow up your browser by simply looking at a Proxy in the debugger. I had to wrap that thing in a closure to keep the proxy from ending up in the global scope or it crashed the frame every single time. Point is that it works to some extent, where nothing else does.
It looks like the answer is No. I have been searching for behavior like this for quite a while. I have not been able to come up with any passable solution. This SO question seems similar. Python has the nice locals keyword.
Since you state you want similar behavior to window/global, I assumed you want this within a given context other that window/global. An easy way to do this is by using the with statement in combination with a local object and a define function which implement Object.defineProperty with local as target. You than simply place your own code within the with block.
IMPORTANT: with overloads the native local variables (var, let, const). Because of this it's very important to keep clear code, and to prevent duplicate names within the scope and parent/child contexts.
Lets start of with the context, in this case I use a closure, but this could also be a function, constructor or any other context.
// This closure represents any function, class or other scoped block.
(function (){
}());
Next we add the storage container and the define function. This is basically what you should always start with if you want access the local properties from anywhere in your code (within this scope).
// This is where we store the local property. (except: var, let, const)
const local = {};
// The define function is used to declare and define the local properties.
function define(name, descriptor){ Object.defineProperty(local, name, descriptor); }
Now you can place any code before the with statement but for this example we'll only add code that requires local in some way so the next step is creating the with statement.
// This with statement extends the current scope with local.
with(local){
// This is where your code goes.
}
Now the outer structure of the with statement is ready, and we can start adding code inside the with statement.
All code placed within the with statement's block has access to the properties of local as if they where defined with for instance var, including properties defined within the with statement.
There are several ways to work with the properties of local. The easiest way to define a property is by setting it within 'local' directly. This only needs to be done once, after that the property is accessable by just it's name.
local.setDirectly = "directly set value";
console.log(setDirectly); // logs "directly set value"
An other way to define a property, but than with support for get/setters as well as options on enumerabiliy and write access, is to use the define function. Expect the same behavior as from Object.defineProperty.
You could for instance add a time property that returns the current time.
define("time", {
get: function(){
var date = new Date();
return date.getHours() + ":" + ("0" + date.getMinutes()).substr(-2);
}
})
console.log(time);
Or you could create a counter property that increments each time it's accessed, placed within a nested closure to protect the counters own variable from unwanted changes.
(function (){
var counterValue = 0;
define("count", {get: function(){ return counterValue++ }});
}());
console.log(count); // logs 0
console.log(count); // logs 1
When you combine all this you will get something similar to the following code
// This closure represeents any function, class or other scoped block.
(function(){
// This is where we store the local property. (except: var, let, const)
const local = {};
// The define function is used to declare and define the local properties.
function define(name, descriptor){ Object.defineProperty(local, name, descriptor); }
// This with statement extends the current scope with local.
with(local){
// This is where your code goes.
// Defining a variable directly into local.
local.setDirectly = "directly set value";
console.log(setDirectly); // logs "directly set value"
// Defining local properties with the define function
// For instance a time variable that return the current time (Hours:Minutes)
define("time", {
get: function(){
var date = new Date();
return date.getHours() + ":" + ("0" + date.getMinutes()).substr(-2);
}
})
console.log(time); // logs HH:MM
// Or a counter property that increments each time it's been accessed.
(function (){
var counterValue = 0;
define("count", {get: function(){ return counterValue++ }});
}());
console.log(count); // logs 0
console.log(count); // logs 1
console.log(count); // logs 2
console.log(count); // logs 3
}
}());
Like I mentioned before, it is important to understand the implications of using the with statement. More information on with can be found at MDN - with. As the question states, it's a search to how you could, not how you should. Use the information on MDN to see if it fits your situation.
I don't know if this answers your question but this works:
Object.defineProperty(window, 'prop', {
get: function () {
alert('you just got me')
},
set: function (val) {
alert('you just set me')
},
configurable: true});
For a solution just with basic objects:
function ref (o)
{
return new Proxy({}, new Proxy({}, {
get (_, prop) { return (_, ...args) => Reflect[prop](o(), ...args) }
}));
}
To work with DOM and primitive objects as well:
function ref (o)
{
return new Proxy({}, new Proxy({}, {
get (_, prop) { return {
get (_, prop) {
let p = o(), r = p[prop];
if (r instanceof Function) r = r.bind(p)
return r
},
set (_, prop, v) { o()[prop] = v },
has (_, prop) { return prop in o() },
keys(_, prop) { return Object.keys(o()) },
apply (_, _this, args) { return Object.apply(o(), _this, args) },
hasOwn (_, prop) { return Object.hasOwnProperty.call(o(), prop) },
ownKeys() {
var p = o();
return Object.getOwnPropertyNames(p).concat(Object.getOwnPropertySymbols(p))
},
deleteProperty (_, prop) { return delete o()[prop] },
defineProperty (_, prop, desc) { return Object.defineProperty(o(), prop, desc) },
getOwnPropertyDescriptor (_, prop) { return Object.getOwnPropertyDescriptor(o(), prop) }
}[prop] ?? ((_, ...args) => Reflect[prop](o(), ...args));
}}));
}
function refs (o)
{
if (!(o instanceof Function)) o = (o => () => o)(o);
return new Proxy({}, {
get (_, prop) { return ref(() => o()[prop]) }
})
}
Usage
let vec = {x: 0, y: 1, z: 2};
let {x, y, z} = refs(() => vec);
outp(`X: ${x}. Y: ${y}. Z: ${z}`); // X: 0. Y: 1. Z: 2
vec.x = 3;
outp(`X: ${x}. Y: ${y}. Z: ${z}`); // X: 3. Y: 1. Z: 2
x = 1;
outp(vec.x); // 3
vec = {y: 1, z: 1};
outp(y === 1); // false
outp(y == 1); // true
outp(z == 1); // true
outp(y == z); // false
// You cannot directly compare these Proxy objects.
outp(y.valueOf() === z.valueOf()); // true
outp(y.valueOf() === 1); // true
outp(z.valueOf() === 1); // true
function ref (o)
{
return new Proxy({}, new Proxy({}, {
get (_, prop) { return {
get (_, prop) {
let p = o(), r = p[prop];
if (r instanceof Function) r = r.bind(p)
return r
},
set (_, prop, v) { o()[prop] = v },
has (_, prop) { return prop in o() },
keys(_, prop) { return Object.keys(o()) },
apply (_, _this, args) { return Object.apply(o(), _this, args) },
hasOwn (_, prop) { return Object.hasOwnProperty.call(o(), prop) },
ownKeys() {
var p = o();
return Object.getOwnPropertyNames(p).concat(Object.getOwnPropertySymbols(p))
},
deleteProperty (_, prop) { return delete o()[prop] },
defineProperty (_, prop, desc) { return Object.defineProperty(o(), prop, desc) },
getOwnPropertyDescriptor (_, prop) { return Object.getOwnPropertyDescriptor(o(), prop) }
}[prop] ?? ((_, ...args) => Reflect[prop](o(), ...args));
}}));
}
function refs (o)
{
if (!(o instanceof Function)) o = (o => () => o)(o);
return new Proxy({}, {
get (_, prop) { return ref(() => o()[prop]) }
})
}
let text = '';
function emitText()
{
document.body.appendChild(
Object.assign(document.createElement('pre'), {innerText: text})
);
}
function outp (t)
{
text += " // " + t;
}
function header (t)
{
emitText();
document.body.appendChild(
Object.assign(document.createElement('h1'), {innerText: t})
);
text = '';
}
function log (t)
{
text += '\n' + t;
}
header("Usage");
let vec = {x: 0, y: 1, z: 2}; log('let vec = {x: 0, y: 1, z: 2};');
let {x, y, z} = refs(() => vec); log('let {x, y, z} = refs(() => vec);');
log('outp(`X: ${x}. Y: ${y}. Z: ${z}`);'); outp(`X: ${x}. Y: ${y}. Z: ${z}`);
vec.x = 3; log('vec.x = 3;');
log('outp(`X: ${x}. Y: ${y}. Z: ${z}`);'); outp(`X: ${x}. Y: ${y}. Z: ${z}`);
x = 1; log('x = 1;');
log('outp(vec.x);'); outp(vec.x);
log('');
vec = {y: 1, z: 1}; log('vec = {y: 1, z: 1};');
log('outp(y === 1);'); outp(y === 1);
log('outp(y == 1);'); outp(y == 1);
log('outp(z == 1);'); outp(z == 1);
log('outp(y == z);'); outp(y == z);
log('// You cannot directly compare these Proxy objects.');
log('outp(y.valueOf() === z.valueOf());'); outp(y.valueOf() === z.valueOf());
log('outp(y.valueOf() === 1);'); outp(y.valueOf() === 1);
log('outp(z.valueOf() === 1);'); outp(z.valueOf() === 1);
header('');

Categories