function
1. arguments
JavaScript also has a free keyword arguments, which only works inside the function and always points to all parameters passed in by the caller of the current function. Arguments is similar to Array, but it is not an Array:
function foo(x) { alert(x); // 10 for (var i=0; i<arguments.length; i++) { alert(arguments[i]); // 10, 20, 30 } } foo(10, 20, 30);
With arguments, you can get all the parameters passed in by the caller. That is to say, even if the function does not define any parameters, it can still get the values of the parameters:
function abs() { if (arguments.length === 0) { return 0; } var x = arguments[0]; return x >= 0 ? x : -x; } abs(); // 0 abs(10); // 10 abs(-9); // 9
2. Be careful with your return statement
As we mentioned earlier, the JavaScript engine has a mechanism for automatically adding semicolons at the end of a line, which can make you fall into a big hole in the return statement:
function foo() { return { name: 'foo' }; } foo(); // { name: 'foo' }
If you split the return statement into two lines:
function foo() { return { name: 'foo' }; } foo(); // undefined
Be careful, because the JavaScript engine automatically adds semicolons at the end of the line, the above code actually becomes:
function foo() { return; // Automatically add a semicolon, equivalent to return undefined; { name: 'foo' }; // This line of statement has not been executed. }
So the correct multi-line writing is:
function foo() { return { // The semicolon is not automatically added here because {indicates that the statement is not finished yet name: 'foo' }; }
3. Range of variables
Because JavaScript functions can be nested, internal functions can access variables defined by external functions, but not vice versa:
'use strict'; function foo() { var x = 1; function bar() { var y = x + 1; // bar can access the variable x of foo! } var z = y + 1; // ReferenceError! foo cannot access bar's variable y! }
What if the variable names of internal and external functions are renamed?
'use strict'; function foo() { var x = 1; function bar() { var x = 'A'; alert('x in bar() = ' + x); // 'A' } alert('x in foo() = ' + x); // 1 bar(); }
This shows that JavaScript functions start from their own function definition when looking for variables, and look from "inside" to "outside". If the internal function defines variables that are renamed with the external function, the variables of the internal function will "shield" the variables of the external function.
4. Variable elevation
JavaScript's function definition has a feature that it first scans the statements of the entire function body and "elevates" all declared variables to the top of the function:
'use strict'; function foo() { var x = 'Hello, ' + y; alert(x); var y = 'Bob'; } foo();
The statement var x ='Hello,'+ y, although in the string mode, does not report an error because the variable y is declared later. But alert shows Hello, undefined, indicating that the value of variable y is undefined. This is precisely because the JavaScript engine automatically improves the declaration of variable y, but does not increase the assignment of variable y.
For the foo() function described above, the JavaScript engine sees code equivalent to:
function foo() { var y; // Declarations of elevated variable y var x = 'Hello, ' + y; alert(x); y = 'Bob'; }
5. Global scope
Variables that are not defined in any function have global scope. In fact, JavaScript has a global object window by default, and variables in the global scope are actually bound to an attribute of windows:
'use strict'; var course = 'Learn JavaScript'; alert(course); // 'Learn JavaScript' alert(window.course); // 'Learn JavaScript
Therefore, direct access to the global variable course is exactly the same as access to window.course.
As you might guess, since the function defined in variable var foo = function () {} is actually a global variable, the definition of the top-level function is also considered a global variable and bound to the window object:
'use strict'; function foo() { alert('foo'); } foo(); // Call foo() directly window.foo(); // Called by window.foo()
This shows that JavaScript actually has only one global scope. Any variable (the function is also treated as a variable), if not found in the current function scope, will continue to look up, and if not found in the global scope, the Reference Error error will be reported.
6. Local scope
Since JavaScript's variable scope is actually within a function, we cannot define variables with local scope in statement blocks such as for loops:
'use strict'; function foo() { for (var i=0; i<100; i++) { // } i += 100; // You can still refer to variable i }
The variable declared in the first statement of the for loop is a global variable, which can certainly be used outside the for loop.
In order to solve block-level scopes, ES6 introduces a new keyword let. Replacing var with let can declare a variable of block-level scopes:
'use strict'; function foo() { var sum = 0; for (let i=0; i<100; i++) { sum += i; } i += 1; // SyntaxError }
7. method
Binding a function in an object is called the method of that object.
If we bind a function to xiaoming, we can do more. For example, write an age() method to return xiaoming's age:
var xiaoming = { name: 'Xiao Ming', birth: 1990, age: function () { var y = new Date().getFullYear(); return y - this.birth; } }; xiaoming.age; // function xiaoming.age() xiaoming.age(); // This year's call is 25, and next year's call will be 26.
A function bound to an object is called a method, which is no different from a normal function, but it uses a this keyword internally. What is this?
Within a method, this is a special variable that always points to the current object, that is, xiaoming. So, this.birth can get the birth attribute of xiaoming.
Let's take apart and write:
function getAge() { var y = new Date().getFullYear(); return y - this.birth; } var xiaoming = { name: 'Xiao Ming', birth: 1990, age: getAge }; xiaoming.age(); // 25. Normal results getAge(); // NaN
How does the single call function getAge() return NaN? Note that we have entered a big pit in JavaScript.
If this is called inside a JavaScript function, who exactly does this point to?
The answer is, it depends!
If called in the form of an object's method, such as xiaoming.age(), this function points to the called object, xiaoming, which is in line with our expectations.
If a function is called individually, such as getAge(), then this function points to the global object, that is, window.
Cheat your papa!
What's more, if you write like this:
var fn = xiaoming.age; // Get xiaoming's age function first fn(); // NaN
No way! To ensure that this points correctly, it must be called in the form of obj.xxx()!
Because this is a huge design error, it's not easy to correct it. ECMA decides to let this of the function point to undefined in string mode, so in string mode, you get an error:
Sometimes, like refactoring, you refactoring the method:
'use strict'; var xiaoming = { name: 'Xiao Ming', birth: 1990, age: function () { function getAgeFromBirth() { var y = new Date().getFullYear(); return y - this.birth; } return getAgeFromBirth(); } }; xiaoming.age(); // Uncaught TypeError: Cannot read property 'birth' of undefined
The result is wrong again! The reason is that this pointer only points to xiaoming in the function of the age method, and the function defined inside the function, and this points to undefined again! (In non-strict mode, it redirects to the global object window!)
The way to fix it is not without it. We first capture this with a that variable.
'use strict'; var xiaoming = { name: 'Xiao Ming', birth: 1990, age: function () { var that = this; // Capture this from the start inside the method function getAgeFromBirth() { var y = new Date().getFullYear(); return y - that.birth; // Use that instead of this } return getAgeFromBirth(); } }; xiaoming.age(); // 25
With var that = this;, you can safely define other functions within a method, rather than stacking all statements into one method.
8.apply
Although in a separate function call, this points to undefined or window, depending on whether it is a string mode, we can still control the direction of this!?
To specify which object the function's this points to, you can use the application method of the function itself, which receives two parameters. The first parameter is the this variable that needs to be bound, and the second parameter is Array, which represents the parameters of the function itself.
Repair getAge() call with apply:
function getAge() { var y = new Date().getFullYear(); return y - this.birth; } var xiaoming = { name: 'Xiao Ming', birth: 1990, age: getAge }; xiaoming.age(); // 25 getAge.apply(xiaoming, []); // 25, this points to xiaoming, the parameter is empty
Another method similar to apply() is call(), the only difference being:
apply() packages the parameters into Array and passes them in.
call() passes the parameters in sequence.
For example, call Math.max(3, 5, 4), using apply() and call() to achieve the following:
Math.max.apply(null, [3, 5, 4]); // 5 Math.max.call(null, 3, 5, 4); // 5
For normal function calls, we usually bind this to null.
9. decorator
With apply(), we can also dynamically change the behavior of functions.
All JavaScript objects are dynamic, and even with built-in functions, we can redirect to new functions.
Now suppose we want to count how many times the code calls parseInt(), find out all the calls, and add count += 1 manually, but that's silly. The best solution is to replace the default parseInt() with our own functions:
var count = 0; var oldParseInt = parseInt; // Save the original function window.parseInt = function () { count += 1; return oldParseInt.apply(null, arguments); // Call the original function }; // Test: parseInt('10'); parseInt('20'); parseInt('30'); count; // 3