preface

Digging from the event loop mechanism to the Promise source code implementation in # covered some complex scoping knowledge. Some friends asked me offline, but some did not understand. From the analysis with TA, I feel that in this complex scene, the scope of this is a little unclear.

In fact, this is not a bad thing, nor does it mean ‘low’, because in most business code, there are very few cases where a class is recursively called and then this is handled. This is a normal problem if you haven’t used something that complicated in a long time, or if you haven’t used something that complicated at all.

Title 1.

I’m going to give you two questions. If you can see them quickly, you have a clear understanding of the this scope chain, so you can skip the rest of the article. If it seems difficult, then it is necessary to read my article in detail, which will definitely help you understand this.

1.1 Arrow function mode

let flag = 0;
class MyClass {
    light = ++flag;
    constructor(fn) {
        const handle = () = > {
            console.log('context... '.this);
        }

        this.then= () = > {
            console.log('this.... '.this);
            return new MyClass(() = > {
                console.log('second this... '.this); handle() }) } fn(); }}new MyClass(() = > {

}).then();

Copy the code

If you look at my # from event loop to Promise source code implementation, you’ll see that this function is actually a simplified version of what I extracted.

Ok, everyone think 30 seconds, don’t rush to look down, have a conclusion in mind, then look down.

The ES5 way

function MyTest(fn) {
    function abc() {
        console.log('abc'.this);
    }
    this.scope = function() {
        console.log('scope-this'.this);
        return new MyTest(function() {
            console.log('second,,,'.this);
            abc();
        })
    }
    fn();
}

new MyTest(function() {
    console.log(this);
}).scope();

Copy the code

Again, stay for 30 minutes to have a conclusion in mind.

I think the vast majority of readers are going to feel a little clunky, because it doesn’t seem that clear.

In fact, when I was writing the code that went from the event loop to the Promise source code implementation, I felt a little bit like I didn’t know this very well. I haven’t written complex code for a long time, so I’m out of practice (it’s really important to write some complex logic code).

Okay, so if you’re interested, you can copy the code and run it on the console.

2. Start at the source

The appearance of this, you can more or less know some of the background, or search the Internet there are a lot of. Let me say it again, maybe a little bit more thoroughly than some people on the Internet.

To talk about this, we have to talk about scope chains. To talk about scope chains, we are talking about storing JS code in memory, which is related to data structures.

Basic data types Are stored directly on the stack. For example, a= 123 is a memory with a value of 123 at the address ‘0x123’. When a variable b=a is added, a new area is created and the contents of 0x123 are copied over. In this case, the memory address of the variable B is ‘0x131’.

We’re going to talk about functions by reference today, because we normally operate on this in functions, and there’s nothing to talk about in non-functions.

Take a look at the following two pieces of code:

   function one () {}
   function two(){}
Copy the code

and

  function one() {
    function two() {

    }
 }
Copy the code

There is no difference between the two types of creation in memory. When a function is created, it creates a separate space in memory to store it. For example, now the address of function one is ‘0x234’ and that of function two is ‘0x334’

Then we reference it in our code:

    var name = 'init';
    function one () {console.log(this.name)}
    var obj = {
        name: 'zhangsan'.part: one
    }
    
    obj.part(); // zhangsan 
    
    one() // init
    

Copy the code

Javascript allows functions to internally reference other variables in the current execution environment (context), which is called scope chains. We also know that the function is in its own block of memory, so it can be called in any environment. That’s a problem, how do we know where the context is? His calls are too flexible.

So, JavaScript provides a mechanism to retrieve the current execution environment, the context, in the function body, so that we can retrieve other variables within that context. This is handled through v8’s c++ section, so this appears.

The basic principle, however, needs many examples to really understand, just like a mathematical axiom, do not use, can not really understand.

So let’s begin to understand this axiom with various examples.

For example, 2.1

Example 1

    var name = 'init';
    function one () {console.log(this.name)}
    var obj = {
        name: 'zhangsan'.part: one
    }
    
    obj.part() // 'zhangsan'
    var fn = obj.part;
    fn() // What is the execution result?
    

Copy the code

The result of fn() execution is not really ‘zhangsan’, but window. Here’s a little formula that most people remember: See if there’s a dot in front of the function, and in ES5 you can see that, but it’s important to know how it works: Fn is a new variable, and its memory address is for example ‘0x555’. This memory address directly stores the memory address ‘0x666’ of the one function. Fn refers directly to the one function.

Obj. Part () is found through obj, so according to the scope chain, this in part refers to obj

Example 2

Moving on to the example:

function MyTest(fn) {
    console.log('init-this'.this);
    function abc() {
        console.log('abc'.this);
    }
    this.scope = function() {
        console.log('scope-this'.this);
        abc();
    }
    fn();
}

new MyTest(function() {
    console.log('callback-this:'.this);
}).scope();

Copy the code

This is a much more complicated example. Let’s analyze it bit by bit:

  • When instantiating the constructor, init-this corresponds to the MyTest instance, because one of the steps in the new constructor is to point this of the constructor’s execution environment to the new instance.
  • Callback-this refers to the window because it is a pure callback and no other variable refers to it. There’s a little bit of caution here.
  • Scope-this also refers to the MyTest instance, because the scope function is triggered whenInstance. The scope ()
  • ABC refers to window, again because no other variable refers to it.

So what if I want this to be consistent? So you need to change the environment in which the function is running, by what? Call /apply/bind will do.

Example 3

Modify example 2 above to change the context to which this points via call.

function MyTest(fn) {
    console.log('init-this'.this);
    function abc() {
        console.log('abc'.this);
    }
    this.scope = function() {
        console.log('scope-this'.this);
        abc.call(this);
    }
    fn.call(this);
}

new MyTest(function() {
    console.log('callback-this:'.this);
}).scope();

Copy the code

Example 4

To the initial example:

function MyTest(fn) {
    function abc() {
        console.log('abc'.this);
    }
    this.scope = function() {
        console.log('scope-this'.this);
        return new MyTest(function() {
            console.log('second,,,'.this);
            abc();
        })
    }
    fn();
}

new MyTest(function() {
    console.log(this);
}).scope();

Copy the code

Now we should be clear about the execution result pull:

  • Scope-this points to the MyTest instance
  • Everything else points to window

3. Arrow functions in ES6

You probably use ES6 more now, because most projects are single-page applications, and there are also various component tools that can be converted to ES5 through Babel

ES6 is both convenient and frustrating. Today’s arrow function is a handy place to do that.

ES5 this problem, in fact, the industry has been criticized, because it is too flexible, too unstable, a little careless, a little understanding of the shallow some easy to fall into the pit. ES6’s arrow function stabilizes this. This is also helped by the fact that ES6 is statically compiled, so the execution environment stabilizes when it is statically compiled.

The arrow function this is not scoped at runtime, but generates a specific scope when it is defined

Let me make a comparison with the es5 example above:

For example, 3.1

Example 1


    var name = 'init';
    const one = () = > {console.log(this.name)}
    var obj = {
        name: 'zhangsan'.part: one
    }
    
    obj.part() // What is the result of the execution?
    var fn = obj.part;
    fn() // What is the execution result?
Copy the code

As you can see, the result of both is the same, ‘init’. This does prove that the scope to which this refers in the function is defined at the time of definition.

Continue!

Example 2

function MyTest(fn) {
    console.log('init-this'.this);
    const abc = () = > {
        console.log('abc'.this);
    }
    this.scope = () = > {
        console.log('scope-this'.this);
        abc();
    }
    fn();
}

new MyTest(() = > {
    console.log('callback-this:'.this);
}).scope();

Copy the code

Except callback-this, which refers to the window, all refer to instances of MyTest.

Example 3

let flag = 0;
class MyClass {
    light = ++flag;
    constructor(fn) {
        const handle = () = > {
            console.log('context... '.this);
        }

        this.then= () = > {
            console.log('this.... '.this);
            return new MyClass(() = > {
                console.log('second this... '.this); handle() }) } fn(); }}new MyClass(() = > {

}).then();

Copy the code

The only difficulty is that the then method returns an instance of MyClass. Although we have the previous foundation, we are not sure immediately.

I added the light variable just to distinguish the scope.

  • this… This must return {light:1, XXX}, because this.then() is not an arrow function, it points to an instance.
  • second this… Then () {light:1, XXX}}; this :2, XXX};Here's the difficulty.
  • context… Return {light:1, XXX} for the same reason.

You can think about it a little bit, it’s a little bit convoluted.

The ultimate big boss

The ultimate boss is in my # from the event loop mechanism deep into the Promise source code implementation, there is more complex logic, involving different layers of environmental scope, like you can take a closer look ~~