I have this chain of methods
MyObject.methodA().methodB().methodC()
Suppose I want methodB() to be added or changed based on some condition:
const inclueMethodB = false;
// change method based on condition
MyObject.methodA().(inclueMethodB ? methodB() : method123()).methodC()
// add method based on condition
MyObject.methodA().(inclueMethodB && methodB()).methodC()
Is there a way to do this in javascript?
I can't think of a way to optionally call a method like that.
If you were choosing between two methods, you could use a subscript where the method name is specified dynamically:
MyObject.methodA()[includeB1 ? "methodB1" : "methodB2"]().methodC()
You could achieve your original goal by defining a method that doesn't do anything except return the same thing that methodB() does. If this is a fluent interface where all the methods just return this, it could be:
doNothing() {
return this;
}
Then you could write:
MyObject.methodA()[includeB ? "methodB" : "doNothing"]().methodC()
But this all seems unnecessarily complex and confusing. Just use ordinary if statements, which express the intent clearly:
let temp = MyObject.methodA();
if (includeB) {
temp = temp.methodB();
}
temp.methodC();
Related
Assume we have some classes A and B:
Class A {
constructor(a) {
this.a = a;
};
showInfo() {
console.log(this.a)
};
};
Class B {
constructor(b) {
this.b = b;
};
printText() {
console.log('its B!');
};
};
Then we create an instance of B like this:
const objB = new B(
new A(3)
);
So now we have objB with its own method inside - printText, and we surely can call it.
But what if i want somehow when calling not existing method in objB to make it pass through to encapsulated A class in there and look for invoking this method on him, like this: objB.showInfo() - to give me 3 here ?
Same story, but at this time i want when calling not existing method on A to make it pass through to B outside (like that printText)?
P.S. Don't wanna use super() and inheritance, just composition and wrapping objects, hope you've got the point.
Just a little warning at the start: this might make your program harder to debug, and it also might be a little complicated for something like this. As others have suggested, you should probably investigate other options which may be simpler and also less in the way of everything else your code does.
Here's the code which provides the functionality:
function makeGetProxy(t){
return new Proxy(t, {
get(obj,prop){
if(prop in t){
return t[prop];
}else{
var keys = Object.keys(obj);
for (var i = 0; i < keys.length; i++) {
var val = t[keys[i]];
if(prop in val){
return val[prop];
// what about a recursive function?
}
}
return undefined;
}
}
});
}
And one itty bitty change to your constructor in B:
class B {
constructor(b) {
this.b = b;
return makeGetProxy(this);
};
printText() {
console.log('its B!');
};
};
If you want, you can also do the same to A.
Let's slow down. What just happened? I'll explain.
Since the properties we might request don't already exist, we're going to have to use a getter (see resources) to properly send back the value required. But, since we don't know the property names, we need a Proxy (see resources) to have a "catch-all" kind of get method.
The proxy will check if the property requested prop already exists, and if so, returns it. If it doesn't exist, it checks all of your properties' properties (all of the sub-properties).
The first result it gets, it returns it. This might cause unexpected bugs in your program. If it doesn't find it, it simply returns undefined.
Then, the proxy is generalized into a function for reuse in multiple classes (as you requested).
So, this can get the properties of a class and the properties of a class' properties, but if you need to go further (with your C class that doesn't exist yet), you can use a recursive function. I currently don't have the implementation for that recursive function, but in my head it would comprise mostly of a modified version of the else block in the makeGetProxy function.
Again, be careful with this code. It might get in the way of other things and cause unnecessary difficulty in debugging.
Resources:
Getters (MDN)
Proxy (MDN)
I borrowed some code from this answer and got the Proxy idea from this answer.
I want to create status object. Which can have values like pending, session, rejected, cancelled...
I want to access it like status.isSession();, status.isPending()... etc`
but i want to assign it like this status.toPending(); , status.toSession();
This way there is no room for spelling errors.
I think this is dynamic programming.
You can achieve this in JavaScript, but it will work only with double equals.
You can create an object with constructor like this :
function Status(value){
this.session = true;
this.value = value;
}
Now if you create an instance of this object :
const status = new Status('session');
Sure enough status.session will return true and status.value will return 'session.'
Now you can override toString() method of this object so it returns status.value
Status.prototype.toString = function(){
return this.value;
}
The reason for this is that, when you try to compare an Object vs Primitive, the Engine will try to put them in 'value context' and coerce your object to String by calling .toString() operation.
Sure enough this will produce :
const status = new Status('session');
console.log(status == 'session');
A true in your console.
But this is highly discouraged because is a hack and you might get weird bugs, so I suggest to stay away.
I created a class that supports chaining by making use of return this;, and
now I need to make the current method tell what methods can be chained. Example:
class MyClass {
constructor(path) {
this.path = path;
}
method1() {
// some code here...
// i must make something here to only allow channing if it's method
// blabliblu
return this;
}
blabliblu() {
// some code here...
// same thing here, if the channing is with the method ar_2, it's ok.
return this;
}
ar_2() {
// And so on...
return this;
}
}
So, i can do: method1().blabliblu(), but i can't do method1().ar_2(). Is there lib to help me achieve this?
What you have asked for is not possible in Javascript. When you do:
return this;
in your methods, you are returning the same object. Any public method can be called on the object that is returned and there is no way to specify that because the object was returned from a method it should somehow not allow some methods to be called.
So, using the code in your example where you return this in .method1(), there's no difference between this:
obj.method1();
obj.ar_2();
and this:
obj.method1().ar_2();
That's because the chained version is essentially this internal to the JS interpreter:
let temp = obj.method1();
temp.ar_2();
So, if temp is the same as obj which it is when you return this from method1(), then obj.method1().ar_2(); is just the same as obj.method1(); obj.ar_2(); (with a little less typing). Thus, you can't prevent the calling of .ar_2().
Both just call .method1() and then .ar_2() on the same object. So you can't prevent one scheme, but allow the other. ar_2 is either a public method or it isn't. You can't have it callable in one place and not callable in another on the same object.
Now, you could make obj.method1() return a different object. If you did that, then that different object could have different methods on it and could be an object that does not have a .ar_2() method.
When you chain array methods like this:
let result = [1,2,3].map(...).filter(...);
Each step in that chain is returning a different object (they are not doing a return this, but are creating a new object and returning it. In this specific Array example, these are returning different objects, but of the same type, but you could return different objects of different types. For example:
let result = ["a","b","c"].join("").toUpperCase();
Here, .join() is an Array method, but returns a string object which you can then only call string methods on. So, you could do something like that where you return a different type of object.
In Ruby I think you can call a method that hasn't been defined and yet capture the name of the method called and do processing of this method at runtime.
Can Javascript do the same kind of thing ?
method_missing does not fit well with JavaScript for the same reason it does not exist in Python: in both languages, methods are just attributes that happen to be functions; and objects often have public attributes that are not callable. Contrast with Ruby, where the public interface of an object is 100% methods.
What is needed in JavaScript is a hook to catch access to missing attributes, whether they are methods or not. Python has it: see the __getattr__ special method.
The __noSuchMethod__ proposal by Mozilla introduced yet another inconsistency in a language riddled with them.
The way forward for JavaScript is the Proxy mechanism (also in ECMAscript Harmony), which is closer to the Python protocol for customizing attribute access than to Ruby's method_missing.
The ruby feature that you are explaining is called "method_missing" http://rubylearning.com/satishtalim/ruby_method_missing.htm.
It's a brand new feature that is present only in some browsers like Firefox (in the spider monkey Javascript engine). In SpiderMonkey it's called "__noSuchMethod__" https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/NoSuchMethod
Please read this article from Yehuda Katz http://yehudakatz.com/2008/08/18/method_missing-in-javascript/ for more details about the upcoming implementation.
Not at the moment, no. There is a proposal for ECMAScript Harmony, called proxies, which implements a similar (actually, much more powerful) feature, but ECMAScript Harmony isn't out yet and probably won't be for a couple of years.
You can use the Proxy class.
var myObj = {
someAttr: 'foo'
};
var p = new Proxy(myObj, {
get: function (target, methodOrAttributeName) {
// target is the first argument passed into new Proxy, aka. target is myObj
// First give the target a chance to handle it
if (Object.keys(target).indexOf(methodOrAttributeName) !== -1) {
return target[methodOrAttributeName];
}
// If the target did not have the method/attribute return whatever we want
// Explicitly handle certain cases
if (methodOrAttributeName === 'specialPants') {
return 'trousers';
}
// return our generic method_missing function
return function () {
// Use the special "arguments" object to access a variable number arguments
return 'For show, myObj.someAttr="' + target.someAttr + '" and "'
+ methodOrAttributeName + '" called with: ['
+ Array.prototype.slice.call(arguments).join(',') + ']';
}
}
});
console.log(p.specialPants);
// outputs: trousers
console.log(p.unknownMethod('hi', 'bye', 'ok'));
// outputs:
// For show, myObj.someAttr="foo" and "unknownMethod" called with: [hi,bye,ok]
About
You would use p in place of myObj.
You should be careful with get because it intercepts all attribute requests of p. So, p.specialPants() would result in an error because specialPants returns a string and not a function.
What's really going on with unknownMethod is equivalent to the following:
var unk = p.unkownMethod;
unk('hi', 'bye', 'ok');
This works because functions are objects in javascript.
Bonus
If you know the number of arguments you expect, you can declare them as normal in the returned function.
eg:
...
get: function (target, name) {
return function(expectedArg1, expectedArg2) {
...
I've created a library for javascript that let you use method_missing in javascript: https://github.com/ramadis/unmiss
It uses ES6 Proxies to work. Here is an example using ES6 Class inheritance. However you can also use decorators to achieve the same results.
import { MethodMissingClass } from 'unmiss'
class Example extends MethodMissingClass {
methodMissing(name, ...args) {
console.log(`Method ${name} was called with arguments: ${args.join(' ')}`);
}
}
const instance = new Example;
instance.what('is', 'this');
> Method what was called with arguments: is this
No, there is no metaprogramming capability in javascript directly analogous to ruby's method_missing hook. The interpreter simply raises an Error which the calling code can catch but cannot be detected by the object being accessed. There are some answers here about defining functions at run time, but that's not the same thing. You can do lots of metaprogramming, changing specific instances of objects, defining functions, doing functional things like memoizing and decorators. But there's no dynamic metaprogramming of missing functions as there is in ruby or python.
I came to this question because I was looking for a way to fall through to another object if the method wasn't present on the first object. It's not quite as flexible as what your asking - for instance if a method is missing from both then it will fail.
I was thinking of doing this for a little library I've got that helps configure extjs objects in a way that also makes them more testable. I had seperate calls to actually get hold of the objects for interaction and thought this might be a nice way of sticking those calls together by effectively returning an augmented type
I can think of two ways of doing this:
Prototypes
You can do this using prototypes - as stuff falls through to the prototype if it isn't on the actual object. It seems like this wouldn't work if the set of functions you want drop through to use the this keyword - obviously your object wont know or care about stuff that the other one knows about.
If its all your own code and you aren't using this and constructors ... which is a good idea for lots of reasons then you can do it like this:
var makeHorse = function () {
var neigh = "neigh";
return {
doTheNoise: function () {
return neigh + " is all im saying"
},
setNeigh: function (newNoise) {
neigh = newNoise;
}
}
};
var createSomething = function (fallThrough) {
var constructor = function () {};
constructor.prototype = fallThrough;
var instance = new constructor();
instance.someMethod = function () {
console.log("aaaaa");
};
instance.callTheOther = function () {
var theNoise = instance.doTheNoise();
console.log(theNoise);
};
return instance;
};
var firstHorse = makeHorse();
var secondHorse = makeHorse();
secondHorse.setNeigh("mooo");
var firstWrapper = createSomething(firstHorse);
var secondWrapper = createSomething(secondHorse);
var nothingWrapper = createSomething();
firstWrapper.someMethod();
firstWrapper.callTheOther();
console.log(firstWrapper.doTheNoise());
secondWrapper.someMethod();
secondWrapper.callTheOther();
console.log(secondWrapper.doTheNoise());
nothingWrapper.someMethod();
//this call fails as we dont have this method on the fall through object (which is undefined)
console.log(nothingWrapper.doTheNoise());
This doesn't work for my use case as the extjs guys have not only mistakenly used 'this' they've also built a whole crazy classical inheritance type system on the principal of using prototypes and 'this'.
This is actually the first time I've used prototypes/constructors and I was slightly baffled that you can't just set the prototype - you also have to use a constructor. There is a magic field in objects (at least in firefox) call __proto which is basically the real prototype. it seems the actual prototype field is only used at construction time... how confusing!
Copying methods
This method is probably more expensive but seems more elegant to me and will also work on code that is using this (eg so you can use it to wrap library objects). It will also work on stuff written using the functional/closure style aswell - I've just illustrated it with this/constructors to show it works with stuff like that.
Here's the mods:
//this is now a constructor
var MakeHorse = function () {
this.neigh = "neigh";
};
MakeHorse.prototype.doTheNoise = function () {
return this.neigh + " is all im saying"
};
MakeHorse.prototype.setNeigh = function (newNoise) {
this.neigh = newNoise;
};
var createSomething = function (fallThrough) {
var instance = {
someMethod : function () {
console.log("aaaaa");
},
callTheOther : function () {
//note this has had to change to directly call the fallThrough object
var theNoise = fallThrough.doTheNoise();
console.log(theNoise);
}
};
//copy stuff over but not if it already exists
for (var propertyName in fallThrough)
if (!instance.hasOwnProperty(propertyName))
instance[propertyName] = fallThrough[propertyName];
return instance;
};
var firstHorse = new MakeHorse();
var secondHorse = new MakeHorse();
secondHorse.setNeigh("mooo");
var firstWrapper = createSomething(firstHorse);
var secondWrapper = createSomething(secondHorse);
var nothingWrapper = createSomething();
firstWrapper.someMethod();
firstWrapper.callTheOther();
console.log(firstWrapper.doTheNoise());
secondWrapper.someMethod();
secondWrapper.callTheOther();
console.log(secondWrapper.doTheNoise());
nothingWrapper.someMethod();
//this call fails as we dont have this method on the fall through object (which is undefined)
console.log(nothingWrapper.doTheNoise());
I was actually anticipating having to use bind in there somewhere but it appears not to be necessary.
Not to my knowledge, but you can simulate it by initializing the function to null at first and then replacing the implementation later.
var foo = null;
var bar = function() { alert(foo()); } // Appear to use foo before definition
// ...
foo = function() { return "ABC"; } /* Define the function */
bar(); /* Alert box pops up with "ABC" */
This trick is similar to a C# trick for implementing recursive lambdas, as described here.
The only downside is that if you do use foo before it's defined, you'll get an error for trying to call null as though it were a function, rather than a more descriptive error message. But you would expect to get some error message for using a function before it's defined.
Because jQuery is a widely used and mature collaborative effort, I can't help but to look at its source for guidance in writing better Javascript. I use the jQuery library all the time along with my PHP applications, but when I look under the hood of this rather sophisticated library I realize just how much I still don't understand about Javascript. Lo, I have a few questions for the SO community. First of all, consider the following code...
$('#element').attr('alt', 'Ivan is SUPER hungry! lolz');
vs
$('#element').attr({'alt': 'Ivan is an ugly monster! omfgz'});
Now, is this to say that the attr() method was designed to accept EITHER an attribute name, an attribute name and a value, or a pair-value map? Can someone give me a short explanation of what a map actually is and the important ways that it differs from an array in Javascript?
Moving on, the whole library is wrapped in this business...
(function(window, undefined) { /* jQuery */ })(window);
I get that the wrapped parentheses cause a behavior similar to body onLoad="function();", but what is this practice called and is it any different than using the onLoad event handler? Also, I can't make heads or tails of the (window) bit there at the end. What exactly is happening with the window object here?
Am I wrong in the assessment that objects are no different than functions in Javascript? Please correct me if I'm wrong on this but $() is the all encompassing jQuery object, but it looks just like a method. Here's another quick question with a code example...
$('#element').attr('alt', 'Adopt a Phantom Cougar from Your Local ASPCA');
... Should look something like this on the inside (maybe I'm wrong about this)...
function $(var element = null) {
if (element != null) {
function attr(var attribute = null, var value = null) {
/* stuff that does things */
}
}
}
Is this the standing procedure for defining objects and their child methods and properties in Javascript? Comparing Javascript to PHP, do you use a period . the same way you would use -> to retrieve a method from an object?
I apologize for this being a bit lengthy, but answers to these questions will reveal a great deal to me about jQuery and Javascript in general. Thanks!
1. Method overloading
$('#element').attr('alt', 'Ivan is SUPER hungry! lolz');
vs
$('#element').attr({'alt': 'Ivan is an ugly monster! omfgz'});
var attr = function (key, value) {
// is first argument an object / map ?
if (typeof key === "object") {
// for each key value pair
for (var k in key) {
// recursively call it.
attr(k, key[k]);
}
} else {
// do magic with key and value
}
}
2. Closures
(function(window, undefined) { /* jQuery */ })(window);
Is not used as an onload handler. It's simply creating new scope inside a function.
This means that var foo is a local variable rather then a global one. It's also creating a real undefined variable to use since Parameters that are not specified passes in undefined
This gaurds againts window.undefined = true which is valid / allowed.
the (window) bit there at the end. What exactly is happening with the window object here?
It's micro optimising window access by making it local. Local variable access is about 0.01% faster then global variable access
Am I wrong in the assessment that objects are no different than functions in Javascript?
Yes and no. All functions are objects. $() just returns a new jQuery object because internally it calls return new jQuery.fn.init();
3. Your snippet
function $(var element = null) {
Javascript does not support default parameter values or optional parameters. Standard practice to emulate this is as follows
function f(o) {
o != null || (o = "default");
}
Comparing Javascript to PHP, do you use a period . the same way you would use -> to retrieve a method from an object?
You can access properties on an object using foo.property or foo["property"] a property can be any type including functions / methods.
4. Miscellanous Questions hidden in your question
Can someone give me a short explanation of what a map actually is and the important ways that it differs from an array in Javascript?
An array is created using var a = [] it simply contains a list of key value pairs where all the keys are positive numbers. It also has all the Array methods. Arrays are also objects.
A map is just an object. An object is simply a bag of key value pairs. You assign some data under a key on the object. This data can be of any type.
For attr, if you give an object instead of a key value pair it will loop on each property.
Look for attr: in jQuery's code, then you'll see it use access. Then look for access: and you will see there is a check on the type of key if it is an object, start a loop.
The wrapping in a function, is to prevent all the code inside to be accessed from outside, and cause unwanted problems. The only parameters that are passed are window that allow to set globals and access the DOM. The undefined I guess it is to make the check on this special value quicker.
I read sometimes jQuery but I didn't start with it, may be you should get some good books to make you an idea first of what some advanced features Javascript has, and then apply your knowledge to the specifics of jQuery.
1 - Yes attr can accept a attribute name for getting a value, a name and a value for setting one value or a map of attribute names and values for settings more than one attribute
2 - A map is basically a JavaScript object e.g:
var map = {
'key1' : 'value1',
'key2' : 'value2'
};
3 - (function(window, undefined) { /* jQuery */ })(window); is something called an anonymous function as it doesn't have a name. In this case it also executes straight away.
A simple example would be:
function test(){
...
}
test();
//As an anonymous function it would be:
(function(){
...
}();
//And it you wanted to pass variables:
function test(abc){
...
}
test(abc);
//As an anonymous function it would be:
(function(abc){
...
}(abc);
this would make it different to the load event, as it is a function not an event.
4 - window is passed as a variable, as it is used internally within jQuery
5 - Objects and functions the same, as everything in JavaScript is an object. jQuery does something like this:
var obj = {
"init" : function(){
}
}
6 - Yes you can use . to retrieve a value on an object but you can also use [] e.g:
var map = {
"test" : 1
}
map.test //1
map["test"] //1
I hope this answers your many questions, let me know if I've missed anything out.
jQuery 1.6.1
The test is typeof key === "object"
if that is true, then you passed a { .... }
jQuery.fn.extend({
attr: function( name, value ) {
return jQuery.access( this, name, value, true, jQuery.attr );
},
// Mutifunctional method to get and set values to a collection
// The value/s can be optionally by executed if its a function
access: function( elems, key, value, exec, fn, pass ) {
var length = elems.length;
// Setting many attributes
if ( typeof key === "object" ) {
for ( var k in key ) {
jQuery.access( elems, k, key[k], exec, fn, value );
}
return elems;
}
// Setting one attribute
if ( value !== undefined ) {
// Optionally, function values get executed if exec is true
exec = !pass && exec && jQuery.isFunction(value);
for ( var i = 0; i < length; i++ ) {
fn( elems[i], key, exec ? value.call( elems[i], i, fn( elems[i], key ) ) : value, pass );
}
return elems;
}
// Getting an attribute
return length ? fn( elems[0], key ) : undefined;
},