var a = 2
function addAll(x,y) {
var b = 1;
console.log(add) // ƒ add(x,y) { return x + y }; 如果没有 add 函数声明,则是 undefined
function add(x,y) {
return x + y
}
var add = function(x,y) {
return x + y
}
console.log(add) // ƒ (x,y) { return x + y }
var result = add(x, y)
return a + b + result
}
addAll(100, 100)
function fn() {
var a = 1
// console.log(b) // Cannot access 'b' before initialization
let b
// 遇到let,const 声明, b会被提升到当前块级作用域顶部,
// 从顶部到声明处是暂时性死区, 无法操作b, 会报错
// 直接在解析词法环境下就报错了
{
let b = 3
var c = 4
console.log(a)
console.log(b)
}
console.log(b)
console.log(c)
}
fn() // a,b,c,d => 11, 3, undefined, 4
浏览器之JS引擎工作机制,事件循环
在解析 HTML 构建 DOM 树时, 渲染线程会解析到script标签, 则会将执行权交给 js 线程(引擎)来接管, 此时 js 引擎开始干活了,那么他到底是怎么个干法呢? 这里其实在作用域那一块已经提到了, 那里是以es3 的规范为基础说的, 这里我们按es6的新规范来理解...
js 引擎结构
内存堆heap
: 存储引用类型数据调用栈call stack
: 存储基础数据类型, 引用类型地址, 存放执行上下文(运行时)解释器
: 对源代码进行解释,编译,执行等js引擎(V8)执行代码
1.源码转换为抽象语法树, 并生成执行上下文
2.生成字节码
3.执行代码
执行上下文
当浏览器加载script的时候, js引擎开始工作; 此时会默认直接进入Global(全局上下文),将全局上下文push到引擎的
调用栈call Stack
; 如果在代码中调用了函数,则会创建Function(函数上下文)并压入调用栈内,变成当前的执行环境上下文; 当执行完该函数,该函数的执行上下文便从调用栈弹出返回到上一个执行上下文.执行上下文分类
函数: 定义在具体某个方法中的上下文,函数调用时就进入函数上下文. 局部变量就是在这个函数中访问
Eval: 定义在Eval方法中的上下文.
es6 的执行上下文
可以看到es3中, js会存在变量提升以及隐式覆盖,for循环的i在全局等问题; 所以在 es6新规范 中引入了块级作用域和 let,const 关键字来规避之前的一些缺陷; 由于 js 需要向下兼容,所以还是会保留变量提升的特性
异步
的JS通过上面我们知道 JS 通过创建
调用栈
和执行上下文环境
来执行一段 js 代码; 由于 js 设计的是单线程(因为JS主要目的用来实现很多交互相关的操作,如DOM相关操作,如果是多线程会造成数据的同步问题),只能从上往下执行单个任务,讲道理遇到耗时任务
就会阻塞了.那怎么呢? js 通过回调函数
来处理这种耗时(异步)
任务;JS引擎其实并不提供异步的支持,异步支持主要依赖于运行环境(浏览器或Node.js)浏览器下事件循环(node下的不一样)
当遇到耗时任务(
Web APIs的调用
)时, 浏览器进程其实会维护一个任务(回调)队列
存放 Web APIs任务的回调函数; 另而JS引擎则会不断监听其主线程调用栈是否为空, 等到执行调用栈空了之后, 就会取出任务队列中的回调函数放到执行栈进行调用; 这种机制就是事件循环
...任务(回调)队列
分类上面任务(回调)队列里面其实存放的任务都成为
宏任务
, 这类任务会通过回调函数的方式滞后执行; 浏览器中的宏任务包括哪些呢:在宏任务中对时间的控制粒度都比较宽泛, 像页面的渲染事件、各种 IO 的完成事件、执行 JavaScript 脚本的事件、用户交互的事件等都随时有可能被添加到消息队列中,而且添加事件是由系统操作的,JavaScript 代码不能准确掌控任务要添加到队列中的位置,控制不了任务在消息队列中的位置,所以很难控制开始执行任务的时间.
为了解决这类问题, 于是引入了
微任务
; 微任务就是一个需要异步执行的函数, 执行时机是在主函数执行结束之后、当前宏任务结束之前.当 JavaScript 执行一段脚本的时候,V8 会为其创建一个全局执行上下文,在创建全局执行上下文的同时,V8 引擎也会在内部创建一个微任务队列。顾名思义,这个微任务队列就是用来存放微任务的,因为在当前宏任务执行的过程中,有时候会产生多个微任务,这时候就需要使用这个微任务队列来保存这些微任务了。不过这个微任务队列是给 V8 引擎内部使用的,所以你是无法通过 JavaScript 直接访问的。
第二种方式是使用 Promise,当调用 Promise.resolve() 或者 Promise.reject() 的时候,也会产生微任务
1.微任务和宏任务是绑定的,每个宏任务在执行时,会创建自己的微任务队列;
2.微任务的执行时长会影响到当前宏任务的时长(多个微任务时长累加);
3.在一个宏任务中,如果创建一个用于回调的宏任务和微任务,无论什么情况下,微任务都早于宏任务执行。