I've written a simple class (for want of a better word) in JavaScript. The class creates two accessor (get/set) properties called foo and bar, and two methods called setProperty and getProperty.
The get/set methods for the properties call getProperty and setProperty, which in turn, get and set the properties from the properties object.
I would expect to see infinite recursion here, where the properties are calling the methods, which are calling the properties, which are calling the methods...etc.
But it works...and I'm mind boggled as to how, and where the values are being stored!
Example code
var Test = (function () {
return function Test() {
var $this = this,
properties = {
foo: {
get: function () { return $this.getProperty("foo"); },
set: function (value) { $this.setProperty("foo", value); },
enumerable: false,
configurable: false
},
bar: {
get: function () { return $this.getProperty("bar"); },
set: function (value) { $this.setProperty("bar", value); },
enumerable: false,
configurable: false
}
};
Object.defineProperties(this, properties);
this.getProperty = function (name) {
console.log("get was definitely called!");
return properties[name];
};
this.setProperty = function (name, value) {
console.log("set was definitely called!");
properties[name] = value;
};
};
})();
var test = new Test();
//undefined
test.foo = "Hello World";
//set was definitely called!
//"Hello World"
test.bar = 3;
//set was definitely called!
//3
test.foo;
//get was definitely called!
//"Hello World"
test.bar;
//get was definitely called!
//3
Very interested to know why I'm not just getting a "too much recursion" error!
When you are calling defineProperties, you are creating properties in the test object, not properties in the properties object.
The properties object is not used as properties any more, only reused as storage for the property values. Setting the foo property will replace the property definition in the property object with the value.
When you set the test.foo property, that will call the setter code for the property and store the value in the properties object, but that doesn't affect the test.foo property. Once the properties are created, the object that was used to define them is not used any more.
The code is a bit confusing, as it first uses the properties object as definition for the properties, then reuses it for storage of property values. If you use two separate objects for this, it's clearer why setting a property doesn't affect the property itself:
var Test = (function () {
return function Test() {
var $this = this,
def = {
foo: {
get: function () { return $this.getProperty("foo"); },
set: function (value) { $this.setProperty("foo", value); },
enumerable: false,
configurable: false
},
bar: {
get: function () { return $this.getProperty("bar"); },
set: function (value) { $this.setProperty("bar", value); },
enumerable: false,
configurable: false
}
},
storage = {};
Object.defineProperties(this, def);
this.getProperty = function (name) {
console.log("get was definitely called!");
return storage[name];
};
this.setProperty = function (name, value) {
console.log("set was definitely called!");
storage[name] = value;
};
};
})();
Related
I want to expand all SVGElement with some new functions.
For example:
SVGElement.prototype.logType= function () {
console.log('I am a SVGelement from type: ' + this.nodeName);
}
If svgText is a svgText-Objekt and i call svgText.logType()
This works fine... -> log is "I am a SVGelement form type: svgText"
But i like to have all my function with a prefix my.
I tryed:
SVGElement.my= {};
SVGElement.prototype.my.logType= function () {
console.log('I am a SVGelement from type: ' + this.nodeName);
}
The Problem is, when i call svgText.my.logType(), "this" points to "my"-Objekt, and not the svgText-Object.
Is there a way? Thx for help and sorry for my english ;)
If you want a "my" prefix on all the additions you make, by far the simplest way is to make it part of the method name instead:
SVGElement.prototype.myLogType = function() { /*...*/ };
// ------------------^^
But in general, don't use direct assignment to create new methods on objects used as prototypes, it creates an enumerable property, which tends to be problematic. Instead, use Object.defineProperty and don't make the new property enumerable (it will be non-enumerable by default).
Object.defineProperty(SVGElement.prototype, "myLogType", {
value: function() { /*...*/ },
writable: true,
configurable: true
});
However, it's possible to do what you want, it's just (slightly) inefficient and cumbersome: Make my a property with an accessor function and custom-generate the object you return the first time it's used on an instance.
See comments:
// Stand-in for SVGElement for the example
function FakeElement(id) {
this.id = id;
}
// An object with the methods we want to add
var ourMethods = {
logText: function() {
return this.id;
}
};
// Add our "my" property
Object.defineProperty(FakeElement.prototype, "my", {
get() {
// If we're being called on the prototype object itself, don't
// do anything and just return null
if (this === FakeElement.prototype) {
return null;
}
// Define 'my' on this specific object with bound functions
console.log("Creating 'my' for element id = " + this.id);
var obj = this;
var my = {};
Object.keys(ourMethods).forEach(function(key) {
my[key] = ourMethods[key].bind(obj);
});
Object.defineProperty(this, "my", {value: my});
return my;
}
});
// Test it
var f1 = new FakeElement(1);
var f2 = new FakeElement(2);
console.log(f1.my.logText());
console.log(f2.my.logText());
console.log(f1.my.logText());
console.log(f2.my.logText());
That's written for clarity, not brevity, and if we were taking advantage of ES2015+ improvements to JavaScript could be more concise, but hopefully it gets you started...
I'm building a javascript library and I would like to be able to do exactly like the PHP's __get does.
My library has a attributes property which stores each model's attributes. Now, I am force to get an attribute using a .get method. But I would be able to do it with a getter. Let's says that User extends my model class.
let instance = new User({firstname: 'John', lastname: 'Doe'});
console.log(instance.get('firstname')); // gives me 'John'
I want to be able to do instance.firstname which will call the .get method passing 'firstname' as parameter. In PHP you can do it that way : http://php.net/manual/fr/language.oop5.overloading.php#object.get
Is this something possible?
Thank you all
This is easy using ES 2015 classes:
class Foo {
constructor () {
this._bar = null;
}
get bar () {
doStuff();
return this._bar;
}
set bar (val) {
doOtherStuff();
this._bar = val;
return this;
}
};
var foo = new Foo();
foo.bar = 3; // calls setter function
console.log(foo.bar); // calls getter function
here's the (simplified) output from babel:
var Foo = function () {
function Foo() {
this._bar = null;
}
_createClass(Foo, [{
key: "bar",
get: function get() {
doStuff();
return this._bar;
},
set: function set(val) {
doOtherStuff();
this._bar = val;
return this;
}
}]);
return Foo;
}();
Note that this isn't just for classes, any arbitrary object can have these:
var baz = {
get qux() {
// arbitrary code
},
set qux(val) {
// arbitrary code
}
};
Source.
EDIT
What you want is possible but only in native ES 6 environments, as Proxy cannot be polyfilled.
var getter = function(target, property, proxy) {
console.log(`Getting the ${property} property of the obj.`);
return target[property];
};
var setter = function(target, property, value, proxy) {
console.log(`Setting the ${property} property to ${value}.`);
target[property] = value;
};
var emptyObj = {};
var obj = new Proxy(emptyObj, {
get: getter,
set: setter
});
obj.a = 3; // logs 'Setting the a property to 3'
var foo = obj.a; // logs 'Getting the a property of the obj'
Quite simply assign the properties in a loop:
User = function (attrs) {
for (var name in attrs) {
this[name] = attrs[name];
}
}
User.prototype = {
// further methods
}
Using the ES6 class syntax, - I have to admit I do not see the point of writing things this way:
class User {
constructor (attrs) {
for (var name in attrs) {
this[name] = attrs[name];
}
}
// further methods
}
Remember: the second syntax is exactly what happens with the first one, only with some sugar on top.
I am using the Typed.React library which includes a method to extend one prototype definition with that of another:
function extractPrototype(clazz) {
var proto = {};
for (var key in clazz.prototype) {
proto[key] = clazz.prototype[key];
}
return proto;
}
If the provided class defines property methods, this function has a side effect of executing the get method e.g.
var TestObject = (function () {
function TestObject() {
this.str = "test string";
}
Object.defineProperty(TestObject.prototype, "TestProperty", {
get: function () {
console.log("exec get");
return this.str;
},
set: function (value) {
console.log("exec set");
this.str = value;
},
enumerable: true,
configurable: true
});
return TestObject;
})();
var extracted = extractPrototype(TestObject);
When extactPrototype accesses TestObject.prototype["TestProperty"], it will execute the property get method and print:
exec get
How would I duplicate a prototype with property methods without executing them?
I think you are looking for the new ES6 Object.assign function.
Of course there's a simpler fix to your problem - just don't access and set properties, copy their property descriptors:
function extractPrototype(clazz) {
var proto = {};
for (var key in clazz.prototype) {
Object.defineProperty(proto, key, Object.getOwnPropertyDescriptor(clazz.prototype, key));
}
return proto;
}
var blah = (function(){
function ret(){
}
ret.prototype = Object.create(Object.prototype, {
getone: {
get: function() { return 1; }
},
funcstuff: function(){ console.log('funcstuff'); }
});
return ret;
})();
var b = new blah();
console.log(b.getone); // 1
b.funcstuff(); // Uncaught TypeError: Property 'funcstuff'
// of object #<Object> is not a function
I would like to know the correct syntax for adding funcstuff to the ret prototype using Object.create() above.
http://jsfiddle.net/Qy9Vm/
I would like to know the correct syntax for adding funcstuff to the ret prototype using Object.create() above.
Since the object you give to Object.create to define the properties is a property descriptor, if you want funcstuff to actually be a function, you define it as the value property in the descriptor:
ret.prototype = Object.create(Object.prototype, {
getone: {
get: function() { return 1; }
},
funcstuff: { // changes
value: function(){ console.log('funcstuff'); } // changes
} // changes
});
I think the correct syntax is:
var blah = (function(){
function ret(){
}
ret.prototype = Object.create(Object.prototype, {
getone: {
get: function() { return 1; }
},
funcstuff: { value: function(){ console.log('funcstuff'); } }
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
});
return ret;
})();
var b = new blah();
console.log(b.getone); // 1
b.funcstuff();
Object.create() doesn't accept functions or properties directly, it takes a property descriptor which is itself an object that has standard properties that can be set like configurable, enumerable ... etc.
In Firefox, I've got several objects that I need to trigger an event when a particular property of each is changed. I'm using object.watch(), however when I return the value of the property that was changed using "this", it returns the old value the first time, and "undefined" the second and subsequent times:
var myObject = {
"aProperty": 1
};
function propChanged(prop) {
alert(prop);
}
myObject.watch("aProperty", function () {
propChanged(this.aProperty);
});
myObject.aProperty = 2;//alerts "1"
myObject.aProperty = 3;//alerts "undefined"
The reason I can't just say alert(myObject.aProperty) is because this is meant to be a dynamic code that will apply the event handler to several, possibly unknown objects.
I'm just unsure exactly how to dynamically get the new value of the property using the watch method. I'm setting up a prototype for IE for this, so I'm not worried about it not working there. I just need to understand "this" and how it applies to the watch method's owner.
Edit>>
Here's the new code I'm using for cross browser, including the IE et al prototype:
var myObject = {};
if (!Object.prototype.watch) {
Object.prototype.watch = function (prop, handler) {
var oldval = this[prop], newval = oldval,
getter = function () {
return newval;
},
setter = function (val) {
oldval = newval;
return newval = handler.call(this, prop, oldval, val);
};
if (delete this[prop]) { // can't watch constants
if (Object.defineProperty) // ECMAScript 5
Object.defineProperty(this, prop, {
get: getter,
set: setter
});
else if (Object.prototype.__defineGetter__ && Object.prototype.__defineSetter__) { // legacy
Object.prototype.__defineGetter__.call(this, prop, getter);
Object.prototype.__defineSetter__.call(this, prop, setter);
}
}
};
}
if (!Object.prototype.unwatch) {
Object.prototype.unwatch = function (prop) {
var val = this[prop];
delete this[prop]; // remove accessors
this[prop] = val;
};
}
function propChanged(t, p, o, n) {
alert(o);
}
Object.defineProperty(myObject, "aProperty", {value: 2,
writable: true,
enumerable: true,
configurable: true});
myObject.watch("aProperty", propChanged);
myObject.aProperty = 3; //alerts 3
myObject.aProperty = 4; //alerts 4 (n is undefined in propChanged?
You need to return the value you want the property to have from the function you pass to watch.
myObject.watch("aProperty", function (prop, oldval, newval) {
propChanged(newVal);
return newVal;
});
should do it.
See the MDN docs for a full detail of the function but the relevant bit is
Watches for assignment to a property named prop in this object, calling handler(prop, oldval, newval) whenever prop is set and storing the return value in that property. A watchpoint can filter (or nullify) the value assignment, by returning a modified newval (or by returning oldval).
EDIT
Your edited code might work better this way
Object.prototype.watch = function (prop, handler) {
var fromPrototype = !Object.hasOwnProperty.call(this, prop),
val = this[prop],
getter = function () {
return fromPrototype ? Object.getPrototypeOf(this)[prop] : val;
},
setter = function (newval) {
fromPrototype = false;
return val = handler.call(this, prop, val, newval);
};
if (delete this[prop]) { // can't watch constants
if (Object.defineProperty) { // ECMAScript 5
Object.defineProperty(this, prop, {
get: getter,
set: setter,
configurable: true,
enumerable: true
});
} else if (Object.prototype.__defineGetter__ && Object.prototype.__defineSetter__) { // legacy
Object.prototype.__defineGetter__.call(this, prop, getter);
Object.prototype.__defineSetter__.call(this, prop, setter);
}
}
};