一个进程就是一个程序的运行实例,
每启动一个应用程序,操作系统都会为此程序创建一块内存,用来存放代码、数据数据、一个执行任务的主线程,我们把这样的一个运行环境叫进程。
一个进程关闭,操作系统则会回收为该进程分配的内存空间
线程是依附于进程的,而进程中使用多线程并行处理能提升运算效率。
进程与线程关系图.png
进程与线程的之间的关系: (进程是火车,线程是每节车厢)
* 默认情况下,每个tab页面一个进程,互不影响 -- 特殊情况1:如多个空白tab会合并成一个进程;undefined-- 特殊情况2:从一个标签页中打开了另一个新标签页,当新标签页和当前标签页属于同一站点的话,那么新标签页会复用当前标签页的渲染进程
* 核心任务是将 HTML、CSS 和 JavaScript 转换为网页图层,通知浏览器主线程进行界面显示;
* 渲染进程都是运行在沙箱模式下
* 渲染进程中包含以下线程: (1).GUI渲染线程undefined(2) Javascript引擎线程undefined(3) 事件触发线程(归属于浏览器而不是JS引擎)undefined(4)定时触发器线程undefined(5)异步http请求线程undefined(6)合成线程undefined(7)IO线程:处理和其他进程进行通信undefined
各进程之间通过 IPC 来通信
css解析和HTML解析并行,不会阻塞HTML解析,但是会阻塞页面渲染(但是Javascript执行,会导致CSS的解析增加HTML解析的时间)
根据DOM和styleSheets生成LayoutTree布局树(渲染树),所有不可见的元素会被忽略,如head标签 ,
display:none的元素,script标签等
布局树.png
渲染引擎根据布局树生成图层树,
GPU进程根据不同图块生成位图,还给合成线程
l浏览器渲染流程图.png
渲染流程中的特殊情况:
指修改了元素几何属性,如位置、尺寸、内容、结构等变化,引发元素几何位置变化,浏览器需要重新计算样式、构建布局树,开始之后的一系列子阶段,这个过程就叫重排。
触发重排的情况:(Javascript操作DOM,引发不同渲染流水线重新工作)
指修改了元素的外观样式,不会引起几何位置变化,直接入绘制阶段,生成绘制列表,然后执行之后的一系列子阶段,这个过程就叫重绘。如背景颜色、边框颜色,文字颜色等
指更改一个既不要布局也不要绘制的属性,直接分割图块阶段,然后交给浏览器主进程并不线上显示,这个过程叫做直接合成。
如 transform:translate(100px, 100px)
相对于重绘和重排,直接合成能大大提升效率
减少重排(回流)、重绘, 方法:
编译结果为两部分:执行上下文、可执行代码
showName();//函数showName被执行
console.log(myname);//undefined
var myname = '小白'
function showName() {
console.log('我是小白');
}
编译时的执行上下文如下:(变量环境部分)
{
showName: xxx, //showName 函数在堆内存的引用地址
myname: undefined
}
可执行上下文如下:
showName();
console.log(myname);//undefined
myname = '小白'
1. 变量环境
2. 词法环境
3. 外部环境,即当前执行上下文中变量的外部引用,用来指向外部的执行上下文,也称为 outer
4. this,this的指向在于当前函数的调用方式 -直接调用指向全局对象window (严格模式下则是undefined)undefined-通过对象调用,this指向该对象undefined-通过apply、call、bind等方法调用则指向第一个参数对象undefined-箭头函数中的this指向外层函数的this(解析箭头函数不会创建执行上下文)
let userInfo = {
userName: "小白",
age: 18,
sayHello: function () {
setTimeout(function () {
console.log(`${this.userName},你好`) //undefined
}, 100)
}
}
userInfo.sayHello()
修改一个函数this指向的方法:
问题:
解决: 引入let、const、块级作用域
一段代码解析完成,即执行上下文创建完成,就立即执行可执行代码
var a = 2
function add(b,c){
return b+c
}
function addAll(b,c){
var d = 10
result = add(b,c)
return a+result+d
}
addAll(3,6)
第一步,解析全局代码,创建全局执行上下文,压入调用栈,并全局的执行可执行代码
执行上下文栈.png
第二步,执行到addAll调用时,生成addAll函数的执行上下文,压入上下文,并执执行addAll函数内部的可执行代码
执行上下文栈.png
第三步,执行到add 函数调用,生成add 函数的执行上下文,压入调用栈
执行上下文栈.png
执行add 函数内部的可执行代码,return 结果,然后add函数执行上下文销毁,弹出调用栈
第四部,执行addAll后续可执行代码,return 结果,addAll函数上下文销毁,弹出调用栈,最后只剩下全局执行上下文,伴随页面整个生命周期
问题: 栈溢出(递归函数)
var 、 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()
函数的作用域由词法作用域决定
词法作用域:是指作用域是函数声明的位置来决定的,和函数怎么调用无关
当函数执行完毕时,函数体内的定义的变量会随着函数执行上下文立即销毁,但是当外部函数包含内部函数,且内部函数使用了外部函数中定义的变量,这些变量就不会销毁,仍然保存在内存,这些变量和内部函数就形成了闭包
闭包的形成条件:
function foo() {
var myName = "小白";
var age = 18;
function sayHello(){
console.log (`你好,我的名字是:${myName},今年${age}`)
}
return sayHello;
}
let hello = foo();
hello()
// myName和age就是foo函数的闭包
1. 引用闭包的函数是全局变量时,闭包则会一直保存在内存中,直到页面关闭
2. 引用闭包的内部函是局部变量时,内部函数执行结束后,内部函数就会立即销毁,下次JavaScript 引擎的执行垃圾回收时,判断不再使用,则销毁闭包,回收内存
问题:内存泄露( 该回收的内存未被及时回收 )
数据被使用之后,不再需要了,就称为垃圾数据,垃圾数据要及时销毁,释放内存空间,否则会内存泄漏。
(1)栈内存回收
当Javascript代码执行时,记录当前执行状态的指针(称为
ESP),指向当前执行上下文的指针,当前函数代码之前完毕,指针下移指向下一个要执行的函数执行上下文,当前执行上下文弹出调用栈进行销毁,这个过程就是该函数栈内存回收的过程
function foo(){
var a = 1
var b = {name:"极客邦"}
function showName(){
var c = 2
var d = {name:"极客时间"}
}
showName()
}
foo()
调用栈.png
(2)堆内存回收
垃圾回收器:
第一步,标记堆内存中活动对象和非活动对象
第二步,回收非活动数据所占据的内存
在所有的标记完成之后,统一清理内存中所有被标记为可回收的对象
第三步,做内存整理
每个渲染进程都有一个 主线程 ,处理以下事件:
消息队列和循环机制保证了页面有条不紊地运行
同步任务 :直接进入主线程执行的任务,只有前一个任务执行完毕,才能执行后一个任务
异步任务 :以回调函数实现,先在其他的任务队列中排队,等待同步任务执行完成,该任务才会进入主线程执行,分为宏任务、微任务
宏任务队列 :宏任务执行队列,回调函数里要执行的任务
微任务队列 :JavaScript 执行一段脚本,V8 引擎会首先创建一个全局执行上下文,同时也会创建一个专为V8 引擎内部使用的微任务队列
宏任务 主要有以下几种:
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
微任务和宏任务是绑定的,每个宏任务在执行时,会创建自己的微任务队列
微任务早于宏任务执行
微任务的执行时长会影响到当前宏任务的时长
微任务主要有:
(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对象的实例 ,直接返回这个 实例
--
参数是一个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 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
async函数实现原理.png
生成器函数 :是一个带星号函数,是可以暂停执行和恢复执行的
执行器 :执行生成器函数的函数,则成为执行器
协程 : 是一种比线程更加轻量级的存在,
function* genDemo() {
console.log("开始执行第一段");
yield 'generator 1';// 遇到yield 关键字,JavaScript 引擎会暂停该函数的执行,并将关键字后面的内容返回给外部,外部函数可以通过next()恢复继续执行
console.log("开始执行第二段");
yield 'generator 2;
}
console.log('main 0')
let gen = genDemo(); //创建了一个gen协程,但是并没有执行
console.log(gen.next().value) ; //generator 1
console.log('main 1')
console.log(gen.next().value); //generator 2
console.log('main 2')
参考文档https://www.jianshu.com/p/12b9f73c5a4f/,这个应该是讲的比较详细的
栈的概念理解(3种):
事件循环执行过程 :
setTimeout(function () {
console.log('timeout1');
},0)
new Promise(function (resolve) {
console.log('promise1');
resolve(100)
}).then(() => {
console.log('then1');
})
new Promise(function (resolve) {
console.log('promise2');
resolve(200)
}).then(() => {
console.log('then2');
})
setTimeout(function () {
console.log('timeout2');
},0)
console.log('global1');
同步任务执行完成时的执行栈.png
页面的生命周期:
从页面的生命周期方向思考:
关键资源(核心资源):阻塞页面首次渲染的资源称为页面的关键资源,HTML、CSS、Javascript
具体优化方法 :
(1)压缩HTML文件,移除 不必要注释
(2)合并并压缩CSS 、JavaScript等文件 ,script 标签加上 async 或 defer属性
(3)避免使用table布局
(4)缓存(第二次请求命中缓存则直接读取缓存)
目标是减少页面渲染过程的重排、重绘
具体优化方法 :
(1)减少DOM操作,将多次操作DOM合并为一次,如插入元素节点
(2)减少逐项更改样式,最好一次性更改style,或者将样式定义为class并一次性更新
(3)前端框架Vue、React(虚拟DOM和Diff算法等)
(3)避免多次读取offset等属性,使用变量做缓存
(4)防抖、节流
(5)做动画效果时,使用will-change和transform 做优化
引入虚拟DOM树执行流程.png
XSS 攻击是指黑客往 HTML 文件中或者 DOM 中注入恶意 JavaScript 脚本,在用户浏览页面用户实施攻击的一种手段
如:<script> --><script>
目的是利用服务器的漏洞和用户的登录状态来实施攻击
发起CSRF攻击的方式:
解决方法:
-- SameSite=Strict,限制此Cookie不能随着跳转链接跨站发送
-- 验证请求来源站点
-- 使用Token验证
服务器第一次返回时生成一个Token
再次请求客户端带着对应的Token,进行验证
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。