Chaining functions and keep a variable in the chain if needed - javascript

I have some code like this:
function Foo( arr, prop ) {
this.arr = arr;
this.isOn = prop;
}
function newFoo( arr, prop ) {
return new Foo( arr, prop );
}
Foo.prototype = {
a: function() {
var result = [];
// do something and push to result
if ( this.prop ) // do something different with result
return newFoo( result );
},
// This is the method that determines if prop = true in the chain
b: function() {
result = [];
// do something and push to result
// this time 'prop' must be 'true'
return newFoo( result, true )
}
};
I want to keep passing true if the previous element in the chain has prop. Obvisouly the above approach doesn't work as you can see here:
var nf = newFoo;
console.log( nf( [1,2,3] ).b().isOn ); //=> true
console.log( nf( [1,2,3] ).b().a().isOn ); //=> undefined
I know I could just return newFoo( result, this.prop ) all the time on every method but I was curious to see if there are any other solutions to this problem. As methods grow in number it'll be hard to keep track of this property over time.

As methods grow in number it'll be hard to keep track of this property over time.
You could just create an extra method with the functionality of newFoo that automatically keeps track of the properties you are not going to overwrite:
function Foo( arr, prop ) {
this.arr = arr;
this.isOn = prop;
}
Foo.prototype = {
clone: function newFoo( arr, prop ) {
return new Foo(
arguments.length >= 1 ? arr : this.arr,
arguments.length >= 2 ? prop : this.isOn
);
},
a: function() {
var result = [];
// do something and push to result
if ( this.prop ) // do something different with result
return this.clone( result );
},
// This is the method that determines if prop = true in the chain
b: function() {
result = [];
// do something and push to result
// this time 'prop' must be 'true'
return this.clone( result, true )
}
};
I've used arguments.length here to check whether a parameter was passed, you could as well test against undefined or use simple arr || this.arr for always-truthy properties.

change 'a' function to
a: function() {
var result = [];
// do something and push to result
if ( this.prop ){} // so something different with result
return newFoo( result );
},

function Foo( arr, prop ) {
this.arr = arr;
this.isOn = prop || false; // if prop is undefined, set false
}
This should sort out your problem.
If you dont add the prop argument, isOn will be set undefined. That's why you get undefined as output.

Related

Returning private object property by name (string) in Javascript

I have worked out a method to access an objects private properties by creating a method that returns those properties. However I would like to create a single function that can return any object property based on the string argument passed.
Here is an example of what I am trying to do:
function MyObj() {
var myProp = 10;
this.getProp = function( propName ) {
return( propName ); // THIS IS WHERE I AM STUCK
};
}
MyObj.prototype.getMyProp = function() {
return this.getProp( 'myProp' );
};
var myObj = new MyObj();
console.log( myObj.getMyProp() );
As you can see from this example the string "myProp" is returned not the variable. I can't use this[propName] as I'm not in the right scope and I can't use the that/self technique to access the scope.
How do return an object property using a string?
One simple solution would be to wrap your private variables in an object like this:
function MyObj() {
var privateVars = {
myProp: 10
};
this.getProp = function( propName ) {
return privateVars[propName];
};
}
MyObj.prototype.getMyProp = function() {
return this.getProp( 'myProp' );
};
var myObj = new MyObj();
console.log( myObj.getMyProp() ); // 10
Update: it appears that eval will work in this case, too, but I wouldn't recommend it:
function MyObj() {
var myProp = 10;
this.getProp = function( propName ) {
return eval(propName);
};
}
MyObj.prototype.getMyProp = function() {
return this.getProp( 'myProp' );
};
var myObj = new MyObj();
console.log( myObj.getMyProp() ); // 10

How to conditionally add properties to a javascript object literal

I am trying to do the following to satisfy the requirements of a code builder (Sencha Cmd to be specific).
This is the essence I what I need to do. The critical factor is that the function body MUST end with a return of an object literal. I cant return a variable due to restrictions in the builder. So, how to add a property 'b' at the point of the pseudo code below if the parameter 'includeB' is true, but NOT add a property AT ALL if it is false. ie b==undefined or b==null is not allowed.
Perhaps it is not possible.
function create(includeB) {
// Can have code here but the final thing MUST be a return of the literal.
// ...
return {
a : 1
// pseudo code:
// if (includeB==true) then create a property called b
// and assign a value of 2 to it.
// Must be done right here within this object literal
}
}
var obj = create(false);
// obj must have property 'a' ONLY
var obj = create(true);
// obj must have properties 'a' and 'b'
Thanks for reading and considering,
Murray
If you can use ES6, use the spread properties.
function create(includeB) {
return {
a : 1,
...(includeB ? { b: 2 } : {}),
};
}
You've pretty much shown a use case for a constructor function instead of using an object literal:
function CustomObject(includeB) {
this.a = 1;
if (includeB) {
this.b = 2;
}
}
//has `a` only
var obj1 = new CustomObject(false);
//has `a` and `b`
var obj2 = new CustomObject(true);
After re-reading your question it appears that you've got limited access in modifying the function. If I'm understanding your question correctly you can only change a limited portion of the script:
function create(includeB) {
// modifications may be done here
// the rest may not change
return {
a : 1
}
}
var obj = create(false);
// obj must have property 'a' ONLY
var obj = create(true);
// obj must have properties 'a' and 'b'
If that's the case, then you could simply skip the later part of the function:
function create(includeB) {
if (includeB) {
return {
a: 1,
b: 2
};
}
return {
a: 1
};
}
You cannot put boolean logic inside a javascript literal definition. So, if your builder requires the the returned object can ONLY be defined as a javascript literal, then you cannot define properties conditionally that way.
If you can create an object inside your function, modify that object using logic and then return that object, then that's pretty easy.
function create(includeB) {
var x = {
a: 1
};
if (includeB) {
x.b = 2;
}
return x;
}
Your other option would be to wrap the create function and do it outside the create function.
function myCreate(includeB) {
var x = create(includeB)
if (includeB) {
x.b = 2;
}
return x;
}
Or, you could even wrap the create function transparently so callers still use create(), but it's behavior has been altered.
var oldCreate = create;
create = function(includeB) {
var x = oldCreate(includeB);
if (includeB) {
x.b = 2;
}
return x;
}
I recently had to do this, and found you could use a self-calling function within an object's definition (if using ES6). This is similar to the accepted answer, but might be useful for others who need to do this without first defining a constructor function.
For example:
let obj = (() => {
let props = { a: 1 };
if ( 1 ) props.b = 2;
return props;
})();
makes the object: { a: 1, b: 2 }
It's handy for more complicated objects, keeping the construction continuous:
let obj = {
a: 1,
b: (() => {
let props = { b1: 1 };
if ( 1 ) props.b2 = 2;
return props;
})(),
c: 3
}
makes the object:
{
a: 1,
b: {
b1: 1,
b2: 2
},
c: 3
}
You could define it later:
var hasA = create(); // has hasA.a
var hasBoth = create();
hasBoth.b = 2; //now has both
Alternatively, using your argument in create:
function create (includeB) {
var obj = {
a : 1
};
if (includeB) {
obj.b = 2;
}
return obj;
}
Below should work. I hope this help.
function create(includeB){
var object = {
a: 1
};
if (includeB)
object.b = 2;
return object;
}
How about this:
function create(includeB) {
return includeB && { a:1, b:2 } || { a:1 };
}
When includeB is true, the create function will return {a:1, b:2}. If includeB is false, it will return whatever is after the or - in this case, the {a:1} object.
create(true) returns { a:1, b:2 }.
create(false) returns { a:1 }
If you would like to use a declaration to satisfy the same requirement once without too much bloat, you can also simply do the following:
var created = function(includeB) {
var returnObj = { a : 1 };
if(includeB) { returnObj.b = 2; }
return returnObj;
}}(); //automatically runs and assigns returnObj to created

Javascript add extra argument

Let's take a look at this code:
var mainFunction = function() {
altFunction.apply(null, arguments);
}
The arguments that are passed to mainFunction are dynamic -- they can be 4 or 10, doesn't matter. However, I have to pass them through to altFunction AND I have to add an EXTRA argument to the argument list.
I have tried this:
var mainFunction = function() {
var mainArguments = arguments;
mainArguments[mainArguments.length] = 'extra data'; // not +1 since length returns "human" count.
altFunction.apply(null, mainArguments);
}
But that does not seem to work. How can I do this?
Use Array.prototype.push
[].push.call(arguments, "new value");
There's no need to shallow clone the arguments object because it and its .length are mutable.
(function() {
console.log(arguments[arguments.length - 1]); // foo
[].push.call(arguments, "bar");
console.log(arguments[arguments.length - 1]); // bar
})("foo");
From ECMAScript 5, 10.6 Arguments Object
Call the [[DefineOwnProperty]] internal method on obj passing "length", the Property Descriptor {[[Value]]: len, [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true}, and false as arguments.
So you can see that .length is writeable, so it will update with Array methods.
arguments is not a pure array. You need to make a normal array out of it:
var mainArguments = Array.prototype.slice.call(arguments);
mainArguments.push("extra data");
The arguments object isn't an array; it's like an array, but it's different. You can turn it into an array however:
var mainArguments = [].slice.call(arguments, 0);
Then you can push another value onto the end:
mainArguments.push("whatever");
The arguments "array" isn't an array (it's a design bug in JavaScript, according to Crockford), so you can't do that. You can turn it into an array, though:
var mainFunction = function() {
var mainArguments = Array.prototype.slice.call(arguments);
mainArguments.push('extra data');
altFunction.apply(null, mainArguments);
}
Update 2016: You must convert the arguments to an array before adding the element. In addition to the slice method mentioned in many posts:
var args = Array.prototype.slice.call(arguments);
You can also use the Array.from() method or the spread operator to convert arguments to a real Array:
var args = Array.from(arguments);
or
var args = [...arguments];
The above may not be optimized by your javascript engine, it has been suggested by the MDN the following may be optimized:
var args = (arguments.length === 1 ? [arguments[0]] : Array.apply(null, arguments));
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/arguments
var mainFunction = function() {
var args = [].slice.call( arguments ); //Convert to array
args.push( "extra data");
return altFunction.apply( this, args );
}
One liner to add additional argument(s) and return the new array:
[].slice.call(arguments).concat(['new value']));
//
// var
// altFn = function () {},
// mainFn = prefilled( altFn /* ...params */ );
//
// mainFn( /* ...params */ );
//
//
function prefilled ( fn /* ...params */ ) {
return ( function ( args1 ) {
var orfn = this;
return function () {
return orfn.apply( this, args1.concat( cslc( arguments ) ) );
};
} ).call( fn, cslc( arguments, 1 ) );
}
// helper fn
function cslc( args, i, j ) {
return Array.prototype.slice.call( args, i, j );
}
// example
var
f1 = function () { console.log( cslc( arguments ) ); },
F1 = prefilled( f1, 98, 99, 100 );
F1( 'a', 'b', 'c' );
//
// logs: [98, 99, 100, "a", "b", "c"]
//
//
In this case it could be more comfortable to use call() instead of apply():
function first(parameter1, parameter2) {
var parameter3 = "123";
secondFunction.call(
this,
parameter1,
parameter2,
parameter3);
},
var myABC = '12321';
someFunction(result, error, myCallback.bind(this, myABC));
Reference: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind
For those who like me was looking for a way to add an argument that is optional and may be not the only omitted one (myFunc = function(reqiured,optional_1,optional_2,targetOptional) with the call like myFunc(justThisOne)), this can be done as follows:
// first we make sure arguments is long enough
// argumentPosition is supposed to be 1,2,3... (4 in the example above)
while(arguments.length < argumentPosition)
[].push.call(arguments,undefined);
// next we assign it
arguments[argumentPosition-1] = arguments[argumentPosition-1] || defaultValue;

How do you check if a JavaScript object is directly an `{}` instance, not a subclass?

I have spent 10 or 20 minutes here and there probably a dozen times in the past year and never found a bulletproof answer to this question.
How do you check if a JavaScript object is an instance of Object, not a subclass?
One use case for this is to check if arguments[0] is an "options" hash vs. a "model" (MVC), both of which extend the native Object, but which should be treated differently.
I have tried these:
// some helper to get constructor name
function klassName(fn) {
if (fn.__name__) {
return fn.__name__;
}
if (fn.name) {
return fn.name;
}
return fn.toString().match(/\W*function\s+([\w\$]+)\(/));
};
var Model = function() {};
m = new Model;
o = {};
Object(o) === o; // true
Object(m) === m; // true, thought it might be false
klassName(o.constructor); // Object
klassName(m.constructor); // Model
That klassName(m.constructor) doesn't work in some cases (can't remember exactly, but maybe a regex.constructor, something like that). Maybe it does, don't know for sure.
Is there a bulletproof way to tell if something is an {} object?
Might something as simple as
function isObj( test ) {
return test.constructor === Object;
}
Be what you are looking for?
Test in jsFiddle
You mean this? http://jsfiddle.net/elclanrs/ukEEw/
var o = {};
var re = /\d/;
var f = function(){};
var d = new Date();
var isObj = function(e){
return e.toString() === '[object Object]';
};
console.log( isObj(o) ); // True
console.log( isObj(re) ); // False
console.log( isObj(f) ); // False
console.log( isObj(d) ); // False
​
I would just change one thing
var isObj = function(e){
if(!e) return false; // change this line
return e.toString() === '[object Object]';
};

Javascript change function arguments

I'm trying to change function arguments in javascript.
f = function(){
console.log(a,b,c);
};
SetArgumentList( f, ["a", "b", "c"] );
f(1,2,3);
// should print "1 2 3"
// [edit]
// alternatively SetArgumentList could also work like
f = SetArgumentList( f, ["a", "b", "c"] );
Is there some solid way of doing this?
Where do I need it?... basically I'm trying to add type checked functions:
Object.prototype.method = function( name, typedef, func ){ ... }
function Thing(){};
Thing.method("log",
{ arr: Array, str: String },
function(){
console.log(arr, str);
});
t = new Thing();
t.log([1,2,3], "ok");
t.log("err", "ok"); // <-- causes an exception
// I know I can do this way
Thing.method("log",
[Array, String],
function(arr, str){
console.log(arr, str);
});
// but that's harder to read
NOTE! I know how to do type checking, but not the new function construction.
As delnan said in the comments, it seems like what you're trying to do is essentially "rename" the variables which are local to a function. Like he said, this is not possible (and for good reason too! Could you imagine debugging that? maaan...)
Anyway, I don't know exactly why you'd want that, but Javascript is a flexible language and you could probably get close using a more sane method. It's hard to know exactly what you're trying to achieve, but perhaps this information might get you on the right track:
The arguments which are passed to a function at call time are referenced in a variable named arguments.
function f() {
console.log(arguments);
}
f(); // []
f(1, 2); // [1, 2]
You can call a function with an arbitrary list of arguments using .apply, which is a method on the Function prototype. It takes 2 parameters. The first is the object which will be this inside the function call, and the second is an array of arguments.
f.apply(null, []); // []
f.apply(null, [1, 2, 3]); [1, 2, 3]
Applying this in your situation, perhaps this is what you're after:
function f() {
console.log.apply(console, arguments);
}
Tested in IE7,8,9, opera, chrome, firefox and safari. Uses evil in the background, but
I cannot see any other way if you must rename arguments.
(function(){
var decompileRE = /function\s*\([\s\S]*?\)\s*\{([\s\S]*)/,
lastBrace = /\}[^}]*$/;
window.SetArgumentList = function( fn, argNames ) {
var match
if( !( match = fn.toString().match( decompileRE ) ) ) {
return fn;
}
argNames.push( match[1].replace( lastBrace, "" ) );
return Function.apply( null, argNames );
};
})()
f = function(){
console.log(a,b,c);
};
f = SetArgumentList( f, ["a","b","c"] );
console.log(f);
Logs this in all browsers mentioned above:
function anonymous(a,b,c) {
console.log(a,b,c);
}
I've got a simpler solution similar to the accepted answer for anyone out there that is looking. Works in Chrome, Firefox, Safari, IE7+
Solution
function SetArgList(fn, args) {
var fnbody = fn.toString().replace(
/^\s*function\s*[\$_a-zA-Z0-9]+\(.*\)\s*\{/, //BEWARE, removes original arguments
''
).replace(
/\s*\}\s*$/,
''
);
return new Function(args, fnbody)
}
How to use
Just redefine your original function like this using SetArgList:
function main() {
alert(hello);
alert(world);
}
main = SetArgList(main, 'hello,world');
main('hello', 'world');
In my solution there's no need for an array but you could edit it, my function only requires argument names separated by a comma.
you can use .apply:
this works for me:
f = function(a,b,c){
console.log(a,b,c);
};
var args = ["a", "b", "c"];
f.apply(this, args); //print "abc"
using arguments:
f = function(){
for(var key in arguments) {
console.log(arguments[key]);
}
};
var args = ["a", "b", "c"];
f.apply(this, args);
it's that you looking?
As said before, you can't do it the way you want, you're breaking lexical scoping.
However, here a minimalism version of the way you can implement it (a lot of improvements should be done !). The only thing required is that you function has the named parameter in arguments.
function getType( el ) {
// TODO
return "";
}
function addMethod( obj, name, typedef, func ) {
var argumentsLength = typedef.length;
obj[ name ] = function() {
var len = arguments.length, i = 0;
if( argumentsLength != len )
{
throw new TypeError( "Wrong number of arguments for method " + name );
}
for( i = 0; i < len; i++ )
{
// TODO better type checking
if( getType( arguments[i] ) != getType( typedef[i] ) )
{
throw new TypeError( "Wrong type for arguments number " + i + " for method " + name );
}
}
return func.apply( obj, arguments );
};
};
var o = {};
addMethod( o, "log", [Array, String], function( arr, str ) {
// arguments MUST be explicitly declared above
console.log( arr, str );
});
o.log( ["a"], "b" ); // OK
o.log( "a", "b" ); // TypeError
o.log( ["a"], "b", "c" ); // TypeError
I found a solution that only works on webkit browsers:
f = function(){ console.log(a,b,c); };
fx = eval(
f.toString().replace(
/^function [^\(]*\(\)/,
"var __temp = function (a,b,c)")
+ "; __temp");
fx(1,2,3);
This can also be generalized.
[edit]
This works for other browsers as well, memory let me down - comments // /**/ in some browsers get discarded.

Categories

Resources