前端开发面试题-问答
Posted Leatitia
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了前端开发面试题-问答相关的知识,希望对你有一定的参考价值。
1、js实现防抖和节流
函数的节流和防抖都是为了限制函数的执行频次,以优化函数触发频率过高导致的响应速度跟不上触发频率,出现延迟、假死或者卡顿现象
- 防抖(将多次操作合并为一次操作进行):触发高频时间后n秒内函数只会执行一次,如果n秒内高频时间再次被触发,则重新计算时间;
- 节流(使得一定时间内只触发一次函数):高频时间触发,但在n秒内只会执行一次,所以节流会稀释函数的执行频率;
//防抖debounce代码:
function debounce(fn,delay)
var timeout = null; // 创建一个标记用来存放定时器的返回值
return function (e)
// 每当用户输入的时候把前一个 setTimeout clear 掉
clearTimeout(timeout);
// 然后又创建一个新的 setTimeout, 这样就能保证interval 间隔内如果时间持续触发,就不会执行 fn 函数
timeout = setTimeout(() =>
fn.apply(this, arguments);
, delay);
;
// 处理函数
function handle()
console.log('防抖:', Math.random());
//滚动事件
window.addEventListener('scroll', debounce(handle,500));
//节流throttle代码:
function throttle(fn,delay)
let canRun = true; // 通过闭包保存一个标记
return function ()
// 在函数开头判断标记是否为true,不为true则return
if (!canRun) return;
// 立即设置为false
canRun = false;
// 将外部传入的函数的执行放在setTimeout中
setTimeout(() =>
// 最后在setTimeout执行完毕后再把标记设置为true(关键)表示可以执行下一次循环了。
// 当定时器没有执行的时候标记永远是false,在开头被return掉
fn.apply(this, arguments);
canRun = true;
, delay);
;
function sayHi(e)
console.log('节流:', e.target.innerWidth, e.target.innerHeight);
window.addEventListener('resize', throttle(sayHi,500));
比如在页面的无限加载场景下,我们需要用户在滚动页面时,每隔一段时间发一次 Ajax 请求,而不是在用户停下滚动页面操作时才去请求数据。这样的场景,就适合用节流技术来实现
2、事件委托和事件捕获
//事件委托
var oUl = document.getElementById("ul1");
oUl.onmouseover = function(ev)
var ev = ev || window.event;
var target = ev.target || ev.srcElement;
if(target.nodeName.toLowerCase() == 'li')
target.style.background = "red";
;
//jquery的事件代理 $.delegate
$('#parent-list').click($.delegate(
'.quit': function() console.log("do quit stuff"); ,
'.edit': function() console.log("do edit stuff");
));
//事件捕获
let box=document.getElementById("box");
box.addEventListener("click",function()console.log("box"),true);
//阻止事件冒泡
let btna = document.getElementById('btn');
btna.onclick=function(e)
window.event? window.event.cancelBubble = true : e.stopPropagation();
;
- 事件冒泡:事件传播过程中,事件在第一个元素上触发后,逐级向上传播给先辈元素为止的现象;
- 事件委托:事件委托也叫作事件代理,即利用事件冒泡,只指定一个事件处理程序,就可以管理某一个类型的所有事件,减少dom操作,提高网页性能。
例:ul下的li的点击事件,可以委托给ul实现- 事件捕获:与事件委托相反,它是从顶层元素开始,直到事件触发元素。通过DOM2事件模型target.addEventListener(type, listener, useCapture)实现。捕获事件先于委托事件发生,从document开始,也在document结束。
//阻止浏览器的默认行为
function stopDefault( e )
//一般情况下
if ( e && e.preventDefault )
e.preventDefault();
//IE中
else
window.event.returnValue = false;
return false;
3、Prototype原型链
已经声明的一个方法,想让所以这个方法的对象实例都能继承这个方法的属性,那就可以使用prototype。
原型链是针对构造函数的,比如我先创建了一个函数,然后通过一个变量new了这个函数,那么这个被new出来的函数就会继承创建出来的那个函数的属性,然后如果我访问new出来的这个函数的某个属性,但是我并没有在这个new出来的函数中定义这个变量,那么它就会往上(向创建出它的函数中)查找,这个查找的过程就叫做原型链。 任何函数都可以作为构造函数,只要被 new 了,就是一个构造函数;
- 每一个函数都有一个prototype属性,指向函数的原型对象(M.prototype);
- 每一个原型对象都有一个constructor构造器,指向引用的那个构造函数(M.prototype.constructor = M)
- 每一个对象都有一个 __ proto__属性,指向对象的构造函数的原型对象,即o2.__ proto__ === M.prototype
*原型链:从一个实例对象向上找,
原型链的顶端是Object.prototype这个原型对象的原型Object.prototype.__proto__为空
4、new Vue()发生了什么
new Vue()创建了根实例并且初始化数据和方法。待执行挂载时,此过程还会递归的应用于它的子组件上,最终形成一个有紧密关系的组件实例树。
5、vue.use是干什么的?原理是什么?
vue.user()是用来使用插件的,可以在插件中扩展全局指令、组件、原型方法等。
- 检查插件是否注册,若已注册,则跳出;
- 处理入参,将第一个参数之后的参数归集,并且在首部塞入 this 上下文;
执行注册方法,调用install方法,处理入参。若没有install方法切插件本身就是function,则直接进行注册;
插件本身是一个函数,直接让函数执行。 代码:plugin.apply(null, args)
6、请说一下响应式数据的理解?
根据数据类型来做不同处理,数组和对象类型当值变化时如何劫持。
- 对象内部通过defineReactive方法,使用Object.defineProperty() 监听数据属性的 get 来进行数据依赖收集,再通过 set 来完成数据更新的派发;
- 数组则通过重写数组方法来实现的。扩展它的 7 个变更⽅法,通过监听这些方法可以做到依赖收集和派发更新;( push/pop/shift/unshift/splice/reverse/sort )
7、Vue如何检测数据变化?
数组考虑性能原因没有用defineProperty对数组的每一项进行拦截,而是选择重写数组 方法以进行重写。当数组调用到这 7 个方法的时候,执行 ob.dep.notify() 进行派发通知 Watcher 更新;
8、Vue.set 方法是如何实现的?
为什么$set可以触发更新,我们给对象和数组本身都增加了dep属性,当给对象新增不存在的属性则触发对象依赖的watcher去更新,当修改数组索引时我们调用数组本身的splice方法去更新数组。
官方定义Vue.set(object, key, value)
- 如果是数组,调用重写的splice方法 (这样可以更新视图 )代码:target.splice(key, 1, val)
- 如果不是响应式的也不需要将其定义成响应式属性。
- 如果是对象,将属性定义成响应式的 defineReactive(ob.value, key, val)通知视图更新 ob.dep.notify()
9、Vue中模板编译原理?
默认.vue文件中的 template处理是通过vue-loader 来进行处理的并不是通过运行时的编译。模板引擎的实现原理就是new Function + with来进行实现的。vue-loader中处理template属性主要靠的是vue-template-compiler。
10、Proxy 与 Object.defineProperty 优劣对比
-
Proxy 的优势如下:
1)Proxy 可以直接监听对象而非属性;
2)Proxy 可以直接监听数组的变化;
3)Proxy 有多达 13 种拦截方法,不限于 apply、ownKeys、deleteProperty、has 等等是 Object.defineProperty 不具备的;
4)Proxy 返回的是一个新对象,我们可以只操作新的对象达到目的,而 Object.defineProperty 只能遍历对象属性直接修改;
5)Proxy 作为新标准将受到浏览器厂商重点持续的性能优化,也就是传说中的新标准的性能红利; -
Object.defineProperty 的优势如下:
兼容性好,支持 IE9,而Proxy 的存在浏览器兼容性问题,而且无法用 polyfill 磨平,因此 Vue 的作者才声明需要等到下个大版本( 3.0 )才能用 Proxy 重写。
11、Vue声明周期
总共分为8个阶段:创建前/后(el还没有),载入前/后(DOM挂载完毕),更新前/后,销毁前/后。
第一次页面加载时会触发:beforeCreate, created, beforeMount, mounted。
12、Vue中的组件的data 为什么是一个函数?
每次使用组件时都会对组件进行实例化操作,并且调用data函数返回一个对象作为组件的数据源。这样可以保证多个组件间数据互不影响。
如果data是对象的话,对象属于引用类型,会影响到所有的实例。
13、 组件通信
- props / $emit() 父子组件通信
- ref与 p a r e n t / parent/ parent/children 父子组件通信
- EventBus ($emit / $on) 适用于 父子、隔代、兄弟组件通信。这种方法通过一个空的 Vue 实例作为中央事件总线(事件中心),用它来触发事件和监听事件,从而实现任何组件间的通信,包括父子、隔代、兄弟组件。等
14、keep-alive平时在哪里使用?原理是?
keep-alive主要用于组件的缓存. - 常用属性:include / exclude,允许组件有条件的进行缓存;
- 两个声明周期:active 、deactivated;
- abstract: true; //抽象组件
15、Vue.minxin的使用场景和原理?
mixin的作用就是抽离公共的业务逻辑,原理类似于“对象的继承”。当组件初始化时会调用_mergeOptions_方法进行合并,混入有冲突时采用“就近原则”取组件数据。其中,mixin的数据不共享。
16、Vue-router有几种钩子函数?
路由钩子类型:全局守卫、路由守卫、组件守卫;
流程:
1.导航被触发;
2.在失活的组件里调用beforeRouteLeave守卫;
3.调用全局beforeEach守卫;
4.在复用组件里调用beforeRouteUpdate守卫;
5.调用路由配置里的beforeEnter守卫;
6.解析异步路由组件;
7.在被激活的组件里调用beforeRouteEnter守卫;
8.调用全局beforeResolve守卫;
9.导航被确认;
10.调用全局的afterEach钩子;
11.DOM更新;
12.用创建好的实例调用beforeRouteEnter守卫中传给next的回调函数。
17、vue-router的模式
vue-router共有2中路由模式:hash(hash在URL中,单不被包括在HTTp请求中)、history、abstract;
18、nextTick在哪里使用?原理是?
vue多次更新数据,最终会进行批处理更新。内部调用的就是nextTick实现了延迟更新,用户自定义的nextTick中的回调会被延迟到更新完成后调用,从而可以获取更新后的DOM。原理就是异步方法。
19、Vue 为什么需要虚拟DOM? 虚拟DOM的优劣如何?
虚拟DOM的实现就是普通对象包含tag、data、children等属性对真实节点的描述。(本质上就是在JS和DOM之间的一个缓存)。虚拟DOM就是真实DOM的一个抽象,减少直接操作DOM导致的操作效率低问题,将DOM操作转化为对象操作,最终通过diff算法比对差异进行更新DOM。
其中,Vue中的key就是为了高效的更新虚拟DO而存在,从而避免频繁更新不用元素,提高性能。
diff原理:vue的diff算法算是平级比较,不考虑跨级比较情况。内部采用深度递归+双指针的方式进行比较。
20、v-if 与 v-for的优先级
v-for优先于v-if被解析。如果同时出现,每次渲染都会先执行循环在判断条件。为避免这种情况,可使用template在外层进行if判断。
21、computed 和 watch 的区别和运用的场景?
- computed: 计算属性。依赖其它属性值,并且 computed 的值有缓存,只有它依赖的属性值发生改变,下一次获取 computed 的值时才会重新计算 computed 的值;
当我们需要进行数值计算,并且依赖于其它数据时,应该使用 computed,因为可以利用 computed 的缓存特性,避免每次获取值时,都要重新计算。 - watch: 监听数据的变化。更多的是「观察」的作用,类似于某些数据的监听回调 ,每当监听的数据变化时都会执行回调进行后续操作;可使用deep实现深度监听;
当我们需要在数据变化时执行异步或开销较大的操作时,应该使用 watch,使用 watch 选项允许我们执行异步操作 ( 访问一个 API ),限制我们执行该操作的频率,并在我们得到最终结果前,设置中间状态。这些都是计算属性无法做到的。
22、你做过哪些Vue的性能优化?
- 编码阶段:在更多的情况下,使用v-if替代v-show;key保证唯一;使用路由懒加载、异步组件;第三方模块按需导入;长列表滚动到可视区域动态加载;图片懒加载;如果需要使用v-for给每项元素绑定事件时使用事件代理;等
- 用户体验:骨架屏、缓存优化
- SEO优化:预加载渲染
- 打包优化:压缩代码;使用cdn加载第三方模块;sourceMap优化;splitChunks抽离公共文件;多线程打包happypack;等
23、跨域问题,怎么解决?
浏览器的同源策略导致了跨域,用于隔离潜在的恶意文件的重要安全机制;
解决办法:
- jsonp,允许script加载第三方资源;
- nginx反向代理(nginx 服务内部配置 Access-Control-Allow-Origin *);
- cors 前后端写作,设置请求头部,Access-Control-Allow-Origin 等头部信息;
- iframe嵌套通讯,post message
以上是关于前端开发面试题-问答的主要内容,如果未能解决你的问题,请参考以下文章