[Front-end Interview] Scope and Closure

1. Topic

Let’s talk about the understanding of variable promotion.

Explain several different usage scenarios for this

Create 10 A tags and click to pop up the corresponding serial numbers

How to Understand Scope

Application of Closure in Practical Development

Manually implement call apply bind

2. Knowledge points

2.1 Execution Context

Scope: a script or a function

Global: variable definition, function declaration script

Functions: variable definition, function declaration, this, arguments (before execution)

The difference between function declaration and function expression:

a();  //Error reporting function expression variable declaration will be advanced.
var a = function(){}

b();  //Do not report error function declaration
function b(){}

When defining a variable, it will raise its variable declaration by default: (limited to its execution context, such as a script and a function)

console.log(a);
 var a = 0;

Actually, it is

var a;
 console.log(a);
 a = 0;

2.2 this

This cannot be confirmed until it is executed and cannot be confirmed when it is defined.

var a = {
 name:'a',
 fn:function(){
 console.log(this.name);
 }
 }
 
 a.fn();  // a
 a.fn.apply({name:'b'});    // b  a.fn.call({name:'b'});
 var fn1 = a.fn();
 fn1();  // undefined

Use scenario of this

In the constructor (pointing to the constructed object)

function Fun(name){
this.name = name;
}
var f = new Fun('a');
console.log(f.name);

In the object property (pointing to the object)

In a normal function (pointing to window)

call apply bind

They are all used to change the direction of this of a function, with slightly different usage.

Call: The following parameter is the parameter list of the calling function

function greet(name) {
 console.log(this.animal,name);
 }
 
 var obj = {
 animal: 'cats'
 };
 
 Greet.call(obj,' cat');

Apply: The second parameter is an array of parameters that call the function

function greet(name) {
 console.log(this.animal,name);
 }
 
 var obj = {
 animal: 'cats'
 };
 
 Great.apply (obj, ['cat']);

Bind: When a binding function is called, parameters passed in by bind are inserted at the beginning of the target function’s parameter list, and parameters passed to the binding function are followed by them.

var fun = function (name1,name2){
console.log(this);
console.log(name);
}.bind({a:1},"name1");
fun("name2");

This in arguments:

var length = 10;
function fn(){
alert(this.length)
}
var obj = {
length: 5,
method: function(fn) {
arguments[0]()
}
}

Obj.method(fn)// output1
There is no output of 5 and no output of 10. Instead, there is an output of 1, which is interesting. Arguments here is a built-in object of javascript (see mdn:arguments-JavaScript) and is an array of classes (that is, long ones are similar to arrays, but there are some methods of arrays that can be converted by slice.call, see the link above for details). It stores the parameters of functions. In other words, arguments[0] here refer to the first argument of your method function: fn, so arguments[0] () means: fn ().

However, there is a question, why is there no output of 5? This is used in my method, shouldn’t it point to obj, and at least it will output 10. what kind of noise is this 1?

In fact, this 1 is arguments.length, which is the number of arguments in this function. Why does this point to arguments? Because in Javascript, arrays only use numbers as attribute names, that is to say, the meaning of arguments[0] () is similar to that of arguments.0 () (of course, this is not allowed), so you can understand it better.

arguments = {
0: fn, // that is, functon() {alert(this.length)}
1: Second parameter,//No.
2: The third parameter,//No.
...,
Length: 1 // Only one parameter
}

So the result of alert here is 1.

If you want to output 5, how should you write it? Direct method: fn will do.

2.3 Scope

No block-level scope

if(true){
var name = "test"
}
console.log(name);

Try not to declare variables in blocks.

Only function-level scope

2.4 scope chain

free variablesVariables not defined in the current scope are free variables.

Free variables will look for it in its parent scope. Is the parent scope at the time of definition, not execution.

var a = 100;
function f1(){
var b = 200;
function f2(){
var c = 300;
console.log(a);  //free variables
console.log(b);  //free variables
console.log(c);
}
f2();
};
f1();

2.5 closure

A closure is created when another function is nested in a function, the function is returned, and then the returned function is saved in a variable.

Two Usage Scenarios of Closure

1. Function as Return Value

function fun(){
var a = 0;
return function(){
console.log(a);  //free variables, find the parent scope when defining
}
}

var f1 = fun();
a = 1000;
f1();

2. Functions as Parameters

function fun(){
 var a = 0;
 return function(){
 console.log(a);  //free variables, find the parent scope when defining
 }
 }
 
 function fun2(f2){
 a = 10000
 f2();
 }
 
 var f1 = fun();
 
 fun2(f1);

For specific explanation, see the explanation in advanced-closure.

Two Functions of Closure:

Functions capable of reading internal variables of other functions

You can keep variables inside a function in memory all the time.

Practical application scenario 1:

Closures can encapsulate some variables that do not want to be exposed globally as “private variables.”

If there is a function that calculates the product, the mult function receives some parameters of type number and returns the product result. In order to improve the performance of the function, we add a caching mechanism to cache the previously calculated results. The next time we encounter the same parameters, we can directly return the results without taking part in the operation. Here, variables that store cached results do not need to be exposed to the outside world, and they need to be saved after the function runs, so closures can be used.

Code above:

function calculate(param){
var cache = {};
return function(){
if(!  cache.parame){
return cache.param;
}else{
//Cache Calculation  ....
//cache.param = result
//Take it directly from the next visit.
}
}
}

Practical application scenario 2

Continues the life of local variables.

Img objects are often used for data reporting, as follows:

var report = function( src ){
 var img = new Image();
 img.src = src;
 };
 report( 'http://xxx.com/getUserInfo' );

However, we know from the records in the background that there are bugs in the implementation of some lower versions of browsers
Under the use of the report function for data reporting will lose about 30% of the data, that is to say, the report function is not every time
Both successfully initiated HTTP requests.

The reason for data loss is that img is a local variable in the report function
At the end of the call, the img local variable is destroyed immediately, and the HTTP request may not have been issued at this time, so this request
It will be lost.

Now we can solve the problem of lost requests by enclosing img variables with closures:

var report = (function(){
var imgs = [];
return function( src ){
var img = new Image();
imgs.push( img );
img.src = src;
}
})();

Closure Disadvantages: Waste of Resources!

3. Answers to questions

3.1 Explain the understanding of variable promotion

Variable Definition and Function Declaration

Notice the difference between function declaration and function expression

When defining a variable, it will raise its variable declaration by default: (limited to its execution context, such as a script and a function)

console.log(a);
var a = 0;

Actually, it is

var a;
console.log(a);
a = 0;

3.2 Explain several different usage scenarios for this

  • In the constructor (pointing to the constructed object)
  • In the object property (pointing to the object)
  • In a normal function (pointing to window)
  • call apply bind

3.3 Create 10 A tags and pop up the corresponding serial numbers when clicking.

Implementation method 1: declare i with let

var body = document.body;
console.log(body);
for (let i = 0;   i < 10;  i++) {
let obj = document.createElement('i');
obj.innerHTML = i + '<br>';
body.appendChild(obj);
obj.addEventListener('click',function(){
alert(i);
})
}

Implementation Method 2 Package Scope

var body = document.body;
 console.log(body);
 for (var i = 0;   i < 10;  i++) {
 (function (i) {
 var obj = document.createElement('i');
 obj.innerHTML = i + '<br>';
 body.appendChild(obj);
 obj.addEventListener('click', function () {
 alert(i);
 })
 })(i)
 }

3.4 Application of Closure in Practical Development

Functions capable of reading internal variables of other functions

You can keep variables inside a function in memory all the time.

Encapsulates variables, permissions converge

Application 1

var report = (function(){
var imgs = [];
return function( src ){
var img = new Image();
imgs.push( img );
img.src = src;
}
})();

Used to prevent variable destruction.

Application 2

function isFirstLoad() {
var arr = [];
return function (str) {
if (arr.indexOf(str) >= 0) {
console.log(false);
} else {
arr.push(str);
console.log(true);
}
}
}

var fun = isFirstLoad();
fun(10);
fun(10);

Wrap arr inside the function, prohibit arbitrary modification and prevent variables from being destroyed.

3.5 implement call apply bind manually

  1. Context is an optional parameter. If it is not passed, the default context is window;;
  2. The context creates a Symbol attribute, which is deleted after being called and does not affect the context.
Function.prototype.myCall = function (context) {
if (typeof this !  == 'function') {
return undefined;  //used to prevent Function.prototype.myCall () from calling directly.
}
context = context || window;
const fn = Symbol();
context[fn] = this;
const args = [...arguments].slice(1);
const result = context[fn](...args);
delete context[fn];
return result;
}

The apply implementation is similar to call with an array of parameters

Function.prototype.myApply = function (context) {
if (typeof this !  == 'function') {
return undefined;  //used to prevent Function.prototype.myCall () from calling directly.
}
context = context || window;
const fn = Symbol();
context[fn] = this;
let result;
if (arguments[1] instanceof Array) {
result = context[fn](...arguments[1]);
} else {
result = context[fn]();
}
delete context[fn];
return result;
}

1. Judge whether it is a constructor call

2. Note that the parameter should be inserted into the starting position of the objective function

Function.prototype.myBind = function (context) {
 if (typeof this !  == 'function') {
 throw new TypeError('Error')
 }
 const _this = this
 const args = [...arguments].slice(1)
 return function F() {
 //Judge whether to use the constructor
 if (this instanceof F) {
 return new _this(...args, ...arguments)
 }
 return _this.apply(context, args.concat(...arguments))
 }
 }