Article Introduction: a large collection of traps in JavaScript. |
This article mainly introduces weird JavaScript, and there is no doubt that it has a weird side. When software developers start using the world's most widely used language to write code, they will find a lot of interesting "features" in the process. Even seasoned JavaScript developers can find interesting new pitfalls in this article, watch out for these pitfalls, and, of course, enjoy the "fun" of these pitfalls!
Functions and operators
Double equals
The = = operator performs a type cast when compared, which means that it can compare two different types of objects, and it will attempt to convert the two objects to the same type before performing the comparison, for example:
However, this tends to mislead us, and we do not need to compare this. In the above example, we can simply convert a string to a numeric type and then compare it with a triple equal sign (= = =) that is sensitive to the type, such as:
1 |
Number ("1") = = 1; True |
Or, better yet, make sure that the type of operand you put in the first place is correct.
Since the double equals sign has the behavior of forcing type conversions, it can break the general transitivity rules, which is a bit scary, see the following:
1 2 3 |
"" = = 0//true-empty string will be cast to number 0. 0 = "0"//true-number 0 will be coerced to the string "0" "" = = "0"//false-two operands are strings so no casts are performed |
If you use a triple equal sign, all three comparisons above will return false.
Parseint doesn't use 10 as a digital base.
If you ignore the second argument of parseint, the cardinality of the number will be determined by the following rule:
- The default cardinality is 10, that is, the 10 binary parsing
- If the number starts with a 0x, then the cardinality is 16, that is, the 16 binary parsing
- If the number starts with 0, then the cardinality is 8, which is the 8-based parsing
A common mistake is to let the user enter a number that starts with 0, and then it parses it in 8-way, so we see the following effect:
1 2 |
parseint ("8"); 8 parseint ("08"); 0 |
Therefore, we often specify the second parameter of parseint, as follows:
1 2 |
parseint ("8", 10); 8 parseint ("08", 10); 8 |
ECMASCRIPT5 Description: ECMAScript no longer supports the 8-binary parsing hypothesis, and ignoring the parseint's second parameter will cause jslint warnings.
String substitution
The string substitution function replaces only the first occurrence and does not replace all occurrences that you expect. The following code:
1 2 |
"Bob". Replace ("B", "X"); "Xob" "Bob". Replace (/b/, "X"); "Xob" (using regular Expressions) |
If you want to replace all occurrences, we can use a regular expression and add a global modifier to it for him, the following code:
1 2 |
"Bob". Replace (/b/g, "X"); "XOX" "Bob". Replace (new RegExp ("B", "G"), "X"); "XOX" (alternate explicit REGEXP) |
The global modifier ensures that the substitution function finds the first match and does not stop the replacement of the next occurrence.
The "+" operator performs the addition operation and the string concatenation operation
PHP as another weak type language, you can use the "." operator to connect to a string. JavaScript is not like this-so "a+b" is usually a concatenation operation when the operand is a string. If you want to add numbers, you're going to get noticed, because the input may be string type, so you need to convert it to a numeric type before you perform the add operation, as follows:
1 2 |
1 + document.getElementById ("Inputelem"). Value; Connection Operation 1 + number (document.getElementById ("Inputelem"). Value); Add operation |
It should be noted that the subtraction attempts to convert the operand to a numeric type with the following code:
Although sometimes you want to subtract a string from another string, you often have some logical errors.
Many times we use numbers and empty strings to add numbers to a string, and the code is as follows:
But this is not good, so we can use String (3) to replace the above method.
typeof
typeof this returns the type of an instance of the JavaScript base type. Array is not actually a basic type, so the TypeOf array object returns object with the following code:
1 2 3 |
typeof {} = = = "Object"//true typeof "" = = = "string"//true typeof [] = = = "Array"; False |
When you use this operator for an instance of your object, you will get the same result (typeof = "Object").
In addition, "typeof Null" will return "object", which is a bit weird.
instanceof
instanceof returns whether the specified object is an instance constructed by a class, which is helpful for us to check whether the specified object is one of the custom types, but if you are creating a built-in type with text syntax, you might get the wrong result, the code reads as follows:
1 2 |
"Hello" instanceof String; False new String ("Hello") instanceof string; True |
Since the array is not actually a built-in type (just masquerading as a built-in type-so it is not possible to use typeof for it), using instanceof will get the desired result, as shown in the following code:
1 2 |
["Item1", "item2"] instanceof Array; True new Array ("Item1", "item2") instanceof Array; True |
Oh, it sucks! In general, if you want to test the type of Boolean, String, number, or function, you can use typeof, and for any other type, you can use the instanceof test.
Oh, and one more thing, in a function, there is a predefined variable called "arguments", which is passed to function as an array. However, it's not really an array, it's just an array-like object with length attributes and attribute values from 0-length. Very strange ... You can convert it to a real array with the following little trick:
1 |
var args = Array.prototype.slice.call (arguments, 0); |
The same is true for the NodeList objects returned by getElementsByTagName-they can all be converted to the appropriate array using the above code.
Eval
Eval can parse a string in the form of JavaScript code, but generally we do not recommend doing so. Because Eval is very slow-when JavaScript is loaded into the browser, it is compiled to the cost of code; However, every time an eval expression is encountered during execution, the compilation engine restarts the compilation, which is too costly. And it's ugly, and there's a lot of examples of eval being abused. In addition, the code in eval executes in the current scope, so it can modify local variables and add something unexpected to your scope.
JSON conversions are something we often do; we usually use "var obj = eval (jsontext);" To make the conversion. However, almost all browsers now support local JSON objects, and you can use "var obj = Json.parse (jsontext);" To replace the previous code. Instead, you can use "json.stringify" to convert JSON objects into strings. Even better, you can use "Jquery.parsejson" to do the job.
The first parameter of the settimeout and setinterval functions can be parsed by a string as a function body, of course, we do not recommend this, we can substitute the actual function.
Finally, the constructor of a function is very similar to eval, except that the function constructor is executed globally.
With
The with expression will give you a shorthand way to access the object's properties, but we still have conflicting views on whether we should use it. Douglas Crockford doesn't like it very much. John Resig in his book a lot of clever use of with, but he admits it will affect performance and create a bit of confusion. Let's look at our detached with code block, he can't tell us exactly what we're doing now, the code looks like this:
1 2 3 4 |
With (obj) {bob = "MMM"; eric = 123;} |
Have I just modified a local variable called Bob? Or did I set up a obj.bob? If Obj.bob has been defined, it will be reset to "MMM". Otherwise, if there is another bob in this range, then he will be changed. Otherwise, the global variable, Bob, is set. Finally, the following wording can express your meaning very clearly:
1 2 |
Obj.bob = "MMM"; Obj.eric = 123; |
ECMASCRIPT5 Note: ES5 strictly does not support the with expression.
Types and constructors
To construct a built-in type using the "new" keyword
JavaScript has object, Array, Boolean, number, String, and function these types, they each have their own text syntax, so there is no need for an explicit constructor.
Explicit constructs (not recommended) |
text Syntax (recommended) |
var a = new Object (); A.greet = "Hello"; |
var a = {greet: "Hello"}; |
var B = new Boolean (true); |
var B = true; |
var c = new Array ("One", "two"); |
var c = ["One", "two"]; |
var d = new String ("Hello"); |
var d = "Hello" |
var e = new Function ("greeting", "alert (greeting);"); |
var e = function (greeting) {alert (greeting);}; |
However, if you use the New keyword to construct one of the above types, you will actually get an object of the type object and inherit from the prototype of the type you want to construct (except for the function type). So even though you construct a number type with the New keyword, it will also be an object type, as follows:
1 2 3 |
typeof new Number (123); "Object" typeof number (123); "Number" typeof 123; "Number" |
The third item above is text syntax, and in order to avoid conflicts, we should use this method to construct these types above.
Use the "new" keyword to construct anything
If you write the constructor and forget the new keyword, then the tragedy happens:
1 2 3 4 5 6 7 8 9 10 |
var car = function (colour) {this.colour = colour;}; var aCar = new Car ("Blue"); Console.log (Acar.colour); "Blue" var Bcar = car ("blue"); Console.log (Bcar.colour); Error Console.log (window.colour); "Blue" |
Calling a function with the New keyword creates a new object, calls the function in the new object context, and then returns the object. Conversely, if you call a function without using the new key, it will become a global object.
Accidentally forgetting to use the new keyword means that many of the optional object construction patterns already appear to completely remove the need to use this keyword, although this is beyond the scope of this article, but I suggest you read it further.
No integer type
Numerical computations are relatively slow because there is no integer type. Only number type-number is a double-precision floating-point operation (64-bit) type in the IEEE standard. This means that number causes the following precision rounding error:
1 |
0.1 + 0.2 = = 0.3//false |
Because integers and floats are no different, unlike C # and Java, the following code is true:
Finally, a question about number, how do we implement the following question:
1 2 |
A = = = B; True 1/a = = 1/b; False |
The answer is that the specification of number is allowed to appear +0 and-0, +0 equals 0, but positive infinity is not equal to negative infinity, the code is as follows:
1 2 3 4 |
var a = 0 * 1; This result is 0 var b = 0 *-1; The result is 0 (you can also direct "b=-0", but why do you do this?) a = = B; True:0 equals-0 1/a = = 1/b; False: Positive infinity is not equal to negative infinity |
Scope
No block scope
Because you may have noticed the previous view, JavaScript does not have the concept of a block scope, only the function scope. You can try the following code:
1 2 3 4 5 |
for (var i=0; i<10; i++) {console.log (i);} var i; Console.log (i); 10 |
When I is defined in the For loop, the exit loop is retained in this scope, so the last call to Console.log output is 10. Here's a jslint warning to help you avoid this problem: Force all variables to be defined at the beginning of the function.
It is possible to create a scope by writing a function that executes immediately:
1 2 3 4 5 6 7 |
(function () {for (var i=0; i<10; i++) {console.log (i);}} ()); var i; Console.log (i); Undefined |
When you declare a variable before the intrinsic function and then declare the variable in the function, there is a strange problem with the example code as follows:
This is so meaningful!
Global variables
Javascript has a global scope and must be cautious when creating namespaces for your code. Global variables can add some performance problems to your application, because when you access them, the runtime has to build on each scope to know how to find them. They will be accessed or modified by you intentionally or unintentionally, which will result in another, more serious problem-Cross-site scripting attacks. If a malicious guy finds out how to execute the code on your page, they can easily disrupt your application by modifying the global variable. Inexperienced developers inadvertently add variables to the global scope, and through this article, they will tell you what happens.
I have seen the following code that will attempt to declare two local variables of equal value:
This very correctly gets a=3 and b=3, but a in the local scope and B in the global scope, "b=3" will be executed first, the result of the global operation, 3, and then assigned to the local variable A.
The following code declares two variables with a value of 3 to achieve the desired effect:
"This" and internal functions
The "This" keyword usually refers to the object in which the function is currently executing, however, if the function is not invoked on the object, such as in an intrinsic function, "This" is set to the Global Object (window), the following code:
1 2 3 4 5 6 7 8 9 10 11 |
var obj = {dosomething:function () {var a = ' Bob '; Console.log (this);////Current Executing object (function () {console.log (this);//W Indow-' This ' is reset Console.log (a); "Bob"-Still in Scope} ()); } }; Obj.dosomething (); |
Miscellaneous
Data does not exist: "null" and "undefined"
There are two kinds of object states to indicate that the data does not exist: null and undefined. This makes it quite confusing for programmers who turn around from other programming languages like C #. Perhaps you would expect the following code to return true:
1 2 3 |
var A; A = = = NULL; false a = = = undefined; True |
"A" is actually undefined (although you can get a true result with a null comparison with a double equals sign), this is just another error that appears to be correct on the surface.
If you want to check whether a variable really exists, then you can't use the double equals = = to judge, using the following method:
1 2 3 |
if (a!== null && a!== undefined) {...} |
"Ha," you might say, since null and undefined are false, then you can do this:
Of course, 0 is false, and so is the empty string. So if one of these is the correct value for a, you're going to use the former. The shorter comparison method is suitable for comparing objects, arrays, and Booleans types.
Redefining undefined
Very correctly, you can redefine undefined because it is not a reserved word:
1 |
undefined = "surprise!"; |
However, you have to assign a value to the undefined variable or use the "void" operator to retrieve the value (otherwise this is rather useless).
That's why the first line of the jquery script library is written like this:
1 2 3 |
(Function (window, undefined) {...//jQuery library!} (window)); |
This function is invoked to pass in a parameter while ensuring that the second argument "undefined" is actually undefined.
By the way, you can't redefine null-but you can redefine nan,infinity and built-in types with constructors. You can try this:
1 2 |
Array = function () {alert ("hello!");} var a = new Array (); |
Of course, you can declare an array anywhere in the literal syntax.
Optional semicolon
The semicolon is optional in JavaScript code, so it's much simpler for beginners to write code. Unfortunately, it is not convenient for anyone to ignore the semicolon. The result is that when the interpreter encounters an error, it must go back and try to guess which semicolon is causing the problem.
Here's a classic example:
1 2 3 4 |
return {a: "Hello"}; |
The code above does not return an object, but instead returns the undefined-but no errors are thrown. The fact is that the semicolon is automatically added to the return statement, the rest of the code is very correct, but nothing is executed, which proves that in JavaScript, the left curly braces should follow this line instead of wrapping, which is not just a matter of programming style. The following code will correctly return an object with a property of a:
1 2 3 |
return {a: "Hello"}; |
NaN
The type of Nan is ... Number
1 |
typeof NaN = = "Number"//true |
In addition Nan and anything are compared to false:
Because Nan is not comparable, the only way to determine whether a number is Nan is by invoking the isNaN method.
From another aspect, we can also use function Isfinite, which returns false when one of the operands is Nan or infinity.
Arguments objects
In a function, we can refer to the arguments object to traverse the incoming argument list, and the first strange place is that the object is not an array, but rather an array-like object (with a length property with a value between 0-length-1). To convert it to an array, we can create its corresponding array arrays by the splice function of the array:
1 2 3 4 5 |
(function () {console.log (arguments instanceof Array);//false var Argsarray = Array.prototype.slice.call (arguments); Console.log (Argsarray instanceof Array); True} ()); |
The second strange place is when there are explicit arguments parameters in the signature of a function, they can be reassigned and the arguments object will be changed. This indicates that the arguments object points to the variable itself. You cannot use the arguments objects to give their initial values:
1 2 3 4 5 |
(function (a) {alert (arguments[0]);//1 a = 2; alert (arguments[0]);//2} (1)); |
1 2 3 4 5 |
var x = 3; (function () {console.log (x + 2);//5 x = 0;//no var declaration} ()); |
However, if you re declaring an X variable in an intrinsic function, there is a strange problem:
1 2 3 4 5 |
var x = 3; (function () {console.log (x + 2);//nan-x is not defined var x = 0;//var declaration} ()); |
This is because the x variable is redefined in the function, which means that the translator moves the Var expression to the top of the function, which eventually becomes the execution:
1 2 3 4 5 6 |
var x = 3; (function () {var x; Console.log (x + 2);//nan-x is not defined x = 0;} ()); |