大家好,我的浏览器输入url之后按回车发生了什么这篇文章中将到其相关的知识,然后上篇给大家写了一份HTTPS原理和HTTPS的安全相关的知识,相信大家对计算机网络知识有了一些了解了,接下来我会继续写TCP/IP协议的知识等,这里我先给大家插一篇Javascript一个大家比较关心的知识点,事件循环和执行上下文
先给大家讲执行上下文
在浏览器输入url之后按回车发生了什么我们说过在浏览器拿到HTML代码时候会先开辟一个栈内存用来给代码执行提供环境,这个栈内存就是用来给我们的执行上下文提供储存环境的。
栈内存概念:栈是一种数据结构,是只能在一个入口进行操作数据的线性表,他的原则就是后进先出,我们一般把往栈中插入元素叫入栈/进栈/压栈,把往栈中移除数据叫出栈/退栈,我们可以把我们的执行上下文当作需要往栈中储存的数据
执行上下文一般分三种:
- 全局执行上下文
- 普通函数(除eval函数)执行上下文
- eval函数执行上下文
在执行上下文中我们需要知道三个知识:
- this
- 变量对象
- 作用域链
这些知识点我会在下一篇Js知识中给大家写,下面我给大家写个例子给大家讲一下执行上下文
var a = 'global';
function fn(){
var a = "Fn";
function f(){
return a;
}
return f();
}
fn(); // "Fn"
我们开始整个代码的执行,
-
首先会创建一个全局执行上下文,然后把这个上下文环境执行压栈操作,将他放到执行上下文栈中
-
然后对这个全局执行上下文初始化,这里主要是先对全局执行上文创建环境记录器,然后填加上下文中this的指向,然后对上下文中的变量和函数进行初始化,在初始化函数的时候会在函数里设置一个作用域引用(之前创建的环境记录器)
-
然后执行fn函数,先创建fn函数的执行上下文,便于记忆我们给他起名fn上下文,同时把fn上下文执行压栈操作,这个时候执行上下文栈中就有两个上下文环境了
-
在fn中,我们首先把之前创建的作用域引用拿出来,建立作用域链,创建fn环境记录器,然后初始化活动对象,把arguments中的形参和变量声名,然后把这些声名的变量设置成一个当前作用域加入到作用域链最前面,把这个作用域链保存到fn中
-
然后执行f函数,这个和fn一样,先压栈,然后创建变量作用域,然后声名变量,然后加入作用域链,然后保存f中
-
f执行完进行出栈操作,把f执行上下文从执行上线文环境中删除(注:闭包的原理就是执行上下文出栈了通过之前创建的作用域链拿到变量的数据)
-
然后fn执行完,出栈 ------
在上面的环境记录器(相当与一个容器,里面放我们代码执行需要的数据……)和作用域引用(这个是作用域链的关键)这两个就是我们的词法环境的两个概念
词法环境是一种规范类型,基于 ECMAScript 代码的词法嵌套结构来定义标识符和具体变量和函数的关联。一个词法环境由环境记录器和一个可能的引用外部词法环境的空值组成。
现在给大家讲事件循环:
首先大家知道我们的Javascript是一个单线程的解释性(不需要预编译)语言,那么这里就引入了一个线程的概念,大家需要区分下线程和进程的概念
什么是进程呢?进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位,相当于我们开起一个程序就是开启一个进程
什么是线程呢?线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位,就相当于一个进程可以分配多个线程,比如说一个连锁的餐馆,没个不同的餐馆就是一个进程,而每个餐馆中会有不同的厨师和服务员,就相当于餐馆中的线程。
那为什么Javascript是一个单线程呢?其实这个和开始的设计是有关的,开始设计者只想用Javascript做简单的脚本改一些样式和表单提交操作,所以设计的时候没有把他设计的这么复杂,其次就是我们修改Dom的时候如果是多线程,多个地方同时改到了同一个Dom,那么这个Dom的变化就不一定是人们写代码时想的了
所以,Javascript设计程一个单线程,但是单线程就会有代码阻塞,如果一个代码执行耗费很多事件,那其他的代码就会一直等这段代码执行完再执行,这个时候事件循环就出来了,他的原理就是开辟其他线程,帮主线程处理一些需要等待的那些代码,把这些需要等待的代码在等待后通过回调再执行
事件循环在浏览器和node环境中有一些细微的不同,基本的原理是一样的
首先给大家讲微任务和宏任务
微任务:Promise.then/catch,process.nextTick(node),MutationObserver(浏览器)
宏任务:I/O,setTimeout,setInterval,setImmediate(node),requestAnimationFrame(浏览器)
在浏览器中,当执行栈中js代码执行完,就会去Task Queue中执行任务,首先会把其中的所有微任务执行,每当一个执行完微任务后,会查看执行栈中是否有代码没有执行,没有的话继续执行微任务,微任务执行完后就会执行宏任务。
我们来看两个代码
button.addEventListener('click', () => {
Promise.resolve().then(() => {
console.log('Microtask 1');
})
console.log('listen 1');
})
button.addEventListener('click', () => {
Promise.resolve().then(() => {
console.log('Microtask 2');
})
console.log('listen 2');
})
// 'listener 1', 'Microtask 1', 'listen 2', 'Microtask 2'
-
当用户点击时,我们会先判断执行栈中是否有代码没有执行完,上面代码是没有的,所以我们会进入第一个click事件中
-
这个时候执行promise中的同步代码,执行完后把promise.then加入到微任务队列,然后往下执行,输出listen 1
-
此时第一个click任务执行完成,发现执行栈中没有代码没执行,于是就直接执行微任务promise.then
-
在promise的回调执行完后,再走第二个click,和之前的逻辑一样,所以是上面的输出
我们现在来改一下代码
button.addEventListener('click', () => {
Promise.resolve().then(() => {
console.log('Microtask 1');
})
console.log('listen 1');
})
button.addEventListener('click', () => {
Promise.resolve().then(() => {
console.log('Microtask 2');
})
console.log('listen 2');
})
button.click()
// 'listen 1', 'listen 2', 'Microtask 1', 'Microtask 2'
-
这个代码多了一行button.click,当执行button.click时,执行栈中没有其他代码没有执行,会进入第一个click事件中
-
这个时候执行promise中的同步代码,执行完后把promise.then加入到微任务队列,然后往下执行,输出listen 1
-
此时第一个click任务执行完成,发现执行栈中有代码没执行完,button.click还有一个回调没有执行,于是进入第二个click事件中,输出listen 2
-
执行完第二个click之后执行栈中没有代码没有执行,就去微任务执行
这个就是大概的浏览器的事件循环
关于node的事件循环之后我再出一篇文章
我的上两篇文章:
大家有什么知识是想学的也可以评论,我会给大家出一些文章来讲这些知识,因为现在比较流行大前端,所以很多的知识都会去追求原理,而不仅仅是之前的会写会做就性,除了之前的浏览器中输入url之后浏览器干了什么里面写到的内容,我还会插一些js的基础知识,一般会讲一些大家平时不太注意到的但是有时会坑到大家的知识,以及会写一些VUE相关的源码理解和思路,也欢迎大家给我建议。
以上的文章如有写的不好的地方也感谢大家指出来,我们共同进步,知识就是用来共享的,在写完这篇HTTPS原理以及HTTPS安全知识之后我会写一个HTTP协议的文章,预计会有三到四篇来讲HTTP协议,然后会写TCP/IP协议,在把上面的这些写完之后我会总和上面的网络协议给大家写一篇计算机网络相关的知识的总览,帮助大家学习计算机网络知识。
在整个浏览器中输入url之后浏览器干了什么系列知识讲完之后我会结合上面的知识给大家写一篇前端优化的知识,会主要讲上面写到的知识如何优化