【Day14】变数的地盘—作用域(scoop)与提升(Hoisting)


作用域(scoop)简单来说,就是变数的地盘,在地盘内,变数都有作用,出了地盘,变数就undefined了。

举个不伦不类的例子,丐帮的帮主洪七公武功再强也没有办法命令桃花岛黄老邪的弟子梅超风去烤一只土窑鸡来吃。因为根本就不同门不同派。

那要怎麽区分变数的范围呢?

在ES6之前,切分变数最小的范围是function为单位,函式里面的变数只能存活在函式里面。

在ES6之後,可以使用let、const来定义变数,这种状况下切分变数最小范围则为{}大括号区块。

var man = '欧阳克';
var marriage = function (){
	var man = '郭靖';
	return man + '娶了黄蓉';
}
console.log(marriage()); //郭靖娶了黄蓉
console.log(man);        //欧阳克

好险!欧阳克娶不到黄蓉!

因为使用var定义变数man,前面提到作用域最小的范围是function,所以marriage()会去捉「var man = '郭靖' 」来用,而不会去抓外层的「 var man = '欧阳克'」来用。

var man = '欧阳克';
var marriage = function (){
	return man + '娶了黄蓉';
}
console.log(marriage()); //欧阳克娶了黄蓉
console.log(man);        //欧阳克

糟糕!欧阳克娶了黄蓉…郭靖哭哭!

但是如果在marriage()里面找不到「var man = '郭靖'」,就会往外层去找,此时「var man = '欧阳克'」就会被捉来用了!郭靖真的会哭!

在外层console.log(man)一直都是欧阳克是因为,切分变数最小的范围是function,所以「var man = '郭靖' 」的作用域只在marriage()函式里面,如果marriage()里面找不到,才会往外层找,一直找到作外层的全域变数,所以console.log(man)才会一直都是'欧阳克'。如果都找不到就会报错:ReferenceError:man is not defined

情人眼里出西施!但重点是你要先进入情人的眼里(作用域)!不管她的眼界是「全域」还是「区域」。

所以我们要记得:

  • ES6之前,切分变数最小范围是function。
  • function可以捉外层变数还使用,但是从外层捉不到function内的变数。

提升(Hoisting)

继续来看看郭靖有没有办法跟黄蓉有情人终成眷属!

var man = '欧阳克';
var marriage = function (){
	console.log(man);  //undefined
	var man = '郭靖';
	return man + '娶了黄蓉';
}
console.log(marriage()); //郭靖娶了黄蓉
console.log(man);        //欧阳克

我们在「var man = '郭靖'」前面加了一个console.log(man),虽然「var man = '郭靖'」宣告在後面,但是console.log(man)并没有去外层抓「var man = '欧阳克'」来用。

那是因为console.log(man)和「var man = '郭靖'」在同一个作用域,只要确认後面 man这个变数有宣告,那就会宣告变数这件事提到上面来,然後先给它一个undefined的值。

var man = '欧阳克';
var marriage = function (){
	console.log(man);  //Uncaught ReferenceError: Cannot access 'man' before initialization
	let man = '郭靖';
	return man + '娶了黄蓉';
}
console.log(marriage()); 
console.log(man);        

如果是使用 let来宣告「let man = '郭靖'」,在还没宣告前,使用console.log(man)来查询,就会报错:Uncaught ReferenceError: Cannot access 'man' before initialization。因为let和const不允许在宣告却没有初始化的状况下使用,从宣告到初始化这中间的时间差称为「暂时死区」(Temporal Dead Zone)(TDZ)。

所以在这里提一下重点:

  • let/const 是使用区块作用域,以一对大括号{}为单位;var 是使用函式(function)作用域
  • 在 let/const 宣告之前就存取对应的变数与常数,会抛出ReferenceError错误;但在 var 宣告之前就存取对应的变数,则会得到undefined

综上所述,最好在作用域(scope)的一开始就宣告好所有的变数再使用,避免发生惨案。

函式的提升

前面提到的是用var设参数的提升,还有一种是「函式的提升」。

而透过「函式宣告」的方式建立的函式,也可以在前面叫用,後面再宣告函式。

marriage('郭靖');  //"郭靖娶了黄蓉"
function marriage(man) {
	return man + '娶了黄蓉';
}

而「函式表达式」就没这样的待遇了,提前叫用只会出现:TypeError。

变数的提升只有宣告被提升,但是没有初始化。但是函式的提升则是函式的内容整个被提升。


<<:  Dungeon Mizarka 002

>>:  [重构倒数第17天] - 重组资料格式减少不必要的回圈执行

【C# 群益 API 开发教学】官方范例下载与安装环境 #CH1

群益 API 是利用自己开发的程序,结合群益 API 在群益券商下单的一种方式,通常是做程序交易下单...

Youtube Reports API 教学 - 告一个段落

「鲑鱼均,因为一场鲑鱼之乱被主管称为鲑鱼世代,广义来说以年龄和脸蛋分类的话这应该算是一种 KNN 的...

陆企使用自由软件原始程序码不共享

故事的开头是着样的... 在中国有一间主打出口的深圳手机制造商优米公司(Umidigi),最近接到一...

Day01-为什麽我要学Vue/Vue简介

我的梦想就是带这一台笔电走遍全世界,「成为一个工程师似乎可以完成这个梦想」,於是在去年底毅然决然的投...

day17 不懂kotlin flow资料流? 那喝杯进口奶茶吧

用过Rx或reactive stream的大大,应该会很好理解flow,从设计概念来讲,flow也属...