程序猿阵线联盟-汇总各类技术干货

javascript之作用域与闭包详解

2017-11-11  本文已影响0人  心远地自偏s

什么是作用域

在js里作用域就是控制计算机内存的使用范围,掌握变量生死大权的一种规则。
在正式开始前,我们有必要了解一下一些基础,由浅入深,以便更好的掌握知识点。

计算机内存? 即 申明一个变量时 这个计算机会在内存中开辟一块空间,供变量存储、访问、修改,变量名就像你的身份证号一样,是唯一标识符。唯一与你不同的是,在同一个作用域里变量名相同的情况下,会后来居上,后面的变量覆盖前面的变量:

var L= 'pig';
var L= 'pink';
console.log(L);   //pink

上面的第一段代码我们可以看到,控制台会输出pink,而不是pig,更不会报错。

  1. 如果申明变量没取名字,会报错吗?
    答案是不会,js其实是编译语言,但是和其他语言不同的是,js不会像java等语言提前编译为中间码,它的编译仅仅发生在运行前的几毫秒,这几毫秒,编译器会做很多事,比如会有提升并自动给你未加var 的变量添加一个var。后面我们会详细讲解提升。
  2. 不会报错,又会自动添加var 为什么我们写代码还要多此一举,手动写上var呢?
    如果不写var 变量会自动成为全局变量。在浏览器环境下,全局变量会成为window对象下面的属性,node环境下,全局变量会成为global下的属性。也就是使用这个全局变量时,默认省略了window,当然,你可以自己加上,区别不大。
  3. 这么说全局变量不应该存在? 有什么坏处?
    应该尽可能避免全局变量,js之父Brendan Eich也曾谈到过这一点。应该尽可能让变量私有化,让外部无法访问,原因是:
    1.多人合作时 全局变量可能会与其他人变量发生冲突 ,当你引用其他框架,插入广告插件等,都可能引起这类事情,特别是开发大型web应用时,代码难以维护。
    2.使用全局变量时 编译引擎会查找所有全局变量 程序性能会大打折扣(没找到该变量会抛出not defined)。 相关资料> 深入理解变量私有化

undefined是变量已经被申明,但是未被赋值,这个情况程序不会被终止运行。not defined是未被申明,又未被赋值,浏览器抛出这个错误,程序会停止运行

var L ;
console.log(L);  //undefined
console.log(LL) // not defined
HH = "haohao"  //  像这样 未申明,但是被赋值,这个变量会直接成为全局变量

但是 如果把未申明,未赋值的变量作为window属性输出,则会成为undefined,而不是not definde因为原本就不会被提升,而window下面又没有这个属性,所以window.LL就会成为undefined。其他自定义对象都会如此。例如可以巧妙的利用这个特征来做ajax的ie浏览器兼容,在ie7以下,ie并不认识XMLHttpRequest,会直接not defined,所以把XMLHttpRequest放在window对象下,即不会报错了:

if(window.XMLHttpRequest){
        var Ajax=new XMLHttpRequest();
    }
    else{
        var Ajax=new ActiveXObject("Microsoft.XMLHTTP");
    }

js中 var申明的变量作用域是以函数为基准的,一个函数就是一个作用域,例如下面的代码,如果在haohao变量前面加一个var和不加var 有什么区别:

function fun1(){
   haohao = 'javascript';
}
fun1();
console.log(haohao);  // javascript   haohao未申明但是赋值,已被提升到全局
var LL = "pig";
function fun1(){
   var haohao = 'javascript';
function fun2(){
   console.log(haohao) 
  }
fun2();
}
function fun3(){
  var LL = "bigPig";
  console.log(LL) 
}
fun1(); //javascript
fun3(); //bigPig
console.log(LL)  //pig
console.log(haohao);  // 报错 not defined 

只有这个函数里面才能访问这个函数的变量,在函数这个外部,无法访问.既内部可以访问外部,外部无法访问内部
全局里面的变量,任何地方都能访问,局部作用域优先于全局作用域,fun1里的变量 fun1和其子作用域fun2可以访问。
作用域的变量使用完毕并且没有被其他地方引用时,js会自动进行垃圾回收,变量生命周期结束(死)

作用域图

在es6语法中, 情况就不同了

在es6后,javascript申明变量的方式不再只有var ,多了letconst,与var有如下区别:

  1. 已经申明的变量如果再申明会报错(not defined) ,var 则不会
  2. 会产生块级作用域,而不是var 那样的函数作用域
  3. let const申明的变量不会被提升,var申明的变量会被提升
  4. const是常量,定义后,值不可被修改,但是和其他语言不同的是,js常量如果存储的是对象,对象下的属性和方法依旧可以修改。

到底什么是作用域

实例1,我们先自己思考一下,下面这段代码运行结果:

<script>
    var animal= 'dog';
        test();
    function test(){
        alert(animal);
        var animal= 'pig';
        alert(animal);
  }     
</script>














你思考好了吗
结果是什么,拖到浏览器去试试就知道了,这段代码答案大家都可以知道,但是遇到同类代码,我们又ctrl c一遍?所以学习,掌握基本原理才是根本。
这是网易曾经的一道前端工程师面试题,(方便记忆,这里只是改了变量名),有多少工作多年的人前端开发者都还迷惑不清,其实原理并不复杂,而且比较简单,现在我们一起来揭开 js稀奇古怪的面纱,看看有多难。
按照常规思维 test函数被调用 首先是 alert(animal);而前面有个ar animal= 'dog'; 所以认为第一次弹出 ‘dog’,接着函数里面的代码是var animal= 'pig';alert(animal);所以自然就继续弹一个‘pig’出来,所以给出的答案是 先弹出 dog 然后pig。
还有答案是认为test在function test(){}函数之前调用了 ,所以会报错。
事实上答案到底是哪个?没错 ,前面两个答案都错了 正确答案是undefined,pig。
怎么会如此诡异? 这段代码究竟经历了啥?dog去哪里了?dog去哪里了?dog去哪里了?被吃了 ,成undefined了???

我们来回放事发现场:

编译器 : 我刚刚在代码里发现一个叫animal的变量是全局的,它装着dog,我把它处理了变成这个样子: var animal;animal = dog;
报告!还发现一个test函数,函数可是一等公民啊!! 我把它调到前面去!
;我又在test函数里发现 var animal= 'pig'; 我先把把它提升了 var animal;animal=pig;
所以我给引擎大人的结果如下:

function test(){
    var animal;
    alert(animal);
    animal= 'pig';
    alert(animal);
  }     
        var animal;
        animal =  'dog';
        test();
    

编译器 :呜呜 累死我了

作用域: 辛苦你啦! 你刚刚发现两个同名的变量吗?

编译器: 对呀!他俩是亲兄弟么?

作用域: NONONO,他俩不是一个世界的动物,一个在test函数;里,一个是全局的

编译器:我知道呀,不去调解一下吗?姓名一样住所一样,打起来咋办

作用域: 不在一个世界 !他俩是完全不同的! 只不过都叫animal而已!互不干扰,我们不管它们。它们没法打起来的。

引擎: 你们商量好了没? 我执行咯。

编译器给的结果恍然大悟没?dog?根本没有dog的事。没有引用全局变量那个animal。

var animal= 'pig';这个简单的赋值操作,实际上编译器帮我们干了两件事,变量被申明和被赋值。
var animal;并且被提升到所在作用域的顶部
然后在原来“=”所在的位置赋值。同样的,函数也会被提升,函数是js一等公民,提升优先级比变量还高,所以代码里函数还没被申明就调用,并不会报错。可以参考前面编译器给引擎的报告结果。

实例2,思考如下代码,控制台会输出unfined还是报错?

function tiger(){
  LL =20; 
}
console.log(LL);

这里 LL变量作用域本应该为全局的但是tiger函数并未被使用,所以被当做垃圾回收了,console.log(LL)找遍所有地方都找不到,无奈只好报错。
现在我们作如下改进:

tiger();
function tiger(){
  LL =20; 
}
console.log(LL);

这时候,tiger被调用了,LL由于没有申明就赋值了,所以被提升为全局变量,首先它的值被变为unfined,然后执行到tiger函数后,LL的值被赋为20;tiger随便在哪里被调用都行,因为 这个函数总是会被提升到作用域顶端,而这个函数作用域在全局里,所以就在代码顶端:

function tiger(){
 LL =20; 
}
tiger();
console.log(LL);

如果tiger()调用在console.log(LL)之后:

function tiger(){
 LL =20; 
}
console.log(LL);
tiger();

同样会报错 not defined,打印到控制台,会找不到这个变量,这个变量在打印函数执行后才出现.。

词法作用域,即是静态作用域
实例3:

function cat(){
    var L ='pig';
  console.log(L);
}
function play(){
  var L = 'ZZ';
  cat();
}
play();
console.log(L);

词法作用域是静态的,是在函数或者变量词法化(原本的位置,而不是调用的位置)时产生的,如上代码,play()后,play函数执行,执行时,var L被赋值'ZZ',然后执行cat(),这时cat函数被调用, cat作用域里面的 var L 被赋值'pig',然后输出 pig,而不是输出‘ZZ’ 。因为作用域不会因为函数调用而改变(eval,with除外),最后由于两个L都分别是两个函数的作用域里的私有变量,没有任何全局的L,所以代码最后一行的console.log(L);会报错。


Loading 持续更新....

上一篇 下一篇

猜你喜欢

热点阅读