金三银四跳槽季,帮你整理了一份初中级前端面试题助力升职加薪

2,040 阅读21分钟

CSS

盒模型

box-sizing:'content-box'//标准盒模型
box-sizing:'border-box'//怪异盒模型
  • 标准盒模型

只包含content,border、padding、margin不计算在内,元素的宽度等于style里的width+margin+border+padding宽度,实际会被撑大

  • 怪异盒模型

包括border、padding,元素宽度等于style里的width宽度,width是多少就是多少

em和rem的区别

  • em

相对于父元素字体计算,假设p标签的font-size:12px,里面span标签设置为font-size:2em,那么就是21*2=24px

  • rem

html设置为font-size:12px,div为2rem时,大小就是24px

display:none和visibility:hidden的区别

  • display:none 隐藏对应的元素,在文档布局中不再给它分配空间,它各边的元素会合拢,就当他从来不存在。

  • visibility:hidden 隐藏对应的元素,但是在文档布局中仍保留原来的空间。

解释下浮动和它的工作原理?

清除浮动的技巧 浮动元素脱离文档流,不占据空间。浮动元素碰到包含它的边框或者浮动元素的边框停留。

  • 使用空标签清除浮动:这种方法是在所有浮动标签后面添加一个空标签 定义css clear:both. 弊端就是增加了无意义标签
  • 使用overflow:设置overflow为hidden或者auto,给包含浮动元素的父标签添加css属性 overflow:auto; zoom:1; zoom:1用于兼容IE6。
  • 使用after伪对象清除浮动:该方法只适用于非IE浏览器。该方法中必须为需要清除浮动元素的伪对象中设置 height:0,否则该元素会比实际高出若干像素。
box:after{ 
content:"."; 
height:0; 
visibility:hidden; 
display:block; 
clear:both; } 

浮动元素引起的问题

父元素的高度无法被撑开,影响与父元素同级的元素 与浮动元素同级的非浮动元素(内联元素)会跟随其后 若非第一个元素浮动,则该元素之前的元素也需要浮动,否则会影响页面显示的结构

水平垂直居中的实现方法

  • flex
.box{
	display:flex
	justify-content:center;
	algin-items:center;
	.inner{
		
	}
}
  • position 需要知道宽高
.box{
positon:relative
width:300px
height:300px
	.inner{
	position:absoult
	top:50%,
	left:50%,
	margin-left:-100px;
	margin-top:-100px;
	width:200px;
	height:200px
	}
}
  • position 不需要知道宽高
.box{
positon:relative
	.inner{
	position:absoult
	top:0,
	left:0,
	bottom:0;
	right:0;
	margin:auto;
	}
}
  • 用变形
.box{
positon:relative
	.inner{
	position:absoult;
	top:50%;
	left:50%;
	transform:(-50%,-50%)
	}
}
  • table-cell
display:table-cell
vertical-algin:middle;
	.inner{
	width:100px;
	height:100px;
	margin:0 auto;
	}
}

BFC

  • 什么是BFC

块格式化上下文(block formatting context) 是一个独立的渲染区域,让处于BFC的元素不会受外部元素影响,相互隔离。

  • 怎么产生BFC
displayflex/inline-block;
float;
position;
overflow:hidden
  • BFC的特性
  • 是一个独立容器,内部不会受外部的影响
  • 盒子从顶端开始垂直一个一个排列,间距由margin决定
  • 在同一个BFC,两个块级盒子会发生外边距重叠

这个外边距的高度等于折叠的外边距高度中的较大者=》比如margin:10px 0理应两者中间相距20px的,但是重叠了,只距离10px

  • 解决外边距重叠: 创建一个不同的 BFC ,就可以避免外边距折叠
  • BFC 有什么用?

最大的一个作用就是:在页面上有一个独立的隔离容器,容器内的元素不会和容器外的相互影响

Flex布局

阮一峰大神的教程写的非常详细,就不多叙述了 Flex 布局教程:语法篇 - 阮一峰的网络日志

JAVASCRIPT

JS的基本数据类型

number、string、nullundefinedBoolean、symbol

JS的数组操作

  • map遍历数组,返回回调函数返回值组成的新数组
  • filter过滤,返回true此元素保留在新数组,返回false则删除
Array.prototype.filter=function(fn){
	let newArr=[]
	//this为原数组;循环原数组,执行回调,为true就返回
	for(let i=0;i<this.length;i++){
	let flag=fn(this[i])
	flag&&newArr.push(this[i])
	}
	return newArr
}
  • forEach,无法break ,可以用try/catchthrow new Error来停止
  • every,有一项返回false,就整体返回false
Array.prototype.every=function(fn){
	let newArr=[]
	for(let i=0;i<this.length;i++){
	let flag=fn(this[i])
	if(!flag){
	return false
	}
  }
	return flag
}
  • some,有一项返回true,就整体返回true
Array.prototype.some=function(fn){
	let newArr=[]
	for(let i=0;i<this.length;i++){
	let flag=fn(this[i])
	if(flag){
	return flag
	}
   }
	return false
}
  • join,特定符号转换成字符串
  • push,把某元素推到数组最后一位
  • reduce
reduce(function(total, currentValue, currentIndex, arr), initialValue) //【`total`:计算结束后的值,`curVal`:当前值,`curIndex`:当前索引,`arr`:原数组,`initialValue`:传递给函数的初始值】前后相加求和
  • unshift/shift:数组开头添加元素/删除元素,改变原数组
  • splice(start, number, value):返回删除元素后组成的数组,改变原数组

闭包

  • 什么是闭包

函数A里面包含了函数B,而函数B又引用了函数A的变量,这是就称函数B为闭包,或者:闭包就是能够读取其他函数内部变量的函数

  • 闭包的特征
  1. 函数内再嵌套函数
  2. 内部函数可以引用外部函数的参数和变量
  3. 参数和变量不会被垃圾回收机制回收
  • 对闭包的理解

使用闭包主要是为了设计私有的方法和变量。闭包的优点是可以避免全局变量的污染,缺点是闭包会常驻内存,会增大内存使用量,使用不当很容易造成内存泄露。在js中,函数即闭包,只有函数才会产生作用域的概念

对this的理解

  • this总是指向函数的调用者
  • 如果有new,this就是指向new出来那个对象
  • 事件中,this是触发事件的这个对象

new操作符都干了什么

  1. 首先函数接收不定量的参数,第一个参数是构造函数,接下来的参数被构造函数使用
  2. 然后内部创建一个空对象obj
  3. 因为obj对象需要访问到构造函数原型链上的属性,所以可以通过obj.__proto__=constr.prototype
  4. 讲obj绑定到构造函数上,并且传入剩余的参数
  5. 最后判断构造函数的值是否返回为对象,如果对象就使用构造函数返回的值,如果不是就是用obj
function creatNew(constr,...args){
	let obj={}
	obj.__proto__=constr.prototype
	let res=constr.apply(obj,args)//讲构造函数的this指向这个obj,并且传递参数
	return res instanceof Object ? res : obj
}

回流(reflow)和重绘(repaint)

  • 重绘:当元素样式确定下来后,浏览器就会把这些元素按照各自特性绘制一遍
  • 回流:当元素的大小、位置、结构或者触发某些属性时,浏览器就会重新渲染页面,称之为回流。因为浏览器需要重新计算,计算后还要布局,是较重的操作,因此损耗大。回流了就一定会有重绘 什么情况会触发回流
  • 字体大小变化
  • 页面初次渲染
  • 元素位置、尺寸、内容发生变化
  • 添加或者删除可见DOM元素
  • 激活hover等伪类

防抖(debounce)和节流(throttle)

  • 防抖

用户输入校验:触发高频事件,n秒内只会执行一次函数

function debounce(fn){
	let timer=null
	clearTimeout(timer)
	timer=setTimeout(()=>{
		fn.apply(this.arguments)
	},500)
}
  • 节流

页面滚动或者resize,一定的时间间隔执行一次

function throttle(fn){
	let canRun=true //进来先保存一个标记
	if(!canRun) return
	canRun=false //、将 canRun 设置为 false,防止执行之前再被执行
	setTimeout(()=>{
		fn.apply(this,arguments)
		canRun=true //执行完事件(比如调用完接口)之后,重新将这个标志设置为 true
	},500)
}

两者区别:截流是每隔一段时间执行一次,防抖是只执行一次,比如用户输入了5秒钟,如果wait都是1000,截流会执行5次,防抖只会执行一次

原型链

属性查找: 从实例本身一层层向上查找,直到访问到对象的原型。 属性修改: 只会修改实例对象本身的属性,如果不存在,则进行添加该属性,如果需要修改原型的属性时,则可以用: F.prototype.xxx = xxx;但是这样会造成所有继承于该构造函数F的实例的属性都发生改变。

作用域

变量和声明的作用范围。可分为 块级作用域函数作用域

下面一道经典面试题可让你更好了解

var name1 = "haha";
    function changeName(){
        var name2="xixi";
        function swapName(){
            console.log(name1);//haha
            console.log(name2);//xixi
            console.log(tempName)//undefined
            var tempName=name2;
            name2=name1;
            name1=tempName;
            console.log(name1);//xixi
            console.log(name2);//haha
            console.log(tempName);//xixi
        }
        swapName();
        console.log(name1);//xixi
        console.log(name2);//haha
       // console.log(tempName);//抛出错误:Uncaught ReferenceError: tempName is not defined
    }
    changeName();
    console.log(name1);//xixi
   // console.log(name2); //抛出错误:Uncaught ReferenceError: name2 is not defined
   // console.log(tempName);//抛出错误:Uncaught ReferenceError: tempName is not defined

对象的拷贝(深浅拷贝)

  • 浅拷贝 以赋值的形式拷贝引用对象,仍指向同一个地址,修改时原对象也会受到影响 (寻址操作,例如:访问数据类型为数组)
Object.assign()
Array.slice()
展开运算符(...)
  • 深拷贝 完全拷贝一个新对象,修改时原对象不再受到任何影响 (寻值操作,例如:访问数据类型为字符串)
JSON.parse(JSON.stringify(obj)): // 性能最快

缺点:

  • 如果obj里面有时间对象,则JSON.stringify后再JSON.parse的结果,时间将只是字符串的形式,而不是对象的形式;
  • 如果obj里有RegExp(正则表达式的缩写)、Error对象,则序列化的结果将只得到空对象;
  • 如果obj里有函数,undefined,则序列化的结果会把函数或 undefined丢失;
  • 如果obj里有NaN、Infinity和-Infinity,则序列化的结果会变成null;
  • JSON.stringify()只能序列化对象的可枚举的自有属性,例如: 如果obj中的对象是有构造函数生成的, 则使用JSON.parse(JSON.stringify(obj))深拷贝后,会丢弃对象的constructor;

手写深拷贝(递归进行逐一赋值)

function deepClone(val){
	let objClone=Array.isArray(val)?[]:{}
	if (val && typeof val==='object'){
		for(key in val){
			if(val.hasOwnProperty(key)){
				// 判断 obj 子元素是否为对象,如果是,递归复制
		        if(val[key] && typeof val[key] === "object") {
		          objClone[key] = deepClone(val[key]);
		        } else {
		          // 如果不是,简单复制
		          objClone[key] = val[key];
		        }
			}
		}
	}
	return objClone
}

函数柯里化

  • 什么是函数柯里化?

通过把一个多参函数转换成一系列嵌套的函数,每个函数依次接受一个参数,这就是函数柯里化。

function curry(fn, len = fn.length) {
  return _curry.call(this, fn, len)
}

function _curry(fn, len, ...args) {
  return function (...params) {
    console.log(params);
    let _args = [...args, ...params];
    console.log(_args);
    if (_args.length >= len) {
      return fn.apply(this, _args);
    } else {
      return _curry.call(this, fn, len, ..._args)
    }
  }
}
let _fn = curry(function (a, b, c, d, e) {
  console.log(a + b + c + d + e)
});

_fn(1, 2, 3, 4, 5); //15
_fn(1)(2)(3, 4, 5); //15
_fn(1, 2)(3, 4)(5); //15
_fn(1)(2)(3)(4)(5); //15

继承

//1、使用原型继承
function Father(){
this.name='father'
}
function Child(){
this.age=10
}
child.prototype=new Father();//在原型上继承
var demo=new Child()
consloe.log(demo.name) // father


//2、用call、apply改变this
function Father(){
this.name='father'
}
function Child(){
Father.call(this)
this.age=10
}

Promise

手写promise建议看Promise的源码实现(完美符合Promise/A+规范),按照规范更加详细!

下面是我收集的一些面试题,结合题目更好理解

const first = () => (new Promise((resolve,reject)=>{
    console.log(3);
    let p = new Promise((resolve, reject)=>{
         console.log(7);
        setTimeout(()=>{
           console.log(5);
           resolve(6); 
        },0)
        resolve(1);
    }); 
    resolve(2);
    p.then((arg)=>{
        console.log(arg);
    });

}));

first().then((arg)=>{
    console.log(arg);
});
console.log(4);

第一轮事件循环

先执行宏任务,主script ,new Promise立即执行,输出【3】,执行p这个new Promise 操作,输出【7】,发现setTimeout,将回调放入下一轮任务队列(Event Queue),p的then,姑且叫做then1,放入微任务队列,发现first的then,叫then2,放入微任务队列。执行console.log(4),输出【4】,宏任务执行结束。 再执行微任务,执行then1,输出【1】,执行then2,输出【2】。到此为止,第一轮事件循环结束。开始执行第二轮。

第二轮事件循环

先执行宏任务里面的,也就是setTimeout的回调,输出【5】。resovle不会生效,因为p这个Promise的状态一旦改变就不会在改变了。 所以最终的输出顺序是3、7、4、1、2、5


async function async1() {
    console.log('async1 start');
    await async2();
    console.log('async1 end');
}
async function async2() {
    console.log('async2');
}
console.log('script start');
setTimeout(function() {
    console.log('setTimeout');
}, 0)
async1();
new Promise(function(resolve) {
    console.log('promise1');
    resolve();
}).then(function() {
    console.log('promise2');
});
console.log('script end');
  1. 输出script start

虽然有两个函数声明,有async关键字,但是没有调用,就不看,直接打印同步代码console.log(‘script start’)

  1. 输出saync1 start

因为执行async1这个函数的时候,async表达式定义的函数也是立即执行

  1. 输出saync2

因为async2是async定义的函数,输出async2并返回promise对象

  1. 输出promise1

因为执行到new promise,promise是立即执行,就先打印promise1

  1. 输出script end

因为上一步先打印了promise1,然后执行到resolve的时候,然后跳出promise继续向下执行,输出script end

  1. 输出promise2

因为前面的new promise放进resolve回调,所以resolve被放到调用栈执行

  1. 输出async1 end

因为await定义的这个promise已经执行完,并且返回结果,所以继续执行async1函数后的任务,就是console.log(‘async1 end’)

  1. 输出setTimeout

const promise = new Promise((resolve, reject) => {
    resolve('success1');
    reject('error');
    resolve('success2');
});

promise.then((res) => {
    console.log('then:', res);
}).catch((err) => {
    console.log('catch:', err);
})

promise的状态一但改变就不会再变 所以reject('error')就不会有作用 只输出then then: success1

事件队列

带你彻底弄懂Event Loop

node在11版本之后的eventloop执行与浏览器一致了,现在node11在timer阶段的setTimeout,setInterval…和在check阶段的immediate都在node11里面都修改为一旦执行一个阶段里的一个任务就立刻执行微任务队列。

  • 浏览器的eventloop

浏览器(多进程)包含了Browser进程(浏览器的主进程)、第三方插件进程和GPU进程(浏览器渲染进程),其中GPU进程(多线程)和Web前端密切相关,包含以下线程:

  • GUI渲染线程
  • JS引擎线程
  • 事件触发线程(和EventLoop密切相关)
  • 定时触发器线程
  • 异步HTTP请求线程

JS中有两种任务类型:宏任务微任务

  • 宏任务:script(主代码块)、setTimeout、setInterval、setImmediate、I/O
  • 微任务:process.nextTick(nodejs)、Promise、Object.observe、MutationObserver

两者的区别:

  • 宏任务是每次执行栈执行的代码(包括每次从事件队列中获取一个事件回调并放到执行栈中执行)
  • 浏览器为了能够使得JS引擎线程GUI渲染线程有序切换,会在当前宏任务结束之后,下一个宏任务执行开始之前,对页面进行重新渲染(宏任务 > 渲染 > 宏任务 > ...)
  • 微任务是在当前宏任务执行结束之后立即执行的任务(在当前 宏任务执行之后,UI渲染之前执行的任务)。微任务的响应速度相比setTimeout(下一个宏任务)会更快,因为无需等待UI渲染。
  • 当前宏任务执行后,会将在它执行期间产生的所有微任务都执行一遍。

事件触发线程管理的任务队列是如何产生的呢?

主线程在运行时会产生执行栈,栈中的代码调用某些异步API时会在任务队列中添加事件,栈中的代码执行完毕后,就会读取任务队列中的事件,去执行事件对应的回调函数,如此循环往复,形成事件循环机制,

执行一个JavaScript代码的具体流程:

  1. 执行全局Script同步代码,这些同步代码有一些是同步语句,有一些是异步语句(比如setTimeout等);
  2. 全局Script代码执行完毕后,调用栈Stack会清空;
  3. 从微队列microtask queue中取出位于队首的回调任务,放入调用栈Stack中执行,执行完后microtask queue长度减1;
  4. 继续取出位于队首的任务,放入调用栈Stack中执行,以此类推,直到直到把microtask queue中的所有任务都执行完毕。注意 ,如果在执行microtask的过程中,又产生了microtask,那么会加入到队列的末尾,也会在这个周期被调用执行;
  5. microtask queue中的所有任务都执行完毕,此时microtask queue为空队列,调用栈Stack也为空;
  6. 取出宏队列macrotask queue中位于队首的任务,放入Stack中执行;
  7. 执行完毕后,调用栈Stack为空;
  8. 重复第3-7个步骤;
  9. 重复第3-7个步骤;
  10. ......
  • node.js中的事件队列
   ┌───────────────────────┐
┌─>│        timers         │<————— 执行 setTimeout()、setInterval() 的回调
│  └──────────┬────────────┘
|             |<-- 执行所有 Next Tick Queue 以及 MicroTask Queue 的回调
│  ┌──────────┴────────────┐
│  │     pending callbacks │<————— 执行由上一个 Tick 延迟下来的 I/O 回调(待完善,可忽略)
│  └──────────┬────────────┘
|             |<-- 执行所有 Next Tick Queue 以及 MicroTask Queue 的回调
│  ┌──────────┴────────────┐
│  │     idle, prepare<————— 内部调用(可忽略)
│  └──────────┬────────────┘     
|             |<-- 执行所有 Next Tick Queue 以及 MicroTask Queue 的回调
|             |                   ┌───────────────┐
│  ┌──────────┴────────────┐      │   incoming:   │ - (执行几乎所有的回调,除了 close callbacks、timers、setImmediate)
│  │         poll          │<─────┤  connections, │ 
│  └──────────┬────────────┘      │   data, etc.  │ 
│             |                   |               | 
|             |                   └───────────────┘
|             |<-- 执行所有 Next Tick Queue 以及 MicroTask Queue 的回调
|  ┌──────────┴────────────┐      
│  │        check<————— setImmediate() 的回调将会在这个阶段执行
│  └──────────┬────────────┘
|             |<-- 执行所有 Next Tick Queue 以及 MicroTask Queue 的回调
│  ┌──────────┴────────────┐
└──┤    close callbacks    │<————— socket.on('close', ...)
   └───────────────────────┘

说说你对AMD和Commonjs的理解

CommonJS是服务器端模块的规范,Node.js采用了这个规范。CommonJS规范加载模块是同步的,也就是说,只有加载完成,才能执行后面的操作。AMD规范则是非同步加载模块,允许指定回调函数。AMD推荐的风格通过返回一个对象做为模块对象,CommonJS的风格通过对module.exportsexports的属性赋值来达到暴露模块对象的目的。

Vue

Vue的响应式原理

MVVM的实现原理(手写一个简版Vue)

Vue的响应式是通过Object.defineProperty对数据进行劫持,并结合观察者模式实现。Vue利用Object.defineProperty创建一个observe来劫持监听所有属性,把这些属性全部转为gettersetter。Vue中每个组件实例都会对应一个watcher实例,它会在组建渲染的过程中把使用过的数据属性通过getter收集依赖。之后当依赖项的setter触发时,会通知watcher,从而使它关联的组件重新渲染。

  • Observer:能够对数据对象的所有属性进行监听,如有变动可拿到最新值并通知订阅者.

Observer的核心是通过Object.defineProprty()来监听数据的变动,这个函数内部可以定义settergetter,每当数据发生变化,就会触发setter。这时候Observer就要通知订阅者,订阅者就是Watcher

  • Compile:对每个元素节点的指令进行扫描和解析,根据指令模板替换数据,以及绑定相应的更新函数。

Compile主要做的事情是解析模板指令,将模板中变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加鉴定数据的订阅者,一旦数据有变动,收到通知,更新视图

  • Watcher:链接Observer和Compile的桥梁,能够订阅并收到每个属性变动的通知,执行指令绑定的相应的回调函数,从而更新视图。

Watcher订阅者作为ObserverCompile之间通信的桥梁,主要做的事情是:

  • 在自身实例化时往属性订阅器(dep)里面添加自己
  • 自身必须有一个update()方法
  • 待属性变动dep.notice()通知时,能调用自身的update()方法,并触发Compile中绑定的回调

proxy和Object.defineProperty的优缺点

  1. proxy可以直接监听对象而不是属性,并返回一个新对象,而Object.defineProperty只能通过遍历对象属性直接修改
  2. 可以直接监听数组的变化
  3. 拦截方法多,不限于apply、ownkeys、deleteProperty、has等等是Object.defineProperty不具备的
  4. proxy是es6新特性,兼容性不好,Object.defineProperty兼容ie9

为什么2.0不用proxy:当时虽然es6的新属性出现了proxy,但是兼容性不好,无法用polyfill来兼容,但是在vue3.0的版本会有效的提供兼容解决方案,在3.0中proxy不用像Object.defineProperty给属性都设置get、set的方法,所以proxy不会触发循环调用。

computed和watch的区别

  1. computed是计算属性,多用于计算值的场景,watch是监听一个值得变化
  2. computed有缓存性,computed的值在getter执行后会被缓存,只有在它依赖的属性值改变之后,下一次获取computed的值时才会重新调用对应的getter来计算。而watch是在每次监听的值发生变化了就会执行回调
  3. computed的函数一定要return,watch不是必须

computed缓存的大致过程:

watcher存在的情况下,首先判断watcher.dirty属性,这个属性主要用于判断这个computed属性是否需要重新求值,因为在上一轮的依赖收集的过程中,观察和已经将这个watcher添加到依赖数组当中,如果观察者发生了变化,就会dep.notify(),通知所有的watcher,而对于computedwatcher接收到变化的请求后,会将watcher.dirty=true 即表明观察者发生了变化,当再次调用computed属性的getter函数时会重新计算,否则还是用之前缓存的值。

组件通讯

  • props(父传子)
  • $emit(子传父)
  • $attrs$listeners
  • provide与inject
  • bus.js

具体参考 vue中8种组件通信方式, 值得收藏!

nextTick

在下次DOM更新循环结束之后执行延迟回调。在修改数据之后,立即使用的这个回调函数,获取更新后的DOM

具体原理参考这篇 Vue.nextTick 的原理和用途

keep-alive

<keep-alive> 是一个抽象组件,它能够把不活动的组件的实例保存在内存中,而不是直接的销毁,它是一个抽象组件,不会被渲染到真实DOM中,也不会出现在父组件链中。

  • 首次渲染的时候设置缓存
  • 提供了两个生命钩子,actived与deactived。因为keep-alive会把组件保存到内存中,并不会销毁或则重新构建,所以不会调用组件的creted等方法,需要使用actived和deactived两个钩子判断组件是否处于活动状态。
  • keep-alive组件提供了include和exclude两个属性来进行有条件的缓存,二者都可以用逗号分隔字符串、正则表达式或则数组表示。

原理解析 keep-alive的实现原理及LRU缓存策略

组件的 data 为什么要写成函数形式

Vue 的组件都是可复用的,一个组件创建好后,可以在多个地方复用,而不管复用多少次,组件内的 data 都应该是相互隔离,互不影响的,所以组件每复用一次,data 就应该复用一次,每一处复用组件的 data 改变应该对其他复用组件的数据不影响。 为了实现这样的效果,data 就不能是单纯的对象,而是以一个函数返回值的形式,所以每个组件实例可以维护独立的数据拷贝,不会相互影响。

Vue 的模板编译原理

这里详细解释了AST(抽象语法树) 掌握了AST,再也不怕被问babel,vue编译,Prettier等原理

HTTP

HTTP协议的主要特点

  1. 简单快速【URI是固定的,操作简单】
  2. 灵活【HTTP协议有一个头部分,一个HTTP协议可以完成不同数据类型的传输】
  3. 无状态【因为会断掉,无法区分两次连接的状态】
  4. 无连接【连接一次就会断掉】

get 和 post的区别

  1. get回退是无害的,post会重新提交请求
  2. get不安全,信息暴露在url中
  3. GET参数通过URL传递,POST放在Requset Body中
  4. GET请求参数会被完整的保留在浏览器历史记录里,而POST中的参数不会被保留

HTTP报文的组成部分

  • 请求报文
  1. 请求行
  2. 请求头
  3. 空行
  4. 请求体
  • 响应报文
  1. 状态行
  2. 响应头
  3. 空行
  4. 响应体

HTTP请求四部分

  • HTTP请求的方法或动作,比如是get还是post请求;

  • 正在请求的URL(请求的地址);

  • 请求头,包含一些客户端环境信息、身份验证信息等;

  • 请求体(请求正文),可以包含客户提交的查询字符串信息、表单信息等。

    请求头的字段

  • Accept:text/html,image/*(告诉服务器,浏览器可以接受文本,网页图片)
  • Accept-Charaset:ISO-8859-1 [接受字符编码:iso-8859-1]
  • Accept-Encoding:gzip/compress[可以接受 gzip,compress压缩后数据]
  • Accept-Language:zh-cn[浏览器支持的语言]
  • Host:localhost:8080[浏览器要找的主机]
  • If-Modified-Since:Tue, 09 May 2017 01:34:02 GMT[告诉服务器我这缓存中有这个文件,该文件的时间是…]
  • User-Agent:Nozilla/4.0(Com…)[告诉服务器我的浏览器内核,客户端环境信]
  • Cookie:[身份验证信息]
  • Connection:close/Keep-Alive [保持链接,发完数据后,我不关闭链接]

HTTP响应三部分

  • 一个数字和文字组成的状态码,用来显示请求是成功还是失败;

  • 响应头,响应头也和请求头一样包含许多有用的信息,例如服务器类型、日期时间、内容类型和长度等;

  • 响应体(响应正文)。

    响应头的字段

  • Cache-Control:[告诉浏览器如何缓存页面(因为浏览器的兼容性最好设置两个)]
  • Connection:close/Keep-Alive [保持链接,发完数据后,我不关闭链接]
  • Content-Type:text/html;charset=gb2312[内容格式和编码]
  • Last-Modified:Tue,11 Juj,2017 18 18:29:20[告诉浏览器该资源上次更新时间是多少]
  • ETag:”540-54f0d59b8b680”
  • Expires:Fri, 26 May 2017 13:28:33 GMT [失效日期]
  • server:apache tomcat nginx [哪种服务器]

什么是持久连接

HTTP协议采用”请求-应答“模式,当使用普通模式,既非Keep-Alive模式时,每个请求/应答客户和服务器都要新建一个连接,完成之后立即断开连接(HTTP协议为无连接的协议) 当使用Keep-Alive模式(又称持久连接、连接重用)时,Keep-Alive功能使客户端到服务器端的链接持续有效,当出现对服务器的后续请求时,Keep-Alive功能避免了建立或者重新建立连接

http的keep-alive 与 tcp的keep-alive

  • http

keep-alive是为了让tcp活得更久一点,以便在同一个连接上传送多个http,提高socket的效率。

  • tcp

keep-alive是TCP的一种检测TCP连接状况的保鲜。注意:keep-alive是1.1版本才有

TCP的三次握手和四次挥手

具体参考: 面试:请说说"从输入URL到页面加载完成的过程中都发生了什么事情"

HTTP和HTTPS

HTTP协议通常承载于TCP协议之上,在HTTP和TCP之间添加一个安全协议层(SSL或TSL),这个时候,就成了我们常说的HTTPS 默认HTTP的端口号为80,HTTPS的端口号为443

HTTPS 相对于 HTTP 性能上差点,因为多了SSL/TLS的几次握手和加密解密的运算处理,但是加密解密的运算处理已经可以通过特有的硬件来加速处理。

说说网络分层里七层模型是哪七层

  1. 应用层
  2. 表示层
  3. 会话层(从上往下)(HTTP、FTP、SMTP、DNS)
  4. 传输层(TCP和UDP)
  5. 网络层(IP)
  6. 物理层
  7. 数据链路层(以太网)

跨域解决方案

  1. jsonp
  • jsonp的原理

利用<script>标签没有跨域限制的漏洞,网页可以得到从其他来源动态产生的JSON数据。JSONP请求一定需要对方的服务器做支持才行

  • jsonp的实现流程
  1. 声明一个回调函数,其函数名当做参数值,要传给跨域请求数据的服务器,函数形参为要获取目标数据(服务器返回的data)
  2. 创建一个<script>标签,把那个跨域的API数据接口地址,赋值给<script>的src,还要在这个地址中向服务器传递该函数名(可以通过问号传参:?callback=show
  3. 服务器接收到请求后,需要进行特殊的处理:把传递进来的函数名和它需要给你的数据拼接成一个字符串,例如:传递进去的函数名是show,它准备好的数据是show('resData')
  4. 最后服务器把准备的数据通过HTTP协议返回给客户端,客户端再调用执行之前声明的回调函数show,对返回的数据进行操作
  1. CORS

服务端设置 Access-Control-Allow-Origin 就可以开启 CORS。 该属性表示哪些域名可以访问资源,如果设置通配符则表示所有网站都可以访问资源。

  1. nginx反向代理

实现思路:通过nginx配置一个代理服务器(域名与domain1相同,端口不同)做跳板机,反向代理访问domain2接口,并且可以顺便修改cookie中domain信息,方便当前域cookie写入,实现跨域登录。

常见HTTP状态码

  • 1xx(临时响应): 表示临时响应并需要请求者继续执行操作的状态码。
  • 2xx(成功): 表示成功处理了请求的状态码。 200(成功):服务器已成功处理了请求。通常,这表示服务器提供了请求的网页。
  • 3xx(重定向):要完成请求,需要进一步操作。
  1. 301(永久移动):请求的网页已永久移动到新位置。
  2. 302(临时移动):服务器目前从不同位置的网页响应请求,但请求者应继续使用原有位置来响应以后的请求。
  3. 304(未修改):自从上次请求后,请求的网页未修改过。
  • 4xx(请求错误):这些状态码表示请求可能出错,妨碍了服务器的处理。
  1. 400(错误请求):服务器不理解请求的语法。
  2. 404(未找到):服务器找不到请求的网页。
  • 5xx(服务器错误): 这些状态码表示服务器在处理请求时发生内部错误。
  1. 500(服务器内部错误):服务器遇到错误,无法完成请求。
  2. 503(服务不可用):服务器目前无法使用(由于超载或停机维护)。

cdn原理

CDN(Content Delivery Network)就是内容分发网络,CND在全球各大城市建立机房,部署大量节点。当用户接入网络后,首先会通过GSLB(Global Sever Load Balance)算法,找到最近的节点;通俗说就是就近原则。

http的缓存机制

http缓存是基于HTTP协议的浏览器文件级缓存机制。即针对文件的重复请求情况下,浏览器可以根据协议头判断从服务器端请求文件还是从本地读取文件判断expires,如果未过期,直接读取http缓存文件

  • 强缓存

命中了就缓存

ExpiresCache-control的区别

- Expires 用来控制缓存的失效日期 
- Cache-Control 用来控制网页的缓存常见的取值有`private、no-cache、max-age、must-revalidate等,默认为private。`

  • 协商缓存 与服务器协商,是否可以缓存

 什么是Etag

Last-ModifiedETag请求的http报头一起使用,可利用客户端(例如浏览器)的缓存。ETag用于标识资源的状态,当资源发生变更时,如果其头信息中一个或者多个发生变化,或者消息实体发生变化,那么ETag也随之发生变化。浏览器下载组件的时候,会将它们存储到浏览器缓存中。如果需要再次获取相同的组件,浏览器将检查组件的缓存时间,假如已经过期,那么浏览器将发送一个条件GET请求到服务器,服务器判断缓存还有效,则发送一个304响应,告诉浏览器可以重用缓存组件。

关于Http 2.0 你知道多少?

HTTP/2引入了“服务端推(server push)”的概念,它允许服务端在客户端需要数据之前就主动地将数据发送到客户端缓存中,从而提高性能。 HTTP/2提供更多的加密支持 HTTP/2使用多路技术,允许多个消息在一个连接上同时交差。 它增加了头压缩(header compression),因此即使非常小的请求,其请求和响应的header都只会占用很小比例的带宽。

常见web安全及防护原理

  • sql注入

原理就是通过把SQL命令插入到Web表单递交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令。

  • XSS 指的是攻击者往Web页面里插入恶意html标签或者javascript代码

比如:攻击者在论坛中放一个看似安全的链接,骗取用户点击后,窃取cookie中的用户私密信息;或者攻击者在论坛中加一个恶意表单,当用户提交表单的时候,却把信息传送到攻击者的服务器中,而不是用户原本以为的信任站点。

  • CSRF

CSRF是代替用户完成指定的动作,需要知道其他用户页面的代码和数据包。要完成一次CSRF攻击,受害者必须依次完成两个步骤:

  1. 登录受信任网站A,并在本地生成Cookie。
  2. 在不登出A的情况下,访问危险网站B。

webpack

首屏加载优化有哪些方案

  1. Vue-router路由懒加载

  2. 入口文件:手动配置webpack多个为入口

这样使得无关系的两个JS模块可以并行加载,这种方式相较于加载一个等价大小的JS模块有速度上的提升。 但是也存在问题:

  1. 如果两个JS文件都引入了lodash,那么lodash将会同时打包到两个JS模块中
  2. 不灵活,不能根据核心应用程序的逻辑来动态分割代码
var path = require('path');
var root_path = path.resolve(__dirname);
module.exports = {
    mode: 'production',
    entry: {  //配置多个入口文件打包成多个代码块
        index: path.resolve(root_path, 'index.js'), 
        a: path.resolve(root_path, 'a.js')
    },
    output: {
        filename: '[name].bundle.js',
        path: path.resolve(root_path, 'dist')
    },
    module: {
        rules: [
            {
                test: /\.js$/,
                loader: 'babel-loader',
                exclude: path.resolve(root_path, 'node_modules'),
                include: root_path
            }
        ]
    }
}
  1. 避免重复:使用splitChunks插件删除并提取公共代码块作为单独的一个chunk

webpack.config.js 使用SplitChunks插件:

optimization: {
     splitChunks: {
         chunks: 'all'
     }
 }

lodash被单独打包成venders~a~index.bundle.jsSplitChunks插件还可以按照自己的需求进行配置,默认配置情况如下:

  1. 模块被重复引用或者来自node_modules中的模块
  2. 在压缩前最小为30kb
  3. 在按需加载时,请求数量小于等于5
  4. 在初始化加载时,请求数量小于等于3

满足以上条件的模块都会被单独拆分到一个代码块里面

  1. 利用CDN加速,将通用的库从vendor进行抽离

  2. webpack开启gzip压缩

使用插件CompressionWebpackPlugin

const CompressionWebpackPlugin = require'compression-webpack-plugin'module.exports = { 
    “plugins”:[new CompressionWebpackPlugin] 
}
  1. Vue异步组件
  2. 服务端渲染ssr
  3. 按需加载UI组件库
  4. 使用link标签的rel属性设置:
  • prefetch(这段资源将会在未来某个导航或者功能要用到,但是资源的下载顺序权重比较低,prefetch通常用于加速下一次导航)
  • preload(preload将会把资源的下载顺序权重提高,使得关键数据提前下载好,优化页面打开速度)

有哪些常用的Loader

  • file-loader:把文件输出到一个文件夹中,在代码中通过相对 URL 去引用输出的文件 (处理图片和字体)
  • url-loader:与 file-loader 类似,区别是用户可以设置一个阈值,大于阈值时返回其 publicPath,小于阈值时返回文件 base64 形式编码 (处理图片和字体)
  • image-loader:加载并且压缩图片文件
  • babel-loader:把ES6转换成ES6
  • sass-loader:将 CSS 代码注入 JavaScript 中,通过 DOM 操作去加载 CSS
  • css-loader:加载 CSS,支持模块化、压缩、文件导入等特性
  • vue-loader:加载 Vue.js 单文件组件

有哪些常用的plugin

  • html-webpack-plugin可以根据模板自动生成html代码,并自动引用css和js文件
  • extract-text-webpack-plugin 将js文件中引用的样式单独抽离成css文件
  • DefinePlugin 编译时配置全局变量
  • HotModuleReplacementPlugin 热更新
  • happypack 通过多进程模型,来加速代码构建

loader 和 plugin 的区别

  • loader 本质是一个函数,在该函数中对接收到的内容进行转换,返回转换后的结果。(相当于翻译官),对其他类型的资源进行转译的预处理工作

  • plguin就是插件,插件可以扩展 webpack 的功能

热更新原理

webpack的热更新又称热替换(Hot Module Replacement),缩写为HMR。 这个机制可以做到不用刷新浏览器而将新变更的模块替换掉旧的模块。

webpack 配合 webpack-dev-server 进行应用开发的模块热更新流程图。 上图是webpack 配合 webpack-dev-server 进行应用开发的模块热更新流程图。

  1. 在 webpack 的 watch 模式下,文件系统中某一个文件发生修改,webpack 监听到文件变化,根据配置文件对模块重新编译打包,并将打包后的代码通过简单的 JavaScript 对象保存在内存中。

  2. 第二步是 webpack-dev-server 和 webpack 之间的接口交互,而在这一步,主要是 dev-server 的中间件 webpack-dev-middleware 和 webpack 之间的交互,webpack-dev-middleware 调用 webpack 暴露的 API对代码变化进行监控,并且告诉 webpack,将代码打包到内存中。

  3. 第三步是 webpack-dev-server 对文件变化的一个监控,这一步不同于第一步,并不是监控代码变化重新打包。当我们在配置文件中配置了devServer.watchContentBase 为 true 的时候,Server 会监听这些配置文件夹中静态文件的变化,变化后会通知浏览器端对应用进行 live reload。注意,这儿是浏览器刷新,和 HMR 是两个概念。

  4. 第四步也是 webpack-dev-server 代码的工作,该步骤主要是通过 sockjs(webpack-dev-server 的依赖)在浏览器端和服务端之间建立一个 websocket 长连接,将 webpack 编译打包的各个阶段的状态信息告知浏览器端,同时也包括第三步中 Server 监听静态文件变化的信息。浏览器端根据这些 socket 消息进行不同的操作。当然服务端传递的最主要信息还是新模块的 hash 值,后面的步骤根据这一 hash 值来进行模块热替换。

  5. webpack-dev-server/client 端并不能够请求更新的代码,也不会执行热更模块操作,而把这些工作又交回给了 webpack,webpack/hot/dev-server 的工作就是根据 webpack-dev-server/client 传给它的信息以及 dev-server 的配置决定是刷新浏览器呢还是进行模块热更新。当然如果仅仅是刷新浏览器,也就没有后面那些步骤了。

  6. HotModuleReplacement.runtime 是客户端 HMR 的中枢,它接收到上一步传递给他的新模块的 hash 值,它通过 JsonpMainTemplate.runtime 向 server 端发送 Ajax 请求,服务端返回一个 json,该 json 包含了所有要更新的模块的 hash 值,获取到更新列表后,该模块再次通过 jsonp 请求,获取到最新的模块代码。这就是上图中 7、8、9 步骤。

  7. 而第 10 步是决定 HMR 成功与否的关键步骤,在该步骤中,HotModulePlugin 将会对新旧模块进行对比,决定是否更新模块,在决定更新模块后,检查模块之间的依赖关系,更新模块的同时更新模块间的依赖引用。

  8. 最后一步,当 HMR 失败后,回退到 live reload 操作,也就是进行浏览器刷新来获取最新打包代码。

冉四夕:Webpack HMR 原理解析