1、怎么理解防抖和节流

这两个概念,在很多人眼中,都很相似,很多人搞不清楚其中的差别

1. 防抖是多次触发,但是只有最后一次触发有效;适用于滚动渲染,或者 socket 一起触发页面渲染很多次

2. 节流是一定时间内触发一次。适用于页面用户提交信息。

防抖

  1. let timer = null; // 定时返回值
  2. function debounce (){
  3. if(timer){
  4. clearTimeout(timer);
  5. }
  6. timer = setTimeout(function(){
  7. console.log('在规定时间内,多少次触发,只有最后一次会触发')
  8. }, 3000)
  9. }

节流

  1. let lastDate = new Date().getTime();
  2. function throttle (){
  3. const curDate = new Date().getTime();
  4. if(curDate - lastDate >= 3000){
  5. console.log('节流触发');
  6. lastDate = new Data().getTime();
  7. }
  8. }

2、讲讲原型、原型链

3、讲下 instanceof 的原理及其实现

4、watch 和 computed 的区别

  1. 都是基于 Watcher
  2. watch 是监听已有变量,变量更新触发 watch 监听的回调函数
  3. computed 是通过已有的变量或者数据,经过计算得到一个新的变量

    5、实现一下冒泡排序

    6、讲下堆栈

    7、讲下宏任务跟微任务 eventLoop 事件队列

    旧理解(2022年2月10日以前):
    js 解析引擎会将代码放在宏任务当中由上向下,由左至右依次执行。执行的过程中,遇到 promiserequireAnimationFrame 等的时候会将其放在微任务当中,遇到 AjaxsetTimeOut 等会将其放在宏任务当中。js 执行引擎在主执行栈中执行完,会 执行微任务队列当中代码。执行完之后,渲染页面。页面渲染完之后,会去查看宏任务队列当中有没有满足执行条件的,如果有,那么就会将其放在主执行栈中执行,然后再分宏微任务,这种循环往复的机制,就是 eventLoop

最新的理解(2022年2月10日):

  1. JS大体分同步和异步两种任务模式。
  2. 浏览器碰到 script 标签,将script 代码作为整体,进行执行。(这算是第一次宏任务执行)
  3. 从上往下,遇到同步,放在执行栈中执行,碰到部分异步的,类似 Promise 等异步,等到其异步有结果了,就在微任务队列中放一个回调函数。
  4. 碰到另外一部分异步的,类似 settimeout,requestAnimationFrame 等异步函数,等其达到执行条件,就将其放到宏任务队列中。
  5. 同步任务执行完毕之后,就会去微任务队列中,查看是否可以执行。微任务队列中,如果没有可以执行的了,就会去宏任务队列中。
  6. 宏任务队列没有可执行的,就会再去查看是否有同步任务执行,循环往复。

别人的理解:

简单来说,一段完整的 JS 代码,浏览器会将整体的 script(作为第一个宏任务)开始执行,所有代码分为同步任务、异步任务两部分:

  1. 同步任务直接进入主线程执行栈依次执行,异步任务会再分为普通异步任务(也是宏任务),和特殊异步任务(即微任务);
  2. 普通的异步任务等有了运行结果其回调就会进入事件触发线程管理的 任务队列(可理解为宏任务队列);
  3. 特殊的异步任务也就是微任务的回调会立即进入一个微任务队列;
  4. 当主线程内的任务执行完毕,即主线程为空时,会检查微任务队列,如果有任务,就全部执行,如果没有就执行下一个宏任务(事件触发线程管理的 任务队列 中);
  5. 上述过程会不断重复,这就是Event Loop,事件循环。
  6. 浏览器中加上渲染的话就是先执行一个宏任务,再执行当前所有的微任务,接着开始执行渲染,然后再执行下一个宏任务,如此循环。

    8、webpack 优化有哪些

    1、Gzip 压缩

    1. // 开启 gzip 压缩
    2. const CompressionWebpackPlugin = require("compression-webpack-plugin");
    3. // gzip 压缩匹配规则
    4. const productionGzipExtensions = /\.(js|css|json|txt|html|ico|svg)(\?.*)?$/i;
    5. plugins: [
    6. config.build.productionGzip && new CompressionWebpackPlugin({
    7. filename: "[path].gz[query]",
    8. algorithm: "gzip",
    9. test: productionGzipExtensions,
    10. threshold: 10240,
    11. minRatio: 0.8
    12. })
    13. ]

    2、公共大库抽离

    9、new 关键词干了那些事?

  1. 创建一个新对象
  2. 将新对象的 __proto__ 指向 构造对象的 prototype
  3. 执行构造函数,并将内部的 this 指向新对象
  4. 如果构造函数返回的是对象,那就返回构造函数的对象,否则则返回新创建的对象
    1. function myNew(){
    2. // 拿到第一个参数,也就是构造函数
    3. const Func = [].shift().call(arguments);
    4. // 创建新的对象,并且将新对象的 __proto__ 指向 构造函数的prototype
    5. const obj = Object.create(Func.prototype);
    6. // 执行构造函数,并且将构造函数内的this指向新创建的对象
    7. const result = Func.apply(obj,arguments);
    8. // 判断构造函数返回值,如果返回对象类型,则返回原构造函数的返回值,否则返回新创建的对象
    9. return typeof result === 'object' && result !== 'null' ? result : obj;
    10. }

    10、JavaScript 的特点?

    封装、继承、多态

11、Vue 路由守卫

1、全局路由守卫

1.1 全局前置守卫

beforeEach

  1. 1. 一个路由实例对象只有一个全局前置守卫
  2. 2. 此路由守卫适合处理判断用户是否登录,用户是否拥有访问该页面的权限等问题

1.2 全局后置守卫

afterEach

  1. 1. 此路由守卫不含 `next` 函数,不改变路由导航

2、 路由独享守卫

routerEnter

3、组件路由

beforeRouteEnter :

  1. 1. 进入组件渲染之前触发,不能访问组件的 `this`
  2. 2. 此时组件还未创建

beforeRouteUpdate :

  1. 1. 组件复用,并且路由改变的时候,进行调用触发

beforeRouteLeave :

  1. 1. 一般是在离开组件之前,进行处理页面逻辑处理,比如给用户进行二次确认提醒

12、数组去重

1、循环递归

2、Set

  1. let arr = [1,3,5,1,3,6,7];
  2. console.log([...new Set(arr)]);

3、双重循环去重。(利用splice,但是会改变原数组)

  1. function unique(arr){
  2. for(var i=0; i<arr.length; i++){
  3. for(var j=i+1; j<arr.length; j++){
  4. if(arr[i]==arr[j]){ //第一个等同于第二个,splice方法删除第二个
  5. arr.splice(j,1);
  6. j--;
  7. }
  8. }
  9. }
  10. return arr;
  11. }
  12. var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}];
  13. console.log(unique(arr));
  14. //[1, "true", 15, false, undefined, NaN, NaN, "NaN", "a", {…}, {…}] //NaN和{}没有去重,两个null直接消失了

4、reduce

  1. const arr = [1,3,3,4,5,1,2,4];
  2. // reduce方法,接收两个参数,1、循环处理逻辑的function,2、初始数组
  3. //
  4. const newArr = arr.reduce((pre,cur)=>pre.includes(cur)?pre:[...pre,cur],[]);
  5. console.log(newArr); // [1,3,4,5,2,4];

5、indexOf / includes

创建一个新数组,如果新数组内不可以 indexOf 到原数组的元素,则添加,否则则不添加。

6、sort

将需要传递的数组进行排序之后,外部定义一个初始数组,包含去重数组的第一项,然后从去重数组的第二项开始循环,拿定义数组的第一项和去重数组的每一项进行对比,如果不同,则追加金定义数组中。

  1. function unique(arr) {
  2. if (!Array.isArray(arr)) {
  3. console.log('type error!')
  4. return;
  5. }
  6. arr = arr.sort()
  7. var arrry= [arr[0]];
  8. for (var i = 1; i < arr.length; i++) {
  9. if (arr[i] !== arr[i-1]) {
  10. arrry.push(arr[i]);
  11. }
  12. }
  13. return arrry;
  14. }
  15. var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}];
  16. console.log(unique(arr)); // [0, 1, 15, "NaN", NaN, NaN, {…}, {…}, "a", false, null, true, "true", undefined] //NaN、{}没有去重

7、利用对象属性不能相同的特点进行去重

原理就是将需要去重的数组的每一项,当成对象的key,进行索引查找,如果在对象的key中没有查找到,则说明新数组中需要添加该元素,如果找到了,那说明新数组中已有该元素。 缺点:这种去重,会将其他类型的数据转成字符串,所以不建议使用

  1. function unique(arr) {
  2. if (!Array.isArray(arr)) {
  3. console.log('type error!')
  4. return
  5. }
  6. var arrry= [];
  7. var obj = {};
  8. for (var i = 0; i < arr.length; i++) {
  9. if (!obj[arr[i]]) {
  10. arrry.push(arr[i])
  11. obj[arr[i]] = 1
  12. } else {
  13. obj[arr[i]]++
  14. }
  15. }
  16. return arrry;
  17. }
  18. var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}];
  19. console.log(unique(arr))
  20. //[1, "true", 15, false, undefined, null, NaN, 0, "a", {…}] //两个true直接去掉了,NaN和{}去重

13、父子组件传值

1、props / $emit

2、$on / $emit

3、$parent / $children

4、$refs

5、provide / inject

这种可以无视组件嵌套层级,但是这种方式传递的是静态值,不是响应式的,只适合共享全局方法

6、eventBus(事件总线)

Vue 中大量使用了发布订阅者模式

7、Vuex

14、Vue指令

15、实现一下发布订阅者模式

16、说一下父子组件的生命周期

17、Promise基础实现

18、this 改变

19、HTTP缓存

20、XMLRequest 状态码

21、大文件上传

22、虚拟Dom

23、Vue DIFF运算