什么是作用域
在js里作用域就是控制计算机内存的使用范围,掌握变量生死大权的一种规则。
在正式开始前,我们有必要了解一下一些基础,由浅入深,以便更好的掌握知识点。
- var
计算机内存? 即 申明一个变量时 这个计算机会在内存中开辟一块空间,供变量存储、访问、修改,变量名就像你的身份证号一样,是唯一标识符。唯一与你不同的是,在同一个作用域里变量名相同的情况下,会后来居上,后面的变量覆盖前面的变量:
var L= 'pig';
var L= 'pink';
console.log(L); //pink
上面的第一段代码我们可以看到,控制台会输出pink,而不是pig,更不会报错。
- 如果申明变量没取名字,会报错吗?
答案是不会,js其实是编译语言,但是和其他语言不同的是,js不会像java等语言提前编译为中间码,它的编译仅仅发生在运行前的几毫秒,这几毫秒,编译器会做很多事,比如会有提升并自动给你未加var 的变量添加一个var。后面我们会详细讲解提升。- 不会报错,又会自动添加var 为什么我们写代码还要多此一举,手动写上var呢?
如果不写var 变量会自动成为全局变量。在浏览器环境下,全局变量会成为window对象下面的属性,node环境下,全局变量会成为global下的属性。也就是使用这个全局变量时,默认省略了window,当然,你可以自己加上,区别不大。- 这么说全局变量不应该存在? 有什么坏处?
应该尽可能避免全局变量,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 ,多了let 和const,与var有如下区别:
- 已经申明的变量如果再申明会报错(not defined) ,var 则不会
- 会产生块级作用域,而不是var 那样的函数作用域
- let const申明的变量不会被提升,var申明的变量会被提升
- 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 持续更新....
网友评论