JS变量声明·提升·函数提升

变量声明

声明变量的关键字

在ES6之前,声明变量的关键字只有var,作用域只有:全局作用域和函数作用域;
到了ES6,引入let,const两个关键字声明变量和常量,同时引入新的作用域:块级作用域。

声明变量的形式

ES5 只有两种声明变量的方法:var命令和function命令。
ES6 除了添加letconst命令,另外两种声明变量的方式:import命令和class命令。所以ES6 一共有 6 种声明变量的方法。函数也是对象。

使用varfunction声明的变量会变量提升(函数名就是变量名),其他的不会被提升(隐式声明也不会)。

局部变量和全局变量

局部变量:在函数内部显式声明的变量(包括形参) + 块级作用域内let,const声明的变量;其他的则为全局变量

在 Web 浏览器中,全局执行环境被认为是 window 对象,可通过window对象访问全局变量和方法。(并非所有的全局变量都会作为全局对象的属性或方法)

显式声明与隐式声明

显式声明:带有声明变量关键字的
隐式声明:不带有声明变量关键字的

  • 隐式声明的变量都是全局变量
  • 显式声明的变量不可以通过delete删除,而隐式声明的却可以
  • 隐式声明的变量不存在变量提升,只有通过var声明的变量才会存在变量提升
  • 如果在函数内部隐式声明变量,直到函数执行后该变量才能称为全局变量

注意:只有var声明或者隐式声明的变量才会作为window对象的属性,命名函数function xx(){}和使用var声明匿名函数var xx=function(){}也会作为window对象的方法。

<body>
	<input type="text" onclick="this.value=window.name" value="点击看name"/>
	<input type="text" onclick="this.value=window.age" value="点击看age"/>
	<input type="text" onclick="this.value=window.isSingle" value="点击看status"/>
	<input type="text" onclick="this.value=window.sex" value="点击看sex"/>
	<input type="text" onclick="this.value=window.animal" value="点击看animal"/>
	<input type="text" onclick="this.value=window.profile()" value="点击看profile执行"/>
</body>
<script>
	var name = 'Free Joe';//显示声明(全局)
	age = 24;//隐式声明
	let animal ='dog';//let 声明的全局变量
	
	function profile(){
		var isSingle = true//显示声明(局部)
		{
			sex = 'male';//隐式声明
		}
		return `My name is ${name},a ${age}-year-old ${sex} who is ${isSingle?'single '+animal:'in couples'}`;
	}
	profile();
</script>

在这里插入图片描述

📌使用name属性作为全局变量时,需要注意的是name属性本身也是window对象的自带属性,所以直接使用或者打印这个变量也不会报错,只是空字符串而已。


练手:

function foo(){
	//console.log(a)
	a=1;
}
foo()
console.log(a)

试下解开注释会是什么效果?

var & let

let 声明的变量只在 let 命令所在的代码块 {} 内有效,在 {} 外不能访问。包含有let,const{}才会被提升为块级作用域。

let不允许重复声明

同一作用域下,let声明的变量不能再被显示声明,var声明的变量是可以被var重复声明

var x = 1;
let x = 2;//SyntaxError: Identifier 'x' has already been declared
console.log(x)//报错重复声明

var y = 1;
var y = 2;
console.log(y);//2
let不存在变量提升

同一作用域下,let 声明的变量不可以先赋值后声明

块级作用域存在变量提升,只是let,const声明的变量不会提升而已

x=1//ReferenceError: Cannot access 'x' before initialization
let x=2
console.log(x)//报错:未初始化x前不可读取x

y=1
var y=2
console.log(y)//打印:2
块级作用域

let声明的变量只在块级作用域内有效

并非所有在块级作用域内声明的变量对外部作用域都是不可见的,只有使用了let,const声明的变量对外才是不可见。

x=1
{
	let x=2
	console.log(++x)//打印:3
}
console.log(x)//打印:1

没有let,const加持的{}是毫无意义的,也不会形成块级作用域,类似if的代码块

{var z = 2;}
console.log(z)//打印:2
暂存性死区

暂存性死区是相对于某一个使用let,const声明的变量/常量而言的,在该代码块内该变量定义之前的区域就是暂存性死区。换言之,在let,const所在的块级作用域中,无法在let,const之前调用其声明的标识符。

var i = 1 
{	
//死区开始
	console.log(i);//Uncaught ReferenceError: Cannot access 'i' before initialization
	let i = 1;
//死区结束
}

解析:{}内使用let声明了变量i,那么 {}内的 i标识符会被屏蔽掉直到遇到let i才开启。所以无论你在{}外怎么申明声明定义该标识符也无济于事。

拓展:为什么需要块级作用域?

1.用来计数的循环变量泄露为全局变量

<body>
<button>测试1</button>
<button>测试2</button>
<button>测试3</button>
</body>
<script>
	   var btns = document.getElementsByTagName('button')
	    for (var i = 0; i < btns.length; i++) {
	      btns[i].onclick = function () {
	        console.log('第' + (i + 1) + '个')
	      }
	    }
</script>

无论点哪个按钮都输出 第4个

循环变量的那部分 (姑且称之为“循环变量区”)声明了变量i,可以视为var i;i=0;,第二次循环就是i=1,滴三次就是i=2,准备第四次声明,i=3,条件不符合,没有进入循环体。当我们点击按钮的时候,触发方法,访问全局变量i,此时i为3,打印i+1则为4。

如何实现点击第几个按钮就提示那个按钮呢?很简单,只要将for循环中的var改成let来声明i即可。

let声明之后,for循环还有一个特别之处,循环变量区是一个父作用域,而循环体内部是一个单独的子作用域。
循环变量区let i;i=0;之后每次循环都是给i赋值而已,但是{}每次循环,都会建立一个全新的块级作用域。

 for (let i = 0; i < btns.length; i++) {
    btns[i].onclick = function () {
      console.log('第' + (i + 1) + '个')
    }
  }

2.内层变量可能会覆盖外层变量。

var tmp = new Date();

function f() {
  console.log(tmp);
  if (false) {
    var tmp = 'hello world';
  }
}

f(); // undefined

同作用域下,函数提升会高于全局变量的。函数内有自己的变量对象VO,编译阶段,tmp变量声明被提升至函数顶层作用域,只有条件为false时候才对tmp赋值,所以输出undefined

const

const命令跟let命令一样:不存在变量提升、具有块级作用域、存在暂时性死区、不允许重复声明 +

声明时候就必须初始化
const y;//SyntaxError: Missing initializer in const declaration
若值为字面量,值不可修改

字面量值保存在变量所指向的那个内存地址,因此等同于常量。

const x=2
console.log(++x)//TypeError: Assignment to constant variable.
若值为引用值(对象等),对象里面的属性值是可以更改的

变量指向的是内存地址,保存的是一个指针,const只能保存这个指针地址是固定的,至于他指向的数据就无法掌控了。

var obj={name:'FreeJoe',age:24}
const x =obj
obj.age=100
console.log(obj.age)//打印:100

当然,如果不希望属性值和属性名改变,也不希望扩充新的属性名和属性值,需要用Object.freeze()方法

const person = {name:'Joe',age:25}
Object.freeze(person)
person.name='Tom';
person.tel='110';
console.log(person);//{name:'Joe',age:25}

变量提升

JS 代码执行过程分为两个阶段

  • 词法分析:词法分析主要包括:分析变量声明、分析函数声明、分析形参三个部分。
  • 执行阶段

换而言之,变量的声明和赋值对于js执行引擎而言并非一步到位,而是先提取变量声明(变量符号表)再赋值。并非读取一条var a = 1就会立马分两步走,而是读取整个<script>域的var x = xx后,把所有的变量声明剥离出来后,再赋值。

使用var关键字声明的变量 ,会在同一域(局部或全局)所有的代码执行之前被声明;但是如果声明变量时不是用var关键字,则变量不会被声明提前。

x=1;
console.log(x)//ReferenceError: Cannot access 'x' before initialization
let x

console.log(y)//undefined
var y=1;

console.log(z)//ReferenceError: z is not defined
z=1;

i=1;
console.log(i)//1
var i=2
console.log(i)//2

注:JavaScript 严格模式(strict mode)不允许使用未(后)声明的变量。

var a,b;
(function(){
	console.log(a)//undefined
	console.log(b)//undefined
	var a=b=1
	console.log(a)//1
	console.log(b)//1
}())
console.log(a)//undefined
console.log(b)//1

不要被var a=b=1这些连等式迷惑,拆解出来,var a=b,b=1 。此处b是全局变量,而a只是局部变量。

函数提升

具名函数的声明有两种方式:1. 函数声明式 2. 函数表达式(匿名方式)

//函数声明式
function bar () {}
//函数表达式声明; 
var foo = function () {}

函数表达式中的函数不会被提升,函数声明会被提升。
声明式函数会提升到做顶层作用域

console.log(sub)//undefined
console.log(sub(1))//TypeError: sub is not a function
var sub =function(a){return --a}

console.log(add(1))//2
function add(a){return ++a}

如果有变量和函数同名,则会忽略掉变量,只提升函数

console.log(foo)
var foo=1
function foo(){}//[Function: foo]
console.log(foo)//1

使用变量注意项

尽可能少用全局变量
容易造成命名污染,也一定程度影响内存释放。

优先推荐使用const -> let 最不推荐var
letconst之间,建议优先使用const,尤其是在全局环境,不应该设置变量,只应设置常量。

JavaScript 编译器会对const进行优化,所以多使用const,有利于提高程序的运行效率。
const声明常量还有两个好处,一是阅读代码的人立刻会意识到不应该修改这个值,二是防止了无意间修改变量值所导致的错误。

他们没有明确说明为什么要这样做,但我认为这样做有以下好处:
1.加强语义化 (常量与变量) 更好的可读性
2.避免for(var ) 循环计数变量泄露为全局变量类似的问题。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值