javascript之作用域与闭包详解

什么是作用域

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

  • var

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

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有何差别?

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的let和const

在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 持续更新....

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,716评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,558评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,431评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,127评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,511评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,692评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,915评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,664评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,412评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,616评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,105评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,424评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,098评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,096评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,869评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,748评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,641评论 2 271

推荐阅读更多精彩内容