Home >Web Front-end >JS Tutorial >Javascript-detailed explanation of scope, scope chain and closure (picture and text)

Javascript-detailed explanation of scope, scope chain and closure (picture and text)

黄舟
黄舟Original
2017-03-21 14:23:371341browse

This article mainly introduces the diagramJavascript-scope, scope chain, closure and other knowledge. Has very good reference value. Let’s take a look with the editor below

What is scope?

Scope is a rule, which is determined during the code compilation stage and stipulates the accessible range of variables and functions. Global variables have global scope, and local variables have local scope. js is a language without block-level scope (curly brace code blocks including if, for and other statements or individual curly brace code blocks cannot form a local scope), so the local scope of js The domain is formed by and only the code block defined within the curly braces of the function, that is, the function scope.

What is scope chain?

Scope chain is the implementation of scope rules. Through the implementation of scope chain, variables can be accessed within its scope and functions can be called within its scope.

The scope chain is a linked list that can only be accessed in one direction. Each node on this linked list is a variable of the execution context. Object (the active object when the code is executed), one-way linked list The head (the first node that can be accessed) is always the variable object (active object) of the function currently being called and executed, and the tail is always the global active object.

The formation of scope chain?

We look at the formation process of the scope chain from the execution of a piece of code.

function fun01 () {
 console.log('i am fun01...');
 fun02();
}
function fun02 () {
 console.log('i am fun02...');
}
fun01();

Javascript-detailed explanation of scope, scope chain and closure (picture and text)

Data access process

As shown in the figure above, when the program accesses a variable, it follows the one-way access feature of the scope chain , first search in the AO of the head node, if not, search in the AO of the next node, up to the tail node (global AO). In this process, if it is found, it will be found. If it is not found, an undefined error will be reported.

Extend the scope chain

From the formation of the scope chain above, it can be seen that each node on the chain unshifts to the chain head when the function is called and executed. AO of the current function, and another way to form a node is to "extend the scope chain", that is, insert an object scope we want at the head of the scope chain. There are two ways to extend the scope chain:

1.with statement

function fun01 () {
 with (document) {
  console.log('I am fun01 and I am in document scope...')
 }
}
fun01();

Javascript-detailed explanation of scope, scope chain and closure (picture and text)

2. The catch block of the try-catch statement

function fun01 () {
 try {
  console.log('Some exceptions will happen...')
 } catch (e) {
  console.log(e)
 }
}
fun01();

Javascript-detailed explanation of scope, scope chain and closure (picture and text)

ps: Personally, I feel that there is not much demand for the use of the with statement, and the use of try-catch It also depends on demand. I personally don’t use these two much, but in the process of organizing this part, I came up with some immature Performance Optimization tips at the scope chain level.

Some immature suggestions on performance optimization caused by the scope chain

1. Reduce the scope chain access of variables Node

Here we define a ranking called "search distance", which represents the number of nodes in the scope chain that the program passes through to access a non-undefined variable. Because if the variable is not found at the current node, it jumps to the next node to search, and it is also necessary to determine whether the variable being searched exists in the next node. The longer the "search distance" is, the more "jumping" actions and "judgment" actions are required, and the greater the resource overhead, thus affecting performance. This performance gap may not cause too many performance problems for a few variable search operations, but if variable search operations are performed multiple times, the performance comparison will be more obvious.


(function(){
 console.time()
 var find = 1      //这个find变量需要在4个作用域链节点进行查找
 function fun () {
  function funn () {
   var funnv = 1;
   var funnvv = 2;
   function funnn () {
    var i = 0
    while(i <= 100000000){
     if(find){
      i++
     }
    }
   }
   funnn()
  }
  funn()
 }
 fun()
 console.timeEnd()
})()

Javascript-detailed explanation of scope, scope chain and closure (picture and text)


(function(){
 console.time()
 function fun () {
  function funn () {
   var funnv = 1;
   var funnvv = 2;
   function funnn () {
    var i = 0
    var find = 1      //这个find变量只在当前节点进行查找
    while(i <= 100000000){
     if(find){
      i++
     }
    }
   }
   funnn()
  }
  funn()
 }
 fun()
 console.timeEnd()
})()

Browse in chrome on mac pro Experiments were conducted under the computer and 100 million search operations were performed.

Experimental results: The former takes an average of 85.599ms when run 5 times, and the latter takes an average of 63.127ms when run 5 times.

2. Avoid too many variable definitions on the node AO in the scope chain

The main reason why too many variable definitions cause performance problems It is the "judgment" operation in the process of finding variables that is expensive. We use with for performance comparison.

(function(){
 console.time()
 function fun () {
  function funn () {
   var funnv = 1;
   var funnvv = 2;
   function funnn () {
    var i = 0
    var find = 10
    with (document) {
     while(i <= 1000000){
      if(find){
       i++
      }
     }
    }
   }
   funnn()
  }
  funn()
 }
 fun()
 console.timeEnd()
})()

Javascript-detailed explanation of scope, scope chain and closure (picture and text)

在mac pro的chrome浏览器下做实验,进行100万次查找运算,借助with使用document进行的延长作用域链,因为document下的变量属性比较多,可以测试在多变量作用域链节点下进行查找的性能差异。

实验结果:5次平均耗时558.802ms,而如果删掉with和document,5次平均耗时0.956ms。

当然,这两个实验是在我们假设的极端环境下进行的,结果仅供参考!

关于闭包

1.什么是闭包?

函数对象可以通过作用域链相互关联起来,函数体内的数据(变量和函数声明)都可以保存在函数作用域内,这种特性在计算机科学文献中被称为“闭包”。既函数体内的数据被隐藏于作用于链内,看起来像是函数将数据“包裹”了起来。从技术角度来说,js的函数都是闭包:函数都是对象,都关联到作用域链,函数内数据都被保存在函数作用域内。

2.闭包的几种实现方式

实现方式就是函数A在函数B的内部进行定义了,并且当函数A在执行时,访问了函数B内部的变量对象,那么B就是一个闭包。如下:

Javascript-detailed explanation of scope, scope chain and closure (picture and text)

Javascript-detailed explanation of scope, scope chain and closure (picture and text)

如上两图所示,是在chrome浏览器下查看闭包的方法。两种方式的共同点是都有一个外部函数outerFun(),都在外部函数内定义了内部函数innerFun(),内部函数都访问了外部函数的数据。不同的是,第一种方式的innerFun()是在outerFun()内被调用的,既声明和被调用均在同一个执行上下文内。而第二种方式的innerFun()则是在outerFun()外被调用的,既声明和被调用不在同一个执行上下文。第二种方式恰好是js使用闭包常用的特性所在:通过闭包的这种特性,可以在其他执行上下文内访问函数内部数据。

我们更常用的一种方式则是这样的:

//闭包实例
function outerFun () {
 var outerV1 = 10
 function outerF1 () {
  console.log(&#39;I am outerF1...&#39;)
 }
 function innerFun () {
  var innerV1 = outerV1
  outerF1()
 }
 return innerFun //return回innerFun()内部函数
}
var fn = outerFun()  //接到return回的innerFun()函数
fn()     //执行接到的内部函数innerFun()

此时它的作用域链是这样的:

Javascript-detailed explanation of scope, scope chain and closure (picture and text)

3.闭包的好处及使用场景

js的垃圾回收机制可以粗略的概括为:如果当前执行上下文执行完毕,且上下文内的数据没有其他引用,则执行上下文pop出call stack,其内数据等待被垃圾回收。而当我们在其他执行上下文通过闭包对执行完的上下文内数据仍然进行引用时,那么被引用的数据则不会被垃圾回收。就像上面代码中的outerV1,放我们在全局上下文通过调用innerFun()仍然访问引用outerV1时,那么outerFun执行完毕后,outerV1也不会被垃圾回收,而是保存在内存中。另外,outerV1看起来像不像一个outerFun的私有内部变量呢?除了innerFun()外,我们无法随意访问outerV1。所以,综上所述,这样闭包的使用情景可以总结为:

(1)进行变量持久化。

(2)使函数对象内有更好的封装性,内部数据私有化。

进行变量持久化方面举个栗子:

我们假设一个需求时写一个函数进行类似id自增或者计算函数被调用的功能,普通青年这样写:

 var count = 0
 function countFun () {
  return count++
 }

这样写固然实现了功能,但是count被暴露在外,可能被其他代码篡改。这个时候闭包青年就会这样写:

function countFun () {
 var count = 0
 return function(){
  return count++
 }
}
var a = countFun()
a()

这样count就不会被不小心篡改了,函数调用一次就count加一次1。而如果结合“函数每次被调用都会创建一个新的执行上下文”,这种count的安全性还有如下体现:

function countFun () {
 var count = 0
 return {
  count: function () {
   count++
  },
  reset: function () {
   count = 0
  },
  printCount: function () {
   console.log(count)
  }
 }
}
var a = countFun()
var b = countFun()
a.count()
a.count()
b.count()
b.reset()
a.printCount()  //打印:2 因为a.count()被调用了两次
b.printCount()  //打印出:0 因为调用了b.reset()

以上便是闭包提供的变量持久化和封装性的体现。

4.闭包的注意事项

Since the variables in closures will not be garbage collected like other normal variables, but will always exist in memory, extensive use of closures may cause performance problems.

The above is the detailed content of Javascript-detailed explanation of scope, scope chain and closure (picture and text). For more information, please follow other related articles on the PHP Chinese website!

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