面试题
自我介绍
面试官下午好,我叫XXX,今天来应聘贵公司的前端工程师岗位。我从事前端开发两年多,有2年多的Vue开发经验和uni-app的开发经验,在上家公司主要从事H5页面,后台管理系统,微信小程序等项目开发。平常喜欢逛一些技术社区丰富自己的技术,像掘金。 我的性格比较温和,跟同事朋友相处时比较外向,在工作中代码开发时我喜欢全心全意的投入,对于工作我总抱着认真负责的态度。面试官,以上是我的介绍,谢谢。
谈谈JS的垃圾回收机制
垃圾回收机制有两种方式,一种是引用法,一种是标记法
引用法
判断一个对象的引用数,引用数为0就回收,引用数大于0就不回收。
缺点:两个对象互相引用,各自引用数都是1,所以不会被回收,从而造成内存泄漏。
标记法
从window的指针开始,递归搜索子节点,并为其进行标记,直到所有子节点被遍历结束。那么没有被遍历到节点,也就没有被标记,也就会被当成没有被任何地方引用,就可以证明这是一个需要被释放内存的对象,可以被垃圾回收器回收。
事件循环(event loop)
JS是单线程的,为了防止一个函数执行时间过长阻塞后面的代码,所以会先将同步代码压入执行栈中,依次执行,将异步代码推入异步队列,异步队列又分为宏任务队列和微任务队列,因为宏任务队列的执行时间较长,所以微任务队列要优先于宏任务队列。微任务队列的代表就是,Promise.then,MutationObserver,宏任务的话就是setImmediate setTimeout setInterval
事件冒泡、捕获(委托)
●事件冒泡指在在一个对象上触发某类事件,如果此对象绑定了事件,就会触发事件,如果没有,就会向这个对象的父级对象传播,最终父级对象触发了事件。
●事件委托本质上是利用了浏览器事件冒泡的机制。因为事件在冒泡过程中会上传到父节点,并且父节点可以通过事件对象获取到目标节点,因此可以把子节点的监听函数定义在父节点上,由父节点的监听函数统一处理多个子元素的事件,这种方式称为事件代理。
event.stopPropagation() 或者 ie下的方法 event.cancelBubble = true; //阻止事件冒泡
ES6新特性
1新增symbol类型 表示独一无二的值,用来定义独一无二的对象属性名;
2const/let 都是用来声明变量,不可重复声明,具有块级作用域。存在暂时性死区,也就是不存在变量提升。(const一般用于声明常量);
3变量的解构赋值(包含数组、对象、字符串、数字及布尔值,函数参数),剩余运算符(...rest);
4.模板字符串(${data});
5.扩展运算符(数组、对象);;
6.箭头函数;
7.Set和Map数据结构;
8.Proxy/Reflect;
9.Promise;
10.async函数;
11.Class;
12.Module语法(import/export)。
深浅拷贝
JavaScript中存在两大数据类型:基本类型、引用类型
基本类型数据保存在在栈内存中,引用类型数据保存在堆内存中,引用数据类型的变量是一个指向堆内存中实际对象的引用,存在栈中。
浅拷贝:指的是创建新的数据,这个数据有着原始数据属性值的一份精确拷贝
如果属性是基本类型,拷贝的就是基本类型的值。如果属性是引用类型,拷贝的就是内存地址
即浅拷贝是拷贝一层,深层次的引用类型则共享内存地址
在JS中存在浅拷贝的API有:Object.assign()、[].slice()、[].concat()、...运算符
深拷贝:递归拷贝深层次,属性为对象时,深拷贝是新开栈,两个对象指向不同的地址
JS深拷贝:JSON.parse(JSON.stringify(对象));
第三方lodash的deepCone()
跨域
JSONP
跨域资源共享(CORS)
服务器代理(Proxy)
原生AJAX
ajax是一种异步通信的方法,从服务端获取数据,达到局部刷新页面的效果。 过程:
1.创建XMLHttpRequest对象;
2.调用open方法传入三个参数 请求方式(GET/POST)、url、同步异步(true/false);
3.监听onreadystatechange事件,当readystate等于4时返回responseText;
4.调用send方法传递参数。
常见状态码
以下状态码意义自行查询
200、201、204、301、304、400、401、403、404、500
输入一个 URL到这个页面呈现出来,中间会发生什么?
DNS 解析 ->TCP 连接 -> 发送 HTTP 请求 ->服务器处理请求并返回 HTTP 报文 -> 浏览器解析渲染页面 -> 连接结束
一、DNS域名解析
输入URL后,首先需要找到这个URL域名的服务器IP,为了寻找这个IP,浏览器首先会寻找缓存,查看缓存中是否有记录,缓存的查找记录为:浏览器缓存->系统缓存->路由器缓存,缓存中没有则查找系统的 hosts 文件中是否有记录,如果没有则查询 DNS 服务器
二、建立TCP连接
根据 IP 地址,客户端与服务端进行三次握手,建立连接。
●客户端首先给服务端发送一个带SYN标志的数据包。
●服务端收到后,回传一个带有SYN/ACK标志的数据包以表示正确传达,并确认信息。
●最后,客户端再回传一个带ACK标志的数据包,代表“握手”结束。
三、传输数据
连接后,客户端向服务端发起 HTTP 请求,服务器接收到请求后,返回请求静态资源,并同时调用服务器请求接口数据。
四、关闭TCP连接
数据传输完成,客户端与服务端进行四次挥手,关闭连接。
●第一次挥手:主动关闭方发送一个FIN,用来关闭主动关闭方到被动关闭方的数据传送,也就是主动关闭方告诉被动关闭方,主动关闭方已经不会再给被动关闭方发送数据了(当然,在FIN包之前发送出去的数据,如果没有收到对应的ACK确认报文,主动关闭方依然会重发这些数据),但是,此时主动关闭方还可以接收数据。
●第二次挥手:被动关闭方收到FIN包后,给对方发送一个ACK,确认序号为收到序号+1(与SYN相同,一个FIN占用一个序号)。
●第三次挥手:被动关闭方发送一个FIN,用来关闭被动关闭方到主动关闭方的数据传送,也就是告诉主动关闭方,被动关闭方的数据也发送完了,不会再给主动关闭方发送数据了。
●第四次挥手:主动关闭方收到FIN后,给被动关闭方发送一个ACK,确认序号为收到序号+1,至此,完成四次握手。
五、渲染页面
对于浏览器根据服务端返回的静态资源,浏览器使用 Native GUI 引擎渲染 HTML 和 CSS ;使用 JS 引擎加载 JS 。
●将 HTML 节点解析成 DOM 树结构
●将 CSS 解析成 CSSOM 规则树
●将 DOM 与 CSSOM 组合成 Render-tree(渲染树)
●布局:计算出每个节点在屏幕中的位置
●绘制:即遍历render树,并使用UI后端层绘制每个节点
六、加载 JavaScript 脚本
虽然 HTML\CSS 与 JS 是通过不同的引擎加载,但是却是互斥的,即加载 HTML\CSS 时,JS 会停止加载,相反亦然,这是因为 JS 引擎可以操作 DOM,改变样式、内容等。所以当执行了 JS 之后,渲染树要重新构建。
谈谈localstorage、sessionstorage和cookie的区别
sessionStorage、localStorage和cookie共同点:都是保存在浏览器端、且同源的
区别在于:
1、cookie数据始终在同源的http请求中携带(即使不需要),即cookie在浏览器和服务器间来回传递,而sessionStorage和localStorage不会自动把数据发送给服务器,仅在本地保存。cookie数据还有路径(path)的概念,可以限制cookie只属于某个路径下
2、存储大小限制也不同,cookie数据不能超过4K,同时因为每次http请求都会携带cookie、所以cookie只适合保存很小的数据,如会话标识。sessionStorage和localStorage虽然也有存储大小的限制,但比cookie大得多,可以达到5M或更大
3、数据有效期不同,sessionStorage:仅在当前浏览器窗口关闭之前有效;localStorage:始终有效,窗口或浏览器关闭也一直保存,因此用作持久数据;cookie:只在设置的cookie过期时间之前有效,即使窗口关闭或浏览器关闭
4、作用域不同,sessionStorage不在不同的浏览器窗口中共享,即使是同一个页面;localstorage在所有同源窗口中都是共享的;cookie也是在所有同源窗口中都是共享的
谈谈你对HTTP缓存的理解
HTTP缓存分为:强制缓存和协商缓存
Vue的组件data为什么必须是一个函数?
new Vue是一个单例模式,不会有任何的合并操作,所以根实例不必校验data一定是一个函数。
组件的data必须是一个函数,是为了防止两个组件的数据产生污染。 如果都是对象的话,指向同一个地址。 而如果是函数返回的对象,会产生两个空间对应的内存地址。
Vue组件生命周期有哪些?
1beforeCreate
2created
3beforeMount 在挂载之前被调用,render尚未被调用
4mounted el被新创建的vm.$el替换,并挂载到实例上去之后调用
5beforeUpdate 数据更新时,被调用,发生在虚拟Dom重新渲染和打补丁之前
6update 由于数据更改导致的虚拟Dom重新渲染和打补丁,在这之后调用
7beforeDestroy 实例销毁之前调用
8destroyed 实例销毁之后调用,调用后Vue实例的所有东西都会被解绑,所有的事件监听会被移除,子实例被销毁,该钩子在服务端渲染期间不被调用
9keep-alive(activated & deactivated)
父子组件生命周期顺序?
子组件的生命周期嵌套在父组件的beforeMount和mounted之间
如何监听子组件的生命周期?
在子组件里通过vm.$emit()触发一个自定义事件,在父组件中监听该事件执行对应函数
使用@hook:生命周期函数名称=“回调函数”
v-show和v-if的区别是什么?
共同点:v-if 和 v-show 都能实现元素的显示隐藏 区别: 1. v-show 只是简单的控制元素的 display 属性,而 v-if 才是条件渲染(条件为真,元素将会被渲染,条件为假,元素会被销毁); 2. v-show 有更高的首次渲染开销,而 v-if 的首次渲染开销要小的多; 3. v-if 有更高的切换开销,v-show 切换开销小; 4. v-if 有配套的 v-else-if 和 v-else,而 v-show 没有 5. v-if 可以搭配 template 使用,而 v-show 不行
v-for和v-if哪个优先级更高?
v-for和v-if 不能在同一个标签中使用。先处理v-for,再处理v-if。
如果同时遇到的时候,应该考虑先用计算属性处理数据,在进行v-for,可以减少循环次数。
v-for和v-if为何不推荐同时使用?
v-for的优先级高于v-if,所以不管条件是否成立,v-for都会做循环,所以是达不到先判断再循环的作用。
如果存在需要先判断条件是否成立,再循环数据的场景我们可以使用template标签使用v-if指令做条件判断,在tempalte中的子标签做循环。
组件的传值方式有哪些?
1props和emit:父组件向子组件传递数据,通过prop传递。子组件传递数据给父组件是通过emit
2parent、children获取当前组件的父组件和当前组件的子组件
3attrs和listeners 。
4父组件通过provide提供,子组件通过inject注入变量
5$ref获取实例
6eventBus平级组件数据传递
7Vuex
computed和watch的区别是什么?
1computed和watch都基于watcher来实现的。
2computed的属性是具备缓存的,依赖的值不发生变化,对其取值时计算属性方法不会重复执行
3watch是监控值的变化,当值发生改变的时候,会调用回调函数
请说明key的作用和原理?
●key的作用主要是为了更高效的对比虚拟DOM中每个节点是否是相同节点;
●Vue在patch过程中判断两个节点是否是相同节点,key是一个必要条件,渲染一组列表时,key往往是唯一标识,所以如果不定义key的话,Vue只能认为比较的两个节点是同一个,哪怕它们实际上不是,这导致了频繁更新元素,使得整个patch过程比较低效,影响性能;
●Vue判断两个节点是否相同时主要判断两者的key和元素类型等,因此如果不设置key,它的值就是undefined,则可能永 远认为这是两个相同的节点,只能去做更新操作,这造成了大量的dom更新操作,明显是不可取的。
Vue 响应式原理是怎么实现的?
响应式的核心是通过 Object.defineProperty 拦截对数据的访问和设置
响应式的数据分为两类:
①对象,循环遍历对象的所有属性,为每个属性设置 getter、setter,以达到拦截访问和设置的目的,如果属性值依旧为对象,则递归为属性值上的每个key设置 getter、setter访问数据时(obj.key)进行依赖收集,在dep中存储相关的watcher设置数据时由dep通知相关的watcher去更新
②数组,增强数组的那7个可以更改自身的原型方法,然后拦截对这些方法的操作添加新数据时进行响应式处理,然后由 dep 通知 watcher 去更新,删除数据时,也要由 dep 通知 watcher 去更新。
Vue中如何检测数组的变化?
vue中对数组没有进行defineProperty,而是重写了数组的7个方法。 分别是:push、shift、pop、splice、unshift、sort、reverse
因为这些方法都会改变数组本身,数组里的索引和长度是无法被监控的。
Vue为什么要用虚拟DOM?
1虚拟dom就是用js对象来描述真实Dom,是对真实Dom的抽象
2由于直接操作Dom性能低,但是js层的操作效率高,可以将Dom操作转化成对象操作。最终通过diff算法比对差异进行更新Dom
3虚拟Dom不依赖真实平台环境,可以实现跨平台
【虚拟DOM保证性能下限】
Vue的diff算法原理是什么?
Vue的diff算法是平级比较,不考虑跨级比较的情况。内部采用深度递归的方式+双指针方式比较
1先比较两个节点是不是相同节点
2相同节点比较属性,复用老节点
3先比较儿子节点,考虑老节点和新节点儿子的情况
4优化比较:头头、尾尾、头尾、尾头
5比对查找,进行复用
谈谈Vue的性能优化有哪些?
1尽量减少data中的数据,合理的设置响应式数据
2使用数据时,缓存值的结果,不频繁取值
3合理设置key,key保证唯一
4v-show(频繁切换性能高)和v-if的合理使用
5v-if和v-for不能连用
6采用函数式组件 -> 函数式组件开销低
7采用异步组件 -> 借助webpack的分包策略
8使用keep-alive来缓存组件
9虚拟滚动、时间分片等策略
10数据冻结
11用路由懒加载、异步组件
12防抖、节流
13如果需要使用v-for给每项元素绑定事件时使用事件代理