自我简介
你好,面试官,我叫XX,有两年的开发经验,来应聘前端开发工程师这一岗位,上一家公司是XXX时代科技有限公司,期间主要负责pc端网站的开发,还有微信小程序和app的维护,开发的技术栈主要就是vue全家桶和uniapp,自己平常休息时间也会经常在网络上学习比较前沿的技术栈或是钻研框架的底层源码,抗压能力也挺好,能够适应合理的加班,希望能够加入公司,成为公司的一员。
1、重排(reflow)和重绘(repaint)
- 重排或回流(reflow):
- 重排是在网络浏览器中执行的一个流程,用于重新计算文档中各元素的位置和几何形状,以便重新呈现该文档的部分内容或全部内容。 每当操作 DOM 树、更改影响布局的样式、更改元素的 className 属性或更改浏览器窗口大小时,都会发生重排现象。
- 重绘(repaint):
- 在不改变文档布局的情况下,文档元素发生的例如背景颜色等外观改变的行为可称为重绘。
2、如何避免重绘或者重排
集中改变样式
,不要一条一条地修改 DOM 的样式,集中改样式可先把所有样式给class,然后再给标签。- 不要把 DOM 结点的属性值放在循环里当成循环里的变量。
- 为动画的 HTML 元件使用
固定定位(fixed)
或绝对定位(absoult)
,那么修改他们的 CSS 是不会 重排的。 - 不使用 table 布局。因为可能很小的一个小改动会造成整个 table 的重新布局。
- 尽量只修改
固定定位(fixed)
或绝对定位(absoult)
元素,对其他元素影响不大
3、简述一下你对 HTML 语义化的理解
合理使用标签:
- 可读性。代码结构清晰,便于理解,对人和机器的可读性都更好。
- 可维护和团队协作。清晰语义化的结构,可维护性更高,更有利于团队协作。
- seo。有利于搜索引擎优化(seo)。
常见语义化标签:h1-h6,header,footer,main,nav,title,article,time,progress,aside,strong,ul,ol 等。
4、HTTP 请求跨域问题
-
跨域的原理
跨域,是指浏览器不能执行其他网站的脚本。它是由浏览器的
同源策略
造成的。
同源策略,是浏览器对 JavaScript 实施的安全限制,只要协议、域名、端口
有任何一个不同,都被当作是不同的域。
跨域原理,即是通过各种方式,避开浏览器的安全限制
。 -
解决方案
- JSONP:
ajax 请求受同源策略影响,不允许进行跨域请求,而 script 标签 src 属性中的链 接却可以访问跨域的 js 脚本,利用这个特性,服务端不再返回 JSON 格式的数据,而是 返回一段调用某个函数的 js 代码,在 src 中进行了调用,这样实现了跨域。
JSONP 的缺点:
JSONP 只支持 get,因为 script 标签只能使用 get 请求; JSONP 需要后端配合返回指定格式的数据。
- proxy代理
目前常用方式。 通俗点说就是客户端浏览器发起一个请求会存在跨域问题,但是服务端向另一个服务端发起请求并无跨域,因为跨域问题归根结底源于同源策略,而同源策略只存在于浏览器
那么我们是不是可以通过
Nginx
配置一个代理服务器,反向代理访问跨域的接口,并且我们还可以修改Cookie
中domain
信息,方便当前域Cookie
写入- CORS
CORS(Cross-origin resource sharing)跨域资源共享,是通过 HTTP 响应头决定浏览器是否阻止前端 JS 代码跨域获取资源,CORS 主要在服务器端进行配置。客户端浏览器无须做任何额外的配置,即可请求开启了 CORS 的接口。
- Websocket
5、一次http的请求过程
-
域名解析
-
发起TCP的3次握手
为什么需要三次握手,两次不行吗?其实这是由 TCP 的自身特点可靠传输决定的。客户端和服务端要进行可靠传输,那么就需要确认双方的接收和发送能力。第一次握手可以确认客服端的发送能力,第二次握手,确认了服务端的发送能力和接收能力,所以第三次握手才可以确认客户端的接收能力。不然容易出现丢包的现象。
-
建立TCP连接后发起http请求
-
服务器端响应http请求,浏览器得到html代码
-
浏览器解析html代码,并请求html代码中的资源
-
浏览器对页面进行渲染呈现给用户
6、同步和异步的区别
同步是阻塞模式,异步是非阻塞模式
同步就是指一个进程在执行某个请求的时候,若该请求需要一段时间才能返回信息,那么这个进程将会一直等待下去,直到收到返回信息才继续执行下去;
异步是指进程不需要一直等下去,而是继续执行下面的操作,不管其他进程的状态。当有消息返回时系统会通知进程进行处理,这样可以提高执行的效率。
7、深拷贝和浅拷贝
-
浅拷贝是创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型.拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址,浅拷贝基本类型之前互不影响,引用类型其中一个对象改变了地址,就会影响另一个对象。
-
深拷贝是将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟个新的区域存放新对象,深拷贝改变新对象不会影响原对象,他们之前互不影响。
8、前端界面又哪三层组成
网页分成三个层次,即:结构层、表示层、行为层。
1.网页的结构层(structurallayer)由HTML 或XHTML之类的标记语言负责创建。标签,也就是那些出现在尖括号里的单词,对网页内容的语义含义做出这些标签不包含任何关于如何显示有关内容的信息。
2.网页的表示层(presentationlayer)由CSS 负责创建。
3.网页的行为层(behaviorlayer)负责回答"内容应该如何对事件做出反应"这一问题。
9、谈谈你对MVVM开发模式的理解
MVVM分为Model、View、ViewModel三者。
1、Model 代表数据模型,数据和业务逻辑都在Model层中定义;
2、View 代表UI视图,负责数据的展示;
3、ViewModel 负责监听 Model 中数据的改变并且控制视图的更新,用于处理用户交互操作;
Model 和 View 并无直接关联,而是通过 ViewModel 来进行联系的,Model 和 ViewModel 之间有着双向数据绑定的联系。因此当 Model 中的数据改变时会触发 View 层的刷新,View 中由于用户交互操作而改变的数据也会在 Model 中同步。这种模式实现了 Model 和 View 的数据自动同步,因此开发者只需要专注对数据的维护操作即可,而不需要自己操作 dom。
10、http和https的区别
http传输的数据都是未加密的,也就是明文的
https协议是由http和ssl协议构建的可进行加密传输和身份认证的网络协议,比http协议的安全性更高。
Https协议需要ca证书,费用较高。
http协议的默认端口为80,https的默认端口为443
11、Vue响应式原理
响应式的核心是通过 Object.defineProperty
拦截对数据的访问和设置
响应式的数据分为两类:
- 对象,循环遍历对象的所有属性,为每个属性设置 getter、setter,以达到拦截访问和设置的目的,如果属性值依旧为对象,则递归,为属性值上的每个 key 设置 getter、setter
- 访问数据时(obj.key)进行依赖收集,在 dep 中存储相关的 watcher
- 设置数据时由 dep 通知相关的 watcher 去更新
- 数组,增强数组的那 7 个可以更改自身的原型方法,然后拦截对这些方法的操作
- 添加新数据时进行响应式处理,然后由 dep 通知 watcher 去更新
- 删除数据时,也要由 dep 通知 watcher 去更新
Dep的作用是依赖收集器
12、computed和watch的区别
在源码里computed 和 watch 的本质是一样的,内部都是通过 Watcher 来实现的,其实没什么区别,非要说区别的化就两点:1、使用场景上的区别,2、computed 默认是懒执行的,切不可更改。
13、路由三种模式
-
hash模式
路由上会有个#号,#后面 hash 值的变化,并不会导致浏览器向服务器发出请求,浏览器不发出请求,也就不会刷新页面。每次 hash 值的变化,会触发
hashchange
这个事件。然后我们便可以监听hashchange
来实现更新页面部分内容的操作 -
history模式
会去掉路径中的 “#”。当用户刷新页面之类的操作时,浏览器会给服务器发送请求,所以这个实现需要服务器的支持,需要把所有路由都重定向到根页面,否则会出现404。
-
abstract模式
适用于所有JavaScript环境,例如服务器端使用Node.js。如果没有浏览器API,路由器将自动被强制进入此模式。
14、封装一个好的组件的要素
- 单一原则:负责单一的页面渲染
- 多重职责:负责多重职责,获取数据,复用逻辑,页面渲染等
- 明确接受参数:必选,非必选,参数尽量设置以_开头,避免变量重复
- 可扩展:需求变动能够及时调整,不影响之前代码
- 代码逻辑清晰
- 封装的组件必须具有高性能,低耦合的特性
- 组件具有单一职责:封装业务组件或者基础组件,如果不能给这个组件起一个有意义的名字,证明这个组件承担的职责可能不够单一,需要继续抽组件,直到它可以是一个独立的组件即可。
15、Vue样式模块化
scope
当 style 标签加上 scoped 属性时,scoped 会在 DOM 结构及 css 样式上加上唯一性的标记 data-v-xxx 属性,从而达到样式私有化,不污染全局的作用;
16、防抖节流函数
防抖
-
原理:在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时。
-
适用场景:
- 按钮提交场景:防止多次提交按钮,只执行最后提交的一次
- 搜索框联想场景:防止联想发送请求,只发送最后一次输入
-
简易版实现
function debounce(func, wait) { let timeout; return function () { const context = this; const args = arguments; clearTimeout(timeout) timeout = setTimeout(function(){ func.apply(context, args) }, wait); } }
节流
-
原理:规定在一个单位时间内,只能触发一次函数。如果这个单位时间内触发多次函数,只有一次生效。
-
适用场景
- 拖拽场景:固定时间内只执行一次,防止超高频次触发位置变动
- 缩放场景:监控浏览器resize
-
使用定时器实现
- 当触发事件的时候,我们设置一个定时器,再触发事件的时候,如果定时器存在,就不执行,直到定时器执行,然后执行函数,清空定时器,这样就可以设置下个定时器。
function throttle(func, wait) { let timeout; return function () { const context = this; const args = arguments; if (!timeout) { timeout = setTimeout(function () { timeout = null; func.apply(context, args) }, wait) } } }
17、Promise
Promise是一种异步编程的解决方案,有三种状态,pending(进行中)、fulfilled(已完成)、rejected(已失败)。当Promise的状态由pending转变为fulfilled或rejected时,会执行相应的方法
只要Promise 的状态发生了变化,之后就再也不会改变,就像整个 Promise 实例凝固了
特性:1、代码立即执行 2、状态的不可逆性 3、回调异步性 4、链式调用
18、Vuex
state、getters、mutations、actions、modules
state
State 提供唯一的公共数据源,所有共享的数据都要放到state中进行存储
mutations
用户变更Store中的数据。
- 只能通过这种方法变更Store中的数据,不可以直接操作Store中的数据。
- 通过这种方式虽然操作起来稍微繁琐一些,但是可以集中监控所有数据的变化
- 一般不在里边写异步操作,否则调试工具不会同步更新
actions
用于处理异步操作
如果通过异步操作变更数据,必须通过Actions,而不能使用Mutations,但是在Action中还是要触发Mutations的方式间接变更数据。
getters
用于对store中的数据进行加工处理形成新的数据。
- getter可以对store中已有的数据加工处理之后形成新的数据,类似Vue中的计算属性
- store中的数据发生变化后,getter的数据也会跟着变化
modules
拆分模板,把复杂的场景按模块来拆开
19、axios二次封装了啥
-
多环境:开发、测试、生产环境。
-
统一错误处理:401、404、500等错误。
-
断网、请求超时处理。
-
请求取消/请求拦截:防止重复请求发送到服务端,造成服务端压力。
-
请求权限:某些接口必须要有登录状态才可以访问。
如何对axios二次封装呢?
首先肯定是要使用create,初始化axios实例,然后设置请求拦截和响应拦截,里边跟根据具体业务需求编写逻辑,比如加token呀,或者是响应拦截的错误处理,然后呢就是api的集中式管理,比如用户服务接口,就都封装在user.js文件里,页面如果使用直接调用传值就可以
20、HTML5新增了哪些特性?
-
Canvas、SVG用于绘画的元素
Canvas 主要是用笔刷来绘制 2D 图形的。 SVG 主要是用标签来绘制不规则矢量图的。 相同点:都是主要用来画 2D 图形的。 不同点:Canvas 画的是位图,SVG 画的是矢量图。 不同点:SVG 节点过多时渲染慢,Canvas 性能更好一点,但写起来更复杂。 不同点:SVG 支持分层和事件,Canvas 不支持,但是可以用库实现。
-
video、audio用于播放视频和音频的媒体
-
Drag 、Drop用于拖放的
-
Geolocation 用于获取地理位置
-
localStorage、sessionStorage用于本地离线存储
-
web Worker 独立于其他脚本,不影响页面性能运行在后台的javascript
-
webSocket 单个TCP连接上进行全双工通讯的协议。
21、原型和原型链
- 原型:每一个 JavaScript 对象(null 除外)在创建的时候就会与之关联另一个对象,这个对象就是我们所说的原型,每一个对象都会从原型"继承"属性,其实就是
prototype
对象。 - 原型链:由相互关联的原型组成的链状结构就是原型链。
22、作用域与作用域链
作用域:规定了如何查找变量,也就是确定当前执行代码对变量的访问权限。换句话说,作用域决定了代码区块中变量和其他资源的可见性。(全局作用域、函数作用域、块级作用域)
作用域链:从当前作用域开始一层层往上找某个变量,如果找到全局作用域还没找到,就放弃寻找 。这种层级关系就是作用域链。
23、async/await 和 Promise 的关系
- async/await 是消灭异步回调的终极武器。
- 但和 Promise 并不互斥,反而,两者相辅相成。
- 执行 async 函数,返回的一定是 Promise 对象。
- await 相当于 Promise 的 then。
- try…catch 可捕获异常,代替了 Promise 的 catch。
24、Cookie、sessionStorage、localStorage 的区别
相同点:
- 存储在客户端
不同点:
- cookie数据大小不能超过4k;sessionStorage和localStorage的存储比cookie大得多,可以达到5M+
- cookie设置的过期时间之前一直有效;localStorage永久存储,浏览器关闭后数据不丢失除非主动删除数据;sessionStorage数据在当前浏览器窗口关闭后自动删除
- cookie的数据会自动的传递到服务器;sessionStorage和localStorage数据保存在本地
25、keep-alive
- 使用keep-alive包裹动态组件时,会对组件进行缓存,避免组件重新创建
- 当组件在被切换时,它的 ‘activated’ 和 ‘deactivated’ 这两个生命周期钩子函数将会被对应执行。
26、flex 布局
flex-direction:属性决定主轴的方向(即子元素的排列方向)
justify-content:设置横轴方向上的对齐方式
align-items:设置纵轴方向上的对齐方式
flex-wrap:设置弹性盒子的子元素超出父容器时是否换行
align-content:修改 flex-wrap 属性的行为,类似 align-items, 但不是设置子元素对齐,而是设置行对齐
这里有个小问题,很多时候我们会用到 flex: 1
,它具体包含了以下的意思:
flex-grow: 1
:该属性默认为0
,如果存在剩余空间,元素也不放大。设置为1
代表会放大。flex-shrink: 1
:该属性默认为1
,如果空间不足,元素缩小。flex-basis: 0%
:该属性定义在分配多余空间之前,元素占据的主轴空间。浏览器就是根据这个属性来计算是否有多余空间的。默认值为auto
27、性能优化
- 防抖和节流(resize,scroll,input)。
- 减少重排(回流)和重绘。
- 尽量减少http请求
- v-show和v-if的合理使用
- keep-alive缓存组件
- 事件委托。
- 减少 DOM 操作。
- 按需加载组件
28、Vue路由守卫有哪些,怎么设置,使用场景等
常用的两个路由守卫:router.beforeEach 和 router.afterEach
每个守卫方法接收三个参数:
- to: Route: 即将要进入的目标 路由对象
- from: Route: 当前导航正要离开的路由
- next: Function: 一定要调用该方法来 resolve 这个钩子。
在项目中,一般在beforeEach这个钩子函数中进行路由跳转的一些信息判断。 判断是否登录,是否拿到对应的路由权限等等。
29、JS的new做了什么
- 创建临时对象
- 绑定原型
- 指定this = 临时对象
- 执行构造函数
- 返回临时对象
30、JS的数据类型有哪些
string、number、boolean、undefined、null、bigint、symbol、object
31、diff算法
进入patch方法对比新旧虚拟节点,如果不相同,会直接暴力删除旧创建新
如果相同进入patchVnode:判断子集情况
1.oldVnode有子节点,newVnode没有子节点:删除old子节点
2.oldVnode没有子节点,newVnode有子节点:暴力新建new子节点
3.都只有文本节点:直接new文本替换old文本
4.都有子节点:进入updateChildren方法,进行首尾指针法
首尾指针法比对是按,头头,尾尾,头尾,尾头来进行的,如果以上几个都比对不上就会直接去旧的里边查找,找到就把旧的那个值设置为undefined,之后再进行进行对比,以此类推
32、如何判断对象是不是一个空对象
- Object.keys(obj).length == 0
- JSON.stringify(obj)===‘{}’
33、如何判断是否是数组
- Array.isArray(arr)
- arr instanceof Array //有可能不准确
- arr.constructor === Array //有可能不准确 注意:constructor可以被重写,所以不能确保一定是数组
- Object.prototype.toString.call(arr) === ‘[object Array]’
34、随机数
获取0-9的随机数 parseInt(Math.random() * 10)
获取0-N的随机数 parseInt(Math.random() * N)
获取1-10的随机数 parseInt(Math.random() * 10 + 1)
获取1-N的随机数 parseInt(Math.random() * N + 1)
用floor()写法和parseInt()一样
34、JS执行原理
js中执行原理主要分为三个东西,分别是:编译器,引擎,作用域,首先编译器运行生成代码块,引擎根据代码块去执行处理,遇到声明值或者取值就去找作用域询问
35、作用域都有哪些
-
词法作用域
此作用域也是js所使用的作用域,它是在代码编译之前就已经根据代码来生成了嵌套作用域
-
动态作用域
实际上动态作用域是JavaScript 另一个重要机制 this 的表亲,它是在代码运行时,谁调用它,它的作用域就属于谁
下面写个案例来演示它们两个的区别
案例:
function foo() {
console.log( a ); // 如果是词法作用域此处输出2,如果是动态作用域此处输出3
}
function bar() {
var a = 3;
foo();
}
var a = 2;
bar();
词法作用域让 foo() 中的 a 通过 RHS 引用到了全局作用域中的 a,因此会输出 2。
而动态作用域并不关心函数和作用域是如何声明以及在何处声明的,只关心它们从何处调
用。换句话说,作用域链是基于调用栈的,而不是代码中的作用域嵌套
因此,如果 JavaScript 具有动态作用域,理论上,代码中的 foo() 在执行时将会输出 3。
因为当 foo() 无法找到 a 的变量引用时,会顺着调用栈在调用 foo() 的地
方查找 a,而不是在嵌套的词法作用域链中向上查找。由于 foo() 是在 bar() 中调用的,
引擎会检查 bar() 的作用域,并在其中找到值为 3 的变量 a