📜 ⬆️ ⬇️

Javascript panopticon

During the time that I happened to write in Javascript, I got the image that js and its specification is a casket with a secret bottom. Sometimes it seems that there is nothing secret about it, when suddenly the magic knocks into your house: the casket opens, devils jump out, the blues play at home, and briskly hide in the casket. Later you will know the reason: the table led and the casket tilted by 5 degrees, which caused devils. Since then, you do not know if this is a feature of the box, or better still, wrap it up with tape. And so on until the next time, until the casket gives a new story.


And if you write down every such story, you may have a small article that I want to share.


"The amount of voids"


When merging an array into a string using the .join() method, some empty types: null, undefined, an array with zero length are converted to an empty string. And this is true only for the case when they are located in an array.


 [void 0, null, []].join("") == false // => true [void 0, null, []].join("") === "" // => true //      . void 0 + "" // => "undefined" null + "" // => "null" [] + "" // => "" 

In practice, this behavior can be used to screen out really empty data.


 var isEmpty = (a, b, c) => { return ![a, b, c].join(""); } var isEmpty = (...rest) => { return !rest.join(""); } isEmpty(void 0, [], null) // => true isEmpty(void 0, [], null, 0) // => false isEmpty(void 0, [], null, {}) // => false.        //  ,      var isEmpty = (arg) => { return !([arg] + ""); } isEmpty(null) // => true isEmpty(void 0) // => true isEmpty(0) // => false 

"Strange Numbers"


Attempting to define types for NaN and Infinity using the typeof operator as the result will return "number"


 typeof NaN // => "number" typeof Infinity// => "number" !isNaN(Infinity) // => true 

The humor is that NaN is short for "Not-A-Number", and infinity ( Infinity ) is hardly a number.


How, then, to determine the numbers? Check out their limb!


 function isNumber(n) { return isFinite(n); } isNumber(parseFloat("mr. Number")) // => false isNumber(0) // => true isNumber("1.2") // => true isNumber("abc") // => false isNumber(1/0) // => false 

"To shoot the feet, take the object"


For javascript, Object is one of the very first data structures and at the same time, in my opinion, is the king of the intricacies.


For example, bypassing an object used as a hash table in a loop, it is advisable to check that the properties being iterated are proper.


Otherwise, the properties from the extension of the prototype can get into the iteration.


 Object.prototype.theThief = " "; Object.prototype.herLover = ""; var obj = { theCook: " ", hisWife: "" }; for (var prop in obj) { obj[prop]; //  : " ", "", " ", "" if (!obj.hasOwnProperty(prop)) continue; obj[prop]; //  : " ", "" } 

Meanwhile, an Object can be created without prototype inheritance.


 //      var obj = Object.create(null); obj.key_a = "value_a"; obj.hasOwnProperty("key_a") // =>  . 

"Hey, cap, why do you need it?"


In such a hash, there are no inherited keys — only private keys (hypothetical memory savings). So, designing APIs to libraries, where the user is allowed to transfer his own data collections, it is easy to forget about it - thereby shooting yourself in the foot.


And since in this case you cannot control the input data, you need a universal way to check your own keys in the object.


Method one. You can get all the keys. Suboptimal if you run indexOf inside a loop: an extra array traversal.


 Object.keys(obj); // => ["key_a"] 

The second way . Call the hasOwnProperty method with a modified context


 Object.prototype.hasOwnProperty.call(obj, "key_a") // => true 

It would seem that here is the perfect way. But, Internet Explorer.


 //   IE //     var obj = Object.create(null); obj[0] = "a"; obj[1] = "b"; obj[2] = "c"; Object.prototype.hasOwnProperty.call(obj, 1); // => false Object.prototype.hasOwnProperty.call(obj, "1"); // => false Object.keys(obj); // => ["0", "1", "2"] obj.a = 1; Object.prototype.hasOwnProperty.call(obj, 1); // => true Object.prototype.hasOwnProperty.call(obj, "1"); // => true //        Object obj = Object.create(Object.prototype); obj["2"] = 2; obj.hasOwnProperty("2"); // => false obj.a = "a"; obj.hasOwnProperty("2"); // => true delete obj.a; obj.hasOwnProperty("2"); // => false 

It did not seem to you, IE really refuses to check digital keys in objects created through Object.create() , until at least one lowercase Object.create() appears in it.


And this fact spoils the whole holiday.


UPD:
Solution proposed by Dmitry Korobkin


UPD:
bingo347 rightly noted that if you do not write scripts for "dinosaurs", then it is more expedient to perform a search of your own properties using Object.keys(obj) and Object.getOwnPropertyNames(obj)


But, it should be borne in mind that getOwnPropertyNames returns all its own keys, even those that are non-removable.


 Object.keys([1, 2, 3]); // => ["0", "1", "2"] Object.getOwnPropertyNames([1, 2, 3]); // => ["0", "1", "2", "length"] 

"False undefined"


Often developers check variables for undefined by direct comparison.


 ((arg) => { return arg === undefined; // => true })(); 

Do the same with assignment


 (() => { return { "undefined": undefined } })(); 

"Ambush" lies in the fact that undefined can be overridden


 ((arg) => { var undefined = "Happy debugging m[\D]+s!"; return { "undefined": undefined, "arg": arg, "arg === undefined": arg === undefined, // => false }; })(); 

This knowledge deprives you of sleep: it turns out that you can break the whole project by simply overriding undefined inside the circuit.


But there are a couple of reliable ways to compare or assign undefined - use the void operator or declare an empty variable.


 ((arg) => { var undefined = "Happy debugging!"; return { "void 0": void 0, "arg": arg, "arg === void 0": arg === void 0 // => true }; })(); ((arg) => { var undef, undefined = "Happy!"; return { "undef": undef, "arg": arg, "arg === undef": arg === undef // => true }; })(); 

Schrödinger Comparison


Once colleagues shared with me an interesting anomaly.


 0 < null; // false 0 > null; // false 0 == null; // false 0 <= null; // true 0 >= null // true 

This happens because the more-less comparison is a numerical comparison, where both parts of the expression are reduced to a number.


While equality of numbers with null always returns false.


If we take into account that null becomes +0 after being cast to a number, the comparison inside the compiler looks approximately like this:


 0 < 0; // false 0 > 0; // false 0 == null; // false 0 <= 0; // true 0 >= 0 // true 

Comparing Numbers with Boolean


 -1 == false; // => false -1 == true; // => false 

In javascript, when comparing Number with Boolean , the latter is reduced to a number, after that, Number == Number is compared.


And, since false reduced to +0, and true reduced to +1, the comparison inside the compiler takes the form:


 -1 == 0 // => false -1 == 1 // => false 

But.


 if (-1) "true"; // => "true" if (0) "false"; // => undefined if (1) "true"; // => "true" if (NaN) "false"; // => undefined if (Infinity) "true" // => "true" 

Because 0 and NaN always result in false, everything else is true.


Check for array


In JS, Array inherited from Object and, in fact, are objects with numeric keys.


 typeof {a: 1}; // => "object" typeof [1, 2, 3]; // => "object" Array.isArray([1, 2, 3]); // => true 

The thing is that Array.isArray() only works since IE9 +


But there is another way


 Object.prototype.toString.call([1, 2, 3]); // => "[object Array]" //  function isArray(arr) { return Object.prototype.toString.call(arr) == "[object Array]"; } isArray([1, 2, 3]) // => true 

In general, using Object.prototype.toString.call(something) you can get many other types.


UPD:
boldyrev_gene asked, in my opinion, a good question: why not use instanceof ?


Array instances created inside frames and other windows will have different constructor instances.


 var iframe = document.querySelector("iframe"), IframeArray = iframe.contentWindow.Array; new IframeArray() instanceof Array; // => false Array.isArray(new IframeArray()); // => true Object.prototype.toString.call(new IframeArray()); // => "[object Array]" 

arguments is not an array


I often forget about it so much that I even decided to write it out.


 (function fn() { return [ typeof arguments, // => "object" Array.isArray(arguments), // => false Object.prototype.toString.call(arguments) // => "[object Arguments]"; ]; })(1, 2, 3); 

And since arguments is not an array, the usual methods .push() , .concat() , etc. are not available in it. And if we need to work with arguments as a collection, there is a solution:


 (function fn() { arguments = Array.prototype.slice.call(arguments, 0); //    return [ typeof arguments, // => "object" Array.isArray(arguments), // => true Object.prototype.toString.call(arguments) // => "[object Array]"; ]; })(1, 2, 3); 

but ... rest is an array


 (function fn(...rest) { return Array.isArray(rest) // => true. Oh, wait... })(1, 2, 3); 

Catch global. Or we define the script execution environment.


When building isomorphic libraries, for example, from a number of those that are collected via a Webpack, sooner or later, it becomes necessary to determine in which environment the script is running.


And since JS does not provide a mechanism for defining a runtime environment at the standard library level, you can make a feint using the behavior of the pointer inside anonymous functions in a nonstrict mode.


In anonymous functions, the this pointer refers to the global object.


 function getEnv() { return (function() { var type = Object.prototype.toString.call(this); if (type == "[object Window]") return "browser"; if (type == "[object global]") return "nodejs"; })(); }; 

However, in strict mode, this is undefined, which breaks the way. This method is relevant if global or window declared manually and globally - protection from "tricky" libraries.




Thanks for attention! I hope someone will find these notes useful and useful.


')

Source: https://habr.com/ru/post/313196/


All Articles