Chapter 4 JS foundation scope and closure

Return to chapter catalog

Catalog

1. Scope and free variable

Scope of action

Free variable

2. closure

3.this

4. exercise questions

1. Handwriting bind function

2. Make a simple cache tool

3. Create 10Tag, the corresponding serial number will pop up when clicking

1. Scope and free variables

Scope of action

There are four red boxes, which represent the legal use scope of variables a, a1, a2 and a3

Scope is divided into

Global scope

Function scope

Block scope (new in ES6)

Block scope here let and const are the same, out of range error reporting

 

Free variable

If a variable is not defined in the current scope, but is used, it should be searched layer by layer from the parent scope until it is found. If it is not found in the global scope, an error xx is not defined will be reported. It can be understood that all variables that cross their own scope are called free variables.

 

2. closure

Closure is a special case of scope application, which has two manifestations:

1. Functions are passed as parameters

2. Function is passed as return value

Simple understanding: when a nested inner (child) function references a variable (function) of a nested outer (parent) function, a closure is generated

function create() {
    const a = 100
    return function () {
        console.log(a)
    }
}

const fn = create()
const a = 200
fn() // 100

This code prints 100, this so easy

function print(fn) {
    const a = 200
    fn()
}
const a = 100
function fn() {
    console.log(a)
}
print(fn) // How much to print

This code prints 100

If these two examples are easy to see, the result shows that close control is better

Let's talk about the second example why it's not 200

The search of all free variables is to search the upper scope where the function is defined, not where it is executed!

So here, the function definition looks up to 100

Further explanation is to search the upper scope where the function is defined. If it is not found, an error will be reported directly! If you can't find it and then find it at the place of execution, 200 will be printed, but if you don't print it here, it means you won't find it at the place of execution.

 

3.this

The application scenario of this is generally as follows

1. Use in ordinary functions

2. Use call, apply and bind

3. Called in object method

4. calling in the class method

5. Use in arrow function

The value of this is determined during function execution, not during function definition. This rule is applicable to all the above scenarios

Let's look at an example

this is window printed by fn1()

this is the object {x: 100} after calling the call

After calling bind, it will not be executed directly. Return another function, execute this function. This points to the object {X: 200} of bind

this in sayHi() is the current object. Here, the function in setTimeout is the same as the normal function. this is window

This in the arrow function is the value of this in the parent scope, so is nested setTimeout. Because this in the outermost setTimeout is this in the parent scope, this in the innermost setTimeout is the same as that in the outer scope, that is, this in the parent scope

this in class refers to the current instance object

 

4. exercise questions

1. Handwriting bind function

If you want to realize the function of the code, how to rewrite the bind? Let's first look at the implementation results of the api

function fn1(a, b, c) {
    console.log('this', this)
    console.log(a, b, c)
    return 'this is fn1'
}

const fn2 = fn1.bind({x: 100}, 10, 20, 30)
const res = fn2()
console.log(res)

Operation result:

The handwritten bind function is as follows:

// The teacher simulated the bind code, I think it needs a little optimization
/*Function.prototype.bind1 = function () {
    // Break parameters into arrays
    const args = Array.prototype.slice.call(arguments)

    // Get this (the first item in the array)
    const t = args.shift() // Get and delete the first object

    // fn1.bind(...) fn1 in
    const self = this // Who calls this function

    // Returns a function
    return function () {
        return self.apply(t, args)
    }
}*/
// I simulate the bind function myself
Function.prototype.bind1 = function () {
    // Get this (the first item in the array)
    const t = arguments[0]

    // fn1 in fn1.bind(...)
    const self = this // Who calls this function

    // Break parameters into arrays
    const args = Array.prototype.slice.call(arguments, 1)


    // Returns a function
    return function () {
        return self.apply(t, args)
    }
}

function fn1(a, b, c) {
    console.log('this', this)
    console.log(a, b, c)
    return 'this is fn1'
}

const fn2 = fn1.bind1({x: 100}, 10, 20, 30)
const res = fn2()
console.log(res)

Of course, the running result is the same as the native bind. Why do I need to optimize the teacher's code?

In the teacher code step, first get a new array (arguments are converted into an array), and then delete the first element (this process is time-consuming, because in addition to the first element moving forward in turn, the last length is reduced by 1)

My bind is to get the first parameter for later application binding, and then get the new array (arguments start from subscript 1, and then move forward), so as to avoid the later steps.

There are many APIs involved here, which need to be accumulated and familiar. You can find the official website documents yourself

 

Take a look at the official website document Polyfill, which is similar to the above, and has better compatibility. This code can make your bind() partially used in an environment without built-in implementation support.

// Does not work with `new funcA.bind(thisArg, args)`
if (!Function.prototype.bind) (function(){
  var slice = Array.prototype.slice;
  Function.prototype.bind = function() {
    var thatFunc = this, thatArg = arguments[0]; // Take the first parameter, the target is the first parameter pointed to by this
    var args = slice.call(arguments, 1); // Convert the arguments class array to an array starting with subscript 1
    if (typeof thatFunc !== 'function') {
      // closest thing possible to the ECMAScript 5
      // internal IsCallable function
      throw new TypeError('Function.prototype.bind - ' +
             'what is trying to be bound is not callable');
    }
    return function(){
      var funcArgs = args.concat(slice.call(arguments)) //This arguments is an anonymous function of return. It has nothing to do with the arguments above. Don't confuse them
      return thatFunc.apply(thatArg, funcArgs);// In the last returned function, return this.apply (the first parameter, except the parameter after the first parameter (which may be returned by bind and passed again))
    };
  };
})();

 

2. Make a simple cache tool

// Closure hides data and only provides API
function createCache() {
    const data = {} // Data in closures, hidden, not accessible
    return {
        set: function (key, val) {
            data[key] = val
        },
        get: function (key) {
            return data[key]
        }
    }
}

const c = createCache()
c.set('a', 100)
console.log( c.get('a') )

If the set method is not called, the key value pair cannot be set. If the get method is not called, the key value pair cannot be stored

For example, if you want to set data.b = 101, data is not defined will be displayed and data cannot be accessed

 

3. Create 10 < a > tags, and the corresponding serial number will pop up when clicking

Wrong demonstration

let i, a
for (i = 0; i < 10; i++) {
    a = document.createElement('a')
    a.innerHTML = i + '<br>'
    a.addEventListener('click', function (e) {
        e.preventDefault()
        alert(i)
    })
    document.body.appendChild(a)
}

The for loop ends soon. i is 10. The callback function references the external i and forms a closure. When does the callback function execute? Whenever you click, it will be executed. When i click, the for loop will be executed. i is 10, so when i click each a, 10 will pop up

let a
for (let i = 0; i < 10; i++) {
    a = document.createElement('a')
    a.innerHTML = i + '<br>'
    a.addEventListener('click', function (e) {
        e.preventDefault()
        alert(i)
    })
    document.body.appendChild(a)
}

It's still a problem of block scope, because i is not global at present, it's in the block of for loop. When the callback function looks up the i variable, it will find it in the block. Each loop is a block scope, which naturally corresponds to the value of i.

 

Pay attention, leave a message, let's learn together.

 

===============Talk is cheap, show me the code================

 

215 original articles published, 349 praised, 900000 visitors+
His message board follow

Tags: ECMAScript

Posted on Wed, 29 Jan 2020 02:10:40 -0800 by mcerveni