老生常谈—Javascript做用域、变量提高、闭包

浅谈做用域

  当咱们新建一个能够储存变量的值,怎么才能读取到这个变量呢?能访问到这个变量,就说明符合做用域规则,做用域规则就能够说是js引擎读取变量的规则。
  在js中变量分为两种,全局变量和局部变量,全局变量(拥有全局做用域)能够在整个js应用中被全部代码访问到,从程序开始分配内存直至结束才会被释放(出于对代码的性能、可读性、以及潜在冲突考虑应该减小使用)。
  局部变量在函数中声明的变量,函数的参数(做用域是局部性的),在函数体外,或者说的当前做用域的上层是没法直接读取的。
  下面再说下做用域嵌套,产生做用域链的事,在咱们实际编写代码的时候,好比:闭包

var a = 'a';
function demo(b) {
  let c = 'c';
  function print(c) {
    console.log(a + ' ' + b + ' ' + c);
  }
  print(c);
}
demo('b');

执行结果:clipboard.png函数

  在print方法执行的时候,会从当前的执行做用域开始查找变量,若是找不到,就继续向上一级继续查找,若是找到则会中止,若是没有就会继续直至当最后到达全局做用域时,此时不管找到仍是没找到,都会结束查找。性能

st=>start: 当前做用域 
io1=>start: 上层做用域
io2=>start: ...
cond=>end: 全局做用域

st->io1->io2->cond

  (简单写了个流程图方便脑补spa

  接下来仍是这段代码让咱们来作一个小小的修改。code

var a = 'a';
function demo(b) {
  let c = 'c',
      a = 'd';
  function print(c) {
    console.log(a + ' ' + b + ' ' + c);
  }
  print(c);
}
demo('b');

执行结果:clipboard.pngip

  结果是输出了上一层的变量a,这也就是做用域查找的机制,做用域始终从运行时所处的最内部做用域开始,逐渐向外层查找,当遇到第一个符合条件的结果就会返回,中止查找。内存

变量提高

  先简单上一段代码作用域

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

执行结果:clipboard.png
  为何会有这种结果呢?声明在后,反而打印出前面声明得值,其实js在编译的时候会先进行声明操做,以后再进行赋值操做,也就是只要是在做用域中的声明一出现,就将在代码自己被执行前优先进行处理。也能够将这个过程理解为全部的声明(变量和函数)都会被“提高”到做用域的最顶端。(虽然函数声明和变量声明都会被提高,可是函数优先级要高于变量开发

在ES6引入了let和const能够用来声明建立块做用域。for循环头部定义强烈建议用let,感兴趣的人能够去看看js函数做用域和块做用域it

闭包

  提到闭包不少人脑壳里都会感受模模糊糊的,闭包在概念上定义是可以读取其余函数内部变量的函数,在中,只有函数内部的子函数才能读取这个函数局部变量,这时候前面说的做用域起到做用了,因此咱们如今能够这样理解,定义在一个函数内部的函数,这个函数能够访问其余函数的变量,就能够称为“闭包”
  下面来聊聊闭包的用处,主要就是。1.可以读取到函数内部的变量,2.可让变量始终保存在内存中(这点要注意后面解释),下面上代码。

function demo() { 
  let a = 'a';
  function print() { 
    console.log(a);
  }
  return print; 
}
var clo = demo();
clo();

执行结果:clipboard.png
  这样咱们就在外层取得了demo函数内部局部变量的a,也就是闭包实现从外部读取局部变量的能力。
  下面说一说让变量始终保存在内存中这点.

function demo() { 
  let a = 1;
  addA = () => a+=1;
  function print() { 
    console.log(a);
  }
  return print; 
}
var clo = demo();
clo();
addA();
clo();

执行结果:clipboard.png
  闭包能够阻止,demo内部做用域都被垃圾清理机制回收销毁,能够看到局部变量a第二次执行比第一次+1,这就说明第一次运行结束后啊,没有被回收,由于print()仍在使用这个做用域因此demo不会被回收。
闭包会使得对应的变量一直保存在内存中,不会被清理回收,对内存消耗大,别滥用

闭包在实际开发中的使用

  1.原生的setTimeout传递的函数不能带参数

//会报错
function func(param){
  console.log(param);
}
var test = func(1);
setTimeout(test, 1000);


//经过闭包实现传参效果
function func(param){
  return function(){
    console.log(param);
  }
}

var test = func(1);
setTimeout(test, 1000);

  2.封装变量

相关文章
相关标签/搜索