自我介绍及实习项目
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.nextTick之前和之后分别打印Vue实例关联的DOM元素的innerHtml,可以看到在使用this.nextTick前它的dom是还没有更新的


这是为什么呢?
vue中对DOM的更新策略是什么?
- vue在更新dom的时候是异步的
- 只要侦听到数据变化,vue将开启一个新的事件队列,并且缓冲同一事件循环中发生的所有数据变更
- 如果同一个watcher被多次触发,只会被推入到事件队列中一次
- 然后再下一次事件循环中,vue刷新事件队列,并执行实际工作
上面的例子中
- 我们通过this.message = ‘new message’ 更新数据
- 但是它不会立马就重新渲染
- 当刷新实际队列的时候,组件会在下一次事件循环tick中重新渲染
- 当我们更新完数据,此时又要基于更新dom状态来做些什么,就需要
Vue.nextTick(callback) - 把基于更新dom后的状态所需要的操作放到回调函数里面,这样回调函数将再dom更新完成之后被调用
好了 到这里知道Vue.nextTick(callback) 是干什么的了,那么vue为什么要设置成这样呢》为什么要异步更新呢
JS的运行机制了。
js执行是单线程的,基于事件循环
事件循环就是有个流程:
- 所以同步任务都在主线程上运行,形成一个执行栈
- 主线程之外,还存在一个任务队列,只要异步任务有运行结果,就再任务队列里面放置一个事件,一旦执行栈中的所有同步任务都执行完毕,系统就会读取任务队列
- 那些对应的异步任务,结束等待状态,进入执行栈,开始执行
主线程不断重复上面三个步骤
消息队列中存放的是一个个的任务(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标签,然后就可以了。
如何实现登录注册
- 注册:
- 前端先做一个注册页面
- 前端需要在用户提交数据后,获取这些数据
- 前端将上一步所获得的数据通过post请求发送给服务器端
- 后端接收前端发来的请求并从中读取到需要的信息(如传过来的邮箱密码)
因为客户端向服务端发送post请求传输数据时,实际上是一段一段传的,所以无法直接获取数据 - 后端拿到数据后,进行简单的数据校验,出错后将错误信息返回给前端,前端进行解释展示给用户。当用户没有填信息就提交时,就需要前端进行校验了,这种情况下应该直接提示用户填信息而不用再次给后端发请求:
- 注册成功后将用户数据传到数据库中
- 后端校验:邮箱是否被注册
- 登录:
- 前端先写一个登录页面
- 前端需要将用户写的登录信息拿到并传给数据库,因此需要做简单的校验
- 后端从数据库中验证用户信息
- 后端设置cookie
- 后端通过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;
}
强缓存和协商缓存
- 缓存
- 第一次获取到资源后,根据返回的信息来告诉如何缓存资源
- 强缓存 没有和http请求 200
cache-control 资源第一次的请求时间和Cache-Control设定的有效期,计算出一个资源过期时间,再拿这个过期时间跟当前的请求时间比较,如果请求时间在过期时间之前,就能命中缓存,否则就不行
no-cache:不使用本地缓存,使用协商
no-store: 直接禁止缓存
public: 所有浏览器都可以
private:只有终端浏览器可以
expires 某个事件点
两者都存在: cache-control
- 协商缓存 304 如果没有名字清缓存,浏览器会发送请求到服务器
判断资源是否可用
服务器发过来的响应头:last-modified etag
- last-modified if-modified-since
-
浏览器第一次向服务器请求一个资源,服务器再返回这个资源的同时,在相应头加上last-modified的头,这个这个header表示这个资源在服务器上的最后修改时间
-
浏览器再次跟服务器请求这个资源时,在request的header上加上If-Modified-Since的header,这个header的值就是上一次请求时返回的Last-Modified的值
-
服务器再次收到资源请求时,根据浏览器传过来If-Modified-Since和资源在服务器上的最后修改时间判断资源是否有变化,如果没有变化则返回304 Not Modified,但是不会返回资源内容;如果有变化,就正常返回资源内容。当服务器返回304 Not Modified的响应时,response header中不会再添加Last-Modified的header,因为既然资源没有变化,那么Last-Modified也就不会改变,这是服务器返回304时的response header
-
浏览器收到304的响应后,就会从缓存中加载资源
-
如果协商缓存没有命中,浏览器直接从服务器加载资源时,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 []
};
本文解析Vue.js中的nextTick回调机制,探讨为何数据变化后需要延迟DOM更新,并通过实例说明如何确保在DOM更新后再操作。同时深入剖析Vue异步更新策略,结合JS单线程机制和事件循环理解其原因。
1736

被折叠的 条评论
为什么被折叠?



