前言
这个是我之前看前端跳槽面试必备技巧这个系列视频整理的一个笔记,其中还有一些内容没有细化,会持续更新细化内容。比较短的就会直接写在下面,长一点的就单独写篇文章。
说实话,这个大佬真的讲的挺好的,尤其是对原型和继承那一块讲的通俗易懂。有些店之前看视频的时候看不懂或者没有在意,其实还是有蛮多点可挖的,我也还会针对一些没太吃透的点重点再写文章记录。
1.面试准备
1.1 对职位描述的分析
- 首先要逐条读懂职位描述中对岗位能力的要求,在真实的面试投递之前通过这个分析首先就可以知道要面试的这个岗位与自己能力的匹配程度,不过平时根据自己的目标薪资浏览岗位描述时就一定要细化自己具体哪个点不足,以及如何补上或者提高相应能力。
- 具体来说岗位描述分为基础能力和难度点
- 职位描述的目的一个是快速识别这个岗位是不是自己喜欢的是不是自己想要的,二个是我能不能胜任这个岗位或者说经过准备短期内hold住这个岗位
- 具备前端工程化知识已经成为一名合格前端的基本要求,具体到技术就是要了解并使用过==webpack、grunt、gulp、sass、less==这几个。
1.2 针对目标公司的技术栈分析
- 可以拿目标网站的源码分析,可以看到他们大体上用到了一些什么技术栈,对于那些自己完全没接触过的点一定要重点了解。
2.根据常见面试题梳理知识体系
2.1 页面布局
例子:假设高度已知,请写出三栏布局,其中左右栏宽度都是300px,中间自适应
- 这个问题目前就我已知的有五种解决方案:
html * {
margin: 0;
padding: 0;
}
.layout .item {
height: 100px;
}
.layout {
margin-bottom: 30px;
}
- 浮动布局
浮动布局
这是浮动布局的内容
- 相对定位
绝对布局
这是绝对定位布局的内容
这是绝对定位布局的内容
这是绝对定位布局的内容
- flx布局 (http://www.runoob.com/w3cnote/flex-grammar.html)
- 以下6个属性设置在容器上
- flex-direction (flex-direction属性决定主轴的方向(即项目的排列方向)
- row(默认值):主轴为水平方向,起点在左端
- row-reverse:主轴为水平方向,起点在右端
- column:主轴为垂直方向,起点在上沿。
- column-reverse:主轴为垂直方向,起点在下沿。
- flex-wrap
- nowrap(默认):不换行
- wrap:换行,第一行在上方
- wrap-reverse:换行,第一行在下方。
- flex-flow
- flex-flow属性是flex-direction属性和flex-wrap属性的简写形式,默认值为row nowrap。
- justify-content (justify-content属性定义了项目在主轴上的对齐方式)
- flex-start(默认值):左对齐
- flex-end:右对齐
- center: 居中
- space-between:两端对齐,项目之间的间隔都相等。
- space-around:每个项目两侧的间隔相等。所以,项目之间的间隔比项目与边框的间隔大一倍。
- align-items
- flex-start:交叉轴的起点对齐。
- flex-end:交叉轴的终点对齐
- center:交叉轴的中点对齐
- baseline: 项目的第一行文字的基线对齐
- stretch(默认值):如果项目未设置高度或设为auto,将占满整个容器的高度
- align-content (定义了多根轴线的对齐方式。如果项目只有一根轴线,该属性不起作用) 竖直方向的
- flex-start:与交叉轴的起点对齐。
- flex-end:与交叉轴的终点对齐。
- center:与交叉轴的中点对齐。
- space-between:与交叉轴两端对齐,轴线之间的间隔平均分布。
- space-around:每根轴线两侧的间隔都相等。所以,轴线之间的间隔比轴线与边框的间隔大一倍。
- stretch(默认值):轴线占满整个交叉轴。
- flex-direction (flex-direction属性决定主轴的方向(即项目的排列方向)
- 以下6个属性设置在项目上
- order (order属性定义项目的排列顺序。数值越小,排列越靠前,默认为0)
- flex-grow (flex-grow属性定义项目的放大比例,默认为0,即如果存在剩余空间,也不放大)
- flex-shrink (flex-shrink属性定义了项目的缩小比例,默认为1,即如果空间不足,该项目将缩小)
- flex-basis: | auto; /* default auto */ (项目占据的主轴空间)
- flex flex属性是flex-grow, flex-shrink 和 flex-basis的简写,默认值为0 1 auto。后两个属性可选
- align-self: auto | flex-start | flex-end | center | baseline | stretch; (align-self属性允许单个项目有与其他项目不一样的对齐方式,可覆盖align-items属性。默认值为auto,表示继承父元素的align-items属性,如果没有父元素,则等同于stretch)
flex布局
这是flex布局的内容
这是flex布局的内容
这是flex布局的内容
- 以下6个属性设置在容器上
- table布局
table布局
这是table布局的内容
这是table布局的内容
这是table布局的内容
- grid网格布局
- 目前浏览器基本上默认不支持,不过可能是未来的一种网页布局方式
- 开启浏览器grid模块功能 (https://www.w3cplus.com/css3/how-to-enable-support-for-grid-layout-in-various-browsers.html)
- grid-template-columns: 设置行的宽度
grid-template-rows: 设置列的高度
网格布局
这是grid布局的内容
这是grid布局的内容
这是grid布局的内容
- 写代码时要注意几点:
- 使用标签时要尽量使用语义化标签,不要通篇div
- 注意缩进与书写的规范化
- 知道这几种解决方案之后还要思考几个问题:
- 他们各自的优缺点,通过比较这几种布局方式的不同来在实际开发中知道如何取舍,比如常见的移动端的上下高度固定,中间高度自适应等。
- 在实际开发中会优先选择哪种解决方案
- 当高度不固定时又要如何处理
2.2 CSS盒模型
- 基本概念:标准模型 + IE模型
- 标准模型和IE模型的区别
- 两种盒模型都包含content,padding,border,margin这四个部分,不同在与标准模型是将content的宽度和高度计算为盒子的宽度与高度,而IE模型是将content、padding与border之和计算为盒子的宽度与高度
- CSS如何设置这两种模型
- box-sizing: content-box (标准模型,默认方式)
- box-sizing:border-box (IE模型)
- JS如何设置获取盒模型对应的宽和高
- dom.style.width/height (这种方法只能取到内联样式中声明的宽或者高)
- dom.currentStyle.width/height (这种方法得到的是渲染之后的准确结果,但是只有IE支持)
- window.getComputedStyle(dom).width/height (兼容性更好,能够兼容FF和Chrom)
- dom.getBoundingClientRect().width/height (能够计算元素的绝对位置,同时也能获得宽高)
- 实例题(根据盒模型解释边距重叠)
- BFC (边距重叠解决方案)
- BFC的基本概念 (块级隔离化上下文)
- BFC的渲染规则
- 在BFC的垂直方向的边距会发生重叠
- BFC的区域不会与浮动元素的BOX重叠
- BFC在页面上是一个独立的容器,容器内与外的元素不会相互影响
- 计算BFC元素高度时浮动元素也会被计算进去
- 如何创建BFC
- float值不为none
- position的值不是stastic或者reletice
- overflow
- table
- BFC的使用场景
2.3 DOM事件类
- 基本概念:DOM事件的级别
- 事件级别
- DOM0: element.onclick=function(){} - DOM1: element.addEventListener('click',function(){},false) - DOM3: element.addEventListener('keyup',function(){},false) (增加了很多事件)
- DOM事件模型 (捕获和冒泡)
- DOM事件流 (从捕获阶段到目标阶段再到冒泡阶段)
- 捕获阶段 (最后那个参数默认是false,是冒泡阶段的,设为true就是捕获阶段的)
window: window.addEventListener('click', function(){ console.log('window'); }, true) document: document.addEventListener('click', function(){ console.log('document'); }, true) html: document.documentElement.addEventListener('click', function(){ console.log('html'); }, true) body: document.body.addEventListener('click', function(){ console.log('body'); }, true)
- 描述DOM事件捕获的具体流程
- window --> document --> html -->body --> ... -->具体元素
- 获取document:document.documentElement
- Event对象的常见应用
- event.preventDefault() 阻止默认事件
- event.stopProgation() 阻止冒泡
- event.stopImmediateProgation() 设定事件响应优先级
- event.currentTarget 当前所绑定的事件
- event.targe 当前被点击的元素
- 自定义事件
例如:自定义一个自定义事件'custome'
var eve=new Event('custome');
ev.addEventListener('custom',function(){
console.log('custome');
})
ev.dispatchEvent(eve);
2.4 HTTP协议类
- HTTP协议的主要特点
- 简单快速 (URL是固定的)
- 灵活 (通过一个HTTP协议就能完成不同数据类型的传输)
- 无连接 (连接一次就会断掉)
- 无状态 (多次连接是无状态的)
- HTTP报文的组成部分
- 请求报文(请求行,请求头,空行,请求体)
- 响应报文(状态行,响应头,空行,响应体)
- HTTP方法
- GET、POST、PUT、DELETE、HEAD(获得报文头部)
- POST和GET的区别
- GET在浏览器回退时时无害的,而POST会再次请求
- GET产生的URL地址是可以被收藏,而POST不可以
- GET请求会被浏览器主动缓存,而POST不会,除非手动设置
- GET请求只能进行URL编码,而POST支持多种编码方式
- GET请求参数会被完整地保留在浏览器历史记录了,而POST请求不会被保留
- GET请求在URL中传送的参数是有长度限制的,而POST请求没有限制
- 对参数数据类型,GET只接受ASCII字符,而POST没有限制
- GET比POST更不安全,因为参数直接暴露在URL上,所以不能用来传递敏感信息
- GET参数通过URL传递,POST放在Request body中
- HTTP状态码
- 1xx:提示信息-表示请求已接收,继续处理
- 2xx:成功-表示请求已被成功接收
- 3xx:重定向-要完成请求必须进行更进一步操作
- 4xx:客户端错误-请求有语法错误或者请求无法实现
- 5xx:服务器错误-服务器未能实现合法的请求
- 什么是持久连接 (keep-alive)
- 什么是管线化 (通道是持久建立的,先发送几个请求,再同时返回这几个请求的结果)
- 管线化通过持久连接完成,进HTTP1.1支持此技术
- 只有GET和HEAD请求可以管线化,而POST有所限制
管线化不会影响响应到来的顺序
2.5 原型链
创建对象有几种方法
// 第一种很方式:字面量 var o1 = {name: 'o1'}; var o11 = new Object({name: 'o11'}); // 第二种方式:通过构造函数 var M = function(){ this.name = 'o2'; } var o2 = new M(); // 第三种方式:Object,create var P = {name: 'o3'}; var o3 = Object.create(P);
- 原型、构造函数、实例、原型链
- 任何函数都可以作为构造函数,只要被new了,就是一个构造函数
- 每个函数都有一个prototype属性,这个是生成函数时自动加上的,函数的这个prototype属性指向的就是函数的原型对象
M的原型对象:M.prototype
- 每个原型对象都有一个constructor构造器,这个属性指向的就是引用的那个构造函数,也就是声明这个原型对象的函数
M.prototype.constructor === M 任意一个实例对象o2 o2.__proto__.constructor === M 这个可以用来判断实例对象的构造函数
- 每个对象都有一个__proto__属性,这个属性指向的是这个的对象的构造函数的的原型对象,也就是说一个实例对象的原型是等于它的构造函数的原型对象
o2.__proto__ === M.prototype
- ~~原型链:所谓原型链就是从一个实例对象向上找,通过查找实例对象的构造函数的原型对象向上找,然后找原型对象的构造函数的原型对象,一直找到构造函数为Object时终止,因为 **Object.prototype.__proto__===null**; 这样就形成了一个原型链~~
- 原型链:从一个对象上获取某一个属性或者方法时,如果这个对象本身不具备这个属性或者方法,那就会从这个对象的构造函数的原型对象上去找,如果还没找到就会从构造函数的构造函数上去找,直到找到构造函数为Object时终止,因为**Object.prototype.__proto__===null**,这样形成的一个链条就叫原型链。
原型链的顶端是Object.prototype这个原型对象的原型Object.prototype.__proto__为空
- instanceof原理
- 判断一个实例对象的instanceof到底是不是一个构造函数时实际上判断的是实例对象的__proto__属性是否跟这个构造函数的prototype属性相等,在上面的例子中,就是:o2 instanceof M为true,因为o2.__proto__ === M.prototype
- 并且,只要是在 这个原型链上的构造函数,都会等于这个实例对象的instanceof属性,例如:o2 instanceof M毫无疑问是true的,同时o2 instanceof Object也是true的,因为M.prototype.__proto__ === Object.prototype,Object是在o2的原型链上的一个构造函数
- 那怎么严谨地判断一个实例对象是不是一个构造函数的实例对象呢,需要用constructor,例如:o2.__proto__.constructor === M,就可以确定M是o2的构造函数了
- 判断一个实例对象的instanceof到底是不是一个构造函数时实际上判断的是实例对象的__proto__属性是否跟这个构造函数的prototype属性相等,在上面的例子中,就是:o2 instanceof M为true,因为o2.__proto__ === M.prototype
- new 运算符
var new2 = function(func){
var o = Object.create(func.prototype);
var k = func.call(o);
if (typeof k === 'object') {
return k
}else{
return o
}
}
o6 = new2(M); 这个就等价于对构造函数M实例化了一个实例对象
2.6 面向对象类
- 类与实例
- 类的声明
/*类的声明*/ function Animal () { this.name = 'name'; } /*ES6中的类的声明*/ class Animal2 { constructor (name) { this.name = name; } }
- 生成实例
/*实例化类的对象*/ console.log(new Animal(), new Animal2())
- 类与继承
- 如何实现继承
- 构造函数(call() 和apply(方法))
- 原型链
- 继承的几种方式
- 借助构造函数实现继承
/*借助构造函数实现继承*/ function Parent1 () { this.name = 'parent1'; } function Child1 () { Parent1.call(this); //就是将Child1运行在Parent1的上下文环境里 // Parent1.apply(this, []); //另一中写法 this.type = 'child1'; } console.log(new Child1)
- 利用原型链实现继承
/*利用原型链实现继承*/ function Parent2 () { this.name = 'parent2'; this.play = [1,2,3]; } function Child2 () { this.type = 'child2'; } Parent2.prototype.say = function () {console.log('hello')}; Child2.prototype = new Parent2(); //实际上这个方法就是相当于把Parent2的原型链嫁接到了Child2上,所以子级能通过原型链访问到父级的方法了 console.log(new Child2()); // (new Child2()).__proto__ === Child2.prototype = new Parent2(),所以子集实例的原型链指向的是父级的一个实例 var s1 = new Child2(); var s2 = new Child2(); s1.play.push(7); console.log(s1.play, s2.play); //他们永远会是相等的,因为他们指向的地址是一样的,他们的原型对象是一样的,s1.__proto__===s2.__proto__
- 组合方式
/*组合方式*/ function Parent3 () { this.name = 'parent3'; this.play = [1,2,3]; } function Child3 () { Parent3.call(this); this.type = 'child3'; } Child3.prototype = new Parent3(); //构造函数Parent3执行了两次,有一次是多余的 var s3 = new Child3(); var s4 = new Child3(); s3.play.push(6); console.log(s3, s4)
- 组合方式优化1
/*组合方式优化1*/ function Parent4 () { this.name = 'parent4'; this.play = [1,2,3]; } function Child4 () { Parent4.call(this); this.type = 'child4'; } Child4.prototype = Parent4.prototype; //这样构造函数Parent4就只执行了一次 console.log(Parent4.prototype) var s5 = new Child4(); var s6 = new Child4(); var s6_ = new Parent4(); console.log(s5 instanceof Child4, s5 instanceof Parent4); console.log(s5.constructor); console.log(s6_.constructor); //都指向了构造函数Parent4
- 组合方式优化2 (比较完美的解决方案)
/*组合方式优化2*/ // 这种优化方式是为了解决无法分辨父级和子级的实例对象的问题,也就是说父级和子级的实例对象都指向了同一个原型对象 function Parent5 () { this.name = 'parent5'; this.play = [1,2,3]; } function Child5 () { Parent5.call(this); this.type = 'child5'; } Child5.prototype = Object.create(Parent5.prototype); //相当于创建了一个空的中间对象,这样的话子级构造函数的原型对象不是简单地与父级构造函数的原型函数相等了,而是先等于一个空的中间对象,再对这个中间的对象进行赋值 Child5.prototype.constructor = Child5; //这一步实际上就是做的赋值,当然没有上面那步让子级的构造函数的原型对象先等于一个空对象的话还是无法将他们分开的 var s7 = new Child5(); var s8 = new Child5(); var s9 = new Parent5(); console.log(s7 instanceof Child5, s8 instanceof Parent5) console.log(s7.constructor) console.log(s9.constructor)
2.7 通信类
- 如何实现继承
- 什么是同源策略及限制
- 协议、域名、端口这三个构成了一个源,这三个之中有一个不一样就是源不一样了,就是跨域了。所以不是同一个源是不能操作同源的文件
- Cookie、LocalStorage、和indexDB无法读取
- DOM无法获得
- AJAX请求不能发送
- 前后端如何通信
- Ajax (受同源策略限制)
- WebSocket (不受同源策略限制)
- CORS (支持跨域通信,也支持同源通信)
- 如何创建Ajax
- 跨域通信的几种方式
- JSONP
- 原理:利用js、css,img等标签可以加载跨域资源的原理,动态创建script标签,再请求一个带参网址实现跨域通信。
- 缺点:只能实现get请求
- 跨域资源共享(CORS)
- 原理:普通跨域请求:只服务端设置Access-Control-Allow-Origin即可,前端无须设置,若要带cookie请求:前后端都需要设置。(withCredentials)
- nginx代理跨域
- 原理:同源策略是浏览器的安全策略,不是HTTP协议的一部分,服务器端调用HTTP接口只是使用HTTP协议,不会执行JS脚本,不存在同源策略,也就不存在跨域问题。
- Nodejs 中间件代理
- 原理大致与nginx相同,都是通过启一个代理服务器,实现数据的转发
- WebSocket协议跨域
- 原理:WebSocket protocol是HTML5一种新的协议。它实现了浏览器与服务器全双工通信,同时允许跨域通讯
- document.domain + iframe跨域
- 原理:此方案仅限主域相同子域不同的的跨域场景。两个页面都通过js强制设置document.domain为基础主域,就实现了同域。
- location.hash + iframe跨域
- 原理:a域与b跨域相互通信,通过中间页c来实现。三个页面,不同域之间利用iframe的location.hash传值,相同域之间直接js访问来通信。
- window.name + iframe跨域
- 原理:window.name属性的独特之处:name值在不同的页面(甚至不同域名)加载后依旧存在,并且可以支持非常长的 name 值(2MB)
- postMessage跨域 (H5中新增的)
- 原理:postMessage是HTML5 XMLHttpRequest Level 2中的API,且是为数不多可以跨域操作的window属性之一
- JSONP
2.8 安全类
- CSRF
- 基本概念:跨站请求伪造
- 特点是用户一定在注册网址登录过
- 被攻击网站本身存在漏洞
- 防御措施:
- Token验证
- Referer验证
- 隐藏令牌
- XSS
- 基本概念:跨域脚本攻击
2.9 渲染机制类
- 什么是DOCTYPE及作用
- 说白了就是告诉浏览器文档类型
- 浏览器的简要渲染过程
- 解析HTML-->构建DOM树(DOM Tree)
- 加载样式-->解析样式-->构建样式规则树(CSS Tree)
- 加载js-->执行js代码
- 把DOM树和样式规则树匹配构建渲染树(Render Tree)
- 计算元素进行布局 (Layout)
- 绘制 (Paiting)
- 图片记载和渲染的时机
- 解析HTML【遇到标签加载图片】-->构建DOM树
- 加载样式-->解析样式 【遇到背景图片链接不加载】-->构建样式规则树
- 加载js-->执行js代码
- 把DOM树和样式规则树匹配构建渲染树【加载渲染树上的背景图片】
- 计算元素位置进行布局
- 绘制【开始渲染图片】
- 设置了display:none属性的元素,图片会加载,但不会渲染出来,而这个设置了display:none的子元素不会渲染,也不会加载。 https://mp.weixin.qq.com/s?__biz=MzAwNjI5MTYyMw%3D%3D&mid=2651494431&idx=1&sn=ab0f92186e44c8e4575c9c0c831ad6cc&chksm=80f191d7b78618c11a4489b39af45e3279f5248843d2b05804aebecc13678a45432116260e03
- 浏览器渲染过程
- Create/Update DOM And request css/image/js:浏览器请求到HTML之后,生成DOM树,并同时请求相应的CSS,js文件
- Create/Update Render CSSOM:CSS文件下载完成,开始构建CSSOM
- Create/Update Render Tree:CSSOM树构建完之后和DOM一起生成Render Tree
- Layout: 浏览器根据render tree上各个节点的样式信息计算各个点在屏幕中的位置
- Painting:按照计算出来的规则,通过显卡,把内容画到屏幕上
- 重排Reflow
- 定义:浏览器根据各种样式计算记过将各种DOM元素放到它该出现的位置,这个过程就是Reflow
- 触发条件:
- 增加、删除、修改DOM节点,绘导致Reflow或Repaint
- 移动DOM位置或者动画
- 修改CSS样式
- Resize窗口或者滚动的时候
- 修改网页的默认字体时
- 重绘Repaint
- 定义:页面要呈现的内容统统画在屏幕上
- 触发:
- DOM改动
- CSS改动
- 布局Layout
2.10 运行机制类
- JS是单线程的,从上到下执行,但是任务又分为同步任务和异步任务,绘先执行同步任务,把异步任务先挂起,等同步任务完成了再执行异步任务
- 同步人物和异步任务的优先关系
console.log(1);
setTimeout(function(){
console.log(2)
}, 0)
console.log(3);
打印顺序是:1,3,2,因为setTimeout是异步任务
console.log('A');
setTimeout(function(){
console.log('C');
},0)
while(1){
console.log(5)
}
console.log('B');
会输出:A; 因为while是一个同步任务,会优先执行这个任务,但是这个while循环是一个死循环,会一直陷在循环中,那后面的打印和定时器的异步任务都不会执行了
- 异步对垒执行时间:浏览器有一个time模块,专门识别setTimeou和setInterval,当遇到这两种方法时会先将他们挂起,等到时间到了,才会放到异步队列中去执行
for (var i = 0; i < 4; i++) {
setTimeout(function(){
console.log(i);
},1000)
}
输出结果: 4个4
- 如何理解JS的单线程
- 一个时间之内JS只能干一件事
- 什么是任务队列
- 分为同步任务和异步任务,异步任务后于同步任务执行
- 什么是EventLoop
- 异步任务
- setTimeout和setInterval
- DOM事件
- ES6中的promise
2.11 页面性能类
- 资源压缩合并,减少HTTP请求
- 非核心代码异步加载
- 异步加载的方式
- 动态脚本加载
- defer
- async
- 异步加载的区别
- defer是在HTML解析完之后才会执行,如果是多个,按照加载的顺序依次执行
- async实在加载完之后立即执行,如果是多个,执行顺序和加载顺序无关
- defer是在HTML解析完之后才会执行,如果是多个,按照加载的顺序依次执行
- 异步加载的方式
- 利用浏览器缓存
- 缓存的分类
- 强缓存:相对时间、绝对时间
- 协商缓存
- 缓存的原理
- 缓存的分类
- 使用CDN
- 预解析DNS
这句话是说在https协议下默认强制让页面上所有的a标签也可以DNS预解析
针对某一个域名DNS预解析