目录
前言
理解let与var的时候会涉及到作用域与变量提升的知识,而var是js创建时候遗留下来的小弊端,存在很多麻烦,let的出现就是为了解决作用域的问题。而这些与函数的闭包密切相关,说起函数闭包,不得不提及js执行机制中的同步与异步的概念与区别,然后牵扯到执行栈与任务队列,再究其本质,实际上应该是内存空间分布的问题,即堆和栈存放了哪些数据类型,const的定义常量涉及到了内存模型,而这又让我想起了深拷贝与浅拷贝的区别,所以本文讲的可能有点多,逻辑关系或许没有处理的特别好~
let与var 的区别
let
ES6引进了块级作用域的概念,在ES5之前只有函数作用域,因此会形成函数闭包
var
存在变量提升,很多情况下会有麻烦
先看一个常见的例子:
javascript">for (var i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i);
}, 1000);
}
结果输出的是
5
5
5
5
5
再对比let:
javascript">for (let i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i);
}, 1000);
}
结果输出是
0
1
2
3
4
使用var定义的时候,i存在变量提升,可以每个循环都让i的值加一,最后i=4的时候符合4<5的条件,还有一次i++自增,此时i的值变成了5
全局作用域下,i=5提升到最前面,每个for循环中的i的值都是5。
使用let定义的时候,由于引入了块级作用域的概念,let只在当前作用域生效,即i在当前循环中的值是什么,就直接能输出什么。
此外,仔细观察会发现,定时器的加入,过了一秒之后是全部结果都一下子显示出来,那是因为在for循环遍历完成之后才开始的进行定时器的事件。这也是js中setTimeout定时器的异步执行机制
同步异步的区别
"同步模式"就是后一个任务等待前一个任务结束,然后再执行,程序的执行顺序与任务的排列顺序是一致的、同步的。
"异步模式"则完全不同,每一个任务有一个或多个回调函数(callback),前一个任务结束后,不是执行后一个任务,而是执行回调函数,后一个任务则是不等前一个任务结束就执行,所以程序的执行顺序与任务的排列顺序是不一致的、异步的。
异步执行的概念
(1)所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。
(2)主线程之外,还存在一个"任务队列"(task queue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。
(3)一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
(4)主线程不断重复上面的第三步。
因此可以解释上面定时器的原因
for循环对i进行自增赋值,这些是同步任务,发生在执行栈中,而后面接了一个是定时器,是异步任务,定时器函数放在任务队列中,此时栈中由下到上是 i=01234,分别指向的是定时器一秒过后输出i的值,而虽然都是一秒后输出,但都是才任务队列中,异步进行,全部一起执行,即一秒之后全部输出i对应的值。
使用var达到正常效果
可以使用立即执行函数也能达到let块级作用域的效果
实际上也是使用了闭包,来解决这个问题
因为函数也是一个作用域
里面的j一旦赋予给了函数里面,外面改变也不会影响里面的值
javascript">for (var j = 0; j < 5; j++) {
(function(num) {
setTimeout(function() {
console.log(num);
}, 1000);
})(j)
}
这种方法相当于开辟了五个单独的函数作用域,闭包并不会不会消失
只不过用的比较少
函数闭包
javascript"> var name = 'why';
abc(name);
function abc(name) {
console.log(name);
}
name = 'who';
abc('aaa');
abc('bbb');
abc(name);
结果:
abc()函数里面传入的参数,形成了函数闭包,第一个abc()函数里面的值是why没有改变,第二个abc()函数的值是who,因为函数下方对name进行了重新赋值
简单来说,第一个函数里面传入了形参,输出的值不会改变;第二个改变是因为传入的形参发生了改变而改变。
但闭包可能会存在着内存泄漏~
var有点像多个线程共享同一个资源,而let是每个线程都有自己的资源~
const的注意事项与内存模型的浅析
讲了var与let,不得不提及ES6中的const
其实const在很多语言都有出现,实际上就是使用const定义了的值不能再改变,也就是变成了常量,而var和let定义的都是变量。
let定义的变量在块级作用域内是不能改变的,也就是不能重复声明,而var是全局变量,都可以改变与重新赋值。
const声明的常量必须赋值;const赋值之后的常量不能再修改;常量的含义是指向的对象不能修改,但可以改变对象内部的值
javascript"> const obj = {
name: '小a',
age: 18,
height: 183,
}
console.log(obj);
obj.name = '小b';
obj.age = 28;
obj.height = 188;
console.log(obj);
输出结果:
内存模型:
const修饰,存的是内存地址,不能改变,但可以修改里面对象指向的值,因为并不会影响obj的地址数值
基本数据类型与复杂数据类型
基本数据类型
String、Number、Boolean、Undefined、Null、Symbol(es6新增)
基本数据类型直接存储在栈中
引用(复杂)数据类型
Object
存储的是该对象在栈中引用,真实的数据存放在堆内存中
引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。
当解释器寻找引用值的时候,会首先检索在栈中的地址,取得地址后从堆中获取实体。
浅析浅拷贝与深拷贝
浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存。但深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象。
内存模型: