参考:《浏览器工作原理与实践》— 李兵
参考文档: https://blog.csdn.net/ThinPikachu/article/details/121325198
【进程】:一个进程就是一个程序的运行实例,启动一个程序的时候,操作系统会为该程序创建一块内存,用来存放进程运行所需的所有状态信息,包含代码、运行中的数据和一个执行任务的主线程,我们把这样的一个运行环境叫进程的上下文。
进程是资源分配的最小单位,也就是说是程序运行的基本单位;
进程之间的内容相互隔离,不同进程通过IPC通信
一个进程关闭,操作系统则会回收为该进程分配的内存空间;
多个进程共享CPU,轮流使用CPU
每个进程都独占一块内存,有独立的代码和数据空间(进程的上下文),进程之间切换是通过操作系统调度的,上下文切换的开销较大
-- 操作系统对把CPU控制权在不同进程之间交换执行,即保存当前进程的上下文,恢复新进程的上下文,然后将CPU控制权转移到新进程,新进程就会从上次停止的地方开始。
【线程】: 线程是CPU调度和执行的最小单位,线程依附于进程,是程序的实际执行者。
线程是不能单独存在的,它是由进程来启动和管理的
线程之间共享进程中的公共数据,线程之间切换的开销小,因此也被称为轻量级的进程
每个线程有各自的调用栈和线程本地存储
一个线程崩溃了,则所在的进程也会终止
【协程】:运行在线程之上,协程并没有增加线程数量,只是在线程的基础之上通过分时复用的方式运行多个协程。
一个线程可以拥有多个协程但是它们是串行执行的,当一个协程运行时,其他协程必须挂起
协程是一种用户态的轻量级线程,协程的调度不是被操作系统,而是完全由程序控制

早期的浏览器是单进程,浏览器的所有功能模块都是运行在同一个进程里,带来一系列问题,如:
为了解决这些问题,现在的浏览器操作系统实行了多进程架构,但是也带来了一些问题,如:
浏览器的架构是多进程的,Chrome 打开一个页面需要启动多少进程呢?

Chrome 浏览器的架构是多进程的,它主要包含以下几个进程:
浏览器主进程(Browser process): 是浏览器的顶层进程,主要负责页面显示、用户交互、管理子进程、提供存储等功能;
渲染进程(Renderer Process):核心任务是将 HTML、CSS 和 JavaScript 转换为用户可以与之交互的网页;
GPU进程(GPU Process) :负责图形处理
网络进程(Network Process):负责网络资源的下载
插件进程Plugin Process:负责网页插件
参考文档:https://zhuanlan.zhihu.com/p/398901440
浏览器的渲染进程也是浏览器内核的,在前端开发过程中,我们主要和渲染进程打交道,它主要包含以下几个线程:
用于解析html、css,构建DOM树LayoutTree布局树(渲染树),布局和绘制生成最终渲染页面,传递给浏览器主进程进行展示。
也就是我们常说的js内核,也称JS引擎(例如V8引擎),用于解析、运行javascript代码;
Javascript引擎线程是单线程的。
·注意:GUI渲染线程和Javascript引擎线程是互斥的,不能同时执行,因此JS的执行会阻塞页面解析和渲染。
用于监听和触发事件。
注意:事件触发线程归属于渲染进程,不受JS引擎线程控制。
如何理解事件触发线程 和 Javascript引擎线程的关系 ?
Javascript引擎线程执行事件绑定代码,用户执行交互操作后,触发事件;
事件触发线程监听到事件触发之后,把事件处理函数里面的代码交给Javascript引擎线程来执行;
但是由于Javascript引擎是单线程的,事件处理函数里面的代码会被添加到待处理任务队列的末尾,也就是等Javascript引擎线程空闲以后才会执行。
就是经常使用的setInterval与setTimeout所使用的线程。
负责发送发送http请求。
负责浏览器页面中各个图层的绘制。

按照渲染的时间顺序,可以把渲染流程分为以下几个阶段:
HTML解析器开始解析HTML,生成DOM Tree并输出,保存在浏览器内存中;
-- 同时开启一个预解析线程,用来分析 HTML 文件中包含的Javascript、 CSS 、Img等需要下载的资源,通知网络进程提前加载这些资源

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script>
alert('哈哈哈,我阻挡了HTML页面的继续解析。。。。')
</script>
<style>
div {
width: 100px;
height: 100px;
background: rosybrown;
}
</style>
</head>
<body>
<div></div>
</body>
</html>

有 DOM了 树和 计算后的CSS样式,还不足以显示页面,因为我们还不知道 DOM 元素的几何位置信息,那么接下来就需要计算出 DOM 树中可见元素的几何位置,这个计算过程叫做【布局】。
注意:所有不可见的元素会被忽略,如head标签 , display:none的元素,script标签等;

如果页面中有复杂的效果,如使用了3D 变换、页面滚动、 z-index(z 轴排序)等情况下,渲染引擎还需要为这些特定的元素节点生成专用图层,并生成一棵对应的图层树(LayerTree);
并不是布局树的每个节点都包含一个图层,如果一个节点没有对应的层,那么这个节点就从属于父节点的图层。
渲染引擎会为哪特定的节点创建新的图层呢?

渲染引擎给页面分了很多图层,这些图层按照一定顺序叠加在一起,就形成了最终的页面。

染引擎会对图层树中的每个图层进行绘制,生成待【绘制列表】,交给合成线程
【视口】:屏幕的可是窗口成为视口(viewport);
页面可能很大,视口相对较小,为了节省开销,合成线程会将图层划分为图块(tile),然后合成线程会按照视口附近的图块来优先生成位图
-- 实际生成位图的操作是由栅格化来执行的;
-- 所谓【栅格化】,是指使用 GPU 进程将图块转换为位图的过程;
GPU进程根据不同图块生成位图,还给合成线程
所有图块都被光栅化,合成线程就会生成一个绘制图块的命令——“DrawQuad”,然后将该命令提交给浏览器进程。
浏览器进程收到合成线程发过来的 DrawQuad 命令后,其内部组件viz 的组件会根据DrawQuad 命令生成页面并保存在内存中,最后再将页面显示在屏幕上。

渲染流程中的特殊情况:
指修改了元素几何属性,如位置、尺寸、内容、结构等变化,引发元素几何位置变化,浏览器需要重新计算样式、构建布局树,开始之后的一系列子阶段,这个过程就叫重排。
触发重排的情况:(Javascript操作DOM,引发不同渲染流水线重新工作)
指修改了元素的外观样式,不会引起几何位置变化,直接入绘制阶段,生成绘制列表,然后执行之后的一系列子阶段,这个过程就叫重绘。如背景颜色、边框颜色,文字颜色等
指更改一个既不要布局也不要绘制的属性,直接分割图块阶段,然后交给浏览器主进程并不线上显示,这个过程叫做直接合成。
如 transform:translate(100px, 100px)
相对于重绘和重排,直接合成能大大提升效率
减少重排(回流)、重绘, 方法:
.box {
will-change: transform, opacity;
}
编译结果为两部分:执行上下文、可执行代码
showName();//函数showName被执行
console.log(myname);//undefined
var myname = '小白'
function showName() {
console.log('我是小白');
}
编译时的执行上下文如下:(变量环境部分)
{
showName: xxx, //showName 函数在堆内存的引用地址
myname: undefined
}
可执行上下文如下:
showName();
console.log(myname);//undefined
myname = '小白'
let userInfo = {
userName: "小白",
age: 18,
sayHello: function () {
setTimeout(function () {
console.log(`${this.userName},你好`) //undefined
}, 100)
}
}
userInfo.sayHello()
修改一个函数this指向的方法:
问题:
var变量提升
编译时变量声明提升,并初始化值为undefind,
函数声明提升
解决: 引入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)
第一步,解析全局代码,创建全局执行上下文,压入调用栈,并全局的执行可执行代码

第二步,执行到addAll调用时,生成addAll函数的执行上下文,压入上下文,并执执行addAll函数内部的可执行代码

第三步,执行到add 函数调用,生成add 函数的执行上下文,压入调用栈

执行add 函数内部的可执行代码,return 结果,然后add函数执行上下文销毁,弹出调用栈
第四部,执行addAll后续可执行代码,return 结果,addAll函数上下文销毁,弹出调用栈,最后只剩下全局执行上下文,伴随页面整个生命周期
问题: 栈溢出(递归函数)
全局作用域:代码中任何地方都能被访问,即全局执行上下文中的变量和函数能在任何地方被访问,生命周期伴随着页面的生命周期。
函数作用域:函数内部定义的变量或函数只能在函数内部被访问,函数执行结束之后,函数内部定义的变量会随着函数执行上下文一起销毁(闭包除外)
块级作用域 { }
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()
函数的作用域由词法作用域决定
词法作用域:是指作用域是函数声明的位置来决定的,和函数怎么调用无关
当函数执行完毕时,函数体内的定义的变量会随着函数执行上下文立即销毁,但是当外部函数包含内部函数,且内部函数使用了外部函数中定义的变量,这些变量就不会销毁,仍然保存在内存,这些变量和内部函数就形成了闭包
闭包的形成条件:
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对象就是闭包
闭包何时回收?
问题:内存泄露( 该回收的内存未被及时回收 )
栈内存: 存储基本类型数据(调用栈,执行上下文栈)
变量是引用类型时,存储的是引用类型的引用地址(编号)
堆内存:存储引用类型数据
代码空间:存储可执行代码
数据被使用之后,不再需要了,就称为垃圾数据,垃圾数据要及时销毁,释放内存空间,否则会内存泄漏。
全局变量会常驻到老生代内存中是直到整个进程结束后才会释放,建议少使用,释放使用delete即可。
非全局变量在该作用域摧毁时会自动释放,主动释放建议赋值undefined。
(1)栈内存回收
当Javascript代码执行时,记录当前执行状态的指针(称为 ESP),指向当前执行上下文的指针,当前函数代码之前完毕,指针下移指向下一个要执行的函数执行上下文,当前执行上下文弹出调用栈进行销毁,这个过程就是该函数栈内存回收的过程
function foo(){
var a = 1
var b = {name:"极客邦"}
function showName(){
var c = 2
var d = {name:"极客时间"}
}
showName()
}
foo()

(2)堆内存回收
垃圾回收器:
第一步,标记堆内存中活动对象和非活动对象
第二步,回收非活动数据所占据的内存
在所有的标记完成之后,统一清理内存中所有被标记为可回收的对象
第三步,做内存整理
每个渲染进程都有一个主线程,处理以下事件:
要让这么多不同类型的任务在主线程中有条不紊地执行,这就需要一个系统来统筹调度这些任务,这个统筹调度系统就是消息队列和事件循环系统,浏览器页面是由消息队列和事件循环系统来驱动的。
同步任务:直接进入主线程执行的任务,只有前一个任务执行完毕,才能执行后一个任务
异步任务:以回调函数实现,先在其他的任务队列中排队,等待同步任务执行完成,该任务才会进入主线程执行,分为宏任务、微任务
宏任务队列:宏任务执行队列,回调函数里要执行的任务
微任务队列:JavaScript 执行一段脚本,V8 引擎会首先创建一个全局执行上下文,同时也会创建一个专为V8 引擎内部使用的微任务队列
在 Chrome 中除了正常使用的消息队列之外,还有一个延迟队列,这个队列中维护了需要延迟执行的任务列表,包括了定时器和 Chromium 内部一些需要延迟执行的任务。
延时队列中的任务包含JS通过定时器设置的回调函数、还有一些浏览器内部的延时回调函数,它们属于宏任务;
正常的消息队列中的任务也属于宏任务。
宏任务 主要有以下几种:
1.渲染事件(如解析 DOM、计算布局、绘制)
XMLHttpRequest

XMLHttpRequest 发起请求,是由浏览器的其他进程或者线程去执行,然后再将执行结果利用 IPC 的方式通知渲染进程,之后渲染进程再将对应的消息添加到消息队列中。
function GetWebData(URL){
/**
* 1:新建XMLHttpRequest请求对象
*/
let xhr = new XMLHttpRequest()
/**
* 2:为 xhr 对象注册相关事件回调处理函数,监听网络请求过程中的各种情况
*/
xhr.onreadystatechange = function () {
switch(xhr.readyState){
case 0: //请求未初始化
console.log("请求未初始化")
break;
case 1://已调用xhr.send(),正在发送请求
console.log("OPENED")
break;
case 2://xhr.send()已经执行完成,已经接收到全部的响应资源
console.log("HEADERS_RECEIVED")
break;
case 3://正在解析响应资源
console.log("LOADING")
break;
case 4://资源解析完成,客户端可以直接使用了
if(this.status == 200||this.status == 304){
console.log(this.responseText);
}
console.log("DONE")
break;
}
}
// 请求超时了,对应的处理回调函数
xhr.ontimeout = function(e) { console.log('ontimeout') }
// 请求出错了了,对应的处理回调函数
xhr.onerror = function(e) { console.log('onerror') }
/**
* 3:创建请求
*/
xhr.open('Get', URL, true);//创建一个Get请求,采用异步
/**
* 4:配置请求参数
*/
xhr.timeout = 3000 //设置xhr请求的超时时间
xhr.responseType = "text" //设置响应返回的数据格式
xhr.setRequestHeader("X_TEST","time.geekbang")
/**
* 5:发送请求
*/
xhr.send();
}
当 JavaScript 执行一段脚本的时候,V8 引擎在创建全局执行上下文的同时,也会在内部创建一个微任务队列。
这个微任务队列就是用来存放微任务的,因为在当前宏任务执行的过程中,有时候会产生多个微任务,这时候就需要使用这个微任务队列来保存这些微任务了。
不过这个微任务队列是给 V8 引擎内部使用的,无法通过 JavaScript 直接访问。
每个宏任务都关联了一个微任务队列。
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
微任务和宏任务是绑定的,每个宏任务在执行时,会创建自己的微任务队列
微任务早于宏任务执行,如果在执行微任务的过程中,产生了新的微任务,同样会将该微任务添加到微任务队列中
微任务的执行时长会影响到当前宏任务的时长
微任务主要有:
MutationObserver 核心是采用了“异步 + 微任务”的策略。
const div1 = document.getElementById('div1');
setTimeout(function() {
console.log('setTimeout', div1.childNodes)
})
// 创建MutationObserver实例
const observer = new MutationObserver(function(mutationsList) {
console.log('mutations', mutationsList);
for(let mutation of mutationsList) {
if (mutation.type === 'childList') {
console.log('A child node has been added.');
}
}
})
observer.observe(div1, {
childList: true, // 监听节点变化
})
for(var i=0; i<3; i++) {
const p = document.createElement('p');
const textNode1 = '我是新建的';
p.innerHTML = textNode1;
div1.append(p);
}
Promise.resolve().then(function() {
console.log('Promise', div1.childNodes);
})

Mutation Events使用的是同步回调,且当 DOM 有变动时就会立刻触发相应的事件,即每次DOM 变化都会触发同步回调,造成了严重的性能问题
MutationObserver将响应函数改成异步调用,多次 DOM 变化后,一次触发异步调用,
-- 在每次 DOM 节点发生变化的时候,渲染引擎将变化记录封装成微任务,并将微任务添加进当前的微任务队列
-- 渲染事件本身还是宏任务
(2)执行过状态不可逆,不会再变
要么pending ->fulfilled
要么pending -> rejected
(3)Promise实现原理:
- 采用.then的方式延时绑定回调函数
- 回调函数返回值穿透,then回调函数中的返回值,可以穿透到最外层
- 错误“冒泡”,会一直向后传递,通过链式调用then、catch,不论在那一层出错,都会“冒泡”至catch
-- 就不需要在每个 Promise 对象中单独捕获异常了
function executor(resolve, reject) {
let rand = Math.random();
console.log(rand);
if (rand > 0.5)
resolve()
else
reject()
}
var p1 = new Promise(executor); // 同步任务
var p2 = p1.then((value) => {
console.log("succeed p1")
const p2 = new Promise(executor);
return p2; // p1中的返回值穿透到最外层
})
p2.then((value) => {
console.log("succeed p2");
})
//错误冒泡,最后一个Promise做错误处理即可
p2.catch((error) => {
console.log("error")
})
由于promise采用.then延时绑定回调机制,而new Promise时又需要直接执行promise中的方法,即发生了先执行方法后添加回调的过程,此时需等待promise中的方法执行完成后,才能继续执行.then绑定的回调函数,由于宏任务较多容易堵塞,则采用了微任务
(4)Promise.resolve(value):返回一个以给定值解析后的Promise对象
-- 参数是一个 Promise对象的实例 ,直接返回这个实例
-- 参数是一个thenable对象(即带有then方法),Promise.resolve()返回的是一个执行then方法之后的Promise对象,状态根据then方法执行之后判断
let thenable = {
then: function(resolve, reject) {
resolve(200)
}
}
let p1 = Promise.resolve(thenable); //200,因为p1已经是fulfilled状态,因此直接then,可以获取到返回值
p1.then((data) => {
console.log(data)
})
-- 参数是一个普通值或对象,则直接返回一个状态为fulfilled的 Promise 对象,值为参数本身
-- 参数为空,直接返回一个fulfilled状态的 Promise 对象,值为undefined
(5)链式调用时,
then回调函数执行成功,返回的是一个fulfilled状态的promise,会进入后面的then
then执行失败,返回的是一个rejected的promise,会进入后面的catch
catch回调函数执行成功,返回的也是一个fulfilled状态的promise,进入后面的then
catch执行失败,返回的是一个rejected的promise,进入后面的catch
Promise 的缺点:采用链式回调方式,充满大量的then函数,语义化方面存在缺陷
async function foo() {
console.log(1);
let a = await 100;
console.log(a); // 100
console.log(2);
}
console.log(0);
foo();
console.log(3);
//执行顺序:0,1,3,100,2
生成器函数:是一个带星号函数,是可以暂停执行和恢复执行的
-- 在生成器函数内部执行一段代码,如果遇到 yield 关键字,那么 JavaScript 引擎将返回关键字后面的内容给外部,并暂停该函数的执行。
-- 外部函数可以通过 next 方法恢复函数的执行。
执行器:执行生成器函数的函数,则成为执行器
协程: 是一种比线程更加轻量级的存在,
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')
//foo函数
function* foo() {
let response1 = yield fetch('/some/url1') //fetch基于Promise实现
console.log('response1')
console.log(response1)
let response2 = yield fetch('/some/url1')
console.log('response2')
console.log(response2)
}
//执行foo函数的代码
let gen = foo()
function getGenPromise(gen) {
return gen.next().value
}
getGenPromise(gen).then((response) => {
console.log('response1')
console.log(response)
return getGenPromise(gen)
}).then((response) => {
console.log('response2')
console.log(response)
})
直接使用async时, 返回结果是一个fulfilled状态的Promise对象;
使用await,await后为异步, 会默认创建一个 Promise 对象
await之后的代码相当于then函数里的代码;
如果需要做异常捕获,需要try...catch
(async function () {
const p1 = Promise.resolve(100)
const res = await p1
console.log(res) // 100
})()
(async function () {
const p2 = Promise.reject('some err')
const res = await p2
console.log(res) // 不会执行
})()
(async function () {
const p2 = Promise.reject('some err')
try {
const res = await p2
console.log(res)
} catch (ex) {
console.error(ex) // catch会执行
}
})()
参考文档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');

页面的生命周期:
从页面的生命周期方向思考:
关键资源(核心资源):阻塞页面首次渲染的资源称为页面的关键资源,HTML、CSS、Javascript
具体优化方法:
(1)压缩HTML文件,移除 不必要注释
(2)合并并压缩CSS 、JavaScript等文件 ,script 标签加上 async 或 defer属性
(3)避免使用table布局
(4)缓存(第二次请求命中缓存则直接读取缓存)
目标是减少页面渲染过程的重排、重绘
具体优化方法:
(1)避免强制同步布局(即避免使用JavaScript 强制将计算样式和布局操作)
(1)减少DOM操作,将多次操作DOM合并为一次,如插入元素节点
(2)减少逐项更改样式,最好一次性更改style,或者将样式定义为class并一次性更新
(3)前端框架Vue、React(虚拟DOM和Diff算法等)
(3)避免多次读取offset等属性,使用变量做缓存
(4)防抖、节流
(5)做动画效果时,使用will-change和transform 做优化

XSS 攻击是指黑客往 HTML 文件中或者 DOM 中注入恶意 JavaScript 脚本,在用户浏览页面用户实施攻击的一种手段
窃取用户Cookie信息
-- 通过document.cookie获取用户Cookie 信息,发送到恶意服务器
-- 恶意服务器拿到用户的 Cookie 信息之后,就可以模拟用户的登录,进行转账等操作
监听用户行为
-- 通过addEventListener来监听键盘事件,获取用户账号、密码、信用卡等信息, 发送到恶意服务器
-- 恶意服务器拿拿到这些信息,又可以做很多违法的事情
修改 DOM 伪造假的登录窗口,用来欺骗用户输入用户名和密码等信息
生成广告等影响用户体验
如:<script> --><script>
响应头Set-Cookie加使用限制
-- httpOnly,通知浏览器此 Cookie 只能通过浏览器 HTTP 协议传输,浏览器的 JS 引擎就会禁用 document.cookie ;
-- SameSite=Strict,限制此Cookie不能随着跳转链接跨站发送
充分利用 CSP
-- 限制加载其他域下的资源文件,
-- 禁止向第三方域提交数据;
-- 禁止执行未授权的脚本;
目的是利用服务器的漏洞和用户的登录状态来实施攻击
解决方法:
-- 尽量使用post,避免低级的 CSRF 攻击
-- 增加验证,如使用验证码、短信验证码,验证是否是用户主动发起的
-- 服务器验证Referer,即请求的来源站点与当前发起的请求的站点是否一致
-- 使用Token验证
服务器第一次返回时生成一个Token,客户端存储起来
再次请求客户端带着对应的Token,进行验证
我在从html页面生成PDF时遇到问题。我正在使用PDFkit。在安装它的过程中,我注意到我需要wkhtmltopdf。所以我也安装了它。我做了PDFkit的文档所说的一切......现在我在尝试加载PDF时遇到了这个错误。这里是错误:commandfailed:"/usr/local/bin/wkhtmltopdf""--margin-right""0.75in""--page-size""Letter""--margin-top""0.75in""--margin-bottom""0.75in""--encoding""UTF-8""--margin-left""0.75in""-
我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t
我花了三天的时间用头撞墙,试图弄清楚为什么简单的“rake”不能通过我的规范文件。如果您遇到这种情况:任何文件夹路径中都不要有空格!。严重地。事实上,从现在开始,您命名的任何内容都没有空格。这是我的控制台输出:(在/Users/*****/Desktop/LearningRuby/learn_ruby)$rake/Users/*******/Desktop/LearningRuby/learn_ruby/00_hello/hello_spec.rb:116:in`require':cannotloadsuchfile--hello(LoadError) 最佳
关闭。这个问题需要detailsorclarity.它目前不接受答案。想改进这个问题吗?通过editingthispost添加细节并澄清问题.关闭8年前。Improvethisquestion在首页我有:汽车:VolvoSaabMercedesAudistatic_pages_spec.rb中的测试代码:it"shouldhavetherightselect"dovisithome_pathit{shouldhave_select('cars',:options=>['volvo','saab','mercedes','audi'])}end响应是rspec./spec/request
在Rails4.0.2中,我使用s3_direct_upload和aws-sdkgems直接为s3存储桶上传文件。在开发环境中它工作正常,但在生产环境中它会抛出如下错误,ActionView::Template::Error(noimplicitconversionofnilintoString)在View中,create_cv_url,:id=>"s3_uploader",:key=>"cv_uploads/{unique_id}/${filename}",:key_starts_with=>"cv_uploads/",:callback_param=>"cv[direct_uplo
我正在尝试在Ruby中制作一个cli应用程序,它接受一个给定的数组,然后将其显示为一个列表,我可以使用箭头键浏览它。我觉得我已经在Ruby中看到一个库已经这样做了,但我记不起它的名字了。我正在尝试对soundcloud2000中的代码进行逆向工程做类似的事情,但他的代码与SoundcloudAPI的使用紧密耦合。我知道cursesgem,我正在考虑更抽象的东西。广告有没有人见过可以做到这一点的库或一些概念证明的Ruby代码可以做到这一点? 最佳答案 我不知道这是否是您正在寻找的,但也许您可以使用我的想法。由于我没有关于您要完成的工作
我的主要目标是能够完全理解我正在使用的库/gem。我尝试在Github上从头到尾阅读源代码,但这真的很难。我认为更有趣、更温和的踏脚石就是在使用时阅读每个库/gem方法的源代码。例如,我想知道RubyonRails中的redirect_to方法是如何工作的:如何查找redirect_to方法的源代码?我知道在pry中我可以执行类似show-methodmethod的操作,但我如何才能对Rails框架中的方法执行此操作?您对我如何更好地理解Gem及其API有什么建议吗?仅仅阅读源代码似乎真的很难,尤其是对于框架。谢谢! 最佳答案 Ru
使用Ruby1.9.2运行IDE提示说需要gemruby-debug-base19x并提供安装它。但是,在尝试安装它时会显示消息Failedtoinstallgems.Followinggemswerenotinstalled:C:/ProgramFiles(x86)/JetBrains/RubyMine3.2.4/rb/gems/ruby-debug-base19x-0.11.30.pre2.gem:Errorinstallingruby-debug-base19x-0.11.30.pre2.gem:The'linecache19'nativegemrequiresinstall
我知道全局变量$!包含最新的异常对象,但我对下面的语法感到困惑。谁能帮助我理解以下语法?rescue$! 最佳答案 此构造可防止异常停止您的程序并使堆栈跟踪冒泡。它还会将该异常作为值返回,这很有用。a=get_me_datarescue$!在此行之后,a将保存请求的数据或异常。然后您可以分析该异常并采取相应措施。defget_me_dataraise'Nodataforyou'enda=get_me_datarescue$!puts"Executioncarrieson"pa#>>Executioncarrieson#>>#更现实的
我在我正在处理的一些代码中发现了这一点。它旨在解决从磁盘读取key文件的要求。在生产环境中,key文件的内容位于环境变量中。旧代码:key=File.read('path/to/key.pem')新代码:key=File.read('|echo$KEY_VARIABLE')这是如何工作的? 最佳答案 来自IOdocs:Astringstartingwith“|”indicatesasubprocess.Theremainderofthestringfollowingthe“|”isinvokedasaprocesswithappro