彻底理解var/let/const三者的区别

创建时的区别

  1. ES3创建变量用的是 var
  2. ES6创建变量用的是 let / const

var/let/const三者指针指向的区别

  1. LET VS CONST =>突出的是指针指向以及等号赋值的底层机制
  2. =等号赋值其实就是指针指向的过程:先创建值,再创建变量(引申:堆栈内存上以及AO/VO上 [点到为止]),最后指针关联;LET创建的是变量,是因为可以修改当前变量的指针指向,而CONST创建的变量,指针指向一但确定,则不能再更改(引申:平时也看一些好的文章,有的文章中介绍CONST创建的是常量,我认为这样说法是不严谨的,因为毕竟它只是指针不能改变,如果指向的是一个对象,在不改变指针的情况下,我们是可以修改对象值的)
  3. 真实项目中,我们对于一些需要宏观管控的标识值,一般都是基于CONST创建(例如:vuex或者redux中需要派发的行为标识),以及一些后续不能再更改指向的变量或者函数,也会基于CONST创建!

VAR 和 LET / CONST的区别

  1. LET不存在变量提升(加一句:所以不能在声明的代码之前使用),我之前在公司写项目,基本上都是基于ES6 LET处理了,这样我会把后续需要用到的变量,在代码起始的位置都先声明定义一下(给的是默认值) “变量声明前置”,而且创建函数也一般都是基于函数表达式的方式创建了,保证只有在创建代码的后面函数才可以有效执行…
  2. LET不允许重复声明 (真实项目中,代码量较多,除了声明前置,可以避免重复声明报错的问题,而且我会尽可能把业务拆分,每一个小模块单独在一个闭包中,降低相同上下文中变量声明的个数,这也是防止重复声明的办法…)
  3. 在全局上下文中,基于LET声明的全局变量和全局对象GO(window)没有任何的关系,VAR声明的变量会和GO有映射机制(把映射机制简单阐述一下…)
  4. 暂时性死区问题,基于typeof检测一个未被声明的变量,不会报错,结果是undefined(引申:我之前看过一些类库的部分源码,比如JQ,他们利用这个机制,检测上下文中是否存在window/module,从而验证出是浏览器环境还是Node环境…);但是如果此变量在后面会被LET声明,则直接报错,错误信息是:不允许在声明之前使用变量(这是浏览器词法解析阶段处理的事情)
  5. 我认为除了上述,最重要的还是 LET/CONST/FUNCTION 都会把当前所在的大括号(除函数以外)作为一个全新的块级上下文;应用这个机制,我们在项目开发的时候,如果遇到循环事件绑定等类似的需求,无需在自己构建闭包来存储了,只需要基于LET的块作用的特征即可解决,很方便…当然还有一些其它的应用,也是利用块上下文的特点完成的…

看过的一些文章,还有一些其它的区别,但是我认为最常见的区别就是上述五个!

下面用代码演示一遍中:

let VS const的主要区别就是指针指向的区别。
 let n = 10;
n = 11;
console.log(n);//=>11

const m = 10;
m = 11;//index.js:6 Uncaught TypeError: Assignment to constant variable.
console.log(m);

const obj = { name: 'cat' };
obj.age = 1;
console.log(obj);//{name: "cat", age: 1}

一般前两组代码常被一些文章拿来说明const是定义常量的说明,这样的说法是不严谨的。
对比分析上面的三组代码:等号赋值其实就是指针指向的过程:
在代码执行过程中即执行上下文中,变量赋值的操作基本都是先创建值存储在栈或者堆里,再创建变量存储在AO或VO,最后让变量和指针关联。

  1. LET创建的是变量,是因为可以修改当前变量的指针指向
  2. CONST创建的变量,指针指向一但确定,则不能再更改(引申,它只是指针不能改变,如果指向的是一个对象,在不改变指针的情况下,我们是可以修改对象值的)
  3. 真实项目中,我们对于一些需要宏观管控的标识值,一般都是基于CONST创建(例如:vuex或者redux中需要派发的行为标识),以及一些后续不能再更改指向的变量或者函数,也会基于CONST创建!
代码演示VAR 和 LET / CONST的区别
  1. let没有变量提升,因为 LET不存在变量提升所以不能在声明的代码之前使用,把后续需要用到的变量,在代码起始的位置都先声明定义一下(没有具体值的给的是默认值)即 “变量声明前置”,而且创建函数也一般都是基于函数表达式的方式创建了,保证只有在创建代码的后面函数才可以有效执行。
console.log(n);//undefined
var n = 10;

console.log(m);//index.js:4 Uncaught ReferenceError: Cannot access 'm' before initialization
let m = 10;
//======================
fn1();
function fn1(){}

fn();//Uncaught ReferenceError: Cannot access 'fn' before initialization
//函数表达式,函数只能在其声明之后使用
let fn = function () { };
  1. let不可以重复声明:Uncaught SyntaxError: Identifier ‘n’ has already been declared 词法解析阶段发现相同的上下文中有重复声明的操作(而且其中有LET/CONST,就会报错)。
var m = 1;
var m = 1;//不报错

let m = 1;
let m = 1;//Uncaught SyntaxError: Identifier 'm' has already been declared

let m = 1;
var m = 1;//Uncaught SyntaxError: Identifier 'm' has already been declared

var m = 1;
let m = 2;//Uncaught SyntaxError: Identifier 'm' has already been declared

真实项目中,代码量较多,除了声明前置,可以避免重复声明报错的问题,要尽可能把业务拆分,每一个小模块单独在一个闭包中,降低相同上下文中变量声明的个数,这也是防止重复声明的办法。
3.在全局上下文中,基于LET声明的全局变量和全局对象GO(window)没有任何的关系,VAR声明的变量会和GO有映射机制

let bb = 10;
console.log(window.bb);//undefined
//window没有当前的属性bb,对象的成员访问,没有此属性时是undefined

var aa = 11;
console.log(window.aa);//11

彻底理解var/let/const三者的区别_第1张图片
4. 浏览器的暂时性死区

console.log(typeof n); //=>"undefined" 检测一个没有被声明过的变量,不会报错,结果是UNDEFINED而已   “暂时性死区”:浏览器BUG

使用let声明会报错,有效解除暂时性死区

console.log(typeof n); //Uncaught ReferenceError: Cannot access 'n' before initialization
let n;
  1. LET/CONST/FUNCTION 都会把当前所在的大括号(除函数以外)作为一个全新的块级上下文
for (var i = 0; i < 5; i++) {
	setTimeout(_ => {
		console.log(i);
	}, 1000);
} 
//1s后输出5个都是5的值

//解决方法一:闭包,每一次的循环都形成一个不被销毁的闭包,这样会产生很多闭包
for (var i = 0; i < 5; i++) {
	(i => {
		setTimeout(_ => {
			console.log(i);
		}, 1000);
	})(i)
} 
//1s后输出 0、1、2、3、4

//解决方法二:使用let产生块级上下文
for (let i = 0; i < 5; i++) {
	setTimeout(_ => {
		console.log(i);
	}, 1000);
} 
//1s后输出 0、1、2、3、4

在理解上述代码前先来看下块级作用域,除函数外的块级上下文,在大括号中用let会形成一个私有的块级上下文,在此之外的上下文不能访问块级上下文中的变量。

let n = 10;
{
	let n = 11;
	console.log(n);//11
}
console.log(n);//10
//==================
{
	let n = 11;
	console.log(n);//11
}
console.log(n);//n is not defined

在循环中使用let的时候,是如何处理块级上下文的.

for (let i = 0; i < 5; i++) {
	setTimeout(_ => {
		console.log(i);
	}, 1000);
} 

用图片来展示循环的上下文是如何创建的:
彻底理解var/let/const三者的区别_第2张图片
现在有一个循环五次的循环,创建了六个块级上下文,不是五个。
首先有一个大的块级上下文,这个上下文是控制i的值累加和验证循环条件=>控制循环的走向,就是小括号中的部分。
第一轮循环:i=0–>条件成立,每一次条件成立都要执行循环体中的内容,循环体执行,形成小的执行上下文i=0–>设置定时器
【大的执行上下文中执行i++,大的块级上下文中i=1】
第二轮循环:i=1–>条件成立–>设置定时器
…循环五次,接下来的操作都是类似的
五次结束之后一共形成六个执行上下文,第一个大的执行上下文是控制循环走向的,每一次只要条件成立执行循环体就形成一个独立的小的执行上下文。在小的上下文中要给全局下window添加定时器,就是window.setTimeout设置方法,我们形成的这些小的上下文是不销毁的,整个循环结束后大的执行上下文是会释放(销毁)的。每一个小的执行上下文 中的定时器都赋值一个堆地址,机制跟闭包一样,所以小的执行上下文不会被销毁。定时器到达时间后要输出i,i不是定时器的这个小函数私有的,顺着原型链找到上级上下文即这个函数创建的地方,找到设置定时器的循环体中这个小的执行上下文,用的i就是此处的i。

你可能感兴趣的:(javascript)