前言
大家好我是小胡不糊涂,这已经是第二次发布文章了,续上上次的面试题在追加一些,这些题都是我自己整理和查阅资料总结出来的,目的就是想巩固知识和锻炼自己的写作总结能力,希望能一直坚持下去,如有说的不太对的地方还望多多包涵指点。想看上一篇文章在这# 浅总结一下前端常见面试题(一),前半部分比较简单基础,想看稍难一点的可以直接跳到最后,话不多说,咱们直接撸题😆😆!
CSS3、HTML5部分
光知道js和vue的知识还有有点不够的,基础也要打牢,浅复习一下css和html的部分吧,内容不多,但很常见,相信你们很快就可以看完🤣🤣
1.HTML 文件中的 DOCTYPE 是什么作用?
- HTML超文本标记语言: 是一个标记语言, 就有对应的语法标准 DOCTYPE 即 Document Type,网页文件的文档类型标准。
- 主要作用是告诉浏览器的解析器要使用哪种 HTML规范 或 XHTML规范来
解析页面
。 DOCTYPE 需要放置在 HTML 文件的标签之前
2.XML和JSON
- 都是后台和前端进行交互时
传递数据
的一种格式 ,XML对数据的描述比较详细,体积比较大,而JSON的数据体积更小 ,传递速度比较快 相对而言更加轻量,而且与JavaScript交互比较方便,更容易解析处理
3.meta 标签有哪些常用用法?
meta在W3C的定义中是指元数据 也就是关于数据的一些信息
它不会显示在页面上 但是浏览器是可以读取到的
- 设置网页关键词 (SEO)
- 设置网页视口(viewport)控制视⼝的⼤⼩、缩放和⽐例等 (移动端开发)
- 设置 http 响应头:Content-Type 网页内容类型 (字符集)
4.HTML5的新增内容
- 只有一种文件类型声明:
<!DOCTYPE html>
- 增加了一些新的标签元素(aside、nav、video、section、footer,、header等)
- input 支持了几个新的类型值 :date, email, url 等等
- 新增了一些标签属性 :charset(⽤于 meta 标签);async,defer(⽤于 script 标签)
- 新增的全域属性 :contenteditable, draggable... hidden...
- 新增API:本地存储, 地理定位, Canvas绘图, 拖拽API, 即时通信 WebSocket...
5.link和@import到底有什么区别?
- link属于html标签,而@import是css提供的。
- link引入css时,在页面载入时同步进行加载,@import需要等页面网页完全载入以后才加载
- link是XHTML标签,没有兼容性问题,@import在CSS2.1提出,低版本游览器不支持
- link支持使用JavaScript控制DOM改变样式,而@import不支持
6.img标签 alt和title的区别
- alt 和 title 是两个常用的属性,alt定义元素的
替换文本
,title定义元素的提示文本
。
7.BFC的理解
BFC 的全称是 Block Formatting Context,块级格式化上下文
。这是一个用于在盒模型下布局块级盒子的独立渲染区域, 将处于BFC区域内和区域外的元素进行互相隔离,互不影响。
-
何时会形成 BFC: 满足下列条件之一就可触发BFC:
- HTML根元素
- position 值为 absolute 或 fixed
- float 值不为 none
- overflow 值不为 visible
- display 值为 inline-block 、 table-cell 或 table-caption
-
BFC 的应用场景
- 防止两个相邻块级元素的上下 margin 发生重叠 (上下margin合并问题)
- 清除浮动
- 实现自适应布局, 防止元素被浮动元素覆盖(左边固定, 右边自适应)
8.怎么实现右边固定,左边自适应
flex
:让两个盒子平级处于同一个父盒子内,给父盒子设置display:flex,将右边的盒子宽度写死,左边的盒子设置flex:1定位+overflow
:让两个盒子平级处于同一个父盒子内,给父盒子设置position:relative,右边的子盒子设置宽高,设置position为absolute或fixed,top和right都为0,将左边的盒子设置overflow:hidden即可float
:将右边的盒子设置好宽高并设置float:right,使两个盒子在一行显示,然后给左边盒子设为margin-right,设置的值等于右边盒子的宽度即可
9.如何实现三角形
将盒子的宽高设置为0,再设置盒子的border属性,将四个方向的盒子都设置的比较宽,然后清除盒子的border-top,并将border的left和right都设置为透明(transparent)即可得到一个三角形
div {
width: 0;
height: 0;
border: 50px solid #333;
border-top: 0;
border-left: 50px solid transparent;
border-right: 50px solid transparent;
}
10.高度为0.5像素的线条
- 绘制1px的线条然后使用transform:scale(0.5) 或zoom(0.5)进行缩放
- 绘制一个渐变色的背景,一半有颜色一半无颜色
11.flex:1的理解
- flex是一个
复合属性
是 flex-grow,flex-shrink,flex-basis 简写 - flex:1 -> flex-grow:1;flex-shrink:1;flex-basis:0%;
- flex-grow:1; 当父盒子宽度比所有子盒子宽度宽的时候,剩余空间放大比例, flex-shrink 剩余空间缩小比例
- flex-basis不加的话,width就是减掉自身设置的数值,加上flex-basis:0%;原来width相当于没有写(flex 和 width)
12.在 script 标签上使用 defer 和 async 的区别是什么?
明确: defer 和 async 的使用, 可以用于提升网页性能
-
<script src="example.js"></script>
没有defer或async属性,浏览器会立即加载并执行相应的脚本。 不等待后续加载的文档元素,读到就开始加载和执行,此举会阻塞后续文档的加载
-
<script async src="example.js"></script>
有了async属性,表示后续文档的加载和渲染与js脚本的加载和执行是并行进行的,即异步执行;
-
<script defer src="example.js"></script>
有了defer属性,加载后续文档的过程和js脚本的加载是并行进行的(异步),此时的js脚本仅加载不执行, js脚本的 执行需要等到文档所有元素解析完成之后,DOMContentLoaded事件触发执行之前。
总结:
- defer和async在网络加载过程是一致的,都是
异步执行
的;(放在页面顶部, 也不会阻塞页面的加载, 与页面加 载同时进行) - 两者的区别, 脚本加载完成之后, async是立刻执行, defer会等一等 (等前面的defer脚本执行, 等dom的加载) 所以, js脚本加上 async 或 defer, 放在头部可以减少网页的下载加载时间, 如果不考虑兼容性, 可以用于
优化页面加载的性能
13.通过 CSS 的哪些方式可以实现隐藏页面上的元素?
- opacity: 0;通过将元素的透明度设置为0,实现看起来隐藏的效果;但是依然会占用空间并可以进 行交互
- visibility: hidden ;与透明度为0的方案非常类似,会占据空间,但不可以进行交互
- overflow: hidden;只会隐藏元素溢出的部分;占据空间且不可交互
- display: none ;可以彻底隐藏元素并从文档流中消失,不占据空间也不能交互,且不影响布局
- z-index: -9999;通过将元素的层级置于最底层,让其他元素覆盖住它,达到看起来隐藏的效果
- transform: scale(0,0);通过将元素进行缩放,缩小为0;依然会占据空间,但不可交互
- left: -9999px ;通过将元素定位到屏幕外面,达到看起来看不到的效果
js部分
这部分直接上难点,也是非常常问的题,这四题几乎可以串联一起讲,如果都彻底理解了,面试也就大部分能灵活说明白了。😼😼
1.事件循环——EventLoop
JavaScript 是一门单线程执行的编程语言。也就是说,同一时间只能做一件事情。
事件循环(EventLoop)是JavaScript的运行机制,主线程从"任务队列"中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为Event Loop(事件循环)
单线程执行任务队列的问题: 如果前一个任务非常耗时,则后续的任务就不得不一直等待,从而导致程序假死
的问题。
- 为了防止某个耗时任务导致程序假死的问题,JavaScript 把待执行的任务分为了两类:
① 同步任务(synchronous)
1.又叫做非耗时任务,指的是在主线程上排队执行的那些任务
2.只有前一个任务执行完毕,才能执行后一个任务
② 异步任务(asynchronous)
1.又叫做耗时任务,异步任务由 JavaScript 委托给宿主环境进行执行
2.当异步任务执行完成后,会通知 JavaScript 主线程执行异步任务的回调函数
- 同步任务和异步任务的执行过程
① 同步任务由 JavaScript 主线程次序执行
② 异步任务委托给宿主环境执行
③ 已完成的异步任务对应的回调函数,会被 加入到任务队列中等待执行
④ JavaScript 主线程的执行栈被清空后,会 读取任务队列中的回调函数,次序执行
⑤ JavaScript 主线程不断重复上面的第 4 步
EventLoop 的基本概念
JavaScript 主线程从
任务队列
中读取异步任务的回调函数,放到执行栈中依次执行。这个过程是循环不断的,所以整个的这种运行机制又称为 EventLoop(事件循环)。
2.什么是宏任务和微任务
JavaScript 把异步任务又做了进一步的划分,异步任务又分为两类,分别是:
① 宏任务(macrotask)
1.异步 Ajax 请求、
2.setTimeout、setInterval、
3.文件操作
4.其它宏任务
② 微任务(microtask)
1.Promise.then、.catch 和 .finally
2.process.nextTick
3.其它微任务
宏任务和微任务的执行顺序
- 每一个宏任务执行完之后,都会检查是否存在待执行的微任务, 如果有,则执行完所有微任务之后,再继续执行下一个宏任务。
做两个小案例巩固一下
用Promise写一个红绿灯
function red() {
console.log('red')
}
function green() {
console.log('green')
}
function yellow() {
console.log('yellow')
}
const light = function (timer, cb) {
return new Promise((resolve) => {
setTimeout(() => {
cb()
resolve()
}, timer)
})
}
const step = function () { // 定义红绿灯
Promise.resolve()
.then(() => {
return light(3000, red)
})
.then(() => {
return light(2000, green)
})
.then(() => {
return light(1000, yellow)
})
.then(() => {
return step()
})
}
step()
看代码说出执行顺序
async function testSometing() {
console.log('执行testSometing')
return 'testSometing'
}
async function testAsync() {
console.log('执行testAsync')
return Promise.resolve('hello async')
}
async function test() {
console.log('test start...')
const v1 = await testSometing()
console.log(v1)
const v2 = await testAsync()
console.log(v2)
console.log(v1, v2)
}
test()
var promise = new Promise((resolve) => {
console.log('promise start...')
resolve('promise')
})
promise.then((val) => console.log(val))
console.log('test end...')
输出结果
3.你是怎么理解ES6中 Promise的?使用场景
Promise
,译为承诺,是异步编程的一种解决方案(而不会陷入回调地狱),比传统的解决方案(回调函数)更加合理和更加强大
在以往我们如果处理多层异步操作。
异步函数 在底层使用了 promise,因此了解 promise 的工作方式是了解 async 和 await 的基础。
一个 Promise
必然处于以下几种状态之一:
- 待定
(pending)
: 初始状态,既没有被兑现,也没有被拒绝。 - 已成功
(fulfilled/resolved)
: 意味着操作成功完成,承诺实现了。 - 已拒绝
(rejected)
: 意味着操作失败。
promise如何执行呢?
当 promise 被调用后,它会以处理中状态 (pending)
开始。 这意味着调用的函数会继续执行,而 promise 仍处于处理中直到解决为止,从而为调用的函数提供所请求的任何数据。
被创建的 promise 最终会以被解决状态 (resolved)
或 被拒绝状态 (rejected)
结束,并在完成时调用相应的回调函数(传给 then 和 catch)。
有两个注意点:
- Promise只以
第一次为准
,第一次成功就永久
为resolved
,第一次失败就永远状态为rejected
- Promise中有
throw
的话,就相当于执行了reject
promise.all()
- Promise.all() 方法接收一个 promise 的 iterable 类型(注:Array,Map,Set 都属于 ES6 的 iterable 类型)的输入,并且只返回一个
Promise
实例, 那个输入的所有 promise 的 resolve 回调的结果是一个数组。
Promise.all
等待所有都完成(或第一个失败)。
Promise.all
在任意一个传入的promise
失败时返回失败。例如,如果你传入的promise
中,有四个promise
在一定的时间之后调用成功函数,有一个立即调用失败函数,那么Promise.all
将立即变为失败。
promise.race()
Promise.race(iterable)
方法返回一个 promise,一旦迭代器中的某个 promise 解决或拒绝,返回的 promise 就会解决或拒绝。race
函数返回一个Promise
,它将与第一个传递的 promise 相同的完成方式被完成。它可以是完成( resolves),也可以是失败(rejects),这要取决于第一个完成的方式是两个中的哪个。(类似于赛跑)
4.async/await是什么?
ES7 标准中新增的 async
函数,从目前的内部实现来说其实就是 Generator
函数的语法糖
,使得异步操作变得更加方便。
使用 关键字 async 来表示,在函数内部使用 await 来表示异步。
它基于 Promise,并与所有现存的基于 Promise 的 API 兼容。
async 关键字
async
关键字用于声明⼀个异步函数,该函数的执行不会阻塞后面代码的执行- async声明的函数的返回本质上是一个Promise,async函数内部会返回一个Promise对象,then方法回调函数的参数
async
函数内部可以使⽤await
await 关键字
await
用于等待异步的功能执⾏完毕var result = await someAsyncCall()
await
放置在 Promise 调⽤之前,会强制 async 函数中其他代码等待,直到 Promise 完成并返回结果await
只能与 Promise ⼀起使⽤await
只能在async
函数内部使⽤await
后面跟的是Promise 执行后返回的成功或者失败的值,取到值后才会往下执行
总结:
- 同步化代码的阅读体验(Promise 虽然摆脱了回调地狱,但 then 链式调⽤的阅读负担还是存在的)
- 和同步代码更一致的错误处理方式( async/await 可以⽤成熟的 try/catch 做处理,比 Promise 的错误捕获更简洁直观)
- 调试时的阅读性, 也相对更友好
浏览器JSONP原理
基本原理: 主要就是利用了 script 标签
的src没有跨域限制来完成的。
执行过程:
- 前端定义一个解析函数(如: jsonpCallback = function (res) {})
- 通过params的形式包装script标签的请求参数,并且声明执行函数(如cb=jsonpCallback)
- 后端获取到前端声明的执行函数(jsonpCallback),并以带上参数且调用执行函数的方式传递给前端
- 前端在script标签返回资源的时候就会去执行jsonpCallback并通过回调函数的方式拿到数据了
缺点:
- 只能进行GET请求
优点:
- 兼容性好,在一些古老的浏览器中都可以运行
Vue部分
这部分的知识点以后大半都会更新发布较难的点了,都牵涉到原理方向的问题,我会尽力说的明白易懂一些,如有说的不太对的地方还请多多包涵指点😺😺
1.Vue中的$nextTick有什么作用?
- NextTick是什么?
官方对其的定义:
在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM
什么意思呢?
我们可以理解成,Vue
在更新 DOM
时是异步执行的。当数据发生变化,Vue
将开启一个异步更新队列,视图需要等队列中所有数据变化完成之后,再统一进行更新
- 为什么要有nexttick?
举个例子
{{num}}
for(let i=0; i<100000; i++){
num = i
}
如果没有 nextTick
更新机制,那么 num
每次更新值都会触发视图更新(上面这段代码也就是会更新10万次视图),有了nextTick
机制,只需要更新一次,所以nextTick
本质也是一种优化策略
-
使用场景
如果想要在修改数据后立刻得到更新后的
DOM
结构,可以使用Vue.nextTick()
第一个参数为:回调函数(可以获取最近的
DOM
结构)第二个参数为:执行函数上下文
- 组件内使用
vm.$nextTick()
实例方法只需要通过this.$nextTick()
,并且回调函数中的this
将自动绑定到当前的Vue
实例上 $nextTick()
会返回一个Promise
对象,可以是用async/await
完成相同作用的事情
- 组件内使用
2.vue 中 route 和 router 的区别
- route 是当
前路由信息
,可以获取到当前路由地址参数等等 - router 是全局路由(VueRouter)实例对象,可以通过 router 进行
路由的跳转
后退等等
EventBus如何实现
- 使用场景:兄弟组件传值
- 创建一个中央事件总线
EventBus
- 兄弟组件通过
$emit
触发自定义事件,$emit
第二个参数为传递的数值 - 另一个兄弟组件通过
$on
监听自定义事件
Bus.js
// 创建一个中央时间总线类
class Bus {
constructor() {
this.callbacks = {}; // 存放事件的名字
}
$on(name, fn) {
this.callbacks[name] = this.callbacks[name] || [];
this.callbacks[name].push(fn);
}
$emit(name, args) {
if (this.callbacks[name]) {
this.callbacks[name].forEach((cb) => cb(args));
}
}
}
// main.js
Vue.prototype.$bus = new Bus() // 将$bus挂载到vue实例的原型上
// 另一种方式
Vue.prototype.$bus = new Vue() // Vue已经实现了Bus的功能
Children1.vue
this.$bus.$emit('foo')
Children2.vue
this.$bus.$on('foo', this.handle)
3.Vue2数据响应式式的原理
- 用语义化的说话来说这块的知识可能会更懂一点,按照以下这种思路和顺序能让你在面试中更加得心应手
1.所谓的数据响应式指的是数据变了,视图跟着变
2.总结成一句话:就是利用Object.defineProperty数据劫持
和观察者模式来
实现的
3.我们vue内部把data中对象进行了转换,把所有的属性递归实现
了劫持Object.defineProperty,每个数据创建一个Dep被观察者
4.对模板进行编译compiler提取里面所有的需要数据的地方变成watcher观察者
,把watcher加入到对应的Dep的观察者列表中
5.当某个data中属性变化的时候,由于被劫持了,所以vue内部是知道的,调用对应的dep去通知观察者列表
中所有的观察者
6.观察者就去更新视图
可能会追问:
答出关键得分点是重点
- 为什么data中数据变了,vue怎么知道? ->
数据劫持,Object.defineProperty
- 数据变了一个,为什么组件中一堆用到这个数据的地方全变了? ->
观察者模式
,这是一对多的关系 - 为什么数据必须在data中初始化,后添加的为什么不是响应式 -> 因为vue在初始化的时候会把data中属性全部变成Object.defineProperty写法,后添加没有转换,所以就不是响应式(
错过了初始化转换的时机
) - 为什么数组的索引不是响应式的 ?为什么要用this.set实现响应变化)
虚拟DOM和diff算法
4.虚拟DOM
虚拟dom是对真实的dom映射
,它就是一个普通的js对象
。列如:真实的dom写法:<div></div>
,虚拟dom写法: <div id="abc">hello</div>
{tagName:'div',attrs:{id:'abc'},children:['hello']}
为什么要有虚拟dom?
-
真实的 DOM 对象身上有一堆的属性和方法,操作起来
性能慢
,而且直接操作真实的 DOM 每操作一次就会导致一次重绘和回流
,所以不太好,频繁使用还会影响页面性能,反之频繁操作虚拟 dom,不存在性能问题,等数据全部更新完之后,只会更新真实 DOM 树需要改变的地方,而且渲染新的真实的 dom,只会有一次重绘和回流 -
组件初始化的时候会在的时候创建一个虚拟 dom 树,当数据有更新的时候,会生成一棵新的虚拟 dom 树,接下来会对新旧 dom 树按照是
广度优先、同层进行比较
(时间复杂度是 O(n) —>线性复杂度)(数据结构知识,不太明白的同学可以直接略过)
diff算法
当数据变化时,虚拟dom也要进行dom对比更新,使用diif算法使得对比更加高效
,diff算法有三种情况
-
情况一:元素(标签)变了,直接这块销毁重建
-
情况二:元素没有变,属性或内容变了,修改属性或内容(
就地复用
) -
情况三:当使用v-for遍历的时候,存在二种情况:(
key的重要性
)- 没有key的时候或者key是索引index的时候,它会采用就地复用(
就地更新
)原则,在顺序上位置是同一个索引就会被认为是同一个元素,正常情况下效率确实会很高,但是当你采用unshift,sort,reverse这些方法破坏顺序的时候,就会出现不必要的元素也要去更新内容或属性
- 有key的时候,key就是一个
唯一的标识
,就像人的身份证号一样的,有了这个之后,哪怕是顺序被打乱了,但是vue知道只要key一样就是同一个元素,就不会出现去更新旧的元素的情况
- 没有key的时候或者key是索引index的时候,它会采用就地复用(
重点
-
其实虚拟dom最大的作用并不是搭配diff算法高效更新的(数据变了的时候视图更新效率更高),这只是其中一个而已,最重要的是可以跨平台
-
意思说的是一套代码,可以生成不同平台的最终代码
- 如果是html浏览器 渲染成真实的DOM
- 如果是android,渲染成android对应的UI代码
- 如果是ios,渲染成对应IOS代码
- 等等等......
-
写在最后
这篇总结题目就暂时这么多吧,最近有点忙可能还要过一阵子再次更新了,下一篇的题目就不在说小题目了,咱们直接看精髓题目讲解😆😆,想看上一篇的文章在这:# 浅总结一下前端常见面试题(一),下一篇终极文章也更了# 浅总结一下前端那些高频面试题(终极篇),发布文章仅仅是为了复习巩固和分享我的知识,如有不太对的地方还请多多包涵指点一番,关注小胡不迷路,咱们下篇文章见!😼😼