How does the length property work? - javascript

I'm writing code that needs to reference this inside a prototype, but it cannot be a function. Javascript won't let me do this, but it seems like the length property of arrays and strings does this. I know that length is built-in, and my code is not, but if I can, how do I implement this?
I tried:
String.prototype.prototypeName = (function(aThing){
//Do whatever I need to do here
})(this);
But that references to the global object, because this is called outside of the function.
String.prototype.prototypeName = function(aThing){
//Do whatever I need to do here referencing this
};
However, that is a function, and I can't have that.
I can't have a function because the user can call the function and use typeof on it, and the prototype is supposed to return a string.
For example:
String.prototype.reverse = "Put something that is the reversed string (or this)";
console.log("Stuff"); //"ffutS"
console.log("Anything"); //gnihtynA

You can use a Javascript getter to access a computed property without calling a prototype function. Note this is IE9+ only.

Related

using Set.has in Array.filter - incompatible receiver

Suppose I've a Set as a lookup table.
const myset = new Set(["key1", "key2", "key3", "keepMe"]);
I wanted to filter another array of some other keys (say mykeys) which are in myset.
const mykeys = ["ignoreMe", "keepMe", "ignoreMeToo", "key2"];
Question:
Why do I have to use
const filtered = mykeys.filter(k => myset.has(k))
instead of
const filtered = mykeys.filter(myset.has)
// TypeError: Method Set.prototype.has called on incompatible receiver undefined
i.e., why do I've to create an anonymous lambda function in filter? keys.has has same signature (argument - element, return boolean). A friend told me it's related to this.
Whereas mykeys.map(console.log) works without error (although not being of much use).
I came across this article at MDN and I still don't get why "'myset' is not captured as this". I understand the workaround but not the "why". Can anyone explain it with some details and references in a human friendly way?
Update: Thank you all for the responses. Maybe I wasn't clear about what I'm asking. I do understand the workarounds.
#charlietfl understood. Here's his comment, the thing I was looking for:
Because filter() has no implicit this where as set.has needs to have proper this context. Calling it inside anonymous function and manually adding argument makes the call self contained.
You could use thisArg of Array#filter with the set and the prototype of has as callback.
This pattern does not require a binding of an instance of Set to the prototype, because
If a thisArg parameter is provided to filter, it will be used as the callback's this value. Otherwise, the value undefined will be used as its this value. The this value ultimately observable by callback is determined according to the usual rules for determining the this seen by a function.
const
myset = new Set(["key1", "key2", "key3", "keepMe"]),
mykeys = ["ignoreMe", "keepMe", "ignoreMeToo", "key2"],
filtered = mykeys.filter(Set.prototype.has, myset);
console.log(filtered);
This is a fundamental design decision dating back to the first definition of the JavaScript language.
Consider an object
var myObjet = {
someValue: 0,
someFunction: function() {
return this.someValue;
}
};
Now, when you write
var myValue = myObject.someValue;
You get exactly what you have put in it, as if you had written
var myValue = 0;
Similarly, when you write
var myFunction = myObject.someValue;
You get exactly what you have put in it, as if you had written
var myFunction = (function() {
return this.someValue;
});
...except now, you are not in an object anymore. So this doesn't mean anything. Indeed, if you try
console.log(myFunction());
you will see
undefined
exactly as if you had written
console.log(this.someValue);
outside of any object.
So, what is this? Well, JavaScript decides it as follows:
If you write myObject.myFunction(), then when executing the myFunction() part, this is myObject.
If you just write myFunction(), then this is the current global object, which is generally window (not always, there are many special cases).
A number of functions can inject a this in another function (e.g. call, apply, map, ...)
Now, why does it do this? The answer is that this is necessary for prototypes. Indeed, if you now define
var myDerivedObject = Object.create(myObject);
myDerivedObjet.someValue = 42;
you now have an object based on myObject, but with a different property
someValue
console.log(myObject.someFunction()); // Shows 0
console.log(myDerivedObject.someFunction()); // Shows 42
That's because myObject.someFunction() uses myObject for this, while myDerivedObject.someFunction() uses myDerivedObject for this.
If this had been captured during the definition of someFunction, we would have obtained 0 in both lines, but this would also have made prototypes much less useful.

Can't use String.prototype.match as function for Array.some?

This doesn't work:
var s = '^foo';
console.log(['boot', 'foot'].some(s.match));
Uncaught TypeError: String.prototype.match called on null or undefined
But this does:
var s = '^foo';
console.log(['boot', 'foot'].some(function(i) { return i.match(s) }));
Why is this? I imagine somehow the String.prototype.match function is too "primitive" or something, but why exactly? Since I'm not using ES2015, the second version seems quite verbose. Is there an alternative?
EDIT
When I wrote the above, I actually got it backwards compared to my actual need, which was matching one string against a number of regexes. But thanks to the great answers and comments below, I get it: [/^foo/, /^boo/].some(''.match, 'boot').
Note: The value of this is determined by how the function is called! (exception: bound and arrow functions)
If you pass s.match to .some, then the function will be called with this set to the global object (e.g. window) not the string it "belongs" to.
I.e. it would be equivalent to this:
String.prototype.match.call(window, 'foo')
This cannot work because this has to refer to a string object.
You could solve this by binding the function to a specific this value:
['boot', 'foot'].some(s.match.bind(s));
Learn more about this:
MDN - this
You Don't Know JS: this or That?
How to access the correct `this` context inside a callback?
A function value in Javascript does not bring its object along with it. The value of s.match is a plain function value, with no knowledge that you happened to find it attached to s. In fact, no matter what String you access it through, it's always the same function value:
"foo".match === "bar".match
//= true
When you call a function through an object, Javascript sets this to that object for the duration of the function call. But as soon as anything comes between retrieving the function value and calling it, any object association is lost.
You can create a function that does remember a specific this value using bind, as in #Felix King's answer. someFunction.bind(someObject) has approximately the same meaning as function(arg1, arg2,...) { return someObject.someFunction(arg1, arg2,...); }, but it automatically handles the number of parameters properly.

Object has no method 'apply'

I am creating a few DOM elements dynamically like,
var anchorElement = jQuery('<a />',{text:property.text});
var liElement = jQuery('<li />',{"class":"navlink_"+i,id:"navlink_"+i});
anchorElement.on('click',property.fnctn);
liElement.append(anchorElement);
parentID.append(liElement);
Where property is a JSON object.
property.text is the text that I want to put into anchor element. (Works fine)
I want to attach a click event handler to that anchor element.
The function that needs to be bound to that element is specified in JSON and we can access it like
property.fnctn
The following line should bind the event handler to the anchor element.
anchorElement.on('click',property.fnctn);
This was not working so I tried converting it into string like,
anchorElement.on('click',property.fnctn.toString());
No Success...
When I click on this link, the error is logged in the console
The object has no method 'apply'.
What is the reason...???
I am able to get it working with a slight work around like
anchorElement.attr('onclick',property.fnctn+"()");
Above statement works, but I want to know why .on() API is not working.
Thanks :)
AÐitya.
Update:
Youve said that property.actfn is a string, "paySomeoneClick". It's best not to use strings for event handlers, use functions instead. If you want the function paySomeoneClick, defined in the string, to be called, and if that function is global, you can do this:
anchorElement.on('click',function(event) {
return window[property.fnctn](event);
});
That works because global functions are properties of the global object, which is available via window on browsers, and because of the bracketed notation described below.
If the function is on an object you have a reference to, then:
anchorElement.on('click',function(event) {
return theObject[property.fnctn](event);
});
That works because in JavaScript, you can access properties of objects in two ways: Dotted notation with a literal property name (foo.bar accesses the bar propety on foo) and bracketed notation with a string property name (foo["bar"]). They're equivalent, except of course in the bracketed notation, the string can be the result of an expression, including coming from a property value like property.fnctn.
But I would recommend stepping back and refactoring a bit so you're not passing function names around in strings. Sometimes it's the right answer, but in my experience, not often. :-)
Original answer:
(This assumed that property.fnctn was a function, not a string. But may be of some use to someone...)
The code
anchorElement.on('click',property.fnctn);
will attach the function to the event, but during the call to the function, this will refer to the DOM element, not to your property object.
To get around that, use jQuery's $.proxy:
anchorElement.on('click',$.proxy(property.fnctn, property));
...or ES5's Function#bind:
anchorElement.on('click',property.fnctn.bind(property));
...or a closure:
anchorElement.on('click',function(event) {
return property.fnctn(event);
});
More reading (on my blog):
Mythical methods
You must remember this
Closures are not complicated

JavaScript Globals - Method Handling

I have multiple global arrays that a single method performs operations on. The method will only need to operate on one array at a time. I would like to accomplish this by passing a parameter to the method and then let the method decide which array it needs to modify based on that parameter. For example,
var globalarray1;
var globalarray2;
Operate(globalarray1);
function Operate(globalarray){
globalarray.push("test");
}
Of course, the code above only changes the value of the array local to the scope of the method. I know I can do something like this:
var globalarray1;
var globalarray2;
Operate(1);
function Operate(flag){
if (flag == 1){
globalarray1.push("test1");
}
else if (flag == 2){
globalarray2.push("test2")
}
}
However, it just doesn't feel right. How can I change the value of the globals using parameters in a single method without using a bunch of conditional statements?
Your first approach is correct. This statement, however, is not:
Of course, the code above only changes the value of the array local to the scope of the method.
Array objects are passed by reference called by sharing (i.e. the reference is passed by value, not the value itself). When you pass the array to the method, it can (and in your case does) actually modified the global variable. This would not be the case if you passed in an immutable or primitive value such as a number or a string. In those cases, the value is in fact local to the scope of the method.
The fact that your variables are global have nothing to do with it. Take this code, for example:
function Hello(){
var localArray = [];
Operate(localArray);
// now, localArray has been modified by Operate
}
Hello();
Above, localArray is not a global variable, but it can still be affected by Operate() if you pass the array in directly.
.push is a mutator method, and would change the array passed by the method since objects are passed by reference and not value in ECMAScript. So the first way is correct.

jquery object inside an object literal

i am trying not to repeat the selector and get to its children via a the same objects parentElment declared variable.
I tried:
testimonialsBelt={
parentElment:$(".testimonialsCntnr ul"),
childrenElem:this.parentElment.children().length
}
I also tried:
testimonialsBelt={
parentElment:$(".testimonialsCntnr ul"),
childrenElem:$("testimonialsBelt.parentElment").children().length
}
but i keep on getting a undefined when calling alert(testimonialsBelt.childrenElem).
is there anyway to get the jquery object with object literals?
What is the rule? when can i use this and when must i have the full path? (in this case testimonialsBelt.parentElment).
i am trying to have all these variables in one object called testimonialsBelt. i know i can do this with loose javaScript.
Thanks
In object literals, you can only use this to refer to the object that you're declaring inside of a function. Try the following:
var testimonialsBelt = {
parentElment: $(".testimonialsCntnr ul"),
childrenElem: function() {
return this.parentElment.children().length;
}
};
The difference in calling childrenElem is that instead of using alert(testimonialsBelt.childrenElem), you would instead have alert(testimonialsBelt.childrenElem()).
Otherwise, this refers to the current scope that you are in (typically window if you are declaring the object literal as a global).
Addressing your edit: I'm not sure what you mean by "loose javascript," but I assume you mean as simple as possible. In which case, you can try the following, although I'm not a big fan of the method. It's more verbose, but is easy to understand.
var testimonialsBelt = {
parentElment: $(".testimonialsCntnr ul")
};
testimonialsBelt.childrenElem = parentElment.children().length;
This gives you an object where childrenElem is static (it doesn't change) and avoids calling $(".testimonialsCntnr ul") twice. However, if you expect $(".testimonialsCntnr ul").children() to change, then you will need to use my first example.
In JavaScript (not ECMAScript) you can use this:
testimonialsBelt={
parentElment:#1=$(".testimonialsCntnr ul"),
childrenElem:#1#.children().length
}

Categories

Resources