第二步,执行到addAll调用时,生成addAll函数的执行上下文,压入上下文,并执行addAll函数内部的可执行代码
第三步,执行到add 函数调用,生成add 函数的执行上下文,压入调用栈
执行上下文栈.png
执行add 函数内部的可执行代码,return 结果,然后add函数执行上下文销毁,弹出调用栈
第四部,执行addAll后续可执行代码,return 结果,addAll函数上下文销毁,弹出调用栈,最后只剩下全局执行上下文,伴随页面整个生命周期
问题: 栈溢出(递归函数)
(三)作用域、作用域链、闭包1. 作用域:是指变量和函数可以被访问的范围- 全局作用域:代码中任何地方都能被访问,即全局执行上下文中的变量和函数能在任何地方被访问,生命周期伴随着页面的生命周期。
- 函数作用域:函数内部定义的变量或函数只能在函数内部被访问,函数执行结束之后,函数内部定义的变量会随着函数执行上下文一起销毁(闭包除外)
- 块级作用域 { }
var 、 let、const的区别:
- var:
-- 在javascript解析时, 声明和初始化提升,声明之前访问不报错,值为undefined;
-- 存放在执行上下文中的变量环境中
-- 可以多次声明同一个变量,后一个值会覆盖之前的值;
-- 不支持块级作用域 - let :
-- 用来声明一个变量,在解析时,声明会提升,但是初始化不会提升,声明之前访问报错;
-- 存放在执行上下中的词法环境中
-- 同一作用域内不能多次声明;
-- 支持块级作用域 - const :
-- 用来声明一个常量,不能再次修改
--声明会提升,但是初始化不会提升,声明之前访问报错;
-- 存放在执行上下中的词法环境中
-- 同一作用域内不能多次声明;
-- 支持块级作用域
function foo(){
var a = 1
let b = 2
{
let b = 3
var c = 4
let d = 5
console.log(a); //1
console.log(b); //3
}
console.log(b) ;//2
console.log(c); //4
console.log(d); //报错:d is not defined
}
foo()
2. 作用域链:变量查找沿着各作用域一层层向外部引用指向的执行上下文查找,形成一个链条,即作用域链条
函数的作用域由词法作用域决定
词法作用域:是指作用域是函数声明的位置来决定的,和函数怎么调用无关
当函数执行完毕时,函数体内的定义的变量会随着函数执行上下文立即销毁,但是当外部函数包含内部函数,且内部函数使用了外部函数中定义的变量,这些变量就不会销毁,仍然保存在内存,这些变量和内部函数就形成了闭包
闭包的形成条件:
- 外部函数里有内部函数
- 内部函数中使用了外部函数中定义的变量
function foo() {
var myName = "小白";
var age = 18;
function sayHello(){
console.log (`你好,我的名字是:${myName},今年${age}`)
}
return sayHello;
}
let hello = foo();
hello()
// myName和age就是foo函数的闭包
- 闭包形成原因:
Javascript在代码编译阶段,遇到内部函数 时,JavaScript 引擎会对内部函数做一次快速的词法扫描,
发现该内部函数引用了外部函数定义的变量,于是在堆空间创建换一个“closure”的对象,用来保存内部函数使用的变量,这个closure对象就是闭包 - 闭包何时回收?引用闭包的函数是全局变量时,闭包则会一直保存在内存中,直到页面关闭引用闭包的内部函数局部变量时,内部函数执行结束后,内部函数就会立即销毁,下次JavaScript 引擎的执行垃圾回收时,判断不再使用,则销毁闭包,回收内存
问题:内存泄露( 该回收的内存未被及时回收 )
(四)Javascrip的垃圾回收机制1. Javascript的内存机制- 栈内存: 存储基本类型数据(调用栈,执行上下文栈)
变量是引用类型时,存储的是引用类型的引用地址(编号) - 堆内存:存储引用类型数据
- 代码空间:存储可执行代码
数据被使用之后,不再需要了,就称为垃圾数据,垃圾数据要及时销毁,释放内存空间,否则会内存泄漏。
- 手动回收,如设置变量为null
- 自动回收
(1)栈内存回收
当Javascript代码执行时,记录当前执行状态的指针(称为 ESP),指向当前执行上下文的指针,当前函数代码之前完毕,指针下移指向下一个要执行的函数执行上下文,当前执行上下文弹出调用栈进行销毁,这个过程就是该函数栈内存回收的过程
function foo(){
var a = 1
var b = {name:"极客邦"}
function showName(){
var c = 2
var d = {name:"极客时间"}
}
showName()
}
foo()
调用栈.png
(2)堆内存回收
垃圾回收器:
- 主垃圾回收器: 负责回收生存时间长的垃圾数据(老生代垃圾数据)
- 副垃圾回收器:负责回收生存时间短的垃圾数据(新生代垃圾数据)
第一步,标记堆内存中活动对象和非活动对象
- 活动对象:还在使用的数据
- 非活动对象:垃圾数据
第二步,回收非活动数据所占据的内存
在所有的标记完成之后,统一清理内存中所有被标记为可回收的对象
第三步,做内存整理
(五)浏览器的事件循环机制每个渲染进程都有一个主线程,处理以下事件:
- 渲染事件(如解析 DOM、计算布局、绘制)
- 用户交互事件(如鼠标点击、滚动页面、放大缩小等)
- JavaScript 脚本执行事件
- 网络请求完成、文件读写完成事件
消息队列和循环机制保证了页面有条不紊地运行
1. 任务队列:是一种数据结构,用来放要执行的任务,先进先出同步任务:直接进入主线程执行的任务,只有前一个任务执行完毕,才能执行后一个任务
异步任务:以回调函数实现,先在其他的任务队列中排队,等待同步任务执行完成,该任务才会进入主线程执行,分为宏任务、微任务
宏任务队列:宏任务执行队列,回调函数里要执行的任务
微任务队列:JavaScript 执行一段脚本,V8 引擎会首先创建一个全局执行上下文,同时也会创建一个专为V8 引擎内部使用的微任务队列
(1)宏任务:宿主环境即浏览器分配的任务宏任务 主要有以下几种:
- setInterval、setTimeout
-- setTimeout回调函数的真正执行时间>=设定时间,原因是受消息队列中其他任务执行时间的影响 - XMLHttpRequest
Javascript脚本执行本身就也是一个宏任务,宏任务中又包含同步任务、微任务、宏任务
console.log(1);
setTimeout(()=>{
console.log(3);
Promise.resolve(4).then((data) => {
console.log(data)
})
setTimeout(() =>{
console.log(5)
},0)
}, 0)
Promise.resolve(2).then((data) => {
console.log(data)
})
//执行结果:1, 2, 3,5
微任务和宏任务是绑定的,每个宏任务在执行时,会创建自己的微任务队列
微任务早于宏任务执行
微任务的执行时长会影响到当前宏任务的时长
微任务主要有:
- MotutaionObserver
- Promise
(1) Promise的三种状态
pending(待执行状态)、fulfilled(执行成功状态)、rejected(执行失败状态)
(2)执行过状态不可逆,不会再变
要么pending ->fulfilled
要么pending -> rejected
(3)Promise实现原理:
- 回调函数延迟绑定(微任务)
- 回调函数返回值穿透,then回调函数中的返回值,可以穿透到最外层
- 错误“冒泡”,通过链式调用then、catch,不论在哪一层出错,都会“冒泡”至catch
//封装一个函数,简单模拟promise
function MyPomise(executor) {
let _this = this;
let _onResolve = null;
this.then = function (onResolve) {
_onResolve = onResolve;
}
this.resolve = function (value) {
//此处用setTimeout模拟延迟绑定回调任务,也是微任务出现的原因
setTimeout(() => {
_onResolve(value)
}, 0)
}
executor(this.resolve, this.reject);
}
let demo = new MyPomise((resolve, reject) => {
resolve(200)
})
demo.then((data) => {
console.log(data)
})
(4)Promise.resolve(value):返回一个以给定值解析后的Promise对象
Promise.resolve(value)方法的参数分成四种情况:-- 参数是一个 Promise对象的实例 ,直接返回这个 实例
-- 参数是一个thenable对象(即带有then方法),Promise.resolve()返回的是一个执行then方法之后的Promise对象,并且采用执行之后的状态
let thenable = {
then: function(resolve, reject) {
resolve(200)
}
}
let p1 = Promise.resolve(thenable); //200,因为p1已经是fulfilled状态,因此直接then,可以获取到返回值
p1.then((data) => {
console.log(data)
})
-- 参数是一个普通值或对象,则直接返回新的 Promise 对象,状态为fulfilled(值为参数本身)
-- 参数为空,直接返回一个fulfilled状态的 Promise 对象,(值为undefined)
(5)链式调用时,
then回调函数执行成功,返回的是一个fulfilled状态的promise,会进入后面的then
then执行失败,返回的是一个rejected的promise,会进入后面的catch
catch回调函数执行成功,返回的也是一个fulfilled状态的promise,进入后面的then
catch执行失败,返回的是一个rejected的promise,进入后面的catch
- async/await
- async/await出现的原因:
Promise 的编程模型采用链式回调方式,充满大量的then函数,语义化方面存在缺陷 - async/await的原理:
- 使用了Promise
- 在Promise基础配合生成器函数和协程,以同步代码编程的风格来实现异步回调
async function foo() {
console.log(1);
let a = await 100; // await之后的代码相当于then函数里的代码
console.log(a);
console.log(2);
}
console.log(0);
foo();
console.log(3);
//执行顺序:0,1,3,100,2