Closure multiplate call function im javascript - javascript

Example i write down
myFunc('asdas')
It's console.log me
'asdas'
Then after i write down
myFunc('as')('ds')('ko')....('other')
function must console.log me
"as ds ko .... other"
I tried do realize this but have many problems with it.
function me (str){
//var temp = str;
return function mes(val) {
val += ' '+ str;
console.log(val);
//return mes;
}
}
How correctly realize this function?

Well, this is a bit funny, but works:
concat = function(x, val) {
val = (val || "") + x;
var p = function(y) { return concat(y, val) };
p.toString = function() { return val };
return p
}
x = concat('a')('b')('c')('d');
document.write(x)

You can generate multiple console logs and chain it like this:
function me(str) {
console.log(str);
return me; // or whatever you called the function
}
me(1)(2)(3);
As far as I'm aware though, there's no way for the function to know when it should be outputting if you're just chaining, though.
The best option I can think of would be this:
function me(str) {
me.str = me.str || ''; // make sure me.str is set
// set me.write if this is the first call to me()
me.write = me.write || function() {
console.log(me.str);
}
me.str += (me.str.length ? ' ' : '') + str; // add a space if needed
return me; // or whatever you called the function
}
me(1)(2)(3).write();

Related

jQuery loop vs vanilla JS loop: break/continue/return [duplicate]

Can anybody tell me why the loop did not stop after the 5 entry?
http://jsbin.com/ucuqot/edit#preview
$(document).ready(function()
{
someArray = new Array();
someArray[0] = 't5';
someArray[1] = 'z12';
someArray[2] = 'b88';
someArray[3] = 's55';
someArray[4] = 'e51';
someArray[5] = 'o322';
someArray[6] = 'i22';
someArray[7] = 'k954';
var test = findXX('o322');
});
function findXX(word)
{
$.each(someArray, function(i)
{
$('body').append('-> '+i+'<br />');
if(someArray[i] == 'someArray')
{
return someArray[i]; //<--- did not stop the loop!
}
});
}
Because when you use a return statement inside an each loop, a "non-false" value will act as a continue, whereas false will act as a break. You will need to return false from the each function. Something like this:
function findXX(word) {
var toReturn;
$.each(someArray, function(i) {
$('body').append('-> '+i+'<br />');
if(someArray[i] == word) {
toReturn = someArray[i];
return false;
}
});
return toReturn;
}
From the docs:
We can break the $.each() loop at a particular iteration by making the
callback function return false. Returning non-false is the same as a
continue statement in a for loop; it will skip immediately to the next
iteration.
modified $.each function
$.fn.eachReturn = function(arr, callback) {
var result = null;
$.each(arr, function(index, value){
var test = callback(index, value);
if (test) {
result = test;
return false;
}
});
return result ;
}
it will break loop on non-false/non-empty result and return it back, so in your case it would be
return $.eachReturn(someArray, function(i){
...
here :
http://jsbin.com/ucuqot/3/edit
function findXX(word)
{
$.each(someArray, function(i,n)
{
$('body').append('-> '+i+'<br />');
if(n == word)
{
return false;
}
});
}
Try this ...
someArray = new Array();
someArray[0] = 't5';
someArray[1] = 'z12';
someArray[2] = 'b88';
someArray[3] = 's55';
someArray[4] = 'e51';
someArray[5] = 'o322';
someArray[6] = 'i22';
someArray[7] = 'k954';
var test = findXX('o322');
console.log(test);
function findXX(word)
{
for(var i in someArray){
if(someArray[i] == word)
{
return someArray[i]; //<--- stop the loop!
}
}
}
"We can break the $.each() loop at a particular iteration by making
the callback function return false. Returning non-false is the same as
a continue statement in a for loop; it will skip immediately to the
next iteration."
from http://api.jquery.com/jquery.each/
Yea, this is old BUT, JUST to answer the question, this can be a bit simpler:
function findXX(word) {
$.each(someArray, function(index, value) {
$('body').append('-> ' + index + ":" + value + '<br />');
return !(value == word);
});
}
$(function() {
someArray = new Array();
someArray[0] = 't5';
someArray[1] = 'z12';
someArray[2] = 'b88';
someArray[3] = 's55';
someArray[4] = 'e51';
someArray[5] = 'o322';
someArray[6] = 'i22';
someArray[7] = 'k954';
findXX('o322');
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
A bit more with comments:
function findXX(myA, word) {
let br = '<br />';//create once
let myHolder = $("<div />");//get a holder to not hit DOM a lot
let found = false;//default return
$.each(myA, function(index, value) {
found = (value == word);
myHolder.append('-> ' + index + ":" + value + br);
return !found;
});
$('body').append(myHolder.html());// hit DOM once
return found;
}
$(function() {
// no horrid global array, easier array setup;
let someArray = ['t5', 'z12', 'b88', 's55', 'e51', 'o322', 'i22', 'k954'];
// pass the array and the value we want to find, return back a value
let test = findXX(someArray, 'o322');
$('body').append("<div>Found:" + test + "</div>");
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
NOTE: array .includes() may better suit here https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/includes
Or just .find() to get that https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find
Rather than setting a flag, it could be more elegant to use JavaScript's Array.prototype.find to find the matching item in the array. The loop will end as soon as a truthy value is returned from the callback, and the array value during that iteration will be the .find call's return value:
function findXX(word) {
return someArray.find((item, i) => {
$('body').append('-> '+i+'<br />');
return item === word;
});
}
const someArray = new Array();
someArray[0] = 't5';
someArray[1] = 'z12';
someArray[2] = 'b88';
someArray[3] = 's55';
someArray[4] = 'e51';
someArray[5] = 'o322';
someArray[6] = 'i22';
someArray[7] = 'k954';
var test = findXX('o322');
console.log('found word:', test);
function findXX(word) {
return someArray.find((item, i) => {
$('body').append('-> ' + i + '<br />');
return item === word;
});
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

Javascript issue with precedence

I have built this function to replace a group of characters in a string by a random value from another list within a function:
function replaceExpr(a) {
var expToReplace = 0
var newSent = a
while (expToReplace == 0) {
if (a.search("zx") == -1) {
expToReplace = 1
} else {
var startPos = a.search("zx");
startPos += 2;
var endPos = a.search("xz");
var b = a.substring(startPos, endPos);
var fn = window[b];
if (typeof fn === "function") var newWord = fn();
final = newSent.replace("zx" + b + "xz", newWord);
newSent = final
a = a.replace("zx" + b + "xz", "")
}
}
return final
}
function appearance() {
var list = [
"attractive",
"fit",
"handsome",
"plain",
"short",
"tall",
"skinny",
"well-built",
"unkempt",
"unattractive"
]
return list[Math.floor(Math.random() * list.length)];
}
function personality() {
var list = [
"aggresive",
"absent-minded",
"cautious",
"detached from the real world",
"easygoing",
"focused",
"honest",
"dishonest",
"polite",
"uncivilized"
]
return list[Math.floor(Math.random() * list.length)];
}
An example :
var a = replaceExpr("Theodor is a zxappearancexz man. He seems rather zxpersonalityxz.")
alert(a)
// Theodor is a unattractive man. He seems rather cautious.
Everything works perfectly with the function but I have an issue related to it. As you can see, there's one grammar mistake : it's written "a unattractive" where it should be "an unattractive".
There's a function I usually use to to fix the a\an issue which is :
var AvsAnSimple = (function (root) {
//by Eamon Nerbonne (from http://home.nerbonne.org/A-vs-An), Apache 2.0 license
// finds if a word needs a "a" or "an" before it
var dict = "2h.#2.a;i;&1.N;*4.a;e;i;o;/9.a;e;h1.o.i;l1./;n1.o.o;r1.e.s1./;01.8;12.1a;01.0;12.8;9;2.31.7;4.5.6.7.8.9.8a;0a.0;1;2;3;4;5;6;7;8;9;11; .22; .–.31; .42; .–.55; .,.h.k.m.62; .k.72; .–.82; .,.92; .–.8;<2.m1.d;o;=1.=1.E;#;A6;A1;A1.S;i1;r1;o.m1;a1;r1; .n1;d1;a1;l1;u1;c1.i1.a1.n;s1;t1;u1;r1;i1;a1;s.t1;h1;l1;e1;t1;e1.s;B2.h2.a1.i1;r1;a.á;o1.r1.d1. ;C3.a1.i1.s1.s.h4.a2.i1.s1;e.o1.i;l1.á;r1.o1.í;u2.i;r1.r1.a;o1.n1.g1.j;D7.a1.o1.q;i2.n1.a1.s;o1.t;u1.a1.l1.c;á1. ;ò;ù;ư;E7;U1;R.b1;o1;l1;i.m1;p1;e1;z.n1;a1;m.s1;p5.a1.c;e;h;o;r;u1.l1;o.w1;i.F11. ;,;.;/;0;1;2;3;4;5;6;71.0.8;9;Ae;B.C.D.F.I2.L.R.K.L.M.N.P.Q.R.S.T.B;C1;M.D;E2.C;I;F1;r.H;I3.A1;T.R1. ;U;J;L3.C;N;P;M;O1. ;P1;..R2.A1. ;S;S;T1;S.U2.,;.;X;Y1;V.c;f1.o.h;σ;G7.e1.r1.n1.e;h1.a3.e;i;o;i1.a1.n1.g;o2.f1. ;t1.t1. ;r1.i1.a;w1.a1.r1.r;ú;Hs. ;&;,;.2;A.I.1;2;3;5;7;B1;P.C;D;F;G;H1;I.I6;C.G.N.P.S1.D;T.K1.9;L;M1;..N;O2. ;V;P;R1;T.S1.F.T;V;e2.i1.r;r1.r1.n;o2.n6;d.e1.s;g.k.o2;l.r1;i1.f;v.u1.r;I3;I2;*.I.n1;d1;e1;p1;e1;n1;d2;e1;n1;c1;i.ê.s1;l1;a1;n1;d1;s.J1.i1.a1.o;Ly. ;,;.;1;2;3;4;8;A3. ;P;X;B;C;D;E2. ;D;F1;T.G;H1.D.I1.R;L;M;N;P;R;S1;m.T;U1. ;V1;C.W1.T;Z;^;a1.o1.i1.g;o1.c1.h1.a1;b.p;u1.s1.h1;o.ộ;M15. ;&;,;.1;A1;.1;S./;1;2;3;4;5;6;7;8;Ai;B.C.D.F.G.J.L.M.N.P.R.S.T.V.W.X.Y.Z.B1;S1;T.C;D;E3.P1;S.W;n;F;G;H;I4. ;5;6;T1;M.K;L;M;N;O1.U;P;Q;R;S;T1;R.U2. ;V;V;X;b1.u1.m;f;h;o2.D1.e.U1;..p1.3;s1.c;Ny. ;+;.1.E.4;7;8;:;A3.A1;F.I;S1.L;B;C;D;E3.A;H;S1. ;F1;U.G;H;I7.C.D1. ;K.L.N.O.S.K;L;M1;M.N2.R;T;P1.O1.V1./1.B;R2;J.T.S1;W.T1;L1.D.U1.S;V;W2.A;O1.H;X;Y3.C1.L;P;U;a1.s1.a1.n;t1.h;v;²;×;O5;N1;E.l1;v.n2;c1.e.e1.i;o1;p.u1;i.P1.h2.i1.a;o2.b2;i.o.i;Q1.i1.n1.g1.x;Rz. ;&;,;.1;J./;1;4;6;A3. ;.;F1;T.B1;R.C;D;E3. ;S1.P;U;F;G;H1.S;I2.A;C1. ;J;K;L1;P.M5;1.2.3.5.6.N;O2.H;T2;A.O.P;Q;R1;F.S4;,...?.T.T;U4;B.M.N.S.V;X;c;f1;M1...h2.A;B;ò;S11. ;&;,;.4.E;M;O;T1..3.B;D;M;1;3;4;5;6;8;9;A3. ;8;S2;E.I.B;C3.A1. ;R2.A.U.T;D;E6. ;5;C3;A.O.R.I1.F.O;U;F3;&.H.O1.S.G1;D.H3.2;3;L;I2. ;S1.O.K2.I.Y.L3;A2. ;.;I1. ;O.M3;A1. ;I.U1.R.N5.A.C3.A.B.C.E.F.O.O5. ;A1.I;E;S1;U.V;P7;A7;A.C.D.M.N.R.S.E1. ;I4;C.D.N.R.L1;O.O.U.Y.Q1. ;R;S1;W.T9.A1. ;C;D;F;I;L;M;S;V;U7.B.L.M.N.P.R.S.V;W1.R;X1.M;h1.i1.g1.a1.o;p1.i1.o1;n.t2.B;i1.c1.i;T4.a2.i2.g1.a.s1.c;v1.e1.s;e1.a1.m1.p;u1.i2.l;r;à;Um..1.N1..1.C;/1.1;11. .21.1;L1.T;M1.N;N4.C1.L;D2. .P.K;R1. .a;b2;a.i.d;g1.l;i1.g.l2;i.y.m;no. ;a1.n.b;c;d;e1;s.f;g;h;i2.d;n;j;k;l;m;n;o;p;q;r;s;t;u;v;w;p;r3;a.e.u1.k;s3. ;h;t1;r.t4.h;n;r;t;x;z;í;W2.P1.:4.A1.F;I2.B;N1.H.O1.V;R1.F1.C2.N.U.i1.k1.i1.E1.l1.i;X7;a.e.h.i.o.u.y.Y3.e1.t1.h;p;s;[5.A;E;I;a;e;_2._1.i;e;`3.a;e;i;a7; .m1;a1;r1. .n1;d2; .ě.p1;r1;t.r1;t1;í.u1;s1;s1;i1. .v1;u1;t.d3.a1.s1. ;e2.m1. ;r1. ;i2.c1.h1. ;e1.s1.e2.m;r;e8;c1;o1;n1;o1;m1;i1;a.e1;w.l1;i1;t1;e1;i.m1;p1;e1;z.n1;t1;e1;n1;d.s2;a1. .t4;a1; .e1; .i1;m1;a1;r.r1;u1.t.u1.p1. ;w.f3. ;M;y1.i;h9. ;,;.;C;a1.u1.t1;b.e2.i1.r1;a.r1.m1.a1.n;o4.m2.a1; .m;n8; .b.d.e3; .d.y.g.i.k.v.r1.s1. ;u1.r;r1. ;t1;t1;p1;:.i6;b1;n.e1;r.n2;f2;l1;u1;ê.o1;a.s1;t1;a1;l1;a.r1; .s1; .u.k1.u1. ;l3.c1.d;s1. ;v1.a;ma. ;,;R;b1.a.e1.i1.n;f;p;t1.a.u1.l1.t1.i1.c1.a1.m1.p1.i;×;n6. ;V;W;d1; .t;×;o8;c2;h1;o.u1;p.d1;d1;y.f1; .g1;g1;i.no. ;';,;/;a;b;c1.o;d;e2.i;r;f;g;i;l;m;n;o;r;s;t;u;w;y;z;–;r1;i1;g1;e.t1;r1.s;u1;i.r3. ;&;f;s9.,;?;R;f2.e.o.i1.c1.h;l1. ;p2.3;i1. ;r1.g;v3.a.e.i.t2.A;S;uc; ...b2.e;l;f.k2.a;i;m1;a1. .n3;a3; .n5.a;c;n;s;t;r1;y.e2; .i.i8.c2.o1.r1.p;u1.m;d1;i1.o;g1.n;l1.l;m1;o.n;s1.s;v1.o1;c.r5;a.e.i.l.o.s3. ;h;u1.r2;e.p3;a.e.i.t2.m;t;v.w1.a;xb. ;';,;.;8;b;k;l;m1;a.t;y1. ;y1.l;{1.a;|1.a;£1.8;À;Á;Ä;Å;Æ;É;Ò;Ó;Ö;Ü;à;á;æ;è;é1;t3.a;o;u;í;ö;ü1; .Ā;ā;ī;İ;Ō;ō;œ;Ω;α;ε;ω;ϵ;е;–2.e;i;ℓ;";
function fill(node) {
var kidCount = parseInt(dict, 36) || 0,
offset = kidCount && kidCount.toString(36).length;
node.article = dict[offset] == "." ? "a" : "an";
dict = dict.substr(1 + offset);
for (var i = 0; i < kidCount; i++) {
var kid = node[dict[0]] = {}
dict = dict.substr(1);
fill(kid);
}
}
fill(root);
return {
raw: root,
//Usage example: AvsAnSimple.query("example")
//example returns: "an"
query: function (word) {
var node = root, sI = 0, result, c;
do {
c = word[sI++];
} while ('"‘’“”$\''.indexOf(c) >= 0);//also terminates on end-of-string "undefined".
while (1) {
result = node.article || result;
node = node[c];
if (!node) return result;
c = word[sI++] || " ";
}
}
};
})({})
Now, the problem is that I can't find a way to use this function in conjunction with the replaceExpr. The following obviously wouldn't work because of order precedence :
var a = replaceExpr("Theodor is " + AvsAnSimple(zxappearancexz) + "man. He seems rather " + AvsAnSimple(zxpersonalityxz).")
I just recently started learning javascript so my knowledge is rather limited. Any ideas how I could overcome this?
Thank you!
You could use a regular expression to optionally match the " a " or "an" before your word in the input string and store that matched portion in a variable using the String.match() function, then check if that " a " or " an " exists in your matched string, do the manipulations you need to do and store that manipulated string in a separate variable, then use String.replace() to find that previously matched string again, and replace it wit
your manipulated string. The regular expression you could use for this is /(\san?\s)?(zx\w*zx)/gm
See the regular expression here for more context.
Thank you Joseph! With your help I managed to find something that works by using your regular expression. Here's my function :
function replaceExpr(a) {
var nbExprToReplace = 1;
while (nbExprToReplace == 1) {
if (a.search("zx") == -1) {
nbExprToReplace = 0;
} else {
var currentGroup = a.match(/(\san?\s)?(zx\w*xz)/);
var exprToChange = currentGroup[2];
exprToChange = exprToChange.slice(2,-2);
var exprToChange = window[exprToChange];
if (typeof exprToChange !== "function") {
alert("the keyword is not a recognized function!");
break;
} else {
exprToChange = exprToChange();
var final = exprToChange
};
if (currentGroup[1] === undefined) {
} else {
var newArticle = AvsAnSimple.query(exprToChange);
final = newArticle.concat(" " + final)
};
a = a.replace(currentGroup[0], " " + final);
};
};
return a;
};

Code only works when not wrapped in a function

Really not sure what's going on here as am new to JavaScript, but actually cannot figure it out, even after reading other posts like this one: Simple function returning 'undefined' value. For some reason, when my code is placed inside a function, it returns 'undefined' instead of true or false.
If I use the code without a function and define var a on the first line, it works OK:
var a = "wjebh ghbui ayub";
var b = (a.split(' ').join('')).split('a'); // creates array
var trueOrFalse = b.map(function(c, i){ // puts into array true/false for each index
if (c[2] == 'b') {
console.log('value: ' + c[2] + ' is b; true');
return true;
} else {
console.log('false');
return false;
}
});
var answer = function(el) {
// checks whether any element is true
return el === true;
};
trueOrFalse.some(answer); // return true/false
But the moment I add it inside a function, it doesn't work.
function bThreeAfterA(a) {
var b = (a.split(' ').join('')).split('a'); // creates array
var trueOrFalse = b.map(function(c, i){ // puts into array true/false for each index
if (c[2] == 'b') {
console.log('value: ' + c[2] + ' is b; true');
return true;
} else {
console.log('false');
return false;
}
});
var answer = function(el) {
// checks whether any element is true
return el === true;
};
trueOrFalse.some(answer); // return true/false
}
Even if it's put in an IIFE it doesn't work properly:
(function(){
var a = "wjebh ghbui ayub";
var b = (a.split(' ').join('')).split('a'); // creates array
var trueOrFalse = b.map(function(c, i){ // puts into array true/false for each index
if (c[2] == 'b') {
console.log('value: ' + c[2] + ' is b; true');
return true;
} else {
console.log('false');
return false;
}
});
var answer = function(el) {
// checks whether any element is true
return el === true;
};
trueOrFalse.some(answer); // return true/false
})();
I feel I'm doing something very daft here that most will easily spot. Can someone explain what I'm doing wrong here? Presumably this is some sort of beginner syntax error. Links to any reading resources would be helpful too.
We've all been there.
Give this a run.
function bThreeAfterA(a) {
var b = (a.split(' ').join('')).split('a'); // creates array
var trueOrFalse = b.map(function(c, i){ // puts into array true/false for each index
if (c[2] == 'b') {
console.log('value: ' + c[2] + ' is b; true');
return true;
} else {
console.log('false');
return false;
}
});
var answer = function(el) {
// checks whether any element is true
return el === true;
};
return trueOrFalse.some(answer); //ACTUALLY return true/false
}
console.log("Answer: " + bThreeAfterA("wjebh ghbui ayub")); // Returns true.
When you're operating on variables outside of the block scope, they persist. When they're inside, they vanish.
Hence, it "worked" but it didn't "work" when you have it in a function.

Compose in JavaScript, not applying functions correctly?

Here's my compose function, as a polyfill
Function.prototype.compose = function(prevFunc) {
var nextFunc = this;
return function() {
return nextFunc.call(this, prevFunc.apply(this,arguments));
}
}
These work:
function function1(a){return a + ' do function1 ';}
function function2(b){return b + ' do function2 ';}
function function3(c){return c + ' do function3 ';}
var myFunction = alert(function1).compose(function2).compose(function3);
myFunction('do');
var roundedSqrt = Math.round.compose(Math.sqrt)
roundedSqrt(6);
var squaredDate = alert.compose(roundedSqrt).compose(Date.parse)
quaredDate("January 1, 2014");
But this does not work!
var d = new Date();
var alertMonth = alert.compose(getMonth); <--
alertMonth(d); ^^^^
Error throws error "Uncaught ReferenceError: getMonth is not defined" in google chrome.
Now, if I try either of these instead:
var d = new Date();
function pluckMonth(dateObject) {return dateObject.getMonth();}
var alertMonth = alert.compose(pluckMonth);
var alertMonth2 = alert.compose(function(d){return d.getMonth()});
alertMonth(d);
alertMonth2(d);
They work.
Ok, so, why is that? I don't want to write extra functions, I want it to just work. The compose function uses the apply utility and just uses this for the thisArg, so it should work for object members as well as stand-alone functions, right??
i.e., these are equivalent
this.method()
method.call.apply(this)
jsFiddle: http://jsfiddle.net/kohq7zub/3/
If it comes to prototyping the Function object a whole bunch of libraries really do not acknowledge functions as methods.
The last entry in such arguments signatures always should be a target object, a method then can act upon. It solves the binding within the implementation; Thus avoiding to be forced later on having to use bind again and again as sole solution.
The given example slightly needs to be changed (jsFiddle) to ...
Function.prototype.compose = function(prevFunc, target) {
var nextFunc = this;
return function() {
return nextFunc.call(this, prevFunc.apply(target, arguments));
};
};
const d = (new Date);
let alertMonth;
// both variants do work.
alertMonth = alert.compose(d.getMonth, d);
alertMonth();
alertMonth = alert.compose(Date.prototype.getMonth, d);
alertMonth();
Within a next code iteration the above provided code then might result in something similar to the following implementation ...
const protoGetMonth = Date.prototype.getMonth;
let logMonth;
logMonth = ((month) => console.log(month))
.compose(protoGetMonth, (new Date));
logMonth();
logMonth = console.log
.compose(protoGetMonth, (new Date))
.bind(console);
logMonth();
let alertMonth = alert.compose(protoGetMonth, (new Date));
alertMonth();
.as-console-wrapper { min-height: 100%!important; top: 0; }
<script>
(function (function_prototype) {
var
isFunction = (function (TYPEOF_FUNCTION_TYPE) {
return function (type) {
return (
(typeof type == TYPEOF_FUNCTION_TYPE)
&& (typeof type.call == TYPEOF_FUNCTION_TYPE)
&& (typeof type.apply == TYPEOF_FUNCTION_TYPE)
);
}
}(typeof function_prototype)),
getSanitizedTarget = function (target) {
return (target == null) ? null : target;
}
;
function_prototype.compose = function (previous, target) { // compose
var
previousTarget = getSanitizedTarget(target),
proceed = this
;
return (isFunction(previous) && isFunction(proceed) && function () {
return proceed.call(this, previous.apply(previousTarget, arguments));
}) || proceed;
};
}(Function.prototype));
</script>

Get object caller name by function call JavaScript

I'm writing a piece of code to easily save error logs in an object for debugging.
What I'm trying to achieve is to get the Object name from the function it was called from like so:
var MainObject = {
test : function() {
return MainObject.test.caller;
// When called from MainObject.testcaller,
// it should return MainObject.testcaller.
},
testcaller : function() {
return MainObject.test(); // Should return MainObject.testcaller, Returns own function code.
},
anothercaller : function() {
return MainObject.test(); // Should return MainObject.anothercaller, Returns own function code.
}
}
However when I run this code it returns the function code from MainObject.testcaller.
JSFiddle example
Is there any way this is possible?
Update
After looking at Rhumborl's answer, I discovered that assigning the value through another function would lead it to point back at the function name without the object itself.
Code:
(function (name, func) {
MainObject[name] = func;
})('invalid', function() {
return MainObject.test("blah");
});
// This now points at invalid() rather than MainObject.invalid()
Updated fiddle
There is a non–standard caller property of functions that returns the caller function, however that is a pointer to a function object and doesn't tell you the object it was called as a method of, or the object's name. You can get a reference to the function through arguments.callee.
There is also the obsolete arguments.caller, but don't use that. It also provides a reference to the calling function (where supported).
Once you have a reference to the calling function (if there is one), you then have the issue of resolving its name. Given that Functions are Objects, and objects can be referenced by multiple properties and variables, the concept of a function having a particular name is alluvial.
However, if you know that the function is a property of some object, you can iterate over the object's own enumerable properties to find out which one it is.
But that seems to be a rather odd thing to do. What are you actually trying to do? You may be trying to solve a problem that can be worked around in a much more robust and simpler way.
Edit
You can do what you want in a very limited way using the method described above for the case in the OP, however it is not robust or a general solution:
var mainObject = {
test : function() {
var obj = this;
var caller = arguments.callee.caller;
var global = (function(){return this}());
var fnName, objName;
for (var p in global) {
if (global[p] === obj) {
objName = p;
}
}
for (var f in obj) {
if (obj[f] === caller) {
fnName = f;
}
}
return objName + '.' + fnName;
},
testcaller : function() {
return mainObject.test();
},
anothercaller : function() {
return mainObject.test();
}
}
console.log(mainObject.testcaller()); // mainObject.testcaller
console.log(mainObject.anothercaller()); // mainObject.anothercaller
but it's brittle:
var a = mainObject.anothercaller;
console.log(a()); // mainObject.anothercaller
var b = {
foo : mainObject.anothercaller
}
console.log(b.foo()); // mainObject.anothercaller
Oops.
You can use this trick at http://www.eriwen.com/javascript/js-stack-trace/ which throws an error, then parses the stack trace.
I have updated it for the latest versions of Firefox, Chrome and IE. Unfortunately it doesn't work well on my IE9 (and I haven't tested it on Opera).
function getStackTrace() {
var callstack = [];
var isCallstackPopulated = false;
try {
i.dont.exist += 0; //doesn't exist- that's the point
} catch (e) {
if (e.stack) { //Firefox/Chrome/IE11
var lines = e.stack.split('\n');
for (var i = 0, len = lines.length; i < len; i++) {
var line = lines[i].trim();
if (line.match(/^at [A-Za-z0-9\.\-_\$]+\s*\(/)) {
// Chrome/IE: " at Object.MainObject.testcaller (url:line:char)"
var entry = line.substring(3, line.indexOf('(') - 1);
// Chrome appends "Object." to the front of the object functions, so strip it off
if (entry.indexOf("Object.") == 0) {
entry = entry.substr(7, entry.length);
}
callstack.push(entry);
} else if (line.match(/^[A-Za-z0-9\.\-_\$]+\s*#/)) {
// Firefox: "MainObject.testcaller#url:line:char"
callstack.push(line.substring(0, lines[i].indexOf('#')));
}
}
//Remove call to getStackTrace()
callstack.shift();
isCallstackPopulated = true;
} else if (window.opera && e.message) { //Opera
var lines = e.message.split('\n');
for (var i = 0, len = lines.length; i < len; i++) {
if (lines[i].match(/^\s*[A-Za-z0-9\-_\$]+\(/)) {
var entry = lines[i];
//Append next line also since it has the file info
if (lines[i + 1]) {
entry += lines[i + 1];
i++;
}
callstack.push(entry);
}
}
//Remove call to getStackTrace()
callstack.shift();
isCallstackPopulated = true;
}
}
if (!isCallstackPopulated) { //IE9 and Safari
var currentFunction = arguments.callee.caller;
while (currentFunction) {
var fn = currentFunction.toString();
var fname = fn.substring(fn.indexOf("function") + 8, fn.indexOf('')) || 'anonymous';
callstack.push(fname);
currentFunction = currentFunction.caller;
}
}
return callstack;
}
var MainObject = {
test: function (x) {
// first entry is the current function (test), second entry is the caller
var stackTrace = getStackTrace();
var caller = stackTrace[1];
return caller + "()";
},
testcaller: function () {
return MainObject.test(1, null);
}
}
function SomeFunction() {
return MainObject.test("blah");
}
document.body.innerHTML += '<b style="color: red">' + MainObject.testcaller() + '</b>';
document.body.innerHTML += '<div>Calling SomeFunction() returns: <b style="color: red">' + SomeFunction() + '</b></div>';
MainObject.test() should return: <b style="color: blue">MainObject.testcaller()</b>
<hr />
MainObject.test() returns:
Updated fiddle here

Categories