前端开发面试题-问答

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、请说一下响应式数据的理解?

根据数据类型来做不同处理,数组和对象类型当值变化时如何劫持。

  1. 对象内部通过defineReactive方法,使用Object.defineProperty() 监听数据属性的 get 来进行数据依赖收集,再通过 set 来完成数据更新的派发;
  2. 数组则通过重写数组方法来实现的。扩展它的 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

以上是关于前端开发面试题-问答的主要内容,如果未能解决你的问题,请参考以下文章

前端开发面试题-问答

前端开发面试题-问答

前端开发面试题-问答

前端开发面试题-问答

前端开发面试题-问答

前端开发面试题-问答