Home  >  Article  >  Web Front-end  >  In-depth understanding of JavaScript series (15) Functions_javascript skills

In-depth understanding of JavaScript series (15) Functions_javascript skills

WBOY
WBOYOriginal
2016-05-16 17:54:26985browse

Introduction
In this chapter we will focus on a very common ECMAScript object - function. We will explain in detail how various types of functions affect the context variable object and the scope chain of each function. What does it all include, and answers questions like: Is there any difference between the functions declared below? (If so, what is the difference).
Original text: http://dmitrysoshnikov.com/ecmascript/chapter-5-functions/

Copy code The code is as follows:

var foo = function () {
...
};

Usual way:
Copy code The code is as follows:

function foo() {
...
}

Or, why are the following functions enclosed in parentheses?
Copy code The code is as follows:

(function () {
...
})();

For specific introduction, Chapter 12 Variable Objects and Chapter 14 Scope Chain have been introduced earlier. If you need to know more about these contents, please check the above two Chapter details.

But we still have to look at them one by one, starting with the types of functions:

Function types
There are three function types in ECMAScript: Function Functions created by declarations, function expressions, and function constructors. Each has its own characteristics.
Function declaration
A function declaration (abbreviated as FD) is a function that:
has a specific name
location in the source code: either at the program level (Program level), or in the body (FunctionBody) of other functions
Create
influence variable object
in the context phase and declare it in the following way
Copy code The code is as follows:

function exampleFunc() {
...
}

The main feature of this function type is that they only affect variable objects (i.e. those stored in the VO of the context). This feature also explains the second important point (which is a result of the variable object properties) - they are already available during the code execution phase (because FD already exists in the VO when entering the context phase - before the code execution).
For example (function is called before its declaration)
Copy code The code is as follows:

foo();
function foo() {
alert('foo');
}

Another key knowledge point is the second point in the above definition - —The position of the function declaration in the source code:
Copy code The code is as follows:

// Function It can be declared in the following places:
// 1) Directly in the global context
function globalFD() {
// 2) Or within the function body of a function
function innerFD() {}
}

Only these 2 positions can declare a function, that is: it is not possible to define it in an expression position or in a code block.
Another way to replace a function declaration is a function expression, which is explained as follows:
Function expression
A function expression (abbreviated as FE) is a function:
Must be included in the source code Appears at the position of the expression
Has an optional name
Does not affect the variable object
Created during the code execution phase
The main feature of this function type is that it is always expressed in the source code type position. The simplest example is an assignment statement:
Copy the code The code is as follows:

var foo = function () {
...
};

This example demonstrates assigning an anonymous function expression to the variable foo, and then the function can be accessed with the name foo ——foo().
At the same time, as described in the definition, function expressions can also have optional names:
Copy code The code is as follows :

var foo = function _foo() {
...
};

It should be noted that the external FE is accessed through the variable "foo" - —foo(), while inside a function (such as a recursive call), it is possible to use the name "_foo".
If FE had a name, it would be difficult to distinguish it from FD. However, if you understand the definition, the distinction is simple and clear: FE is always in expression position. In the following example we can see various ECMAScript expressions:
// Parentheses (grouping operators) can only contain expressions
(function foo() {});
// Within the array initializer, only expressions
[function bar() {}];
// commas can only operate on expressions
1, function baz() {};
expressions It is stated in the formula definition: FE can only be created during the code execution phase and does not exist in the variable object. Let us look at an example behavior:
Copy code The code is as follows:

// FE is not available before the definition phase (because it is created during the code execution phase)
alert(foo); // "foo" is not available Definition
(function foo() {});
// It is not available after the definition stage because it is not in the variable object VO
alert(foo); // "foo" is not defined
Quite a few questions arise, why do we need function expressions? The answer is obvious - using them in expressions "doesn't pollute" variable objects. The simplest example is passing a function as a parameter to other functions.
function foo(callback) {
callback();
}
foo(function bar() {
alert('foo.bar');
});
foo(function baz() {
alert('foo.baz');
});

In the above example, FE is assigned to a variable (that is, a parameter ), the function saves the expression in memory and accesses it through the variable name (because variables affect variable objects), as follows:
Copy code The code is as follows:

var foo = function () {
alert('foo');
};
foo();

Another example is to create encapsulating closures to hide auxiliary data from the external context (in the following example we use FE, which is called immediately after creation):
Copy code The code is as follows:

var foo = {};
(function initialize() {
var x = 10;
foo.bar = function () {
alert(x);
};
})();
foo.bar(); // 10;
alert( x); // "x" is undefined

We see that the function foo.bar (through the [[Scope]] attribute) accesses the internal variable "x" of the function initialize. At the same time, "x" is not directly accessible externally. In many libraries, this strategy is used to create "private" data and hide auxiliary entities. In this mode, the name of the initialized FE is usually ignored:
Copy code The code is as follows:

(function () {
// Initialization scope
})();

Another example is: creating FE through conditional statements during the code execution phase, Will not pollute the variable object VO.
Copy code The code is as follows:

var foo = 10;
var bar = ( foo % 2 == 0
? function () { alert(0); }
: function () { alert(1); }
);
bar(); // 0

Questions about parentheses
Let’s go back and answer the question mentioned at the beginning of the article - "Why do you have to surround a function with parentheses when calling it immediately after it is created?", The answer is: the restriction of expression sentences is like this.
According to the standard, an expression statement cannot start with a brace { because it is difficult to distinguish it from a code block. Similarly, it cannot start with a function keyword because it is difficult to distinguish it from a function declaration. That is, so if we define a function that executes immediately, immediately after its creation, it is called as follows: As follows:
function () { ... }(); // Even if there is a name function foo() { ...
}();


We used function declarations. For the above two definitions, the interpreter will report an error when interpreting, but there may be many reasons.
If it is defined in global code (that is, program level), the interpreter will regard it as a function declaration because it starts with the function keyword. In the first example, we will get a SyntaxError because the function The declaration has no name (we mentioned earlier that function declarations must have names).
In the second example, we have a function declaration named foo that is created normally, but we still get a syntax error - grouping operator error without any expression. It is indeed a grouping operator after the function declaration, not the parentheses used in a function call. So if we declare the following code:
Copy the code The code is as follows:

// "foo" Is a function declaration, created when entering the context
alert(foo); // Function
function foo(x) {
alert(x);
}(1); // This Just a grouping operator, not a function call!
foo(10); // This is a real function call, the result is 10

There is no problem with the above code, because 2 objects are generated when declared: a function Statement, a grouping operation with 1, the above example can be understood as the following code:
Copy code The code is as follows:

// Function declaration
function foo(x) {
alert(x);
}
// A grouping operator, containing an expression 1

(1);
Copy code The code is as follows:

// Another operator contains a function expression
(function () {});
// This operator also contains an expression "foo"
("foo");
// Wait

If we define the following code (the definition contains a statement), we may say that the definition is ambiguous and an error will be reported:
if (true) function foo () {alert(1)}
According to the specification, the above code is wrong (an expression statement cannot start with the function keyword), but the following example does not report an error. Think about why?
If we tell the interpreter: I want to call it immediately after the function declaration, the answer is very clear, you have to declare the function expression, not the function declaration, and the easiest way to create an expression Just use grouping operator brackets, and what is put inside is always an expression, so there will be no ambiguity when the interpreter interprets it. This function will be created during the code execution phase, executed immediately, and then automatically destroyed (if there is no reference).
Copy code The code is as follows:

(function foo(x) {
alert( x);
})(1); // This is a call, not a grouping operator

The above code is what we call enclosing an expression in parentheses, and then passing (1) Go to call.
Note that for the following function that is executed immediately, the surrounding parentheses are not necessary, because the function is already at the position of the expression, and the parser knows that it is dealing with the FE that should be created during the function execution phase, so that when the function is created The function is called immediately after.
Copy code The code is as follows:

var foo = {
bar: function (x ) {
return x % 2 != 0 ? 'yes' : 'no';
}(1)
};
alert(foo.bar); // 'yes'

As we can see, foo.bar is a string and not a function. The function here is only used to initialize this property based on the conditional parameters - it is created and called immediately.
Therefore, the complete answer to the "About parentheses" question is as follows: When the function is not in the expression position, the grouping operator parentheses are necessary - that is, manually converting the function into FE.
If the parser knows that it is dealing with FE, there is no need to use parentheses.
In addition to braces, the following form can also convert functions into FE types, for example:
Copy code The code is as follows:

// Note that it is 1, the following statement is
1, function () {
alert('anonymous function is called');
}();
// or This
!function () {
alert('ECMAScript');
}();
// Other manual conversion forms
...

But, in this example, parentheses are the most concise way.
By the way, the group expression surrounding function description may not have calling parentheses, or it may contain calling parentheses, that is, the following two expressions are both correct FEs.
Implement extension: function statement
The following code should not be executed according to any of your function statements:
Copy code The code is as follows:

if (true) {
function foo() {
alert(0);
}
} else {
function foo() {
alert(1);
}
}
foo(); // 1 or 0? In fact, the results are different when tested in different environments

It is necessary to note here that this syntactic structure is usually incorrect according to the standard, because we also remember that a function declaration (FD) cannot appear in a code block (here if and else contain code piece). We once said that FD only appears in two places: program level (Program level) or directly in other function bodies.
This is incorrect because the code block only contains statements. The only place a function can appear in a block is in one of these statements - the expression statement already discussed above. However, by definition it cannot start with a brace (since it is different from a code block) or with a function keyword (since it is different from FD).
However, in the standard error handling chapter, it allows for extended implementation of the program syntax. One such extension is the function we see appearing in code blocks. In this example, all existing executions will not throw the exception and will handle it. But they all have their own way.
The appearance of the if-else branch statement means a dynamic choice. That is, logically it should be a function expression (FE) that is dynamically created during the code execution phase. However, most implementations simply create a function declaration (FD) when entering the context phase, and use the last declared function. That is, function foo will display "1", and in fact the else branch will never be executed.
However, SpiderMonkey (and TraceMonkey) treats this situation in two ways: on the one hand it does not handle the function as a declaration (i.e. the function is created based on the condition during the code execution phase), but on the other hand, since there are no parentheses surrounded (again, parsing error - "different from FD"), they cannot be called, so are not really function expressions, they are stored in variable objects.
I personally think that SpiderMonkey behaves correctly in this example, splitting its own function intermediate type - (FE FD). These functions are created at the right time, based on conditions. Unlike FE, but like an FD that can be called from the outside, SpiderMonkey calls this syntax extension function statement (abbreviated as FS); this syntax is mentioned in MDC Pass.
Characteristics of named function expressions
When a function expression FE has a name (called a named function expression, abbreviated as NFE), an important feature will appear. From the definition (as we saw from the example above) we know that function expressions do not affect a context's variable object (that means it is neither possible to call the function by name before it is declared, nor after it is declared call it). However, FE can call itself by name in recursive calls.
Copy code The code is as follows:

(function foo(bar) {
if ( bar) {
return;
}
foo(true); // "foo" is available
})();
// Externally, it is not available
foo(); // "foo" is undefined

Where is "foo" stored? In foo's active object? No, because there is no "foo" defined in foo. Create foo in context's parent variable object? No, because by definition - FE does not affect VO (variable object) - we can actually see it when calling foo from the outside. So where?
Here are the key points. When the interpreter encounters a named FE during the code execution phase, before the FE is created, it creates an auxiliary specific object and adds it to the front of the current scope chain. It then creates the FE, at which point (as we learned from Chapter 4 Scope Chains) the function acquires the [[Scope]] attribute—the scope chain that created this function context). Thereafter, the name of the FE is added to the specific object as a unique attribute; the value of this attribute is a reference to the FE. The final step is to remove that specific object from the parent scope chain. Let’s look at this algorithm in pseudocode:
Copy the code The code is as follows:

specialObject = {};
Scope = specialObject Scope;
foo = new FunctionExpression;
foo.[[Scope]] = Scope;
specialObject.foo = foo; // {DontDelete}, {ReadOnly }
delete Scope[0]; // Delete the defined special object specialObject from the scope chain

Therefore, this name is not available outside the function (because it is not in the parent scope chain ), however, the specific object is already stored in the function's [[scope]], where the name is available.
But it should be noted that some implementations (such as Rhino) store this optional name not in a specific object but in the activation object of FE. The implementation in Microsoft completely breaks the FE rules, it keeps the name in the parent variable object so that the function becomes accessible externally.
NFE vs. SpiderMonkey
Let’s take a look at the difference between NFE and SpiderMonkey. Some versions of SpiderMonkey have a property related to a specific object, which can be treated as a bug (although according to the standard all are implemented that way, but More like a bug in the ECMAScript standard). It is related to the parsing mechanism of identifiers: the analysis of scope chains is two-dimensional. In the parsing of identifiers, the prototype chain of each object in the scope chain is also taken into account.
If we define a property in Object.prototype and refer to a "nonexistent" variable. We can see this execution mechanism. Thus, in the "x" parsing example below, we will reach the global object, but not find "x". However, in SpiderMonkey the global object inherits the properties in Object.prototype, and accordingly, "x" can also be parsed.
Copy code The code is as follows:

Object.prototype.x = 10;
( function () {
alert(x); // 10
})();

Active objects have no prototype. Following the same starting conditions, in the above example, it is impossible to see this behavior of the inner function. If you define a local variable "x" and define an internal function (FD or anonymous FE), then reference "x" in the internal function. Then this variable will be resolved in the parent function context (i.e. where it should be resolved) instead of in Object.prototype.
Copy code The code is as follows:

Object.prototype.x = 10;
function foo() {
var x = 20;
// Function declaration
function bar() {
alert(x);
}
bar(); // 20, Query from the variable object AO of foo
// The same is true for anonymous function expressions
(function () {
alert(x); // 20, also query from the variable object AO of foo
})();
}
foo();

However, some implementations will have exceptions, which set a prototype for the active object. Therefore, in the Blackberry implementation, "x" in the above example is interpreted as "10". That is, since the value of foo has been found in Object.prototype, it will not reach foo's active object.
AO(bar FD or anonymous FE) -> no ->
AO(bar FD or anonymous FE).[[Prototype]] -> yes - 10
In SpiderMonkey, the same This situation can be seen completely in the specific object named FE. This particular object is (by the standard) a normal object - "like the expression new Object()", and accordingly it should inherit properties from Object.prototype, which is exactly what we see in SpiderMonkey (version 1.7+) execution. The rest of the execution (including the new TraceMonkey) does not set a prototype for a specific object.
Copy code The code is as follows:

function foo() {
var x = 10;
(function bar() {
alert(x); // 20, not above 10, not an active object from foo The
// "x" obtained from the chain is searched:
// AO(bar) - no -> __specialObject(bar) -> no
// __specialObject(bar).[[ Prototype]] - yes: 20
})();
}
Object.prototype.x = 20;
foo();

NFE and Jscript
The JScript execution built into the current IE browser (until JScript 5.8 — IE8) has many bugs related to function expressions (NFE). All of these bugs completely contradict the ECMA-262-3 standard; some may cause serious errors.
First of all, JScript in this example breaks the main rule of FE, it should not be stored in variable objects by function names. Optional FE names should be stored in a specific object and accessible only within the function itself (and not elsewhere). But IE stores it directly in the parent variable object. Additionally, named FEs are treated as function declarations (FDs) in JScript. That is, it is created at the stage of entering the context and can be accessed before the definition in the source code.
Copy code The code is as follows:

// FE is visible in the variable object
testNFE ();
(function testNFE() {
alert('testNFE');
});
// FE is also visible after the definition is finished
// just like a function declaration
testNFE();

As we can see, it completely breaks the rules.
Secondly, when assigning the named FE to a variable in the declaration, JScript creates two different function objects. Logically (note in particular that outside the NFE its name should not be accessed at all) it is difficult to name this behavior.
Copy code The code is as follows:

var foo = function bar() {
alert ('foo');
};
alert(typeof bar); // "function",
// What's interesting is that
alert(foo === bar); // false!
foo.x = 10;
alert(bar.x); // Undefined
// But the result is the same when executed
foo(); // "foo"
bar (); // "foo"

See again, it's a mess.
However, it is important to note that if you describe the NFE separately from the variable assignment (such as through the group operator), then assign it to a variable, and check its equality, the result is true, as if it were an object .
Copy code The code is as follows:

(function bar() {});
var foo = bar;
alert(foo === bar); // true
foo.x = 10;
alert(bar.x); // 10

It's open to interpretation at this point. Effectively, two objects are created again, but that actually remains one. If we think again the NFE here is treated as FD and then the FD bar is created on entering context phase. Afterwards, during the code execution phase a second object - function expression (FE) bar is created, which is not stored. Accordingly, there is no reference to the FE bar, it is removed. This way there is only one object - FD bar, and its reference is assigned to the variable foo.
Third, in terms of indirectly referencing a function through arguments.callee, it refers to the name of the object that is activated (to be precise - there are two function objects here.
Copy code The code is as follows:

var foo = function bar() {
alert([
arguments. callee === foo,
arguments.callee === bar
]);
};
foo(); // [true, false]
bar(); // [false, true]

Fourth, JScript treats the NFE like an ordinary FD, it does not obey the conditional expression rules, that is, like an FD, the NFE is created when entering the context. The last definition in the code is used
var foo = function. bar() {
alert(1);
};
if (false) {
foo = function bar() {
alert(2);
};
}
bar(); // 2
foo(); // 1


This behavior can also be explained "logically". Upon entering the context phase, the last encountered FD bar is created, which is the function containing alert(2). Afterwards, during the code execution phase, a new function - FE bar is created and a reference to it is assigned to the variable foo. This activation of foo produces alert(1). The logic is clear, but I put quotes around the word "logically" because of IE bugs, since the execution is obviously broken and relies on JScript bugs.
The fifth bug in JScript is related to the creation of properties of global objects, which are generated by assigning to an unqualified identifier (i.e., without the var keyword). Since the NFE is treated here as a FD, accordingly it is stored in the variable object, assigned an unqualified identifier (i.e. not to a variable but to a normal property of the global object), in case the name of the function is the same as unqualified has the same identifier, so the property is not global.
Copy code The code is as follows:

(function () {
// No need for var If so, it is not a variable of the current context
// but a property of the global object
foo = function foo() {};
})();
// However, in Outside the anonymous function, the name foo is not available
alert(typeof foo); // Undefined

The "logic" is already clear: before entering the context phase, the function declares foo Obtained the active object of the local context of the anonymous function. During the code execution phase, the name foo already exists in AO, that is, it is treated as a local variable. Accordingly, in the assignment operation, the attribute foo that already exists in the AO is simply updated, rather than creating a new attribute of the global object according to the logic of ECMA-262-3.
Function created through function constructor
Since this kind of function object also has its own characteristics, we distinguish it from FD and FE. Its main feature is that the [[Scope]] attribute of this function only contains global objects:
Copy code The code is as follows:

var x = 10;
function foo() {
var x = 20;
var y = 30;
var bar = new Function('alert(x) ; alert(y);');
bar(); // 10, "y" is undefined
}

We see that the [[Scope]] of function bar Property does not contain Ao of foo context - variable 'y' is not accessible, variable 'x' is taken from the global object. By the way, the Function constructor can be used with or without the new keyword, so these variants are equivalent.

Other features of these functions are related to Equated Grammar Productions and Joined Objects. The specification provides these mechanisms as optimization recommendations (however, implementations may not use optimizations). For example, if we have an array of 100 elements, within a loop of a function, the execution might use the Joined Objects mechanism. The result is that only one function object is available for all elements in the array.

Copy code The code is as follows:

var a = [];

for (var k = 0; k < 100; k ) {
a[k] = function () {}; // Joined objects may be used
}

But Functions created via the function constructor are not wired.
Copy code The code is as follows:

var a = [];

for (var k = 0; k < 100; k ) {
a[k] = Function(''); // There are always 100 different functions
}

Another example related to joined objects:
Copy code The code is as follows:

function foo() {

function bar(z) {
return z * z;
}

return bar;
}

var x = foo();
var y = foo();

The implementation here also has the right to connect object x and object y (using the same object), because the functions (including their internal [[Scope]] properties) are fundamentally indistinguishable. Therefore, functions created through function constructors always require more memory resources.
Algorithm for function creation
The pseudocode below describes the algorithm for function creation (except for the steps related to union objects). These descriptions will help you understand more details about function objects in ECMAScript. This algorithm is suitable for all function types.
Copy code The code is as follows:

F = new NativeObject();

// Attribute [[Class]] is "Function"
F.[[Class]] = "Function"

// The prototype of the function object is the prototype of Function
F.[[ Prototype]] = Function.prototype

// The function itself is used
// When calling expression F, activate [[Call]]
// and create a new execution context
F.[[Call]] =

// Compile in the ordinary constructor of the object
// [[Construct]] Activate through the new keyword
// And allocate memory to the new object
// Then call F.[[Call]] to initialize the newly created object passed as this
F.[[Construct]] = internalConstructor

// Current Execution context scope chain
// For example, create the context of F
F.[[Scope]] = activeContext.Scope
// If the function is created through new Function(...),
// Then
F.[[Scope]] = globalContext.Scope

// The number of parameters passed in
F.length = countParameters

// F The prototype of object creation
__objectPrototype = new Object();
__objectPrototype.constructor = F // {DontEnum}, not enumerable in the loop x
F.prototype = __objectPrototype

return F

Note that F.[[Prototype]] is a prototype of a function (constructor), and F.prototype is the prototype of an object created by this function (because the terminology is often confusing, in some articles F.prototype is called the "constructor prototype", which is incorrect).

Conclusion
This article is a bit long. But again, I'll be happy to answer any questions you have in the comments as we continue to discuss functions in the next chapter about objects and prototypes.

Other references
Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn