JavaScript arrow function exploration

If you want to understand the difference between arrow function and ordinary function and anonymous function, you must first clarify three concepts, prototype chain, scope, and context. These three concepts are very important and easy to confuse.

In general, the scope describes the scope of the variable, the prototype chain describes what path we follow to find a variable, and the context mostly refers to who this is, and getting a variable through this does not look up like the prototype chain.

The part about prototype chain and scope is intercepted这篇博客Part of the content, and add some personal understanding and explanation

Basic concepts

Prototype chain

1
2
3
4
5
6
function Person() {

}
var person = new Person();
person.name = 'Ray';
console.log(person.name) // Kevin

In the above code, Person is a constructor function (there is no special class and constructor function in js, as long as you can call a function through new, this function can be a constructor function). We create an instance of Person person through new, and add an attribute name to this instance.

However, if we create a new Person, there will be no name attribute in this new instance.

So how do we make every instance of Person have a name attribute?

The answer is to write the name attribute on the prototype

prototype

Each function has a prototype property

1
2
3
4
5
6
7
8
9
10
function Person() {

}
//Although it is written in the comments, you should pay attention to:
//prototype is a property that only functions have
Person.prototype.name = 'Kevin';
var person1 = new Person();
var person2 = new Person();
console.log(person1.name) // Kevin
console.log(person2.name) // Kevin

The prototype of this function refers to an object, which is the prototype of the instance created by calling the constructor function, that is, the common prototype of all instances created by constructing the function

proto

This is a property that every JavaScript object (except null) has, called proto, which points to the prototype of the object.

To prove this, we can type in Firefox or Google:

1
2
3
4
5
function Person() {

}
var person = new Person();
console.log(person.__proto__ = Person.prototype); // true

constructor

Prototypes cannot point to constructors, because a constructor can generate multiple instances, but prototypes do point to constructors, which brings us to the third property: constructor. Every prototype has a constructor property that points to the associated constructor function.

To verify this, we can try:

1
2
3
4
function Person() {

}
console.log(Person = Person.prototype.constructor); // true

Examples and prototypes

When reading the properties of an instance, if it cannot be found, it will look for the properties in the prototype associated with the object. If it cannot be found, it will go to the prototype of the prototype, all the way to the top level.

For example:

1
2
3
4
5
6
7
8
9
10
11
12
13
function Person() {

}

Person.prototype.name = 'Kevin';

var person = new Person();

person.name = 'Daisy';
console.log(person.name) // Daisy

delete person.name;
console.log(person.name) // Kevin

In this example, we add the name attribute to the instance object person, and when we print the person.name, the result is Daisy.

But when we delete the name property of person, read person.name, can not find the name property from the person object will be from the prototype of the person is person.proto, that is, Person.prototype to find, fortunately we found the name property, the result is Kevin.

Prototype of prototype

Earlier, we have said that the prototype is also an object. Since it is an object, we can create it in the most primitive way, that is:

1
2
3
var obj = new Object();
obj.name = 'Kevin'
console.log(obj.name) // Kevin

In fact, the prototype object is generated by the Object constructor function. Combined with the previous discussion, the proto of the instance points to the prototype of the constructor function, so we update the relationship diagram:

Prototype chain

What about the prototype of Object.prototype?

Null, we can print:

1
console.log(Object.prototype.__proto__ = null) // true

But what exactly does null represent? Null means “no object”, that is, there should be no value there.

So the value of Object.prototype.proto is null and Object.prototype has no prototype, which actually expresses a meaning.

So when searching for properties, you can stop searching when you find Object.prototype.

The chain structure composed of interrelated prototypes in the figure is the prototype chain, which is the rightmost line.

Scope

The scope refers to the area where variables are defined in the program source code.

The scope specifies how to find a variable, that is, determine the access permission of the current executable code to the variable.

JavaScript uses lexical scoping, which is static scoping.

Because JavaScript uses lexical scope, the scope of a function is determined when the function is defined.

The opposite of lexical scope is dynamic scope. The scope of a function is determined only when the function is called.

Let’s take a closer look at an example to understand the difference:

1
2
3
4
5
6
7
8
9
10
11
var value = 1;
function foo() {
console.log(value);
}
function bar() {
var value = 2;
foo();
}
bar();

//The result is???

Assuming that JavaScript uses static scope, let’s analyze the execution process.

Execute foo function, first from the inside of foo function to find if there is a local variable value, if not, according to the writing position, find the code of the above layer, that is, value is equal to 1, so the result will print 1.

Assuming that JavaScript uses dynamic scope, let’s analyze the execution process.

Execute the foo function and still look for the local variable value from inside the foo function. If not, look for the value variable from the scope of the calling function, that is, inside the bar function, so the result will print 2.

As we mentioned earlier, JavaScript uses static scope, so the result of this example is 1.

Context

The contextual explanation is a translated blog of a foreign boss, which is链接

Execution context is an abstract concept of the environment in which JavaScript code is evaluated and executed. Whenever Javascript code is running, it runs in the execution context.

Type of execution context

There are three types of execution context in JavaScript.

  • ** Global Execution Context ** - This is the default or base context, any code that is not inside the function is in the global context. It will do two things: create a global window object (in the case of browsers), and set the value of’this’ to be equal to this global object. There will only be one global execution context in a program.
  • ** Function Execution Context ** - Whenever a function is called, a new context is created for that function. Each function has its own execution context, but it is created when the function is called. Function contexts can have any number of them. Whenever a new execution context is created, it performs a series of steps in the defined order (discussed later).
  • ** Eval function execution context ** - The code executed inside the’eval 'function will also have its own execution context,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let a = 'Hello World!';

function first() {
console.log('Inside first function');
second();
console.log('Again inside first function');
}

function second() {
console.log('Inside second function');
}

first();
console.log('Inside Global Execution Context');

When the above code loads in the browser, the JavaScript engine creates a global execution context and pushes it into the current execution stack. When encountering a’first () 'function call, the JavaScript engine creates a new execution context for the function and pushes it to the top of the current execution stack.

When the second () function is called from inside the first () function, the JavaScript engine creates a new execution context for the second () function and pushes it to the top of the current execution stack. When the second () function finishes executing, its execution context pops up from the current stack and the control flow reaches the next execution context, that of the first () function.

When’first () 'finishes executing, its execution context pops up from the stack and the control flow reaches the global execution context. Once all the code has been executed, the JavaScript engine removes the global execution context from the current stack.

Now let’s understand how the JavaScript engine creates execution contexts.

There are two stages in creating an execution context: ** 1) the creation stage ** and ** 2) the execution stage **.

The

Before the JavaScript code executes, the execution context will go through the creation phase. Three things happen during the creation phase:

  1. The determination of the value of ** this **, known as ** This binding **.
  2. Create the Lexical Environment component.
  3. Create a variable environment component.

Therefore, the execution context is conceptually represented as follows:

1
2
3
4
5
ExecutionContext = {
ThisBinding = <this value>,
LexicalEnvironment = { ... },
VariableEnvironment = { ... },
}
**This

In the global execution context, the value of this refers to the global object. (In the browser, this refers to the Window object.)

In the context of function execution, the value of’this’ depends on how the function is called. If it is called by a reference object, then’this’ will be set to that object, otherwise the value of’this’ will be set to a global object or’undefined ’ (in strict mode). For example:

1
2
3
4
5
6
7
8
9
10
let foo = {
baz: function() {
console.log(this);
}
}
Foo.baz (); // ' this'quotes'foo ', because'baz' is
//object'foo 'call
let bar = foo.baz;
Bar (); // ' this' points to the global window object because
No reference object is specified
Lexical environment

官方的 ES6 文档把词法环境定义为

** Lexical Environment ** is a canonical type, based on

Simply put, a Lexical Environment is a structure that holds an identifier, a variable map. (The identifier here refers to the name of the variable/function, and the variable is a reference to the actual object [containing object of type function] or the original data source).

Now, inside the Lexical Environment there are two components: (1) the Environment Recorder and (2) a reference to the External Environment.

  1. The environment logger is the actual place where variable and function declarations are stored.
  2. A reference to an external environment means that it has access to its parent lexical environment (scope).

There are two types of Lexical Environments:

  • ** The global environment ** (in the global execution context) is a lexical environment without an external environment reference. The external environment reference of the global environment is ** null **. It has built-in Object/Array/etc., prototype functions in the environment logger (associated with global objects, such as window objects), and any user-defined global variables, and the value of’this’ refers to the global object.
  • In the function environment, user-defined variables inside the function are stored in the environment logger. And the referenced external environment may be the global environment, or any external function that contains this internal function.

** Ambient loggers ** are also available in two types (as above!):

  1. Declarative Environment Logger stores variables, functions, and parameters.
  2. The object environment logger is used to define the relationships between variables and functions that appear in the global context.

In short,

  • In the global environment, the environment logger is the object environment logger.
    In the function environment, the environment logger is a declarative environment logger.

Note that for the function environment, the declarative environment logger also contains an arguments object passed to function (which stores the mapping of indexes and parameters) and a length of the arguments passed to function.

In the abstract, the lexical environment looks like this in pseudocode:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
GlobalExectionContext = {
LexicalEnvironment: {
EnvironmentRecord: {
Type: "Object",
//Bind the identifier here
}
outer: <null>
}
}

FunctionExectionContext = {
LexicalEnvironment: {
EnvironmentRecord: {
Type: "Declarative",
//Bind the identifier here
}
outer: <Global or outer function environment reference>
}
}
Variable environment:

It is also a lexical environment, and its environment logger holds the binding relationships created by variable declaration statements in the execution context.

As mentioned above, the variable environment is also a lexical environment, so it has all the properties of the lexical environment defined above.

In ES6, one difference between Lexical Environment components and Variable Environment components is that the former is used to store function declarations and variable (let and const) bindings, while the latter is only used to store var variable bindings.

Let’s look at some sample code to understand the above concept.

1
2
3
4
5
6
7
8
9
10
let a = 20;
const b = 30;
var c;

function multiply(e, f) {
var g = 20;
return e * f * g;
}

c = multiply(20, 30);

The execution context looks like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
GlobalExectionContext = {

ThisBinding: <Global Object>,

LexicalEnvironment: {
EnvironmentRecord: {
Type: "Object",
//Bind the identifier here
a: < uninitialized >,
b: < uninitialized >,
multiply: < func >
}
outer: <null>
},

VariableEnvironment: {
EnvironmentRecord: {
Type: "Object",
//Bind the identifier here
c: undefined,
}
outer: <null>
}
}

FunctionExectionContext = {
ThisBinding: <Global Object>,

LexicalEnvironment: {
EnvironmentRecord: {
Type: "Declarative",
//Bind the identifier here
Arguments: {0: 20, 1: 30, length: 2},
},
outer: <GlobalLexicalEnvironment>
},

VariableEnvironment: {
EnvironmentRecord: {
Type: "Declarative",
//Bind the identifier here
g: undefined
},
outer: <GlobalLexicalEnvironment>
}
}

** Note ** - The function execution context will only be created when calling function’multiply '.

You may have noticed that the variables defined by’let ‘and’const’ do not have any associated values, but the variables defined by’var ‘are set to’undefined’.

This is because during the creation phase, the engine examines the code to find variable and function declarations. Although the function declaration is completely stored in the environment, the variable is initially set to’undefined ’ (in the case of’var’), or uninitialized (in the case of’let ‘and’const’).

This is why you can access variables defined by’var ‘before declaration (although it is’undefined’), but accessing variables defined by’let ‘and’const’ before declaration will result in a reference error.

This is what we call variable declaration promotion.

Arrow function exploration

Difference between arrow function and anonymous function

Arrow function looks like a shorthand for anonymous function, but in fact, there is a clear difference between arrow function and anonymous function: the’this’ inside arrow function is lexical scope, determined by context.

Looking back at the previous example, due to the incorrect handling of the’this’ binding by the JavaScript function, the following example does not get the expected result:

1
2
3
4
5
6
7
8
9
10
var obj = {
birth: 1990,
getAge: function () {
var b = this.birth; // 1990
var fn = function () {
return new Date().getFullYear() - this.birth; // this指向window或undefined
};
return fn();
}
};

Now, the arrow function completely fixes the pointing of’this’, which always points to the lexical scope, i.e. the outer caller’obj ':

1
2
3
4
5
6
7
8
9
var obj = {
birth: 1990,
getAge: function () {
var b = this.birth; // 1990
var fn = () => new Date().getFullYear() - this.birth; // this指向obj对象
return fn();
}
};
obj.getAge(); // 25

If you use the arrow function, the previous hack is written:

1
var that = this;

It’s no longer needed.

Since’this’ is already bound in lexical scope in arrow function, when calling arrow function with’call () 'or’apply () ', ‘this’ cannot be bound, i.e. the first parameter passed in is ignored:

1
2
3
4
5
6
7
8
9
var obj = {
birth: 1990,
getAge: function (year) {
var b = this.birth; // 1990
var fn = (y) => y - this.birth; // this.birth仍是1990
return fn.call({birth:2000}, year);
}
};
obj.getAge(2015); // 25

Difference between normal function and arrow function

The arrow function this points to:

1.
1
2
const a = () => {};
console.log(a.prototype); // undefined
2.

The definition here refers not to the definition code when we code, but to the definition in memory when we actually run to the location where the function is defined

1
2
3
4
5
6
7
8
9
10
11
12
13
Let a, barObj = {msg: 'bar this points to'};
fooObj = {msg: 'foo this points to'};
bar.call (barObj);//point this of bar to barObj
foo.call (fooObj);//point foo's this to fooObj
function foo() {
A (); // result: {msg: 'bar this points to'}
}
function bar() {
a = () => {
Console.log (this, 'this refers to the first normal function '); //
};//define this in bar inherits from the this point of the bar function
}

In the above code, the arrow function a is defined in the bar function, which is executed in foo, so the object passed in when the bar.call () is executed is the object pointed to by the arrow function this, because the arrow function is defined when the bar.call is executed, and this time The outer function of the arrow function is called to barObj.

3.
1
2
3
4
Let fnObj = {msg: 'Attempt to change the this point of the arrow function directly'};
function foo() {
a.call (fnObj);//result: {msg: 'bar this points to'}
}

Can only be changed by recalling the defined outer function and changing the this pointing of the outer function

4.
5.

Arrow function has no arguments inside

Arrow functions cannot use yield command

Reference article

https://juejin.im/post/5c76972af265da2dc4538b64

https://github.com/mqyqingfeng/Blog

https://juejin.im/post/5ba32171f265da0ab719a6d7