2020年的春招实习比往年来的更早一些,疫情导致找工作的形势更加严峻,从3月中旬开始投简历,投递了20多家公司,参加了11场笔试,30多场面试,先来说下面试结果:作业帮、京东、快手、抖音、网易雷火的offer,其中网易雷火给的是精英offer,入职抖音之后收到了微软的面试,于是也参加了一下,其实这时候面试结果已经没那么重要了,重要的是重新开始,为秋招做准备。
以下是我在面试中遇到的一些题目,整理出来与大家共享。
一、JavaScript
1. JS的基本数据类型
Boolean, String, Number, Null, Undefined, Symble, BigInt, Object
2. 原始值与引用值类型及区别
类型:
原始值:null, undefined, string, number, boolean, symbol, bigint
引用值:Object, Array, Function, RegExp, Date
原始类型就是数据本身无法被改变,JavaScript 中的字符串就是原始类型,如果你修改了 JavaScript 中字符串的值,那么 V8 会返回给你一个新的字符串,原始字符串并没有被改变,我们称这些类型的值为“原始值”。
区别:
1. 存储位置不同--栈和堆
原始类型占据的空间是固定的,存储在较小的内存区域-栈中,便于快速查找变量值;
引用类型存储在堆中,该对象在堆中的地址存放在栈中
2. 赋值方式
原始值赋值:拷贝值的副本给新的变量,两个变量相互独立,改变其中一个值不会影响另外一个;
引用值赋值:将引用传递给新的变量,实际上是将地址引用赋值给新的变量,两个变量有相同的地址,指向堆中的同一块区域,改变其中一个的值,另外一个随之改变
3. 比较方式
原始值是值的比较,引用值是引用(变量地址)的比较
3. 如何判断数据类型
有以下四种方式:typeof、instanceof、constructor、Object.prototype.toString.call()
- typeof
typeof用于判断原始值和引用值的类型: 对基础类型有效,对引用类型失效
- typeof number/string/boolean/symbol/undefined // 返回对应类型
- typeof 引用值 // 返回 ‘object’
- 除了 Function 外的所有构造函数的类型都是 ‘object’
- typeof null // ‘object’ 遗留的 bug
- instanceof
instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上,不能判断 null和undefined
- 区分字面量和实例对象:
- 字面量:var XXX = ‘yyy’ // ‘yyy’就是一个字面量: instanceof不能正确判断字面量类型
- 实例对象:通过构造函数创建出来的对象为实例对象,字面量 -> 实例对象:new Array(xxx)
注意:如果 obj instanceof Foo 返回true,也不意味着永远返回 true,因为Foo.prototype属性的值有可能会改变,实例对象 obj 的原型链也可能会改变
- constructor属性
返回创建此对象的数组函数的引用
用 constructor 判断类型的缺陷:如果先创建一个对象,更改它的原型,之后创建该对象的实例,这种方式就不可靠了
- Object.prototype.toString.call()终极大法
- 返回一个字符串,字符串是一个数组的形式,数组的第二个参数就是变量类型
4. 类数组与数组的区别及转换
类数组:通常把拥有数值length属性和对应非负整数属性的对象看成一种类型的数组,不能直接调用Array所具有的方法
常见的类数组:arguments和DOM方法返回的元素筛选(getElementsByTagName, getElementsByClassName)
举个栗子:
- 对象具有length属性,可遍历,两个条件缺一不可
- var a = {0: "a", "1": "b", "2": "c", length: 3}
类数组转为数组:
// 方法一:最原始的方法:借用一个空数组,遍历
var eles = document.getElementsByTagName('div')
var res = []
for(let i = 0; i < eles.length; i++) {
res.push(eles[i])
}
// 方法二:Array.prototype.slice.call(eles)
var res = Array.prototype.slice.call(eles);
// 方法三:Array.from(eles)(有length属性、是可遍历对象,缺一不可)
var res = Array.from(eles);
// 方法四:Array.prototype.map.call()
var res = Array.prototype.map.call(a, ele => {
return ele;
});
// 方法五:扩展运算符[...eles](有iterator接口的对象才可以用)
var res = [...eles];
5. 数组常见API
考察点:熟悉常见的API并了解其用法
注意:map、reduce、flat、slice、push、pop、shift、unshift、splice等常用的重点了解
concat、every、fill、filter、find、findIndex、flat、forEach、indexOf、join、keys、map、push、pop、reduce、reverse、shift、unshift、slice、splice
6. bind、call、apply的区别,手动实现
- bind()方法创建一个新的函数,新函数的 this 指定为 bind() 的第一个参数,其余参数作为新函数的参数,以参数列表的形式传递参数(参数为调用bind方法的参数)
- apply()方法调用一个具有给定 this 值的函数,参数为数组的形式
- call() 方法与 apply 类似,不同的是参数是以参数列表的形式传递
Function.prototype.myCall = function(context) { // foo.myCall(obj)
var context = context || window;
context.fn = this; //this就是函数fn
var args = [...arguments].slice(1);
var result = context.fn(...args);
delete context.fn;
return result;
}
Function.prototype.myApply = function(context){
var context = context || window;
context.fn = this;
var _args = [...arguments].slice(1);
var result = context.fn(_args);//绑定上下文,这不就是绑定上下文吗
delete context.fn;
return result;
}
Function.prototype.myBind = function(context) {
var context = context || window;
context.fn = this; //this就是函数fn
var args = [...arguments].slice(1);
return function() {
var result = context.fn(...args);
delete context.fn;
return result;
}
}
function foo() {
console.log(this.name)
}
var obj = {
name: 'alice',
age: 17
}
foo();
foo.myCall(obj)
foo.myApply(obj)
foo.myBind(obj)()
> "jenny"
> "alice"
> "alice"
> "alice"
7. new的原理,手动实现
new 一个构造函数,相当于实例化一个对象,总共分四步:
- 1)创建一个全新的对象,即:var o = {}
- 2)新对象被执行原型连接,将 o 对象的 proto 指向构造函数 Person 的原型对象(Person.prototype)
- 3)将新对象绑定到构造函数调用的 this,即将 o 作为 this 调用构造函数 Person,从而设置 o 的属性和方法并初始化
- 4)返回构造函数中的返回值,如果没有,则返回新对象
代码实现:
function _new() {
let o = {};
let _self = Array.from(arguments).slice(0, 1)[0];
let _args = Array.from(arguments).slice(1);
o.__proto__ = _self.prototype;
_self.apply(o, _args);
return o;
}
function Person(name, age){
this.name = name;
this.age = age;
}
Person.prototype.sayhi = function(){
console.log('hi', this.name)
}
var p = _new(Person, 'lucy', 12);
p.sayhi(); // hi, lucy
8. 如何正确判断this
- 1)通过 new 创建的实例对象,this 绑定到新创建的对象
- 2)call bind apply,绑定到指定的对象
- 3)上下文调用,绑定到上下文对象上
- 4)都不是,则为默认绑定。严格模式下,绑定到 undefined ,否则绑定到全局对象
- 5)箭头函数根据外层函数的作用域来决定 this (箭头函数定义时所在的作用域)
箭头函数中的 this 是函数定义时所在的对象(有作用域的算,没有作用域的不算),而不是函数运行时所在的对象
箭头函数 this 是指向上级的,上级指的是函数作用域,是非箭头函数被 function(){ } 的 {} 包裹的作用域,包裹一次就是一级,顶层是 window 箭头函数的 this 指向其外面的那个非箭头函数的 this
级是个比较笼统的说法,其中一个指的是函数作用域,箭头函数 this 指向上级是指这个级
// this 指向 window
function a() {
// this 指向 a 1级
function b() {
// this 指向 b 2级
}
}
// 还有一个级是指Object 对象嵌套层级
var a = {
// 1级
o: {
// 2级
b: () => {
console.log('this is', this)
}
}
}
9. 闭包及其作用
在JavaScript中,根据词法作用域规则,内部函数总是可以访问其外部函数中声明的变量,当通过调用外部函数返回一个内部函数之后,即使外部函数执行结束了,内部函数中引用的外部函数的变量仍然在内存中,这些变量的集合就是闭包。
function foo(){
var name = 'foo'
var obj = {
getName: function(){
return name;
},
setName(val) {
name = val;
}
}
return obj;
}
var obj = foo();
console.log(obj.getName())
闭包的作用:
- 1.避免全局污染 大量使用全局变量会耗费较高的性能,将变量定义在外层函数中作为内部数据,通过闭包将变量返回给全局变量,降低全局污染概率
- 2.缓存数据 比如用闭包实现斐波那契数列
// 0, 1, 1, 2, 3, 5, 8...
var fib = (function() {
var arr = [0, 1, 1];// 也必须得是3个
return function(n){
var res = arr[n];
if (res) {
return res;
} else {
arr[n] = fib(n-1) + fib(n-2);
return arr[n];
}
}
})()
console.log(fib(2))
console.log(fib(5))
- 3.高阶函数(柯里化)
实现一个add函数,能通过传递一个参数的形式调用,实现加和功能 add(1)(2) // 3 add(1)(2)(3) // 6
//实现一个add功能
function add(a) {
let fn = function(b) {
a = a + b;
return fn;
}
fn.toString = function() {
return a;
}
return fn;
}
add(1); // 1
add(1)(2); // 3
add(1)(2)(3) // 6
add(1)(2)(3)(4) // 10
function add(){
let _outer = [...arguments];
let fn = function(){
let _inner = [...arguments];
return add.apply(null, _outer.concat(_inner));
}
fn.toString = function(){
return _outer.reduce((a,b)=>a+b);
}
return fn;
}
add(1); // 1
add(1)(2); // 3
add(1)(2)(3) // 6
add(1)(2, 3); // 6
add(1, 2)(3); // 6
add(1, 2, 3); // 6
- 4.回调函数传参
for(var i = 0; i < 5; i++) {
(function(i){
setTimeout(() => {
console.log(i)
}, 0)
})(i)
}
> 0
> 1
> 2
> 3
> 4
10. 原型和原型链
原型:每个函数都有一个prototype属性,这个属性指向函数的原型
以构造函数Person为例,Person为构造函数,Person.prototype是函数原型,挂载着Person的属性、方法等
理解原型链,首先理清构造函数、原型、实例的关系
每个构造函数都有一个原型对象,原型对象包含一个指向构造函数的指针,实例包含一个指向原型对象的内部指针。假如让原型对象作为另一个类型的实例,此时原型对象将包含一个指向另一个原型的指针,另一个原型也包含一个指向另一个构造函数的指针,假如另一个原型又是另一个类型的实例,上述关系仍然成立,如此层层递进,就构成了实例与原型的链条,这就是所谓原型链的基本概念。
Object.prototype 的原型为 null, 所以查找属性或方法的时候查找到Object.prototype就可以停止查找了
关系图:
11. 继承的实现方式及比较
继承的方式有:原型链继承、借用构造函数、组合继承、原型继承、寄生继承、寄生组合继承、增强型寄生组合继承
放一个链接供参考
12. 深拷贝与浅拷贝
浅拷贝:是拷贝第一层的拷贝
- 通过Object.assign实现浅拷贝
var a = {name: 'lucy', age: 20}
var b = Object.assign({}, a)
a.age = 2
console.log(b.age) // 20
- 通过展开运算符...来实现浅拷贝
let a = {
age: 1
}
let b = {...a};
a.age = 2;
console.log(b.age) // 1
浅拷贝不能解决的问题
let a = {
name: 'lucy',
others: {
title: 'student',
level: 666
}
}
let b = {...a};
a.others.title = 'teacher';
console.log(b.others.title) // teacher
深拷贝:解决对象的嵌套拷贝问题
function deepClone(obj) {
let types = new Set(['boolean', 'string', 'number', 'undefined']);
let _type = typeof obj;
if (_type in types || obj === null) {
return obj;
}
let objClone = Array.isArray(obj) ? [] : {};
for(let key in obj) {
if (obj.hasOwnProperty(key)) {
objClone[key] = (typeof(obj[key]) === 'object') ? deepClone(obj[key]) : obj[key]
}
}
return objClone;
}
let a = {
name: 'lucy',
others: {
title: 'student',
level: 666
}
}
let b = deepClone(a);
a.others.title = 'teacher';
console.log(b.others.title) // student
13. 防抖和节流
防抖和节流都是为了解决短时间内大量触发某函数而导致的性能问题
- 防抖:在事件被触发 n 秒后执行回调函数,如果 n 秒内又被触发,则重新计时
- 说明:防抖函数适用于更关注结果的操作,不太关注操作过程,常见的事件有input、keyup等
使用场景:用户在输入框中连续输入,输入完成后执行最后一次的查询请求;百度输入框在输入稍有停顿时才更新推荐热词;
function debounce(handler, delay) {
var time;
delay = delay || 1000
return function(){
var _args = arguments
var _self = this
time && clearTimeout(time)
time = setTimeout(() => {
handler.call(_self, _args)
}, delay)
}
}
- 节流:函数在大于执行周期时才执行,周期内调用不执行
- 说明:节流函数限制目标函数的执行频率,适用于关注变化过程的操作,调整目标函数执行频率使得变化更加平滑,常用事件resize、scroll、mouseWheel、touchmove、mouseover等
应用:鼠标不断点击触发,mousedown单位时间只触发一次
function throttle(handler, delay) {
var lastTime = Date.now()
return function(){
var _args = arguments
var _self = this
var curTime = Date.now()
if (curTime - lastTime >= delay) {
handler.apply(_self, _args)
lastTime = Date.now()
}
}
}
加强版
- 多次触发操作,最后一次的操作时间不够间隔,因此不会触发操作
- 要求:最后总会执行一次
function throttle(handler, delay) {
var lastTime = Date.now()
var id;
return function(){
var _args = arguments
var _self = this
var curTime = Date.now()
if (curTime - lastTime >= delay) {
if (id){
clearTimeout(id);
}
handler.apply(_self, _args)
lastTime = Date.now()
} else {
id && clearTimeout(id);//把上一次的定时器清除
id = setTimeout(() => {
handler.apply(_self, _args)
}, 2000);
}
}
}
14. 作用域和作用域链
- 作用域是指程序中定义变量的区域,该位置决定了变量的生命周期;通俗地讲,作用域就是变量与函数的可访问范围
ES6之前,ES的作用域只有全局作用域和函数作用域;ES6引入了块级作用域
- 内部函数使用了外部变量时,JavaScript引擎会去外层作用域中查找,这个查找的链条就称为作用域链
15. JS的垃圾回收机制
javascript中的内存管理是自动执行的,而且是不可见的
- 可达性:以某种方式可以访问或者可以用的值,被保证存储在内存中
- 根:有一些基本的固有可达值,由于显而易见的原因无法删除
- 本地函数的局部变量和参数
- 当前嵌套调用链上的其他函数的变量和参数
- 全局变量
- 其它...
- 可访问性:如果引用或引用链可以从根访问其他值,则认为该值是可访问的
内部算法:基本的垃圾回收算法称为“标记-清除”
- 垃圾回收器获取根并“标记”它们
- 访问并标记所有来自根的引用
- 访问标记的对象并标记其引用。
- 以此类推,直到有未访问的引用为止
- 除了标记对象,所有对象都被删除
javascript引擎在垃圾回收的优化:
-
分代回收:对象分为“新对象”和“旧对象”,新对象出现,工作结束后就会被清理干净,存在时间长的对象,就会很少接受检查
-
增量回收:将垃圾回收分解为多个部分,分别执行 空闲时间收集:垃圾回收器只在CPU空闲时运行
垃圾:没有被引用的对象或者有对象引用,但对象之间为相互引用,根访问不到
如何回收:标记-清除算法
16. addEventListener和onClick区别
onClick:
- onClick: 给元素多次绑定onclick事件,只有最后一次绑定会被触发,之前的会被覆盖;
- onclick事件只在冒泡阶段捕获;
addEventListener:
- addEventlistener绑定多次,就会触发多次;
- 可以通过设置参数,来规定事件在冒泡期间和捕获期间触发;
- 对任何DOM元素都有效,不只针对HTML元素有效
17. 说下浏览器对象(BOM)有哪些
- window
- location
- navigator
- history
- screen
18. 跨域、同源策略以及跨域解决方案
跨域:协议、域名、端口三者有一个不同即为跨域,跨域是浏览器的一种安全策略 跨域方案
一、JSONP
说明:JSONP方法是利用script标签的src属性不受同源策略影像的特性,拥有此类特性的标签还有img, iframe,link
缺点:需要服务器端支持;只能发起get请求
- 在客户端定义一个回调方法,预定义对数据的操作;
- 把回调方法的名称通过URL传参的形式,提交到服务器的数据接口;
- 服务器数据接口组织好要发送给客户端的数据,再根据回调方法名称,拼接出一个调用这个方法的字符串,发送给客户端;
- 客户端收到字符串之后,当做script脚本解析执行,获取数据
二、CORS(Cross-origin resource sharing)
服务器端设置响应头,控制是否允许跨域
优点:服务器端控制;支持各种请求方式
缺点:会产生额外请求(option预检请求)
三、 document.domain
只能用于二级域名相同的情况下,比如a.test.com和b.test.com 只需要给两个页面都添加document.domain=’test.com’,表示二级域名相同就可以实现跨域; 比如在域a.test.com中的一个网页a.html引入了b.test.com中的一个网页b.html,此时a.html不能操作b.html,因为document.domain不一样,在两个页面中加入document.domain=’test.com’就可以相互访问了
注意:二级域名必须相同,协议、端口要一致,否则无法使用document.domain跨域
四、 postMessage
html5引入的postMessage()方法允许来自不同源的脚本采用异步方式进行有限的通信,可以实现跨文档、多窗口、跨域消息传递
postMessage(data,origin)
data: 要传递的数据;origin:目标窗口的源(协议+主机+端口号)
五、 postMessage和onMessage
场景:可以用于iframe与父容器传递消息
六、 Nginx反向代理
反向代理:客服端访问服务器受限,通过设置中间代理,中间代理与服务器通信,再将响应发送给客户端;过程中客户端不知道中间代理的存在
19. JavaScript中的事件循环机制,宏任务与微任务
javascript是一门单线程语言,单线程就意味着在同一时刻只能有一个任务被执行,前一个任务结束,才会执行后一个任务。前一个任务耗时很长,后一个任务就不得不等待。
如果排队是因为计算量大,CPU忙不过来倒也算了,但很多时候CPU是空闲的,因为IO设备很慢,不得不等待结果输出,再往下执行。
JavaScript语言的设计者意识到,此时主线程完全可以不管IO设备,挂起处于等待中的任务,先处理排在后面的任务,等到IO设备返回了结果,再回来继续执行刚刚挂起的任务。
于是,所有任务可以分为两种,一种是同步任务,一种是异步任务。同步任务指的是,在主线程上排队执行的任务,只有当前一个任务执行完毕,才能执行后一个任务;异步任务指的是,不进入主线程、而进入“任务队列”(task queue)的任务,只有“任务队列”通知主线程,异步任务可以执行了,该任务才会进入主线程执行。
- 同步任务和异步任务分别进入主线程和Event Table并注册函数
- 当 Event Table 中指定的事情完成时,会将这个函数移入 Event Queue
- 主线程上执行栈为空,会去 Event Queue 中读取对应的函数,进入主线程执行
- 上述过程不断重复,也就是事件循环(Event Loop)
javascript引擎存在mointoring process进程,会持续不断地检查主线程执行栈是否为空,一旦为空,就会去Event Queue中检查是否有等待被调用的函数。
JavaScript中的宏任务与微任务
JavaScript除了广义上的同步任务异步任务,其对任务还有更精细的定义:
- 宏任务(macro-tack):包括整体代码script, setTimeout, setInterval
- 微任务(micor-tack):有Promise, process.nextTick
事件循环的顺序,决定js代码的执行顺序。
进入整体代码(宏任务)后,开始第一次循环,接着执行所有的微任务,完成第一轮循环;
接着从宏任务开始,找到其中的一个任务队列,执行完毕,再执行所有的微任务;
如此循环往复。
以百度春招面试题为例
console.log(1)
setTimeout(function() {
console.log(2)
}, 0);
const intervalId = setInterval(function() {
console.log(3)
}, 0)
setTimeout(function() {
console.log(10)
new Promise(function(resolve) {
console.log(11)
resolve()
})
.then(function() {
console.log(12)
})
.then(function() {
console.log(13)
clearInterval(intervalId)
})
}, 0);
Promise.resolve()
.then(function() {
console.log(7)
})
.then(function() {
console.log(8)
})
console.log(9)
-
第一轮事件循环流程:
-
整体script作为第一个宏任务进入主线程,遇到console.log,输出1
-
遇到setTimeout, 其回调函数被分发到宏任务Event Queue中,记为setTimeout1,
-
遇到setIntrval,其回调函数被分发到宏任务Event Queue中,记为setInterval1,
-
遇到setTimeout,其回调函数被分发到宏任务Event Queue中,记为setTimeout2,
-
遇到Promise.resolve,then被分发到微任务Event Queue中,记为then1
-
遇到console.log,输出9
此时的Event Queue:
宏任务 微任务 setTimeout1 then1 setInterval1 setTimeout2 - 上表是第一轮事件循环宏任务结束时各Event Queue的情况,此时已经输出了1和9 继续执行微任务,执行then1,输出7,8
-
第一轮事件循环正式结束,输出结果是1,9,7,8
-
第二轮事件循环从setTimeout1开始 首先输出2,此时没有微任务,继续执行下一个宏任务
-
第三轮事件循环从setInterval1开始 输出3,此时没有微任务,继续执行下一个宏任务
前三轮输出结果为1,9,7,8,2,3
-
第四轮事件循环从setTimeout2开始
- 首先输出10,遇到了promise,首先输出 11
- then被分发到微任务Event Queue中,记为then2
任务类型 任务名称 微任务 then2 -
继续执行微任务then2,输出12,13,setInterval被清除
最终输出结果为1,9,7,8,2,3,10,11,12,13
20. 函数柯里化、实现一个add方法
function add(){
let _outer = [...arguments];
let sum = function(){
let _inner = [...arguments];
_outer = _outer.concat(_inner);
return sum;
}
sum.toString = function(){
return _outer.reduce((prev, cur) => {
prev = prev + cur;
return prev;
}, 0)
}
return sum;
}
21. "==="和"=="区别
"==="不会进行类型转换,如果类型不同,直接返回false;也就是说,"==="只有在两个运算数类型一致且拥有相同的值,才会返回返回true
"=="进行比较的时候会强制转换值的类型,转换规则复杂难以记忆
console.log('' == '0') // false
console.log(0 == '') // true
console.log(0 == '0') // true
"==" 运算符缺乏传递性,因此建议永远不要使用"==",始终使用"==="和"!=="
百度面试题:判断运行结果
String('11') == new String('11');
String('11') === new String('11');
答案是true, false
String("11") 类型是 string, new String("11")类型是 object,二者虽然值相同,但类型不同
非原生值的比较,比如对象(函数和数组),是比较引用是否匹配,而不关心引用的值是什么,所以"=="和"==="比较只是简单地检查这些引用是否匹配
var a = [1,2,3]
var b = [1,2,3]
var c = "1,2,3"
console.log(a == c)
console.log(b == c)
console.log(a == b)
> true
> true
> false
二、ES6
1.ES6新特性有哪些(★★★★★)
ES6新特性就包括以下介绍的内容
2.let、const、var的区别(★★★★)
var 是声明变量的一种方式,可选择性地为变量赋值;
let 和 const 都是 ES6 新增的声明方式,分别用于声明一个变量和一个常量
let V.S. var:
- let不存在变量提升
- let存在暂时性死区 (let命令声明之前,该变量不可用)
- let不允许重复声明
- let在声明的块作用域内有效
const用于声明一个只读常量,一旦声明,该值无法改变,要求在声明的时候赋值;与let一样,不存在变量提升、存在暂时性死区、块级作用域、不允许重复声明;
const声明的常量无法改变,是指变量指向的地址无法改变,对于简单类型(数值、字符串、布尔)来讲,值就保存在变量指向的那个地址,等同于常量;对于符合类型(对象、数组),const只能保证指针固定,至于指针指向的数据结构,就无法控制了
3.变量的解构赋值(★★)
ES6中按照一定模式,从数组和对象中提取值,为变量进行赋值的过程为解构赋值
包括数组、对象、字符串、数值和布尔值、函数参数等的解构赋值
let [x, y = 'b'] = ['a', undefined]; // x='a', y='b'
let { bar, foo } = { foo: "aaa", bar: "bbb" };
const [a, b, c, d, e] = 'hello';
解构赋值的应用:
- 交换变量值
[x,y] = [y, x]
- 从函数返回多个值
function example(){
return [1,2,3]
}
[a, b, c] = example()
- 用于函数参数,将变量名与函数参数对应起来
function f([x, y, z]){
// 函数体
...
}
f({x: 1, z: 3, y: 2}) // 参数无序
- 指定参数的默认值
// 参数 b 指定默认值 1
function foo(a, b = 1) {
return a * b;
}
console.log(foo(3, 2))
console.log(foo(3))
- 提取json数据
let jsonData = {name: 'alice', age: 23, data: [12, 45]}
let {name, age, data: number} = jsonData
console.log(name, age, number) // "alice" 23 Array [12, 45]
- 遍历 Map 结构
let map = new Map();
map.set('name', '翠芝')
map.set('city', '上海')
map.set('lover', '世均')
for(let [key, value] of map) {
console.log(`key=${key}, value=${value}`)
}
> "key=name, value=翠芝"
> "key=city, value=上海"
> "key=lover, value=世均"
// 获取键名
for(let [key] of map) {
// ...
}
// 获取键值
for(let [value] of map) {
// ...
}
- 模块按需导入
const {SourceMapConsumer, SourceNode} = require("source-map")
4.箭头函数及其this指向(★★★★★)
ES6新增了箭头函数:
(x, y) => {return x+y}
-
只有一个参数时,圆括号可省略;
-
只有一个语句表达式时,花括号可省略
箭头函数与普通函数的区别:
- 箭头函数中的 this 是函数定义时所在的对象,而不是函数运行时所在的对象
- 不可以作为构造函数,因为箭头函数的原型为 undefined
var foo = (name, age) => {
let obj = {};
obj.name = name,
obj.age = age
return obj
}
var p = new foo('lucy', 12) // Uncaught TypeError: foo is not a constructor
foo.prototype === undefined // true
- 不可以使用 arguments 对象,可以用 rest 参数代替
const sum = (...args) => {
let total = 0;
args.forEach(item => {
total += item
})
return total;
}
res = sum(1,2,3)
console.log(res) // 6
- 箭头函数不可以使用 yield 命令,因此箭头函数不能用作 Generator 函数
5.Symbol概念及作用
-
- Symbol是ES6新增的基础数据类型,不能用new来实例化
let s1 = Symbol()
-
- 每个Symbol实例都是唯一的,比较两个Symbol实例的时候,总会返回false
-
- Sybmol使用场景
(1) 使用Symbol作为对象属性名
let obj = {
[Symbol('name')]: 'lucyzhao',
age: 18,
title: 'Engineer'
}
Object.keys(obj) // ['age', 'title']
for (let p in obj) {
console.log(p) // 分别会输出:'age' 和 'title'
}
Object.getOwnPropertyNames(obj) // ['age', 'title']
由上可知,Symbol类型的key是不能通过Object.keys()或者for...in来枚举的,利用该特性,可以把一些不需要对外操作和访问的属性使用Symbol来定义 (2)使用Symbol来替代常量,不用为命名烦恼 (3)使用Symbol定义类的私有属性/方法
传送门 => 理解和使用Symbol
6.Set和Map数据结构
- ES6新增数据结构Set,它类似于数组,但成员的值唯一
const s = new Set();
s.add(1);
s.add(2);
s.add(3);
for(let ele of s) {
console.log(ele)
} // 1 2 3
s.delete(2)
- ES6提供了Map数据结构,是键值对的集合,各种类型的值都可以作为键;
相对于Object结构提供了“字符串-值”的对应,Map结构提供了“值-值”的对应,是一种更完善的Hash结构实现
const map = new Map();
map.set("name", 'alice');
map.set("age", 24);
for(let [key, value] of map) {
console.log(`${key}=${value}`)
}
输出:
name=alice
age=24
7.Proxy
Proxy用于修改某些操作的默认行为,可以理解为,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截.
Vue3.0中采用Proxy代替Vue2.0中的Object.defineProperty进行数据劫持
var obj = new Proxy({}, {
get: function(target, property) {
if (property in target) {
return target[property];
} else {
throw new ReferenceError(`Property ${property} doesn't exist!`)
}
},
set: function(obj, prop, value) {
if (prop === 'age') {
if (!Number.isInteger(value)) {
throw new TypeError('not an integer')
}
if (value > 200){
throw new RangeError('invalid input')
}
obj[prop] = value;
}
obj[prop] = value; // 挂载其它属性
}
})
obj.age = 100;
obj.name = 'alice'
for(let key in obj) {
if (obj.hasOwnProperty(key)) {
console.log(key,'=',obj[key])
}
}
age = 100
name = alice
8.Promise A+规范
Promise是JS中的异步编程方案,主要用于解决回调地狱问题
- Promise是一个构造函数,new Promise返回一个promise对象,构造函数接收一个executor执行函数作为参数,executor有两个形参resolve, reject;
- promise有三种状态: pending, fulfilled, rejected,
- 只能由pending=>fulfilled/rejected,一旦修改就不能改变
promise对象方法:
- then方法注册,当resolve/reject的回调函数;
- then方法异步执行, resolve成功onFulfilled会被调用,reject失败onRejected会被调用
- then方法每次调用都会返回一个新的promise对象,所以可以链式写法
- promise.catch,用于捕获异常,如果发生异常时有相应的onRejected函数接收,那么catch就捕获不到
var p = new Promise((resolve, reject) => {
reject('failed');
} )
p
.then(value=>{
console.log(value)
}, err => {
console.log(err) // "failed"
})
.catch(err=>{
console.log('catch ', err)
})
Promise的静态方法
-
- Promise.resolve 返回一个fulfilled状态的promise对象
-
- Promise.reject 返回一个rejected状态的promise对象
-
- Promise.all 接收一个promise对象数组为参数,只有全部resolve回调完成;如果参数中promise有一个失败(rejected),此实例回调失败
-
- Promise.race(iterable) 方法返回一个promise,一旦迭代器中的某个promise解决或拒绝,返回的promise就会解决或拒绝。
9.循环语法比较以及使用场景(for, forEach, for...in, for...of)
-
for 按顺序遍历,遍历的对象有 length 属性
var arr = [233, 666, 2333]
for(var index = 0; index < arr.length; index++) {
console.log(arr[index])
}
-
forEach: for循环的缺陷在于写法比较麻烦,因此数组提供了内置的 forEach 方法
arr.forEach((val, index) => {
console.log(index,val)
})
0 233
1 666
2 2333
forEach循环的缺陷是无法中途跳出循环,break或return都不能奏效
-
for...in 语句主要是为遍历对象而设计的,可以用来遍历对象的所有属性名,也会遍历出原型链上的成员属性;属性名出现的顺序是不确定的
实现只遍历自身属性:
for(let prop in obj) { if (obj.hasOwnProperty(prop)) { //... } }
for...in 循环缺点:
- 1.数组的键名是数字,但是 for...in 循环是以字符串作为键名"0", "1", "2"等
- 2.for...in循环不仅遍历数字键名,还会遍历手动添加的其它键,包括原型链上的键
- 3.某些情况下,for...in循环会以任意顺序遍历键名
Note: 其实
for...in
在遍历对象属性时,也并不是以任意顺序遍历的,整体上是数字类型的属性会先输出,升序排序,字符串属性按书写的顺序输出var obj = {}; obj[100] = '100' obj[1] = '1' obj['B'] = 'B' obj['A'] = 'A' obj['C'] = 'C' obj[5] = '5' obj[101] = '101' for(key in obj) { console.log(`key=${key}, value=${obj[key]}`) } key=1, value=1 key=5, value=5 key=100, value=100 key=101, value=101 key=B, value=B key=A, value=A key=C, value=C
-
for...of
ES6引入了for...of循环,作为遍历所有数据结构的统一的方法; 一个数据结构部署了 Symbol.iterator 属性,就被视为具有 iterator 接口,就可以用 for...of 循环遍历成员 for...of 循环适用范围:数组、Set和Map结构、类数组对象、字符串等;
for...of 特点:
> for...of语法简洁,但没有for...in的缺点; > 不同于forEach,for...of可以与break, continue, return配合使用; > 提供了遍历所有数据结构的统一操作接口;
var arr = [3,5,7] arr.bar = "hello, world" for(let i in arr) { console.log(typeof i) // '0' '1' '2' 'bar' } for(let i of arr) { console.log(i) // 3 5 7 }
10.Generator及其异步方面的应用
function * test(a, b){
var cur = 1;
cur += a;
yield cur;
cur += b;
yield cur;
return 'ok';
}
var gen = test(2, 3);
gen.next(); // {value: 3, done: false}
gen.next(); // {value: 6, done: false}
gen.next(); // {value: "ok", done: true}
11.async和await
async函数就是Generator函数的语法糖
- 1.async函数返回一个promise对象,async函数内部 return 语句返回的值,会成为 then 方法回调函数的参数
async function test(){
return 'hello, world'
}
test().then(val => console.log(val))
hello, world
- 2.async函数内部抛出错误,会导致返回返回的Promise对象变为 reject 状态。抛出的错误对象会被 catch 方法回调函数接收到
async function test(){
throw new Error('something went wrong')
}
test().then(
val => console.log(val),
error => console.log(error)
)
Error: something went wrong
- 3.async函数内部的异步操作执行完,才会执行 then 方法指定的回调函数
async function getText(){
return 'text';
}
async function getData(){
return 'data';
}
async function test() {
let res1 = await getText();
let res2 = await getData();
return [res1, res2];
}
test().then(val => console.log(val))
["text", "data"]
test中的两个操作:getText, getData都完成之后,才会执行then方法里面的 console.log
- 4.只要一个 await 语句后面的Promise变为 reject,那么整个 async 函数都会中断执行;如果希望前面的异步操作失败不会影响后续,那么可以将await放在try...catch结构里面,这样后续的await总会执行;或者await后面的Promise对象再跟一个 catch 方法,处理可能出现的错误
async function f() {
try {
await Promise.reject('something went wrong')
} catch(e) {} // 去掉catch,后续的await语句不会执行
return await Promise.resolve('hello, world')
}
f()
.then(v => console.log(v))
hello, world
或
async function f() {
await Promise.reject('something went wrong')
.catch(e => console.log(e));
return await Promise.resolve('hello, world')
}
f()
.then(v => console.log(v))
something went wrong
hello, world
async函数注意事项:
- 1.async 函数的实现原理,就是将 Generator 函数和自动执行器,包装在一个函数里
- 2.await命令后面的 Promise 对象,最好加入错误处理机制
- 3.多个 await 命令后面的异步操作,如果不存在继发关系,最好同时触发,减少等待时间(await Promise.all([getRes1(), getRes2()])
- 4.await命令只能用在 async 函数之中,如果用在普通函数中,就会报错
12.class基本语法及继承
class Point{
constructor(x, y) {
this.x = x;
this.y = y;
}
static hello(){
console.log('hello, world!!!')
}
toString(){
return `${this.x}, ${this.y}`
}
}
class ColorPoint extends Point{
constructor(x, y, color) {
//1. 子类必须在constructor方法中调用 super 方法,否则创建实例时就会报错
//是因为子类没有自己的 this 对象,而是继承父类的 this 对象
//2. 子类的构造函数中,只有调用 super 之后,才可以使用 this 关键字
//是因为子类实例的构建,是基于对父类实例进行加工,只有 super 方法才能返回父类实例
super(x, y);
this.color = color;
}
toString(){
return `${this.color}, ${super.toString()}`;
}
}
var cp = new ColorPoint(14, 16, 'red');
console.log(cp)
ColorPoint.hello()
13.ES6模块加载和CommonJS加载的原理、区别
CommonJS: require引入, module.exports = {}导出 1.基本数据类型,属于复制,在另一个模块中改变值,原文件不受影响
2.复杂数据类型,属于浅拷贝,在另一个模块中改变值,原文件会响应改变
3.使用require加载某个模块时,就会运行整个模块
4.只需要require一次,因为模块内容会被缓存;若要重新加载,需要手动清除系统缓存
5.循环加载时,属于加载时执行,模块一旦被require就会全部执行;a.js与b.js循环引用,运行a.js,脚本执行到require(‘./b.js’)时就会执行b.js,如果a还未导出,b.js中对a的引用全部为undefined
ES6 Module: import引入 1.对于只读基本数据类型,不允许修改
2.对于动态来说,原始值发生变化,import加载的值也会变化,不论是基本数据类型还是复杂类型
3.循环加载时,ES6模块是动态引用,存在变量提升,函数会被提升到顶部
三、HTML
1.H5的语义化标签以及语义化作用
语义化标签:header, footer, nav, article, aside, section,mark,details,summary
作用: 提高开发的易读性和可维护性;提高可访问性,有利于SEO
2.Web Worker和Web Socket
Web Worker: 为JavaScript创造多线程环境
Web worker的作用,就是为JavaScript创造多线程环境,允许主线程创建worker线程,将一些任务分配给后者运行,worker线程可以负担一些计算密集型或高延迟的任务
Worker 线程一旦新建成功,就会始终运行,不会被主线程上的活动(比如用户点击按钮、提交表单)打断。这样有利于随时响应主线程的通信。但是,这也造成了 Worker 比较耗费资源,不应该过度使用,而且一旦使用完毕,就应该关闭。
注意:
- 分配给worker线程运行的脚本文件,必须与主线程的脚本文件同源
- worker无法操作DOM
- worker线程与主线程必须通过消息完成(postMessage, onMessage)
- worker线程不能执行alert()方法和confirm()方法
- worker线程无法读取本地文件
Web socket: 一种通信协议,服务器可以主动给客户端推送消息
3.HTML5新特性
-
- 语义化标签
-
- 增强型表单
-
- 视频和音频
-
- canvas绘图
-
- svg绘图
-
- 地理定位
-
- 拖放API
-
- webworker
-
- websocket
-
- webstorage
四、CSS
1.CSS引入方式
- 行内样式
<div style='width: 200px; height:300px;'>
- 内部样式表
<style>
...
</style>
- 外部样式表
<link rel=‘stylesheet’ href=‘css文件路径’>
2.CSS中样式选择器的优先级以及计算方式
-
-
优先级
行内样式 > id选择器 > class选择器 > 元素选择器
-
-
- !important表示强制应用样式,优先级最高
-
- 优先级计算
- 权重表示方式0.0.0.0,按照计算规则给每位填充数字,对应位置相等,则比较下一位;
- 另一种方式,以1000,100,10,1四个权值系数对CSS选择器进行权重计算,权重系数*对应选择器个数
3.元素垂直水平居中(至少两种)
-
- flex布局
<style>
.wrap {
display: flex;
justify-content: center;
align-items: center;
}
</style>
<div class="wrap">
<div>child</div>
</div>
-
- 相对定位
<style>
.center {
position: relative;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
</style>
<div class="wrap">
<div class="center">child</div>
</div>
<style>
.center {
position: relative;
top: 50%;
margin: 0 auto
transform: translateY(-50%);
}
</style>
<div class="wrap">
<div class="center">child</div>
</div>
-
- 绝对定位
<style>
.wrap{
position: relative
}
.center {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
</style>
<div class="wrap">
<div class="center">child</div>
</div>
前提:父元素要先定位,因为absolute属性值是相对于最先定位的祖先元素进行定位
注意:如果不限定元素未知宽高的话,用transform移动元素坐标的地方,可以用margin-left/top: 子元素宽高/2的负值 ,也就是反向移动来实现垂直水平居中
4.两栏布局
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
* {
margin: 0;
padding: 0;
}
.left {
float: left;
width: 300px;
background-color: rebeccapurple;
}
.right {
background-color: rgb(206, 65, 225);
}
.main1 {
display: flex;
}
.left1 {
width: 300px;
flex-shrink: 0;
flex-grow: 0;
background-color: rgb(153, 129, 51);
}
.right1 {
flex: 1;
background-color: royalblue;
}
/* 第三种 */
.main2 {
position: relative;
width: 100%;
}
.left2 {
position: absolute;
width: 300px;
left: 0;
background-color: rgb(51, 153, 97);
}
.right2 {
position: absolute;
right: 0;
left: 300px;
background-color: rgb(166, 65, 225);
}
</style>
</head>
<body>
<div class="main">
<div class="left">左边</div>
<div class="right">右边</div>
</div>
<div class="main1">
<div class="left1">左边</div>
<div class="right1">右边</div>
</div>
<div class="main2">
<div class="left2">左边</div>
<div class="right2">右边</div>
</div>
</body>
</html>
5.盒模型有哪些,区别
css中的box-sizing属性可以设置盒模型类型,盒模型有两种:content-box、border-box
content-box
- content-box的元素大小会被padding, border撑开
- 元素width=元素初始width+padding+border
border-box
- border-box元素不会被撑开,padding, border占据元素的宽和高 也就是说元素大小保持不变
两者对比: 宽高、边框、内边距都相同的元素,设置了不同的box-sizing属性值,呈现出来的样式和计算结果如下:
<style>
.box{
box-sizing: border-box;
width: 100px;
height: 100px;
background-color:yellowgreen;
border: 10px solid red;
padding: 5px;
}
</style>
<div class="box">盒模型</div>
6.display属性值以及区别
- none:隐藏元素,不占据空间
- block:块状元素,前后有换行符,能设置宽高
- inline:行内元素,没有换行符,不能设置宽高
- inline-block:行内块状元素,没有换行符,且具有宽高
- flex, table, grid: 布局方式
7.position属性值以及区别
1. position: static;
2. position: inherit;
3. position: relative;
4. position: absolute;
5. position: fixed;
6. position: sticky;(新的属性值)
- 1.position: static 默认值,没有定位
- 2.position: inherit 继承父元素的position值
- 3.position: relative 相对定位,相对于自己的初始位置,不脱离文档流。也就是说元素框偏移某个距离,元素仍保持其未定位前的形状,它原本所占的空间仍保留。
- 4.position: absolute 绝对定位的元素的位置相对于最近的已定位祖先元素,如果元素没有已定位的祖先元素,那么它的位置相对于最初的包含块。
- 5.position: fixed fixed元素脱离正常的文档流,所以它与absolute元素很相似,同样会被周围元素忽略,支持top,bottom,left,right属性
- 6.position: sticky sticky很像relative和fixed的结合,元素偏移在达到特定阈值前为相对定位,达到阈值之后为固定定位
8.flex布局
flex容器
- flex-direction
- flex-wrap
- flex-flow:[flex-direction] [flex-wrap]
- justify-content
- align-items
- align-content
flex项目
- order
- flex-grow
- flex-shrink
- flex-basis:设置子项目的宽度(1表示等分)
- flex: [flex-grow] [flex-shrink] [flex-basis]
- align-self
9.自适应布局的实现方式
- rem+媒体查询
- rem+lib-flexible
- vw实现
10.px、em、rem区别
px: 固定的像素值,一旦设置就无法自适应页面,随着页面的大小而改变
em: 相对父元素来设置字体大小
rem: 相对根元素来设置大小,em和rem都能够实现自适应
11.Less和Sass
CSS预处理, Less和Sass
12.CSS实现轮播图
- 可以通过使用js控制每张图片的相对位置来实现,在第一张图片之前和最后一张图片之后分别补充最后一张和第一张图片,在最后一张切换到第一张时将容器的left设置为0,实现无缝轮播
- 通过CSS的动画实现,控制z-index和opacity,同时给每张图设置延时显示时间
- vue可以利用v-show和transition实现
采用CSS动画实现轮播图
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>轮播</title>
<style>
li {
display: block;
width: 300px;
height: 200px;
position: absolute;
animation: show 16s infinite;
}
li:nth-child(1) {
background-color: red;
animation-delay: 0s;
}
li:nth-child(2) {
background-color: powderblue;
animation-delay: 4s;
}
li:nth-child(3) {
background-color: yellow;
animation-delay: 8s;
}
li:nth-child(4) {
background-color: blue;
animation-delay: 12s;
}
@keyframes show{
0%{
opacity: 0;
z-index: 2;
}
6%,25%{
opacity: 1;
z-index: 1;
}
31%,100%{
opacity: 0;
z-index: 0;
}
}
</style>
</head>
<body>
<div class="box">
<ul class="lunbo">
<li class="lis1">1</li>
<li class="lis2">2</li>
<li class="lis3">3</li>
<li class="lis4">4</li>
</ul>
</div>
</body>
</html>
13.如何触发硬件加速
CSS中常见的硬件加速的css属性
- transform
- opacity
- filters
- will-change
will-change 会告知浏览器该元素会有哪些变化,这样浏览器就可以在元素属性真正发生变化之前做好应对的准备工作
五、HTTP协议与计算机网络
1.TCP和UDP
- | UDP | TCP |
---|---|---|
是否连接 | 无连接 | 面向连接 |
是否可靠 | 不可靠传输,不使用流量控制和拥塞控制 | 可靠传输 |
连接对象个数 | 一对一、一对多、多对一、多对多 | 一对一 |
传输方式 | 面向报文 | 面向字节流 |
首部开销 | 首部开销小,8字节 | 首部最小20字节,最大60字节 |
使用场景 | 实时应用(IP电话、视频会议、直播等) | 适用于要求可靠传输的应用,比如文件传输 |
2.TCP三次握手,两次行不行,四次行不行,四次挥手?
三次握手
第一次:client -> SYN=1, seq=x;
第二次:server -> ACK=x+1,SYN=1,seq=y;
第三次:client -> ACK=y+1
两次握手行不行? 答案当然是否定的
三次握手是通信双方为了确信自己和接收方的发送能力和接收能力都是正常的
第一次:服务器:客户端发送能力√,服务端接收能力√
第二次:客户端:客户端发送能力√,接收能力√,服务器发送能力√,接收能力√ ;
第三次:服务器:客户端接收能力√,服务端发送能力√
经过三次握手,通信双方都能够确定双方发送能力和接收能力正常
四次行不行? 当然不行 三次就够了,多了浪费
3.HTTP请求方法有哪些,PUT和POST区别
get, post, delete, head, put
幂等性: 通俗来讲就是指不管进行多少次重复操作,都是实现相同的结果
GET, DELETE, PUT是幂等操作,POST不是,原因是前三个重复多次同一个操作,结果是一样的,而POST重复多次,资源就会提交多少份
选用PUT还是POST取决于最终要实现的操作,比如发送两个同样的请求,要求产生两个结果,那应该用POST,要求产生一个结果,那就用PUT
4.GET和POST区别
GET | POST | |
后退/刷新 | 无害 | 数据会被重新提交 |
书签 | 可收藏为书签 | 不可收藏为书签 |
缓存 | 能被缓存 | 不能缓存 |
编码类型 | application/x-www-form-urlencoded | application/x-www-form-urlencoded或multipart/form-data,二进制数据使用多重编码 |
历史 | 参数保留在浏览器历史中 | 参数不会保存在浏览器历史中 |
安全性 | 安全性较差,参数拼接在url中 | 相对GET更安全 |
可见性 | 数据在URL中,所有人可见 | 数据在form data中 |
由于get请求的数据能够被缓存,所以有时不能返回正确的数据
解决:服务器端设置缓存失效时间;在请求中添加时间戳的方式
5.HTTP状态码,301,302
- 1XX:服务器收到请求,需要请求者继续执行操作
- 2XX:响应成功,操作被成功接收并处理
- 200: ok
- 203: Non-Authoritative, 非授权信息,请求成功,但返回的meta信息不在原始的服务器,而是一个副本
- 3XX:重定向,需要进一步操作以完成请求
- 301: 永久重定向
- 302: 临时重定向
- 304: not modified, 缓存有效的状态码,缓存失效时返回200
- 4XX:客户端错误,请求语法错误或无法完成请求
- 400:Bad Request,客户端请求的语法错误
- 401:Unauthorized,要求用户身份认证
- 403: Forbidden,服务器拒绝执行请求
- 404: Not found
- 5XX:服务器错误
- 500:服务器内部错误
- 501:服务器不支持请求的功能,无法完成请求
- 502:Bad Gateway,网关或代理服务器执行请求时,从远程服务器接收到了一个无效的响应
6.Cookie相关首部字段
7.HTTPS与HTTP区别,HTTPS握手?
- http是最为广泛的网络协议,传送明文,不适合传输密码等信息
- https是以安全为目标的http通道,在http中加入SSL(secure socket layer),https的安全基础是SSL 二者区别如下: 1.https协议需要到ca申请证书,需要一定费用 2.http是明文传输,https是具有安全性的SSL加密传输协议 3.http和https适用完全不同的连接方式,端口不同,http端口为80,https端口为443 4.https加密传输,有身份认证,比http协议安全
中间人攻击
在密码学和计算机安全领域中,中间人攻击(Man-in-the-middle attack, MITM)是指攻击者与通信两端分别建立独立的联系,并交换其所收到的数据,使通讯的两端认为他们正在通过一个私密的连接与对方直接对话,但事实上整个会话都被攻击者完全控制。在中间人攻击中,攻击者可以拦截通讯双方的通话并插入新的内容。在许多情况下这是很简单的(例如,在一个未加密的Wi-Fi 无线接入点的接受范围内的中间人攻击者,可以将自己作为一个中间人插入这个网络)
防范中间人攻击
通过向CA(数字证书认证机构)申请数字证书来防范中间人攻击。数字证书就是申请人将一些必要信息(包括公钥、姓名、电子邮件、有效期)等提供给 CA,CA 在通过各种手段确认申请人确实是他所声称的人之后,用自己的私钥对申请人所提供信息计算散列值进行加密,形成数字签名,附在证书最后,再将数字证书颁发给申请人,申请人就可以使用 CA 的证书向别人证明他自己的身份了。对方收到数字证书之后,只需要用 CA 的公钥解密证书最后的签名得到加密之前的散列值,再计算数字证书中信息的散列值,将两者进行对比,只要散列值一致,就证明这张数字证书是有效且未被篡改过的。
HTTPS协议=HTTP协议+SSL/TLS协议
在HTTPS数据传输过程中,需要用SSL/TLS对数据进行加密和解密
说明:
1.https同时使用了对称加密和非对称加密,对数据进行对称加密,对称加密所要使用的密钥通过非对称加密
2.传输过程涉及到3个密钥
3.两次http传输, 第一次传输对client key进行非对称加密,第二次传输对data进行对称加密
8.Web攻击以及防御(XSS、CSRF)
XSS(跨站脚本攻击)
XSS: 诱使用户点击一个嵌入恶意脚本的链接(很多攻击者利用论坛、微博等发布恶意url);将恶意脚本提交到数据库中,用户浏览网页时,恶意脚本从数据库中被加载到页面中执行
防范XSS攻击
1.对危险字符进行转义 <> / $ ... 2.对输入和输出进行转义 3.设置cookie属性http-only
CSRF(跨站请求伪造)
CSRF伪造用户信息发送请求,在用户不知情的情况下以用户的名义进行非法操作,原理是利用浏览器的cookie或服务器的session盗取用户身份
防范CSRF
1.验证referer字段,该字段记录该http请求的来源地址 优点:简单易行 缺点:过渡依赖浏览器,不能保证浏览器自身没有安全漏洞 2.设置Samesite cookie,表明该cookie是个同站cookie,不允许第三方加载cookie信息 优点:简单易行 缺点:影响用户体验 3.在表单中添加令牌,验证码,识别请求者的身份 4.服务器端设置csrftoken
9.浏览器存储(localStorage, sessionStorage, cookie)
特性 | Cookie | localStorage | sessionStorage |
---|---|---|---|
数据生命周期 | 由服务器生成,可设置生效时间;如果在浏览器短生成cookie,默认是浏览器关闭后失效 | 永久有效,除非手动清除 | 仅在当前会话下有效,关闭页面或浏览器后被清除 |
大小 | 4K左右 | 5MB | 5MB |
与服务器通信 | 每次都会携带在http头中,cookie过多会带来性能问题 | 不参与服务器通信 | 不参与服务器通信 |
cookie的容量是同一站点的限制,不同的浏览器对同一个域下的cookie的数量限制不同,同一个域名下的cookie总容量不可超过4K
10.浏览器缓存策略(强制缓存和协商缓存)
强制缓存:浏览器请求数据的时候,服务端在response header里面对该文件进行了缓存配置,浏览器在拿到数据之后,在过期时间之内不会再去重复请求
强制缓存只有首次请求时才会跟服务器通信,读取缓存资源时不会发出任何请求
状态码为200,http1.1的版本的实现优先级高于http1.0版
Key: 如何知道当前时间是否超过了过期时间 http1.0: 通过Expires响应头实现,表示过期时间 http1.1: 通过Cache-Control响应头实现,常用字段是max-age,表示缓存资源将在xxx秒后过期
协商缓存:每次读取数据都要跟服务器通信,并且会增加缓存标识;从第二次通信开始,浏览器会询问服务器资源是否更新,如果命中缓存,资源的状态码为304,未命中则为200
http1.0: last-modified, if-modified-since
http1.1:Etag, if-none-match
11.计算机网络模型以及各层协议
TCP/IP模型
- 1.物理层
- 2.数据链路层
- 3.网络层:IP、ICMP、IGMP,[ARP、RARP]
- 4.传输层:TCP、UDP
- 5.应用层:HTTP、FTP、SMTP、DNS
OSI七层模型
- 1.物理层:FDDI
- 2.数据链路层:HDLC,PPP
- 3.网络层:IP,[ARP、RARP]
- 4.传输层:TCP,UDP
- 5.会话层: RPC
- 6.表示层:TIFF,GIF,JPEG
- 7.应用层:FTP,Telnet,SMTP
12.HTTP报文结构
请求报文:请求行 请求头 空行 请求体(get请求体为空,post为提交的数据)
响应报文:响应行 响应头 空行 响应体(响应数据)
写在最后:
如果这篇文章对你有帮助,那就点个
关注
吧🌹🌹🌹
你们的鼓励就是我写作的最大动力☀️☀️☀️️