在今年春招的时候,有幸面试了小米以及好未来前端工程师的实习岗位,运气也比较好,都成功的拿到了实习的offer。下面是总结的我面试所复习以及面试过程中遇到的一些面试点
去掉或者是样式丢失的时候,还是能让页面呈现清晰的结构,怎样理解这句话呢在学完css之后可能会发现一个问题,就是标签的样式之间可以相互转化。
例如:使用一个div标签,就可以通过css样式实现一些标签的样式。那么这就是问题的所在了,html语义化的效果就是能够使得页面呈现出清晰的结构,当去掉样式之后
还是能看出整个页面的结果,这样的好处有利于seo,利于辨别各标签所包含的内容的作用等。
初始化页面样式,为什么要初始化页面的样式呢?其实这并不难解答,因为浏览器的原因,因为不同的浏览器其标签的默认样式有的是有差异的,在开发页面时,我们需要考虑的就是用户使用的不同浏览器,那么我们写的页面效果就要考虑到如果用户用不同的浏览器,我们写的样式是否还是能达到我们所想要的效果,因此我们就需要初始化页面样式。例如: ** {margin: 0; padding: 0;}*这种采用通配符选择器的写法,其权重最低,也能达到我们想要的效果。这种兼容样式的话,有reset.css一个重置样式的css文件。也可以自己去定义页面的初始样式。
对于h5部分,其实主要就是为html新增了一些语义化的标签,如音频、视频、拖拽(这里与js实现的拖拽有区别),地理位置等一些标签,在这里就不过多的去叙述,
具体的话可以去参考https://www.w3school.com.cn/html5/index.asp,在这里就是关于这些标签的用法
对于css权重,主要说的就是对于css中选择器中的优先级,它们之间采用的256进制,具体的优先级如下:
选择器 | 权重值 |
---|---|
! important | 无限大 //它们之间采用的256进制 |
行间样式 | 1000 |
id选择器 | 100 |
类选择器 属性选择器 伪类选择器 | 10 |
标签 伪元素 | 1 |
通配符 | 0 |
告知浏览器是以何种规范来解析页面,常见的规范有html、xhtml
盒模型中有两种是需要去注意的,盒模型都包括以下部分width height border margin padding
区别:
当使用float后,就会产生浮动流,浮动流会对之后的页面布局有影响,清除浮动常常使用clear: both;
通常清除浮动的方式,采用伪元素选择器代码如下:
元素:after{
display: 'block;
content: ‘’;
clear: both;
}
两者的区别在于以下的几种情况:
在2009年提出的一种布局方案,可以简便、完整、响应式地实现各种页面的布局。flex是flexible box的缩写,翻译为弹性布局。
具体的可参照https://www.ruanyifeng.com/blog/2015/07/flex-grammar.html进行学习
针对不同的浏览器写不同的css code的代码就是css hack
简称:css精灵,是一种网页图片的应用处理返回时,其允许将一个页面涉及到的所有图片都包含到一张大图中,利用css的"background"的组合进行背景定位
访问页面时避免图片载入缓慢的现象
优点:
缺点:
从Ie6开始引入看standards模式,在此模式中,浏览尝试给符合标准的文档在规范上的正确下程度达到指定浏览器中的程度。在IE6之前css还不够成熟,所以IE5等之前的浏览器对css的支持性很差,IE6将对css提供更好的支持,但是之前的旧网站基于旧的样式所写,因为在IE6之后将DTD作为一个"参数",如果该参数存在,则代表用standrads标准,不存在则是Quirks模式(即怪异模式、诡异模式)
src用于替换当前元素,href用于在当前文档和引用资源之间确定联系,src是srouce的缩写,指定外部资源的位置,指定的内容将会嵌入到文档当前标签所在位置,在请求src资源时,会将其指向的资源下载并应用到文档中。当浏览器解析到该元素时,会暂停其他资源的下载和处理,直到该资源加载编译,执行完毕,图片和框架等元素也如此,类似于将所指向资源嵌入到当前标签内部,这也是为什么将js资源放在底部而不是头部的原因
基本数据类型:boolean string number null undefined Symblo()
引用数据类型:object(Array / Date / RegExp)
对于闭包,通俗来说,闭包产生于多个函数嵌套之间,当内层函数被保存到外面时,就会产生。这样说有点抽象看一个实际例子
funciton foo(){
var a = 'hello word'
function bar(){
console.log(a);
}
return bar;
}
foo()
可以参考https://juejin.im/post/5b081f8d6fb9a07a9b3664b6博客来理解
闭包的优点:
闭包的坏处:
与隐式类型转化相对的就是显示类型转化,常使用ParseInt() ParseFloat() Number()方法,对于这几种,可以参考具体的方法来理解
对于隐式类型转化,其实常存在于下面的这几种情况
对于变量提升,发生的情况主要是用了var这个声明变量的方式,在es6之后出现的let, const这两种声明变量的方式,并不会出现这种变量声明。
那么对于变量提升,我们需要掌握的其实就是js中预编译的过程,当你了解到预编译之后,对于整个的变量提升就非常的清楚明白了。
掌握预编译首先要掌握的就是在js当中的全局变量与局部变量
function (){ var a = b = 3;}
当了解完这两个概念之后,那么就来理解函数的预编译过程,首先讨论是在函数中讨论函数的预编译发生过程,总共有四步:
举个栗子,来加深对方面的四句话的理解
function bar(a){
console.log(a,b); // funtion a, undefined
var b = 3;
var a = 1;
console.log(a,b); // 3, 1
function a(){}
console.log(a); //1
}
bar(3);
对于上面的例子,首先先看到第一句话,生成了一个AO对象,然后,再来看第二局,在整个函数的形参和变量声明,在这个例子中只有a和b两个变量。在此时,两者的值都为undefined,然后再来看第三句话,形参和实参相统一,这时a由于传入的实参的值为3,那么此时a的值为3,b的值还是undefined
再来到第四句话,此时有个a的函数声明,那么此时a的值就变为了function,所以此时打印的结果就如上面注释后打印出的结果一样
全局中的预编译过程
在全局中预编译的过程和在函数中,类似,只是少了一个形参和实参相统一的步骤。这里就不多余的复述
作用域链,对于作用域链,首先需要认识什么是自由变量,如果当前的作用域中想要输出一个变量,但是该变量在当前的作用域并没有声明。这种就叫做
自由变量,举个栗子:
var a = 100;
function test(){
var b = 1;
console.log(a,b); //这里的a就是自由变量
}
test();
含义:如果当前自由变量中没有该变量声请,就会再向上一层寻找,直到找到全局作用域还没有找到,就会宣布放弃,这种一层一层的关系就是作用域链。
原型,原型对象也是普通的对象,其带有一个自带的隐式的_proto_属性,原型也又可能是有自己的原型,如果一个对象的原型不为null的话,就称为原型链。原型链是由一些用来继承的共享的对象组成的(有限的)对象链
对于数组,其基本的方法有
举一个简单的例子,这个很简单的例子也有在面试中被要求写过
如:一个数组为[1,2,5,7,10,46,90],选出大于5的数并用’-'连接起来,如这个例子中输出 7-10-46-90
其实这个很简单先创建一个空串,用for循环,然后字符串拼接。实现的方式很多这里用一种较为简单的方法实现
function getStr(arr){
return arr.filter(item => item > 5).join('-');
}
在日常写程序的过程中,数组的使用场景非常的多,对于里面的方法,都需要去掌握,具体关于每个方法参数以及性质可以参考https://developer.mozilla.org/zhCN/docs/Web/JavaScript/Reference/Global_Objects/Array这个网站,该网站比w3c上更新的更加的快也全,建议去参考Api时,就可以在里面去参考
常用的一些方法如下:
具体这些方法的使用,可以参考上面数组中提供的那个网站上去参考
EventLoop:即事件循环,指浏览器或Node的一种解决js单线程不会阻塞的机制,其是一种执行模型,在不同的地方有不同的实现,浏览器和NodeJs都是基于不同的
技术实现了各自的EventLoop,可以概述为以下两点:
理解事件循环,首先需要区分两个任务
上面的这些任务,只需要记住就行了
在浏览器中,只有一个执行栈和一个任务队列,在任务队列中放的是宏任务浏览器中事件循环的执行方式为:每从事件队列中取出一个事件时,有微任务就把微任务执行完,然后才开始执行事件(即从任务队列中去拿一个宏任务)
举个栗子
console.log(1)
setTimeout(function(){
console.log(2);
},0)
new Promise(() => {
console.log(3);
setTimeout(() => {
console.log(4);
})
})
console.log(5);
具体的结果可以自己试试,然后结合在浏览器中的事件循环的执行方式来理解对于node中的事件循环机制,会涉及到六个队列,执行的方式也不一样,具体可以在网上看看博客
对于this指向只需要记住下面的几个点
上面的四点只需要记住就是了,这里就举一个例子说明下
var a = 5;
function test(){
a = 0;
console.log(a);
console.log(this.a);
var a;
console.log(a);
}
test();
new test();
上面这两种打印出的结果分别为 0 5 0 和 0 undefined 0;对于test()执行的这种方式,就是一个方法的调用,此时并没有某个对象去调用这个方法,因此此时this.a指的就是全局变量中的a,因此打印出的就是5。而在new test()就是新建一个对象的过程,在新建对象的过程中,this.a并没有指向的是window对象,也没有指明是当前的实例对象,因此在这里输出的就是undefined
对于数组的去重方式有很多种,这里列举了其中的一些方法,关于去重,自己可以想一下或者去实现自己所能够想到的数组去重的方式
function unique(arr){
return Array.from(new Set(arr));
}
这种方法所实现的去重无法去掉空对象
function unique(arr){
for(let i = 0; i < arr.length - 1; i++){
for(let j = i + 1; j < arr.length; j++){
if(arr[i] == arr[j]){
arr.splice(j, 1);
j--;
}
}
}
}
function unique(arr){
if(! Array.isArray(arr)){
console.log('type error');
return;
}
var array = [ ];
for(let i = 0; i < arr.length; i++){
if(array.indexOf(arr[i]) === -1){
array.push(arr[i]);
}
}
return array;
}
function unique(arr){
if(! Array.isArray(arr)){
console.log('type error');
return;
}
arr = arr.sort(); //使用此方法的目的是为了将重复的内容放在一起
let array = [arr[0]];
for(let i = 1; i < arr.length; i++){
if(arr[i] !== arr[i - 1]){
array.push(arr[i]);
}
}
return array;
}
function unique(arr){
if(! Array.isArray(arr)){
console.log('type error');
return;
}
let array = [ ];
for(let i = 0; i < arr.length; i++){
if(!array.includes(arr[i])){
array.push(arr[i]);
}
}
return array;
}
function unique(arr){
let obj = { };
return arr.filter(function(item, index, arr){
return obj.hasOwnProperty(typeof, item + item) ? false : (obj[typeof item + item] = true)
})
}
使用hasOwnProperty判断是否存在对象属性
function unique(arr){
return arr.filter((item, index, arr) => {
return arr.indexOf(item, 0) === index;
})
}
这里就不全部举例完了,还有一些方式,可以自行参考一些博客
对于深度克隆与浅度克隆,无非就是对于对象的拷贝问题进行的一个探讨,对于基本类型的赋值就是值传递,对于特殊类型对象的赋值时将对象地址的引用赋值,是将对象地址的引用赋值,这时候修改对象中的属性或者值,会导致所有引用的这个对象的值得改变,如果想要真的赋值一个新的对象,而不是复制对象的引用就会用到深拷贝
let str1 = {
a: 1,
b: 2,
c: 3,
d: {
e: 5
}
}
let str2 = str1;
str2.d.e = 4;
console.log(str1,str2)
对于这种形式的对象的拷贝,当更改掉str2中的内容后,会导致str1中的改变
function deepclone(target, origin){
var target = target || {},
toStr = Object.prototype.toString;
arrStr = '[object Array]';
for(var item in origin){
if(origin.hasOwnProperty(item)){
if(origin[item] !== null && typeof(origin[item]) == 'object'){
if(toStr.call(origin) === arrStr){
target[item] = [];
}else{
target[item] = {};
}
deepclone(target[item],origin[item]);
}else{
target[item] = origin[item];
}
}
}
}
即通过多次的传参使得某个函数的参数达到饱和,具体实现思想如下
function FixedParame(fn){
var _args = [].slice.call(arguments, 1);
return function(){
var newArgs = _args.concat([].slice.call(arguments, 0));
return fn.apply(this, newArgs);
}
}
function newCurry(fn, length){
var length = length || fn.length;
return function(){
if(arguments.length < length){
var cobined = [fn].concat([].slice.call(arguments, 0));
return newCurry(FixedParame.apply(this, cobined),length - arguments.length);
}else{
return fn.apply(this, arguments);
}
}
}
其目的就是将几个函数的功能给组合在一起,下面封装一个组合函数,在使用函数时,只需要将函数传入即可
function compose(){
//将类数组转化为数组,这样才能使用数组方法
var args = Array.prototype.slice.call(arguments);
var len = args.length - 1;
return function(x){
var result = args[len](x);
while(len--){
result = args[len](result);
}
return result;
}
}
下面就是封装的一个节流函数
function throttle(handler, wait){
var initTime = 0;
return function (e){
var nowTime = new Date().getTime();
if(nowTime - initTime > wait){
handler.apply(this, arguments);
initTime = nowTime;
}
}
}
下面就是抖动函数的封装实现
function debounce(handler, delay){
var timer = null;
return function(){
var _self = this, _args = arguments;
clearTimeout(timer);
timer = setTimeout(function(){
handler.apply(_self, _args);
},delay);
}
}
函数的扁平化,其目的其实就是将一个多维数组变为一个一维数组的过程。在Array中有一个flat(),其作用就是如此,接下来就手动实现falt()函数
function flatten(){
var arr = arr || [];
var toStr = Object.prototype.toString;
this.forEach(item => {
return toStr.call(item) == '[object Array]' ? arr = arr.concat(item.flatten()): arr.push(item);
})
}
对于了解事件冒泡和事件捕获,其实就是事件流的一个接收事件顺序,而事件流描述的就是从页面中去接收事件的顺序
事件流分为三类:
对于这里需要去加一个问题进行讨论,也是在面试中可能被问道的问题
IE和DOM事件流之间的区别:
是一种用于创建快速动态网页的技术,通过在后台与服务器进行少量数据交换,ajax可以使网页实现异步更新,可以在不重新加载整个页面的情况下,对页面的某个
部分进行更新。
以下的部分是ajax原理实现过程
var xmlhttp;
var xmlhttp;
if(window.XMLHttpRequest){
xmlhttp = new XMLHttpRequest();
}else{
xmlhttp = new ActiveXObject("Microsoft.XMlHTTP");
}
当创建好xmlhttp之后,如果需要将请求放到服务器,需要使用XMLHttpRequest对象的open()和send()方法
对于get和post方法,在使用时的情况是不一样的,大多数的情况下都可以使用get请求,但是在下面的情况中使用post请求
如果需要通过GET方法发送信息,并且向URl添加信息:
xmlhttp.open("GET","demo_get2.asp?fname=Bill&lname=Gates",true);
xmlhttp.send();
如果需要通过POST那样传送数据,还需要对传入的数据做设置,因此就会使用打牌
setRequestHeader()来添加HTTP头
xmlhttp.open("POST","ajax_test.asp",true);
xmlhttp.setRequestHeader("Content-type","application/x-www-form-urlencoded");
xmlhttp.send("fname=Bill&lname=Gates");
属性 | 描述 |
---|---|
onreadystatechange | 存储函数(或函数名),每当 readyState 属性改变时,就会调用该函数。 |
readyState | 存有 XMLHttpRequest 的状态。从 0 到 4 发生变化。0:请求未初始化1: 服务器连接已建立2: 请求已接收3: 请求处理中 4: 请求已完成,且响应已就绪 |
status | 200: “OK”,404: 未找到页面 |
当服务器响应已经做好被处理的准备时,即就有以下的部分
xmlhttp.onreadystatechange=function()
{
if (xmlhttp.readyState == 4 && xmlhttp.status == 200)
{
document.getElementById("myDiv").innerHTML=xmlhttp.responseText;
}
}
在上面的代码中if部分就是表示服务器已经准备好了的描述过程,document.getElementById(“myDiv”).innerHTML=xmlhttp.responseText;中的responseText
表示服务器返回给前端的数据。上面的所有部分就是ajax整个原理的过程,在jq中也有封装好的ajax技术参考https://www.w3school.com.cn/jquery/jquery_ajax_get_post.asp,又或者在react或者是vue中(其依赖包axios,在npm官网上可以查看其用法)
function Person(name, age){
this.name = name;
this.age = age;
}
function Student(school){
this.school = school;
}
Student.prototype = new Person();
const student = new Student();
原型链继承缺点:多个实例对引用类型的操作会被篡改
function Car(name, color, size){
this.name = name;
this.color = color;
this.size = size;
}
function Baoma(name, color, size, model){
Car.call(this, name, color, size);
this.model = model;
}
利用call方法,来实现继承
缺点:
function Animao(type, size, food){
this.type = type;
this.size = size;
this.food = food;
}
Animao.prototype.sayName = funciton(){
console.log(this.name);
}
function Dog(type, size, food, age){
Animao.call(this, type, size, food);
this.age = age;
}
Dog.prototype = new Animao();
缺点:在使用子类创建实例对象时,其原型中会存在两份相同的属性/方法
function object(obj){
function F(){};
F.prototype = obj;
return new F();
}
即object()对传入其中的对象执行了一次浅复制,将构造函数F的原型直接指向传入的对象。
缺点:
var inherit = (function(){
var F = function(){};
return function(Target, Origin){
F.prototype = Origin.prototype;
Target.prototype = new F();
Target.prototype.constructor = Target;
Target.prototype.uper = Origin.prototype;
}
})()
也可以使用下面的方式来实现,,借用构造函数传递参数和寄生模式实现继承
function inheritPrototype(target, origin){
var prototype = Object.create(origin.prototype);
prototype.constructor = target;
target.prototype = prototype;
}
对于图片的懒加载和预加载可参考博客https://juejin.im/post/5b0c3b53f265da09253cbed0
cookie、sessionStorage和localstorage之间的区别
对于缓存其存在的意义在于可以加快相应事件,提高用户的体验,对于一般的html文件,浏览器会自动访问,对于ajax请求所发送的数据,有时也需要缓存,需要注意的是post请求浏览器是不会进行缓存的。
协商缓存:根据前后台状态来判定是否要进行缓存
ETags和If-None-Match
last-Modified和If-Modified-since
http协议规定了浏览器怎样像万维网服务器请求万维网文档,以及服务器怎样把文档传送给浏览器,从层次角度看,其是面向事务的应用层协议,是万维网上能够可靠地传递文件(文本、声音、图像等各种多媒体文件)的重要基础。其协议本身是无连接的,但是其使用了面向连接的TCP作为运输层协议,保证了数据的可靠性传输。
因此http协议在发送请求时,首先需要和服务器建立TCP连接,当建立TCP连接的三报文握手的前两部分完成后,万维网客户就把请求报文,作为TCP连接的三报文握手
中的第三个数据发送给万维网服务器,服务器收到HTTP请求报文后,就把所有请求的文档作为响应报文返回客户。因此整个网络的请求过程可以描述为以下内容:
网络请求的过程:
对于get请求与post请求的常规理解
GET使用URL或Cookie传参,而POST将数据,放在BODY中。
在GET请求中它的参数是要拼接到URl后面的,POST请求它的请求data是拼接在请求主体中
GET的URL会有长度上的限制,POST可以传输很多数据。
GET在长度上有限制是因为请求的data放在URL后面,URL的输入框是有限制的
POST传输的数据也会有一定的限度,因为这是为了安全性的考虑,为了反之恶意攻击
POST比GET安全。
POST请求中的数据是可见的,不要将用户名等私密信息放在POST请求中
http请求中请求报文与响应报文的内容
http(请求报文,响应报文)通过报文进行沟通
请求报文:
请求头 请求行 请求主体
请求头: 在请求头中的内容包括 1. 请求方式 2.请求url 3. http协议及版本
相应报文
响应头 响应行 响应主体
为什么需要头部压缩(header压缩)?
假定一个页面100供资源需要加载,而每次请求都有1kb的消息头,则至少需要消耗100kb来获取消息头,2.0可以维护一个字典,差量更新http头部,大大降低因为头部传输而产生的流量
2.0中多路复用的好处?
http性能优化的关键并不在于高带宽而是低延迟。TCP连接会随着时间进行自我“调谐”,起初会限制连接的最大速度,如果数据成功传输,会隋卓时间的推移,提高传输的速度,在这种调谐则被称为TCP慢启动。由于这种原因,让原本具有突发性和短时性的HTTP连接变得十分低效。2.0通过让所有数据流共用同一个连接,可以有效的使用TCP连接,让高带宽能真正的服务于http的性能
服务器推送是什么?
把客户端所需要的资源伴随着index.html文件一起发送到客户端,省去客户端重新请求的过程
浏览器中有一个很重要的概念–同源策略,所谓的同源是指,域名,协议,端口相同。不同源的客户端脚本在没有明确授权的情况下不能读写对方的资源,只能去访问同源的文件
在https://www.baidu.com/中,其中的http指的是协议,www.baidu.com指的是域名,在.com后面加上:440。这个表示的就是默认的端口号,如果是默认的端口号,则不需要在访问网站的时候去写端口号。
http默认的端口号是:80
https的默认的端口号是:440 https是在http的基础上加了SSL层而形成的,其安全性更高
域名解析,在进行域名解析时,其解析的过程是倒着解析的
一级域名 .com 二级域名 baidu.com 三级域名 zhidao.baidu.com
相关域名代表的含义
com org net 属于顶级域名,是在全世界范围内解析的,cn hk是在一个地区解析的,如:
dns先根据顶级域名判断网络范围再根据域名查找主机ip地址,理论上www开头相当于占用为的 在国外一般不写www
同源策略的解决方式:
服务器代理中转:
首先明白的一点就是同源策略是浏览器与服务器中间存在的,而服务器与服务器之间不存在同源策略问题。因此如果想要实现从浏览器跨域到其他服务器,可以采用的方式是先将浏览器中的请求发送给与自己端口、协议、域名相同的服务器当中,再通过这个服务器与其他服务器进行数据之间的请求,从而就能实现跨域的过程document.domain(针对基础域名相同的情况)采用这种方式去处理跨域问题时,必须有一个要求就是基础域名必须是相同的情况下,才能够用这种方式
JSONP原理:
在这里需要注意就是,在使用src引入文件的时候,src其实不管文件的格式是什么类型,只要文件中含有需要的数据就可以进行引入。采用的jsonp的请求方式都是get请求
跨域资源共享CORS:
注意:CORS需要浏览器和服务器同时支持,目前,所有的浏览器都支持该功能,IE浏览器不能低于IE10
整个CORS通信过程,都是浏览器自动完成,不用用户参与。其余AJAX通信没有区别,代码完全一样,浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,还会多一次附加的请求,但是用户不会有感觉。实现CORS通信的关键是服务器,只要服务器实现CORS接口,就可以实现跨源通信。
两种请求:
浏览器将CORS请求分成两类:简单请求和非简单请求
凡是不满足上面两个条件的就属于非简单请求
首先浏览器需要发送的是那个CORS请求,具体来说,就是在头信息中增加一个Origin字段用于说明本次请求来自哪个源,服务器根据这个值,决定是否同意这次请求
如果Origin指定的源不在许可范围内,服务器会返回正常的Http回应,但是这个回应的头信息没有包含Access-Control-Allow-Origin字段,就会出错
如果成功,那么服务器返回的响应会多出几个头信息字段
非简单请求实现基本流程:
非简单请求是对服务器有特殊要求的请求,比如请求方法是put或者delete,或者Content-Type字段的类型是application/json
预检请求:
非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,即浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词
和头信息字段,只有得到肯定大于,浏览器才会正式的发送XMLHttpRequest请求。
预检请求用的请求方法是options,表示这个请求是用来询问的,除了Origin字段,预检请求得头信息包括两个特殊字段
预检请求的回应:
服务器收到"预检"请求以后,检查了Origin、Access-Control-Request-Method和Access-Control-Request-Headers字段以后,确认允许跨源请求,就可以做出回应。如果否定了预检
请求,会得到一个正常的Http回应,但是没有任何CORS相应的头信息字段,这时在浏览器就会报错,此时服务器回应的CORS字段如下
如果浏览器通过预检,浏览器就会和简单请求一样,会有Origin头信息
在进行项目的开发时,作为一名前端开发人员也需要考虑到网络的安全,作为前端主要涉及到的安全问题就以下两种
可以将xss攻击分为以下两类
五种防御方式:
csrf:跨站点请求伪造,攻击者盗用你的身份,以你的名义发送恶意的请求,从而达到攻击者所期望的一个操作
csrf攻击原理及过程:
csrf的防御方式:
上面大多都是基础部分,都是需要了解的地方。当然还要准备一下算法个人比较推荐的就是力扣还有剑指offer