javascript - add and delete smart getter programmatically - javascript

I'm trying to programmatically add and delete (for caching purposes) getters from an object. I'm adding a getter like this:
Object.defineProperty(obj, 'text', {
get: getter
})
obj.text should only be evaluated the first time it's accessed, and the calculated value cached for subsequent calls.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get shows how to implement a smart getter like this:
get notifier() {
delete this.notifier;
return this.notifier = document.getElementById('bookmarked-notification-anchor');
}
I can't use delete this.text inside my getter function however. What I've found is, that this is the prototype of the Object rather than the instance - is that correct? And if so, how can I delete the getter of the instance and replace it with the calculated value?
edit:
As per the comments, the getter and object looks something like this:
var obj = {}
obj.value = '2018-04-21T12:00:00Z000'
Object.defineProperty(obj, 'text', {
get: function () {
delete this.text // doesn't seem to work
if (this.value == null) return ''
var text = this.value.split('T')[0].split('-').reverse().join('.')
this.text = text
return text // return this.text ends in Maximum call stack size exceeded
}
})

You need to make the property configurable so that you can delete it:
var obj = {value: '2018-04-21T12:00:00Z000'};
Object.defineProperty(obj, 'text', {
get: function () {
delete this.text
if (this.value == null) return ''
var text = this.value.split('T')[0].split('-').reverse().join('.')
console.log("updating")
this.text = text
return text
},
configurable: true
//^^^^^^^^^^^^^^^^^^
});
console.log("first access");
console.log(obj.text);
console.log("second access");
console.log(obj.text);
Apart from that, if you are having issues with properties inherited from a prototype object, you cannot delete it but need to use defineProperty to shadow it.

What I've found is, that this is the prototype of the Object rather than the instance...
Not in the code you've shown, not unless you're calling it oddly.
One way to do it is to use Object.defineProperty to redefine the property.
So for instance, if you're doing this on a one-off object:
var obj = {
get notifier() {
var value = Math.random();
console.log("Getter called");
Object.defineProperty(this, "notifier", {
value: value
});
return value;
}
};
console.log("First use");
console.log(obj.notifier);
console.log("Second use");
console.log(obj.notifier);
Or if it's not a one-off:
function Maker() {
}
Object.defineProperty(Maker.prototype, "notifier", {
get: function() {
var value = Math.random();
console.log("Getter called");
Object.defineProperty(this, "notifier", {
value: value
});
return value;
},
configurable: true
});
var obj = new Maker();
console.log("First use");
console.log(obj.notifier);
console.log("Second use");
console.log(obj.notifier);
I've stuck to ES5-level stuff above since you didn't seem to be using any ES2015+ features.

Related

Creating jQuery prototyped collection

I need to initialize some kind of a prototype on already existing jQuery elements collection. The main problem is that the prototype should be accessible only inside of that collection and on elements produced by built-in jQuery functions like .find() on that collection or on some children objects inside of that collection, for example:
var $a = $('a');
$a.__proto__.foo/*some magic over here*/ = function(){ alert('foo!'); };
$a.foo(); //should show alert('foo!')
$a.find('b').foo(); //should produce the same action
$('a').foo(); //should produce an error (method not found)
If using $a.__proto__ like in example above, the jQuery.prototype is accessed, so all the new elements in outside of that jQuery-collection (for example, $('a')) are granting an access to .foo() method. That behaviour is unacceptable on a problem statement.
Is that actually possible?
Okay, here's the thing, I have a rather complex ES6 solution, so I won't be able to explain it in great depth, but if you have some particular questions, go ahead.
var wrap = (function wrapper() {
var store = {};
function wrap(fn) {
return new Proxy(fn, {
apply(target, thisArg, argumentsList) {
var result = Reflect.apply(target, thisArg, argumentsList);
// `jQuery(...)` returns a "rich" object that always contain `.length`
if (result.length > 0) {
result = new Proxy(result, {
get(target, propertyKey, receiver) {
var value = Reflect.get(target, propertyKey, receiver);
if (Object.keys(store).includes(propertyKey)) {
value = store[propertyKey];
}
return value;
},
set(target, propertyKey, value, receiver) {
// TODO: use `Reflect.set(), somehow`
// return Reflect.set(store, propertyKey, value, receiver);
return (store[propertyKey] = value);
},
});
}
return result;
}
});
}
return wrap;
})();
var $ = wrap(jQuery);
$.prototype.find = wrap(jQuery.prototype.find); // TODO: implement recursively in `wrap()`
var x = $('div');
var xx = x.find('div');
var xxx = x.find('divvv');
xx.foo = 123;
console.log(x.foo); // 123
console.log(xx.foo); // 123
console.log(xxx.foo); // undefined

Cloning Non enumerable properties in javascript

I have all non - enumerable properties in object, I want to clone that object.
My problem non-enumerable properties are not getting cloned.
Take below example
Object.defineProperty(this, 'prop', {
get: function () {
return prop;
},
set: function (value) {
prop= value;
}
});
Object.defineProperty(this, 'newprop', {
get: function () {
return newprop;
},
set: function (value) {
newprop= value;
}
});
For example I have above two properties in my object doing clone using following methods, my properties are not getting cloned I believe it is because they are non - enumerable.
var newObject = $.extend({},oldObject);
var newObject= Object.assign({},oldobject);
How do I copy non-enumerable properties in javascript.
If one or more properties aren't enumerable, how do you want to auto-magically enumerate them?
Since you know their names, you should do something like this:
var sourceObj = this;
var targetObj = {};
["prop", "otherProperty"].forEach(function(property) {
targetObj[property] = sourceObj[property];
});
Or you can build the whole property name array whenever you define a non-enumerable property:
var propertyNames = [];
Object.defineProperty(this, 'newprop', {
get: function () {
return newprop;
},
set: function (value) {
newprop= value;
}
});
propertyNames.push("newprop"); // <---
Object.defineProperty(this, 'newprop2', {
get: function () {
return newprop;
},
set: function (value) {
newprop= value;
}
});
propertyNames.push("newprop2"); // <---
propertyNames.forEach(function(property) {
targetObj[property] = sourceObj[property];
});
Alternate approach: Object.getOwnPropertyNames
The Object.getOwnPropertyNames() method returns an array of all
properties (enumerable or not) found directly upon a given object.
Maybe this is the best approach. Object.getOwnPropertyNames gets the name of own object's properties either if they're enumerable or not. That is, you can avoid building the whole propertyNames array, and this approach should be fine with you because you said that all properties aren't enumerable:
var sourceObj = this;
var targetObj = {};
Object.getOwnPropertyNames(sourceObj).forEach(function(property) {
targetObj[property] = sourceObj[property];
});
You can use Object.defineProperties and Object.getOwnPropertyDescriptors() function
The Object.defineProperties() method defines new or modifies existing properties directly on an object, returning the object.
The Object.getOwnPropertyDescriptors() method returns all own property descriptors of a given object.
So, you can add the properties of the original object into an empty object, as follow
var source_obj = this
var cloned_obj = Object.defineProperties({}, Object.getOwnPropertyDescriptors(source_obj));
I have a similar question in How to implement Ruby's extend module in JavaScript.
And because I don't wanna use iterate attach each method to new object, so the final best way I can do is like that, welcome give me any idea and suggestion, thanks.
class Test {
constructor(name) {
this.name = name;
}
test1(){
return this.name + "test1"
}
}
const Other = {
test2: function(){
return this.name + "test2"
},
test3: function(){
return this.name + "test3"
},
test4: function(){
return this.name + "test4"
}
}
var person = new Test("HKE")
Object.assign(person,Other) //this like object.extend(Module) in ruby
console.log(person.test1()) // HKEtest1
console.log(person.test2()) // HKEtest2
console.log(person.test3()) // HKEtest3
console.log(person.test4()) // HKEtest4
var house = new Test("Tsao")
console.log(house.test1())
console.log(house.test2()) // TypeError: house.test2 is not a function
console.log(house.test3()) // TypeError: house.test3 is not a function
console.log(house.test4()) // TypeError: house.test4 is not a function

execute function when setting an object

I want to execute a function when the value of props.test is changed. I have seen in another question Object.defineProperty can be used. But in my case it will props.test, how to make it work
props.test = props.test || {};
//if props.test value changes it need to execute set function
props.test = "abc";
function setTest(){
console.log("set function is executed");
props.test = "abc";
}
This could be achieved by defining getter and setters on an object such as,
var props = {
get value() {
return this._value;
},
set value(val) {
this._value = val;
alert("value is: " + val);
}
}
For the above example, when you set the value on the props object, an alert function is executed,
props.value; // returns undefined
props.value = "hello"; // alert box will appear, "value is: hello"
props.value; // returns "hello".
This can be replaced with the function you wish to execute when the value changes.
When adding properties onto an existing object you can use Object.defineProperty like so,
var me = {name:'Anthony', location:'RI'};
// let's extend onto the object a new property called, 'age'
// with custom getter and setter
Object.defineProperty(me, 'age', {
get: function() {
return this._age;
},
set: function(newAge) {
this._age = newAge;
alert("I am " + newAge + " now!");
}
});
me.age; // undefined
me.age = 28; // alert box will appear, "I am 28 now!"
me.age; // returns 28.
To take it one-step further if the pre-existing object contains an object, you can perform the following,
Note: Object.defineProperty takes as arguments an, object, key, and descriptor (ie, our custom setters and getters) respectively. That object argument can be any object type, even the nested object of the parent object.
// my pet rules my life, so let's include her in my example object,
var me = {name:'Anthony', location:'RI', pet:{name:'Binks'}};
// let's give her an identifier because that's what good owners do,
// Notice the first argument of, 'me.pet' which is the inner object.
Object.defineProperty(me.pet, 'id', {
get: function() {
return this._id;
},
set: function(newId) {
this._id = newId;
alert("id changed! " + newId);
}
});
me.pet['id']; // undefined
me.pet['id'] = 1; // alert box will appear, "id changed! 1"
me.pet['id']; // returns 1
Update: 07/12/2015
If you are trying to add dynamic values onto an Object, you can extend the above functionality into a generalized function,
var defineProp = function(obj, value) {
Object.defineProperty(obj, value, {
get: function() {
return this['_' + value];
},
set: function(newValue) {
this['_' + value] = newValue;
alert(value + " changed! " + newValue);
}
});
}
The most logical way of doing this is to create a function that is responsible for setting the property. To the best of my knowledge, there is no hook available to monitor when the property of an object has been changed. Alternatively, you object could use a timer to periodically check to see if the property has changed.

Method chaining and/or value caching

Say i have a constructor, and some instance methods, like
function MyClass(name) {
this.name = name || '';
}
MyClass.prototype = {
constructor: MyClass,
isEmptyName: function() {
return this.name === '';
}
}
Now i can write
var myClass = new MyClass('Ben');
myClass.isEmptyName();
which would return false. Now if i make another method what would also return a Boolean
MyClass.prototype = {
constructor: MyClass,
isEmptyName: function() {
return this.name === '';
}
longerThan: function(len) {
return this.name.length > len;
}
}
i would like to chain these methods like this (somehow, thats my question :) )
myClass.isEmptyName().and.longerThan(2);
Just omit now the '.and.' part. I want the upper statement to finally return a value
false && true -> false
Or a more realistic sample:
myClass.notEmptyName().and.longerThan(4);
To summarize my problem i would say, i want my methods return a boolean value if they are called 'directly' myClass.notEmptyName() should return true, but work like i wrote in the samples, otherwise.
Other libraries do this somehow, but i can't guess how, npm's should is a good example:
user.should.have.property('pets').with.lengthOf(4);
user.pets.should.be.instanceof(Array).and.have.lengthOf(4);
Thanks
That's not possible. A method can't return either a boolean or be chainable depending on how it's used later on, because it doesn't know how it will be used later on.
You can chain methods that validate the object in different ways, but you need to get the result at the end if you want to use it in an expression, either by reading a property or calling a method:
function MyClass(name) {
this.name = name;
this.and = this;
}
MyClass.prototype = {
value: true,
isNotEmpty: function() {
this.value = this.value && this.name.length > 0; return this;
},
isLongerThan: function(len) {
this.value = this.value && this.name.length > len; return this;
},
evaluate: function() {
return this.value;
}
};
console.log(new MyClass('Adam').isLongerThan(2).evaluate());
console.log(new MyClass('Bob').isNotEmpty().and.isLongerThan(3).evaluate());
Demo: http://jsfiddle.net/Guffa/62e8dLwL/
Edit:
To allow evaluation more than once, you would reset the value in the evaluate method:
evaluate: function() {
var v = this.value;
this.value = true;
return v;
}
Sure, you can do that. We will define a new intermediate status object, called ChainResult, which remembers the underlying object, the current value, and a pending operation (a function to use to combine the next test). We give this object a valueOf method, so that when JS tries to evaluate it as a primitive, it "looks" like it has a value. To make this work, it turns out that ChainResult actually needs to be a function, and so we hang the necessary properties off the function.
function ChainResult(obj, val) {
function x() { }
x.obj = obj;
x.val = val;
x.op = null;
// the `valueOf` function spits out the current value when the object is evaluated
x.valueOf = function() { return this.val; };
// the test functions combine the results with the current value
// using the current operation as set by a preceding `and` or `or`
x.isEmptyName = function() {
x.val = x.op(x.val, x.obj._isEmptyName());
return this;
};
x.isLongerThan = function(len) {
x.val = x.op(x.val, x.obj._isLongerThan(len));
return this;
};
// we implement `and` and `or` via getters which set the operation
// on the ChainResult object, and return `this` so we can keep chaining
Object.defineProperties(x, {
and: {
get: function() { x.op = function(a,b) { return a && b; }; return x; }
},
or: {
get: function() { x.op = function(a,b) { return a || b; }; return x; }
}
});
return x;
}
The MyClass definition needs a bit of tweaking:
function MyClass(name) {
this.name = name || '';
}
MyClass.prototype = {
constructor: MyClass,
// we implement the testers as pseudo-private functions
_isEmptyName: function() { return this.name === ''; },
_isLongerThan: function(len) { return this.name.length > len; },
// when the public tester functions are invoked directly on the object
// (when they are the first link in the chain), we construct and return a
// ChainResult object with the initial value set correctly
isEmptyName: function() { return ChainResult(this, this._isEmptyName()); },
isLongerThan: function(len) { return ChainResult(this, this._isLongerThan(len)) }
};
Flow:
new MyClass('Bob') // create MyClass object
.isEmptyName() // create ChainResult object with value `false`
.or // remember `or` operation in ChainResult object
.isLongerThan(2) // update value of ChainResult object
; // JS tries to convert to scalar, calls valueOf
// true
This needs to be bullet-proofed and tightened up, but you get the idea.
i want my methods return a boolean value if they are called 'directly' myClass.notEmptyName() should return true
Your methods are always called directly on the instance, and would always need to return a primitive boolean value. By that, the context (myClass) is lost and you cannot have an and method (or property getter) on the result.
I would recommend you to have a look at functional programming, partial application and currying, which helps a lot with fluent interfaces like this. Given
function and(fn1, fn2) {
return function(val) {
return fn1(val) && fn2(val);
};
}
function or(fn1, fn2) {
return function(val) {
return fn1(val) || fn2(val);
};
}
function hasEmptyName: function(val) {
return val.name === '';
}
function hasNameLongerThan: function(len) {
return function(val) {
return val.name.length > len;
};
}
you could write
and(hasEmptyName, hasNameLongerThan(2))(myClass);
Making these functions methods of anything is complicated however. Maybe something like this:
function method(name) {
var args = Array.prototype.slice.call(arguments, 1);
return function(instance) {
return instance[name].apply(instance, args);
};
}
Function.prototype.and = function (fn2) {
var fn1 = this;
return function(val) {
return fn1(val) && fn2(val);
};
}
Function.prototype.or = function (fn2) {
var fn1 = this;
return function(val) {
return fn1(val) || fn2(val);
};
}
Object.defineProperty(Object.prototype, "test", function(pred) {
return pred(this);
});
Now you could write
myClass.test(method("notEmptyName").and(method("longerThan", 4)));
This is an answer (better call it to outcome) for my own question:
Finally i came out with another solution based on the responses in this thread (Thanks guys!) because the original problem can not be solved, since the javascript runtime can't find out wether to return a value, or return itself (the object) when chained. Explanation is messy, sorry :(
Check out my lib, where i have methods like:
check(value).isString() etc..
Originally i wanted to chain these like check(value).isString().and.not.empty() but this, in this way, can not be done. (Challenge me)
Finally i created tokens for chaining, so instead of
check(value).isString().and.not.empty()
I can write
check(value).isstring.and.not.empty.test()
Not nice, but still something.
check.js
For review, visit checkjs on my github repo.
Note: README is outdated.
If you use promises, you could write functions that return values and can be chained. Mind you, these are all asynchronous.
Find this JS plugin: Kris Owal's Q, or if you like to use a JS library, they usually contain deferred objects and promises.

Defining read-only properties in JavaScript

Given an object obj, I would like to define a read-only property 'prop' and set its value to val. Is this the proper way to do that?
Object.defineProperty( obj, 'prop', {
get: function () {
return val;
}
});
The result should be (for val = 'test'):
obj.prop; // 'test'
obj.prop = 'changed';
obj.prop; // still 'test' since it's read-only
This method works btw: http://jsfiddle.net/GHMjN/
I'm just unsure if this is the easiest / smoothest / most proper way to do it...
You could instead use the writable property of the property descriptor, which prevents the need for a get accessor:
var obj = {};
Object.defineProperty(obj, "prop", {
value: "test",
writable: false
});
As mentioned in the comments, the writable option defaults to false so you can omit it in this case:
Object.defineProperty(obj, "prop", {
value: "test"
});
This is ECMAScript 5 so won't work in older browsers.
In new browsers or node.js it is possible to use Proxy to create read-only object.
var obj = {
prop: 'test'
}
obj = new Proxy(obj ,{
setProperty: function(target, key, value){
if(target.hasOwnProperty(key))
return target[key];
return target[key] = value;
},
get: function(target, key){
return target[key];
},
set: function(target, key, value){
return this.setProperty(target, key, value);
},
defineProperty: function (target, key, desc) {
return this.setProperty(target, key, desc.value);
},
deleteProperty: function(target, key) {
return false;
}
});
You can still assign new properties to that object, and they would be read-only as well.
Example
obj.prop
// > 'test'
obj.prop = 'changed';
obj.prop
// > 'test'
// New value
obj.myValue = 'foo';
obj.myValue = 'bar';
obj.myValue
// > 'foo'
In my case I needed an object where we can set its properties only once.
So I made it throw an error when somebody tries to change already set value.
class SetOnlyOnce {
#innerObj = {}; // private field, not accessible from outside
getCurrentPropertyName(){
const stack = new Error().stack; // probably not really performant method
const name = stack.match(/\[as (\w+)\]/)[1];
return name;
}
getValue(){
const key = this.getCurrentPropertyName();
if(this.#innerObj[key] === undefined){
throw new Error('No global param value set for property: ' + key);
}
return this.#innerObj[key];
}
setValue(value){
const key = this.getCurrentPropertyName();
if(this.#innerObj[key] !== undefined){
throw new Error('Changing global parameters is prohibited, as it easily leads to errors: ' + key)
}
this.#innerObj[key] = value;
}
}
class GlobalParams extends SetOnlyOnce {
get couchbaseBucket() { return this.getValue()}
set couchbaseBucket(value){ this.setValue(value)}
get elasticIndex() { return this.getValue()}
set elasticIndex(value){ this.setValue(value)}
}
const _globalParams = new GlobalParams();
_globalParams.couchbaseBucket = 'some-bucket';
_globalParams.elasticIndex = 'some-index';
console.log(_globalParams.couchbaseBucket)
console.log(_globalParams.elasticIndex)
_globalParams.elasticIndex = 'another-index'; // ERROR is thrown here
console.log(_globalParams.elasticIndex)
Because of the old browsers (backwards compatibility) I had to come up with accessor functions for properties. I made it part of bob.js:
var obj = { };
//declare read-only property.
bob.prop.namedProp(obj, 'name', 'Bob', true);
//declare read-write property.
bob.prop.namedProp(obj, 'age', 1);
//get values of properties.
console.log(bob.string.formatString('{0} is {1} years old.', obj.get_name(), obj.get_age()));
//set value of read-write property.
obj.set_age(2);
console.log(bob.string.formatString('Now {0} is {1} years old.', obj.get_name(), obj.get_age()));
//cannot set read-only property of obj. Next line would throw an error.
// obj.set_name('Rob');
//Output:
//========
// Bob is 1 years old.
// Now Bob is 2 years old.
I hope it helps.
I tried and it Works ...
element.readOnly = "readOnly" (then .readonly-> true)
element.readOnly = "" (then .readonly-> false)

Categories