7.21 小米一面 面试题记录和总结

本文解析Vue.js中的nextTick回调机制,探讨为何数据变化后需要延迟DOM更新,并通过实例说明如何确保在DOM更新后再操作。同时深入剖析Vue异步更新策略,结合JS单线程机制和事件循环理解其原因。

自我介绍及实习项目

keep-alive 原理

nextTick

nextTick 是什么?

在下次DOM更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的dom

nextTick 的主要功能就是更新数据后让回调函数作用于更新后的DOM

首先了解一个属性
$el: 获取Vue实例关联的DOM元素;

<template>
  <div class="home">
    <div id="example" @click="msgClick">{{message}}</div>
    <input type="text">
  </div>
</template>

<script>

export default {
  name: 'home',
  data() {
    return {
      message: '123'
    }
  },
  destroyed() {
    console.log('组件被销毁')
  },
  methods: {
    msgClick() {
      this.message = 'new message' // 更改数据
      console.log(this.$el.innerHTML) // '123'
      this.$nextTick(function () {
      console.log(this.$el.innerHTML) // 'new message'
      })
    }
  }
}
</script>

在message上绑定点击事件,触发事件后,数据改变,此时在使用this.nextTick之前和之后分别打印Vue实例关联的DOM元素的innerHtml,可以看到在使用this.nextTick之前和之后分别打印Vue实例关联的DOM元素的innerHtml,可以看到在使用this.nextTickVueDOMinnerHtml使this.nextTick前它的dom是还没有更新的
在这里插入图片描述
在这里插入图片描述

这是为什么呢?

vue中对DOM的更新策略是什么?

  1. vue在更新dom的时候是异步的
  2. 只要侦听到数据变化,vue将开启一个新的事件队列,并且缓冲同一事件循环中发生的所有数据变更
  3. 如果同一个watcher被多次触发,只会被推入到事件队列中一次
  4. 然后再下一次事件循环中,vue刷新事件队列,并执行实际工作

上面的例子中

  1. 我们通过this.message = ‘new message’ 更新数据
  2. 但是它不会立马就重新渲染
  3. 当刷新实际队列的时候,组件会在下一次事件循环tick中重新渲染
  4. 当我们更新完数据,此时又要基于更新dom状态来做些什么,就需要
    Vue.nextTick(callback)
  5. 把基于更新dom后的状态所需要的操作放到回调函数里面,这样回调函数将再dom更新完成之后被调用

好了 到这里知道Vue.nextTick(callback) 是干什么的了,那么vue为什么要设置成这样呢》为什么要异步更新呢

JS的运行机制了。

js执行是单线程的,基于事件循环

事件循环就是有个流程:

  1. 所以同步任务都在主线程上运行,形成一个执行栈
  2. 主线程之外,还存在一个任务队列,只要异步任务有运行结果,就再任务队列里面放置一个事件,一旦执行栈中的所有同步任务都执行完毕,系统就会读取任务队列
  3. 那些对应的异步任务,结束等待状态,进入执行栈,开始执行

主线程不断重复上面三个步骤

消息队列中存放的是一个个的任务(task)。 规范中规定 task 分为两大类,分别是宏任务(macro task) 和微任务(micro task),并且每执行完一个个宏任务(macro task)后,都要去清空该宏任务所对应的微任务队列中所有的微任务(micro task),他们的执行顺序如下所示:

for (macroTask of macroTaskQueue) {
    // 1. 处理当前的宏任务
    handleMacroTask();
      
    // 2. 处理对应的所有微任务
    for (microTask of microTaskQueue) {
        handleMicroTask(microTask);
    }
}

在浏览器环境中,常见的

宏任务(macro task) 有 setTimeout、MessageChannel、postMessage、setImmediate;
微任务(micro task)有MutationObsever 和 Promise.then。

MutationObserver接口提供了监视对DOM树所做更改的能力

nextTick 源码分析

nextTick源码主要分为两块:

  • 能力检测
  • 根据能力检测以不同方式执行回调队列

Vue 在内部对异步队列尝试使用原生的 Promise.then、MutationObserver 和 setImmediate,如果执行环境不支持,则会采用 setTimeout(fn, 0) 代替。

对于 macro task 的实现,优先检测是否支持原生 setImmediate,这是一个高版本 IE 和Edge 才支持的特性,
不支持的话再去检测是否支持原生的 MessageChannel,
如果也不支持的话就会降级为 setTimeout 0;
而对于 micro task 的实现,则检测浏览器是否原生支持 Promise,不支持的话直接指向 macro task 的实现。
promise MutationObserver setImmediate setTimeout(flushCallbacks, 0)

if (typeof Promise !== 'undefined' && isNative(Promise)) {
  const p = Promise.resolve()
  timerFunc = () => {
    p.then(flushCallbacks)
    if (isIOS) setTimeout(noop)
  }
  isUsingMicroTask = true 
} else if (!isIE && typeof MutationObserver !== 'undefined' && (
  isNative(MutationObserver) ||
  MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
  let counter = 1
  const observer = new MutationObserver(flushCallbacks) 
  const textNode = document.createTextNode(String(counter))
  observer.observe(textNode, {
    characterData: true
  })
  timerFunc = () => {
    counter = (counter + 1) % 2
    textNode.data = String(counter)
  }
  isUsingMicroTask = true 
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
  timerFunc = () => {
    setImmediate(flushCallbacks)
  }
} else {
  timerFunc = () => {
    setTimeout(flushCallbacks, 0)
  }
}

nextTick

const callbacks = [] // 回调队列
let pending = false // 异步锁

// 执行队列中的每个回调
function flushCallbacks () {
  pending = false // 重置异步锁
  // 防止出现nextTick 时出现问题,再执行回调函数队列前,提前复制备份并清空回调函数队列
  const copies = callbacks.slice(0)
  callbacks.length = 0
  // 执行回调函数队列
  for (let i = 0; i < copies.length; i++) {
    copies[i]()
  }
}

export function nextTick(cb?: Function, ctx?: Object) {
  let _resolve
  callbacks.push(() => {
    // 将回调函数推入回调队列
    if (cb) {
      try {
        cb.call(ctx)
      } catch (e) {
        handleError(e, ctx, 'nextTick')
      }
    } else if (_resolve) {
      _resolve(ctx)
    }
  })
    // 如果异步锁未锁上,锁上异步锁,调用异步函数,准备等同步函数执行完后,就开始执行回调函数队列
  if (!pending) {
    pending = true
    timerFunc()
  }
  // $flow-disable-line
    // 如果没有提供回调,并且支持Promise,返回一个Promise
  if (!cb && typeof Promise !== 'undefined') {
    return new Promise(resolve => {
      _resolve = resolve
    })
  }
}

如何保证只在接收第一个回调函数时执行异步方法?

nextTick源码中使用了一个异步锁的概念,即接收第一个回调函数时,先关上锁,执行异步方法。此时,浏览器处于等待执行完同步代码就执行异步代码的情况。

执行 flushCallbacks 函数时为什么需要备份回调函数队列?执行的也是备份的回调函数队列?

因为,会出现这么一种情况:nextTick 的回调函数中还使用 nextTick。如果 flushCallbacks 不做特殊处理,直接循环执行回调函数,会导致里面nextTick 中的回调函数会进入回调队列。

a标签切换到b标签的时候,dom还没更新完,然后我就重新赋值了,这个时候vue内部可能认为我已经刷新完毕,然后就一直停在b标签上。

使用nextTick 就是希望再b标签的dom更新完之后,我再改到a标签,然后就可以了。

如何实现登录注册

  • 注册:
  1. 前端先做一个注册页面
  2. 前端需要在用户提交数据后,获取这些数据
  3. 前端将上一步所获得的数据通过post请求发送给服务器端
  4. 后端接收前端发来的请求并从中读取到需要的信息(如传过来的邮箱密码)
    因为客户端向服务端发送post请求传输数据时,实际上是一段一段传的,所以无法直接获取数据
  5. 后端拿到数据后,进行简单的数据校验,出错后将错误信息返回给前端,前端进行解释展示给用户。当用户没有填信息就提交时,就需要前端进行校验了,这种情况下应该直接提示用户填信息而不用再次给后端发请求:
  6. 注册成功后将用户数据传到数据库中
  7. 后端校验:邮箱是否被注册
  • 登录:
  1. 前端先写一个登录页面
  2. 前端需要将用户写的登录信息拿到并传给数据库,因此需要做简单的校验
  3. 后端从数据库中验证用户信息
  4. 后端设置cookie
  5. 后端通过cookie想要对应的用户信息

不能用webstorage去存登录信息,因为它不参与客户端和服务器端的通信。

cookie 了解多少 字段 属性

{
'name': 'QCARJSESSIONID', 
'value': 'DgH6ctvRhc1DGVvf0wp2x1pTLlV6Ltl8sgVQGhnCnNG32BFxnpP1!1426878101', 
'path': '/', 
'domain': 'qcar.apiins.com', 
'secure': False,    // 字段 设置是否只能通过https来传递此条cookie
'httpOnly': True
}

Cookie 的SameSite属性用来限制第三方 Cookie,从而减少安全风险。

它可以设置三个值。

Strict
Lax
None

将其设为None。不过,前提是必须同时设置Secure属性(Cookie 只能通过 HTTPS 协议发送),否则无效。

父传子(多层)方法

  • vuex

  • eventBus
    基于一个消息中心,订阅和发布消息,发布订阅者模式
    on(‘name’,fn) 订阅 name:订阅的消息名称, fn: 订阅的消息
    emit('name,arg) 发布 消息, name:发布的消息名称 , args:发布的消息
    实现一个发布订阅模式

class Bus {
    constructor() {
        this.callback = {}
    }
    on(name, fn) {
        this.callback[name] = this.callback[name] || []
        this.callback[name].push(fn)
    }
    emit(name, args) {
        if (this.callback[name]) {
            this.callback[name].forEach(cb => cb(args));
        }
    }
}

const EventBus = new Bus()
EventBus.on('f1', function (msg) {
    console.log(`订阅的消息是:${msg}`)
})
EventBus.emit('f1','你好世家')
  • provide inject
    父:
 provide: {
    // 父组件用provide提供传参
    uname: "父组件传递的uname",
  },

子:

<template>
  <div>
    子组件接收的数据:{{uname}}
    <child2></child2>
  </div>
</template>
<script>
import child2 from "./child2";

export default {
  name: "childInject1",
  components: {
    child2,
  },
  data() {
    return {};
  },
  inject: ["uname"]
};
</script>

孙:

<template>
  <div>
    孙子组件接收的数据:{{foo}}
    <p>{{arr}}-{{typeof(arr)}}</p>
  </div>
</template>
<script>
export default {
  name: "childInject2",
  data() {
    return {
      // uname:this.uname
    };
  },
  inject: {
    foo: {
      //可以换名字
      from: "uname", //从其父组件获取的属性名
      default: "foo", //2.5.0+可使用default,就像props那样,可以设置默认值,变成可选项
    },
    arr: {
      from: "arr", //没有接收到就显示默认值
      default: () => [1, 2, 3, 4, 5],
    },
  }
};
</script>

原型链 继承

typeof instanceof 检测的缺点

typeof :
number、boolean、string、object、undefined、function等6种数据类型。

instanceof:

instanceof (A,B) = {
var L = A.__proto__;
var R = B.prototype;
if(L === R) {
//A的内部属性__proto__指向B的原型对象
return true;
}
return false;
}

强缓存和协商缓存

  • 缓存
  1. 第一次获取到资源后,根据返回的信息来告诉如何缓存资源
  • 强缓存 没有和http请求 200
    cache-control 资源第一次的请求时间和Cache-Control设定的有效期,计算出一个资源过期时间,再拿这个过期时间跟当前的请求时间比较,如果请求时间在过期时间之前,就能命中缓存,否则就不行
    no-cache:不使用本地缓存,使用协商
    no-store: 直接禁止缓存
    public: 所有浏览器都可以
    private:只有终端浏览器可以
    expires 某个事件点

两者都存在: cache-control

  • 协商缓存 304 如果没有名字清缓存,浏览器会发送请求到服务器

判断资源是否可用
服务器发过来的响应头:last-modified etag

  • last-modified if-modified-since
  1. 浏览器第一次向服务器请求一个资源,服务器再返回这个资源的同时,在相应头加上last-modified的头,这个这个header表示这个资源在服务器上的最后修改时间

  2. 浏览器再次跟服务器请求这个资源时,在request的header上加上If-Modified-Since的header,这个header的值就是上一次请求时返回的Last-Modified的值

  3. 服务器再次收到资源请求时,根据浏览器传过来If-Modified-Since和资源在服务器上的最后修改时间判断资源是否有变化,如果没有变化则返回304 Not Modified,但是不会返回资源内容;如果有变化,就正常返回资源内容。当服务器返回304 Not Modified的响应时,response header中不会再添加Last-Modified的header,因为既然资源没有变化,那么Last-Modified也就不会改变,这是服务器返回304时的response header

  4. 浏览器收到304的响应后,就会从缓存中加载资源

  5. 如果协商缓存没有命中,浏览器直接从服务器加载资源时,Last-Modified的Header在重新加载的时候会被更新,下次请求时,If-Modified-Since会启用上次返回的Last-Modified值

  • etag if-none-match
    只要资源有变化就这个值就会改变;
    若命中,则服务器返回新的响应header信息更新缓存中的对应header信息,但是并不返回资源内容,它会告知浏览器可以直接从缓存获取;否则返回最新的资源内容

Last-Modified与ETag是可以一起使用的,服务器会优先验证ETag,一致的情况下,才会继续比对Last-Modified,最后才决定是否返回304。

  • 如何避免在强缓存时期,服务器端资源发生了改变呢

通过更新页面中引用的资源路径,让浏览器主动放弃缓存,加载新资源。

let的暂时性死区

两数之和 不能暴力解法

var twoSum = function(nums, target) {
    let len = nums.length;
    let map = new Map();
    for (let i = 0;i < len;i++) {
        if (map.has(target - nums[i])) {
            console.log(map.get(target - nums[i]))
            return [map.get(target - nums[i]), i]
        } else {
            // 把值放在前面 把key放到后面去了
            map.set(nums[i],i)
        }
    }
    return []
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值