I was looking up how to check if a variable in JavaScript is an array, but then as the SO page was loading, I thought of a solution to the problem. Looking through the answers, I found that none of them had thought of this simple answer: Just check for the methods we need to use on the array, so that it still works for any user defined types that implement the same methods. Being the helpful person I am, I thought I'd submit my answer for future people trying to solve the same problem..But after testing it, I found it does not work.
function print(object) {
if ('map' in object) { // If the object implements map, treat it like an array
object.map(function(current) {
console.log(current);
});
} else { // Otherwise, treat it as a string
console.log(object);
}
}
Now, this works fine when I call it with an array, but if I use a string it fails. Since strings are objects in javascript, why shouldn't the 'in' keyword work for them? Is there any way to implement this that is as simple as what it currently is?
You can access the property and test its type:
if (object != null && typeof object.map === 'function')
object.map(...);
// ...
Since strings are objects in javascript, why shouldn't the 'in' keyword work for them?
Not quite. There is a difference between a primitive string value and a String instance object.
typeof "foo"; // "string"
typeof new String(); // "object"
Primitive values don't have properties themselves, which is why the in operator throws an Error when used on them. They will, however, be temporarily boxed into Objects by property accessors:
var a = "foo";
a.bar = 'bar'; // no error, but...
console.log(a.bar); // undefined
String.prototype.baz = function () {
return this + this;
};
console.log(a.baz()); // "foofoo"
I suppose you could do something like this:
var y = function (o) {
if (typeof o != "string" && "map" in o)
console.log("not a string"); //your code here
else
console.log("It's a string!"); //your code here
}
I tested this with var x = "hello" and it worked. Because && short circuits in JavaScript, it will stop at typeof o != "string", which is good, since "map" in o fails for strings. Note the intentional use of lowercase, which is for primitives; Strings are objects. I'm not exactly sure if this is what you were aiming for.
Related
I am trying to extend the Number object with this code:
Number.prototype.isNumber = function(i){
if(arguments.length === 1){
return !isNaN(parseFloat(i)) && isFinite(i);
} else {
return !isNaN(parseFloat(this)) && isFinite(this);
}
}
try {
var x = 8.isNumber();
} catch(err) {
console.log(err);
}
I get SyntaxError: identifier starts immediately after numeric literal
also when I try the following:
Number.isNumber(8)
I get Number.isNumber is not a function!!
The JavaScript parser reads 8.isNumber as a number literal.
To access a Number method on a numeric literal you'll have to surround the number with parenthesis so the JavaScript interpreter knows you're trying to use the number properties.
Number.prototype.isNumber = function(i) {
if (arguments.length === 1) {
return !isNaN(parseFloat(i)) && isFinite(i);
}
return !isNaN(parseFloat(this)) && isFinite(this);
}
try {
var x = (8).isNumber();
console.log(x);
} catch(err) {
console.log(err);
}
I couldn't help it but provide an additional answer although you already accepted one.
The first thing you need to know, is that there is a fundamental difference between the Number object, and the Number prototype (see here).
As it stands, you are extending the Number prototype, not the object itself! Your isNumber implementation actually has the same effect like the following:
Number.prototype.isNumber = function(){return isFinite(this)}
Why? Because in order to execute this prototype method, the parser first needs to know the type of the literal you are invoking the function on. That's why you either need to turn your number literal into an expression by wrapping it in parentheses: (8).isNumber() or by using an even weirder notation 8..isNumber() (the first . is the decimal point, the second the property accessor). At this point, the javascript engine already evaluated it as a Number and thus can execute the isNumber() method.
On the other hand, although at first glimpse your code looks like it could handle the following case correctly (since you are doing a parseFloat): "8".isNumber() will always throw an exception, because here we have a string literal, and the String prototype does not have the according method. This means, you will never be able to detect numbers that are actually string literals in the first place.
What you instead should do, is directly extend the Number object so you can actually do a proper check without having to deal with errors:
Number.isFiniteNumber = function(i){
return !Number.isNaN(i) && Number.isFinite(i);
}
Number.isFiniteNumber(8); // returns true
Number.isFiniteNumber("3.141"); // returns true
Number.isFiniteNumber(".2e-34"); // returns true
Number.isFiniteNumber(Infinity); // returns false
// just for informational purposes
typeof Infinity === "number" // is true
Bonus material:
Extending native objects is potentially dangerous.
Number.isNaN() probably does not what you think it does.
I've seen a lot of places that do a string comparison with the typeof keyword and it returns what are obviously constant strings, such as string, object, undefined etc. I would expect to see those defined as constants somewhere but it doesn't seem to be the case (they are listed on MDN here). I don't like to see duplication of strings being used in code as they seem magic. The current solution is to define them as const elsewhere in code myself but it seems JS knows these anyway so why can't I get to them? (or can I?)
magic string:
if(typeof myObject === 'object') {}
This seems a little better, since no duplication of magic string, but I don't see why I should have to define the const:
const TYPE_OBJECT = 'object';
if(typeof myObject === TYPE_OBJECT) {}
if(typeof myOtherObject === TYPE_OBJECT) {}
Or, this seems clumsy too:
if(typeof myObject === Object.name.toLowerCase()) {}
if(typeof "hello" === String.name.toLowerCase()) {}
Is there a better way or are these constants built in somewhere?
These string constants are not available as predefined constants. But if you don't want to specify those string literals yourself, you can do the inverse, and apply typeof to some basic values you provide, and define the constants with those returned strings. For instance:
const TYPES = Object.freeze({
OBJECT: typeof {},
UNDEFINED: typeof undefined,
BOOLEAN: typeof true,
NUMBER: typeof 1,
STRING: typeof "",
FUNCTION: typeof eval,
SYMBOL: typeof Symbol()
});
console.log(TYPES);
// Use it:
let myObject = { "test": 123 };
console.log(typeof myObject === TYPES.OBJECT); // true
I think the presumption that these strings have to come from somewhere is a fallacy. JavaScript is a spec and, in this regard, all there is to it that typeof is an operator that returns a string which can have one of the predefined values listed in the spec. Where these come from is thus implementation-dependent.
Furthermore, while things like typeof myObject === Object.name.toLowerCase() or typeof myObject === TYPE_OBJECT may seem better than using a "bare" string, typeof myObject === 'object' is a well-known pattern. By using something different, you are reducing the readability of your code, as the other two variants are guaranteed to raise some brows and prompt people to ask themselves what this line does.
Had Symbol existed in JS from the very start, I believe typeof would've just returned a Symbol. Sadly, for backwards-compatibility, it has to return a string.
I am utterly confused. I know this has been asked a million times. And I have looked at questions like:
Test if something is not undefined in JavaScript
Now the problem is when doing the check I have found multiple things you can do.
I need to check if an object is an array, to do that I check if the "length" property is there. Now what one would I use?
if (obj.length)
or
if (obj.length === undefined)
or
if (typeof obj.length === "undefined")
or
if (obj.length == null)
or something else?
I understand that === doesn't do any type conversion, and the if statement is just wanting a "truthy" or "falsey" value, meaning obj.length will return false if the length is 0, but that's not what we want. We want to now if it is defined. Which is why I go to type test. But which way is the best?
Here are some tests I did. 2, 3 and 4 work.
Sorry for the stuff in between. I was doing it in the console for this page.
Short answer:
if (obj instanceof Array) {
// obj is an array
}
Or, if you don't know whether obj is defined or not:
if ((typeof obj !== "undefined") && (obj instanceof Array)) {
// obj is an array
}
To discuss why yours aren't quite right:
obj.anyProperty will throw a ReferenceError if obj is undefined. This affects all four of the things you put.
if (obj.length) will evaluate to false for an empty array, which isn't what you want. Because the length is 0 (a falsy value), it will falsely be inaccurate. This could also have issues if another object has a length property.
if (obj.length === undefined) will usually work, but it's frowned upon because undefined can be redefined. Someone could break your code by writing undefined = obj.length. This can also create false negatives; obj could happen to have a property called "length" and it would falsely call it an array.
if (typeof obj.length === "undefined") works fine, but could detect the same false negatives as the above.
if (obj.length == null) will work okay, but has the same bugs as above. In a double-equals (==), it matches null and undefined.
I would do `
obj instanceof Array
to check if obj is an array
http://jsfiddle.net/Tcjk4/
For what it's worth, here's how jQuery checks whether something is an array:
isArray: Array.isArray || function( obj ) {
return jQuery.type(obj) === "array";
},
This uses ES5's Array.isArray if available, or a custom check in older browsers. jQuery makes this function accessible as $.isArray.
jQuery.type is basically an enhanced typeof that works around some limitations of the JavaScript language and browser bugs. Whereas typeof returns 'object' for anything object-like ({}, [], even null), $.type returns the internal JavaScript [[Class]] property of the object.
In fact, this method of determining type is actually safer than instanceof:
Both instanceof and constructor look very innocent and seem like great
ways to check if an object is an array.
The problems arise when it comes to scripting in multi-frame DOM
environments. In a nutshell, Array objects created within one iframe
do not share [[Prototype]]s with arrays created within another
iframe. Their constructors are different objects and so both
instanceof and constructor checks fail:
var iframe = document.createElement('iframe');
document.body.appendChild(iframe);
xArray = window.frames[window.frames.length-1].Array;
var arr = new xArray(1,2,3); // [1,2,3]
// Boom!
arr instanceof Array; // false
// Boom!
arr.constructor === Array; // false
More comment than answer.
While a test like object instanceof Array will work in most cases (it may fail where frames or inter–window communication are involved), it's a good idea to consider what you really need to test for and design the test accordingly. Testing explicitly whether an object is an array or not is almost always unnecessary.
In this case, it seems that you just want to use the length property for iterating over the object's numeric properties.
If that's the case, all you need to do is read the value of the length property and use it. Whether the property is missing, or hasn't been assigned a value, or has a value of undefined or 0, you don't want to do the loop. Fortunately, you can do all of those tests in one go (and also skip processing if the value is Null or '', which seems sensible too):
if (obj.length) {
// iterate over numeric properties of obj
}
That will make the method generic, so it can be applied to any Object that has a suitable length property and some numeric properties (e.g. a jQuery object or an HTMLCollection object).
If you need some other feature of an array (say push or slice), you can also test for those.
If you are using the test as a logic fork (e.g. if it's an array do one thing, if it's a plain object do something else) then you should consider whether that's a sensible thing to do.
var sizeArrayOrObject = function(obj) {
var size = 0, key;
for (key in obj) {
if (typeof obj.key === 'undefined') size++;
}
return size;
};
sizeArrayOrObject([]); // 0
sizeArrayOrObject([5,6]); // 2
sizeArrayOrObject({}); // 0
sizeArrayOrObject({id:8}); // 1
to use underscore.js http://underscorejs.org/#isObject
_.isArray(object)
Returns true if object is an Array.
(function(){ return _.isArray(arguments); })();
=> false
_.isArray([1,2,3]);
=> true
_.isObject(value)
Returns true if value is an Object. Note that JavaScript arrays and functions are objects, while (normal) strings and numbers are not.
_.isObject({});
=> true
_.isObject(1);
=> false
I use the identity function in all my JavaScript programs:
function identity(value) {
return value;
}
The reason is that I often need differentiate between primitives types (undefined, null, boolean, number and string) and object types (object and function) as returned by the typeof operator. I feel using the indentity function for this use case very succuint:
if (new identity(value) == value); // value is of an object type
if (new identity(value) != value); // value is of a primitive type
The identity function is much smaller and simpler than the following code:
function isObject(value) {
var type = typeof value;
return type == "object" || type == "function";
}
However on reading my code a friend of mine complained that my hack is misleading and more computationally expensive than the above alternative.
I don't want to remove this function from any of my programs as I believe it's an elegant hack. Then again I don't write programs solely for myself. Is there any other use case for the identity function in JavaScript?
IMHO:
new identity(value) == value
means absolutely nothing and without extra comment I would have to think for a while to figure out what the intent was. On the other hand:
isObject(value)
is obvious from the very beginning, no matter how it is implemented. Why can't you use your hack inside a function named isObject()?
BTW More suited for http://codereview.stackexchange.com.
I updated my "speedtest" to test if the right results are returned … they aren't:
If you compare with new identity(x) == x, then null is deemed an object. === works, though.
Such pitfalls speak in favor of the isObject(...) solution.
If you compare === 'object'/'function' in the isObject code, then it will be double as fast as your original implementation, and almost a third faster than new identity(x) === x.
I have that function:
function(stringsVar) {
var stringRes = stringsVar || localize_en;
if('window.'+stringsVar === undefined) {
stringRes = localize_en;
}
...
}
and doesn't work. That was like that actually:
function(stringsVar) {
var stringRes = stringsVar || localize_en;
}
that function can take a parameter or not and the code above is checking it correctly. Parameter of that function will be a variable. I want to add that ability to my function. It will check whether that variable is defined or not. If not there is a defined variable at system, localize_en, it will be assigned as default.
How can I correct my code. The second part of my code will be that functionality:
i.e stringsVar is localize_ar and it is not a defined variable (I define that kind of variables with var keyword)
if(window.localize_ar === undefined){
alert('yes');}
else {
alert('no');
}
I will add that functionality as parametric.
Any ideas?
PS: localize_en and something like that variables are object.
EDIT: I am working on JQuery localizer plugin => source code.
I call it as
$('html').localize('localize_' + tr);
However it can not understand it as an object, it works as if I do:
$('html').localize(localize_tr);
It changes it into a string maybe the problem lays on there?
You can use the square bracket notation to refer to object members whose name is stored in a variable, so you're probably looking for this:
if (window[stringsVar] === undefined) {
}
Furthermore, the || operator will return the first truthy; what happens if an object is passed as the first parameter? That's truthy, but you specifically want a string, so whilst the || operator looks cool, you might find the following more appropiate:
if (typeof stringVar !== "string") {
stringVar = "localize_en";
}
It also looks like you're getting confused when to use a string to refer to the object your targeting, and when not to.
When you going to be doing something like:
window[someVar]
someVar needs to be a string.
It is possible to pass an object by reference in JavaScript, and after writing all the above to help you fix the problem you've currently got, a better approach will be to pass the object by reference in the first place and avoid the problem completely, rather than passing the name of the variable storing the object:
function(obj) {
if (typeof obj !== "object") {
obj = localize_en; // here we're wanting the object itself, rather than the name of the object, so we're not using a string.
};
// Now use `obj`. It'll be either the object the user passed, or the default (localize_en).
// You can even store this in a global variable if you want to:
window.selected_obj = obj;
}
Edit:
From your comment, try this:
function (stringsVar) {
if (typeof stringsVar !== "string" || typeof window[stringsVar] !== "object") {
stringsVar = "localize_en"; // Set the default of the argument, if either none is provided, or it isn't a string, or it doesn't point to a valid object
}
var stringRes = window[stringsVar];
// Now do *whatever* you want with stringRes. It will either be the *valid* localization type the parameter specified, or the default ("localize_en").
}
You should pass this function a string.