https://juejin.im/post/5ae3f0956fb9a07ac90cf43e
https://juejin.im/post/5be692936fb9a049e129b741 非常棒
Vue.js在默认情况下,每次触发某个数据的 setter 方法后,对应的 Watcher 对象其实会被 push 进一个队列 queue 中,在下一个 tick 的时候将这个队列 queue 全部拿出来 run一遍。
this.$nextTick也为事件队列push进入了新的一个callback函数,他们都是通过setImmediate —> MessageChannel —> Promise —> setTimeout来定义timeFunc。而Promise.resolve().then则是microTask,所以会先去打印promise。
如果这时候没有异步更新视图,那么每次++都会直接操作DOM更新视图,这是非常消耗性能的。 所以Vue实现了一个queue队列,在下一个Tick(或者是当前Tick的微任务阶段)的时候会统一执行queue中Watcher的run。
this.$nextTick也为事件队列push进入了新的一个callback函数,他们都是通过setImmediate —> MessageChannel —> Promise —> setTimeout来定义timeFunc。而Promise.resolve().then则是microTask,所以会先去打印promise。
::Vue.js在默认情况下,每次触发某个数据的 setter 方法后,对应的 Watcher 对象其实会被 push 进一个队列 queue 中,在下一个 tick 的时候将这个队列 queue 全部拿出来 run一遍。::
::如果同一个 watcher 被多次触发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作上非常重要。然后,在下一个的事件循环“tick”中,Vue 刷新队列并执行实际 (已去重的) 工作。::
::nextTick函数其实做了两件事情,一是生成一个timerFunc,把回调作为microTask或macroTask参与到事件循环中来。二是把回调函数放入一个callbacks队列,等待适当的时机执行::
vue用异步队列的方式来控制DOM更新和nextTick回调先后执行
microtask因为其高优先级特性,能确保队列中的微任务在一次事件循环前被执行完毕
因为兼容性问题,vue不得不做了microtask向macrotask的降级方案
vue中的依赖注入provide/inject遇到的一个坑_javascript_qq_40195282的博客-CSDN博客
问题:不会响应
解决方法:可以使用对象或者提供一个方法
在vue里面直接修改数组里面的length、替换值会出现不响应的现象,只能通过特别的数组原型方法或者vue.$set进行改变
原理如下:
v-html对Xss进行了过滤
Html5中指定不执行有innerHtml插入的script标签
https://juejin.im/post/5d2c5302518825331d14cf37
https://juejin.im/post/5d59f2a451882549be53b170
MVVM:把View和Model的同步逻辑自动化了。View和Model同步不再手动地进行操作,而是交给框架所提供的数据绑定功能进行负责,只需要告诉它View显示的数据对应的是Model哪一部分即可。View通过使用模板语法来声明式的将数据渲染进DOM,当ViewModel对Model进行更新的时候,会通过数据绑定更新到View。
MVC:允许在不改变视图的情况下改变视图对用户输入的响应方式,用户对View的操作交给了Controller处理,在Controller中响应View的事件调用Model的接口对数据进行操作,一旦Model发生变化便通知相关视图进行更新。
Model层:用于封装和应用程序的业务逻辑相关的数据。
View层:作为视图层,主要负责数据的展示。
Controller层:定义用户界面对用户输入的响应方式,连接模型和视图,用于控制应用程序的流程,处理用户的行为和数据上的改变。
MVVM 源自于经典的 Model–View–Controller(MVC)模式 ,MVVM 的出现促进了前端开发与后端业务逻辑的分离,极大地提高了前端开发效率,MVVM 的核心是 ViewModel 层,它就像是一个中转站(value converter),负责转换 Model 中的数据对象来让数据变得更容易管理和使用,该层向上与视图层进行双向数据绑定,向下与 Model 层通过接口请求进行数据交互,起呈上启下作用。如下图所示:
主要是给diff算法
由于vue使用的是复用策略,只要标签名相同就会复用,替换里面的值
如果没有key,举例,checkbox,比对的时候,就会暴力比对,比较标签以及key,如果相同就复用,删除的其实是最后一个,由于标签一样,所以服用了
比较标签以及key,如果相同就复用,所以不用使用index索引做key
如果没有key,那么vue自动使用index索引做key
*第一步:*实现一个监听器 Observer:对数据对象进行遍历,包括子属性对象的属性,利用 Object.defineProperty() 对属性都加上 setter 和 getter。这样的话,给这个对象的某个值赋值,就会触发 setter,那么就能监听到了数据变化。
*第二步:*实现一个解析器 Compile:解析 Vue 模板指令,将模板中的变量都替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,调用更新函数进行数据更新。
*第三步:*实现一个订阅者 Watcher:Watcher 订阅者是 Observer 和 Compile 之间通信的桥梁 ,主要的任务是订阅 Observer 中的属性值变化的消息,当收到属性值变化的消息时,触发解析器 Compile 中对应的更新函数。
*第四步:*实现一个订阅器 Dep:订阅器采用 发布-订阅 设计模式,用来收集订阅者 Watcher,对监听器 Observer 和 订阅者 Watcher 进行统一管理。
Observer(观察者)对要监听的属性使用Object.defineProperty(),其中,setter的时候是需要对使用dep进行发布消息的,getter的时候是需要在dep中添加watcher的
COMPailer(编译): 如果节点中有需要替换的变量,那么需要对此节点添加watcher,其中回调函数是更新节点内容的,添加监听数据的订阅者,一旦数据有变动,收到通知,调用更新函数进行数据更新。
Watcher:是监听函数,调用get,往dep里面添加订阅
Dep:是个依赖收集器,并进行发布,里面主要功能是收集以及发布
在Compailer里面,通过判断节点是否是元素(nodeType为1),通过添加addEventListener来改变数据的值,调用set,set中又会调用notify,notify中的watcher的update方法实现了更新
动态绑定class
1、对象方法
:class=“{ ‘active’: isActive }”
:class=“{ ‘active’: isActive, ‘sort’: isSort }”
:class=“classObject”
data() {
return {
classObject:{ active: true, sort:false }
}
}
2、数组方法
:class=“[isActive,isSort]”
:class=“[isActive==index?’active’:’’]”
3、数组对象结合动态判断
:class=“[{ active: isActive==1 }, ‘sort’]”
Vue原理(手写代码,实现数据劫持)
https://juejin.im/post/5abdd6f6f265da23793c4458#heading-8
不好意思!耽误你的十分钟,让MVVM原理还给你 - 掘金
读书笔记:
https://juejin.im/post/5cac2c945188251b0a1e51ee
VirtualDOM与diff(Vue实现) - 掘金
让虚拟DOM和DOM-diff不再成为你的绊脚石 - 掘金(这个简洁明了)
周所周知,Vue通过数据绑定来修改视图,当某个数据被修改的时候,set方法会让闭包中的Dep调用notify通知所有订阅者Watcher,Watcher通过get方法执行vm._update(vm._render(), hydrating)。
Patch将新老VNode节点进行比对,然后将根据两者的比较结果进行最小单位地修改视图,而不是将整个视图根据新的VNode重绘。patch的核心在于diff算法,这套算法可以高效地比较viturl dom的变更,得出变化以修改视图。
Patch的核心diff算法,diff算法是通过同层的树节点进行比较而非对树进行逐层搜索遍历的方式,所以时间复杂度只有O(n),是一种相当高效的算法。
1.先同级比较,同级比较完后再比较子节点
2.先判断一方有孩子一方没孩子的情况,如果新节点有孩子,老节点没有,直接把老节点的添加新节点的孩子,如果新节点没有,老节点有,直接把老节点的孩子给干掉
3.比较都有儿子的情况
4.递归比较子节点
diff算法就是为了获取补丁
1、同级比较
先比较新旧节点是都一样(key、tag标签名、isComment注释)
1、如果新节点不存在,那么直接remove掉旧节点
2、如果两个都是文本节点并且值不相同,就替换里面的内容
3、如果两个标签的不一样,直接替换掉
4、否则先比较属性是否有修改,把该的内容存储值patchs中,然后看是否有孩子节点
1)如果只有vnode存在子节点,则调用addVnodes添加这些子节点
2)如果只有oldVnode存在子节点,则调用removeVnodes移除这些子节点
3)如果vnode的children和oldVnode的children都存在,且不完全相等,则调用updateChildren更新子节点
updateChildren:
1、如果oldStartVnode不存在,则将oldStartVnode设置为下一个节点
2、如果oldEndVnode不存在,则将oldEndVnode设置为上一个节点
3、如果oldStartVnode和newStartVnode是同一个节点(sameVnode),则调用patchVnode进行patch重复流程,同时将oldStartVnode和newStartVnode设置为下一个节点
4、如果oldEndVnode和newEndVnode是同一个节点(sameVnode),则调用patchVnode进行patch重复流程,同时将oldEndVnode和newEndVnode设置为上一个节点
5、如果oldStartVnode和newEndVnode是同一个节点(sameVnode),则调用patchVnode进行patch重复流程,同时将oldStartVnode设置为下一个节点,newEndVnode设置为上一个节点,需要对DOM进行移动
6、如果oldEndVnode和newStartVnode是同一个节点(sameVnode),则调用patchVnode进行patch重复流程,同时将oldEndVnode设置为上一个节点,newStartVnode设置为下一个节点,需要对DOM进行移动
7、否则,尝试在oldChildren中查找与newStartVnode具有相同key的节点
1)如果没有找到,则说明newStartVnode是一个新节点,则调用createElem创建一个新节点,同时将newStartVnode设置为下一个节点
2)如果找到了具有相同key的节点
(1)如果找到的节点与newStartVnode是同一个节点(sameVnode),则调用patchVnode进行patch重复流程,同时把newStartVnode.elm移动到oldStartVnode.elm之前,并把newStartVnode设置为下一个节点,需要对DOM进行移动
(2)否则,调用createElm创建一个新的节点,同时把newStartVnode设置为下一个节点
上述过程中,如果oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx,即oldChildren和newChildren节点在遍历过程中如果任意一个的开始索引和结束索引重合,则表明遍历结束。
遍历结束后,还需针对oldChildren和newChildren没有遍历的节点进行处理,分为以下两种情况:
1)如果oldStartIdx大于oldEndIdx,说明newChildren可能还未遍历完,则需要调用addVnodes添加newStartIdx到newEndIdx之间的节点
2)如果newStartIdx大于newEndIdx,说明oldChildren可能还未遍历完,则需要调用removeVnodes移除oldStartIdx到oldEndIdx之间的节点
patch:根据diff算法获取到的补丁,对节点进行带补丁,补丁类型大致有移动、替换、文本、属性等
function doPatch(node, patches) {
// 遍历所有打过的补丁
patches.forEach(patch => {
switch (patch.type) {
case 'ATTR':
for (let key in patch.attr) {
let value = patch.attr[key];
if (value) {
setAttr(node, key, value);
} else {
node.removeAttribute(key);
}
}
break;
case 'TEXT':
node.textContent = patch.text;
break;
case 'REPLACE':
let newNode = patch.newNode;
newNode = (newNode instanceof Element) ? render(newNode) : document.createTextNode(newNode);
node.parentNode.replaceChild(newNode, node);
break;
case 'REMOVE':
node.parentNode.removeChild(node);
break;
default:
break;
}
});
}
Vue 官方网站对 Mixins 定义:混入 (Mixins) 提供了一种非常灵活的方式,来分发 Vue 组件中的可复用功能。一个混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项将被“混合”进入该组件本身的选项。
理解:
同一个组件被复用多次,会创建多个实例。这些实例用的是同一个构造函数,如果data 是一个对象的话。那么所有组件都共享了同一个对象。为了保证组件的数据独立性要求每个组件必须通过data 函数
返回一个对象作为组件的状态。
v-for 会比v-if 的优先级高一些,如果连用的话会把v-if 给每个元素都添加一下,会造成性能问题
v-for 会比v-if 的优先级高一些,如果连用的话,会先循环一次,然后给每个元素都添加一下v-if ,会造成性能问题
改造
可以在created、beforeMounted, mounted三个都可以,在此时,data数据已经可以访问,一般在created里面调用,原因有两点,一是更早调用,而是服务端渲染(ssr)的时候是没有beforeMounted,和mounted这两个生命周期的
https://juejin.im/post/5d36cc575188257aea108a74
所有的浏览器渲染引擎工作流程大致分为5步:创建 DOM 树 —> 创建 Style Rules -> 构建 Render 树 —> 布局 Layout -—> 绘制 Painting。
第一步,构建 DOM 树:用 HTML 分析器,分析 HTML 元素,构建一棵 DOM 树;
第二步,生成样式表:用 CSS 分析器,分析 CSS 文件和元素上的 inline 样式,生成页面的样式表;
第三步,构建 Render 树:将 DOM 树和样式表关联起来,构建一棵 Render 树(Attachment)。每个 DOM 节点都有 attach 方法,接受样式信息,返回一个 render 对象(又名 renderer),这些 render 对象最终会被构建成一棵 Render 树;
第四步,确定节点坐标:根据 Render 树结构,为每个 Render 树上的节点确定一个在显示屏上出现的精确坐标;
第五步,绘制页面:根据 Render 树和节点显示坐标,然后调用每个节点的 paint 方法,将它们绘制出来。
1、DOM树的构建是文档加载完成开始的? 构建 DOM 树是一个渐进过程,为达到更好的用户体验,渲染引擎会尽快将内容显示在屏幕上,它不必等到整个 HTML 文档解析完成之后才开始构建 render 树和布局。
2、Render树是DOM树和CSS样式表构建完毕后才开始构建的? 这三个过程在实际进行的时候并不是完全独立的,而是会有交叉,会一边加载,一边解析,以及一边渲染。
3、CSS的解析注意点? CSS 的解析是从右往左逆向解析的,嵌套标签越多,解析越慢。
4、JS操作真实DOM的代价? 用我们传统的开发模式,原生 JS 或 JQ 操作 DOM 时,浏览器会从构建 DOM 树开始从头到尾执行一遍流程。在一次操作中,我需要更新 10 个 DOM 节点,浏览器收到第一个 DOM 请求后并不知道还有 9 次更新操作,因此会马上执行流程,最终执行10 次。例如,第一次计算完,紧接着下一个 DOM 更新请求,这个节点的坐标值就变了,前一次计算为无用功。计算 DOM 节点坐标值等都是白白浪费的性能。即使计算机硬件一直在迭代更新,操作 DOM 的代价仍旧是昂贵的,频繁操作还是会出现页面卡顿,影响用户体验
优势:
虚拟 DOM 就是为了解决浏览器性能问题而被设计出来的。如前,若一次操作中有 10 次更新 DOM 的动作,虚拟 DOM 不会立即操作 DOM,而是将这 10 次更新的 diff 内容保存到本地一个 JS 对象中,最终将这个 JS 对象一次性 attch 到 DOM 树上,再进行后续操作,避免大量无谓的计算量。所以,用 JS 对象模拟 DOM 节点的好处是,页面的更新可以先全部反映在 JS 对象(虚拟 DOM )上,操作内存中的 JS 对象的速度显然要更快,等更新完成后,再将最终的 JS 对象映射成真实的 DOM,交由浏览器去绘制。
要支持render方法,这个之后补充一下
【你不知道的 Proxy】:用 ES6 Proxy 能做哪些有意思的事情? - 掘金
先说一下这两个概念吧
Object.defineProperty作用:
Proxy作用:
Proxy 用于修改某些操作的默认行为,等同于在语言层面做出修改,所以属于一种“元编程”(meta programming),即对编程语言进行编程。
Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”。
Object.defineProperty
defineProperty方法有以下几个参数:
1、不能监听所有属性
无法一次性监听对象所有属性,必须遍历或者递归来实现
2、无法监听新增加的属性
在 Vue 中想动态监听属性,一般用 Vue.set(girl, “hobby”, “game”)
与Proxy不同,Proxy可以
function set(target, key, val) {
if(!target.hasOwnProperty(key)) {
target[key] = val;
defineReactive(target, key, val);
}
}
3、Object.defineProperty### 无法响应数组操作
通过重写原型方法的方式进行
vue里面使用的vue.set或者splice(index,length)方法
在定义变量的时候,判断其是否为数组,如果是数组,那么就修改它的 proto,将其指向 subArrProto,从而实现重写原型链。
const arrayProto = Array.prototype;
const subArrProto = Object.create(arrayProto);
const methods = ['pop', 'shift', 'unshift', 'sort', 'reverse', 'splice', 'push'];
methods.forEach(method => {
/* 重写原型方法 */
subArrProto[method] = function() {
arrayProto[method].call(this, ...arguments);
};
/* 监听这些方法 */
Object.defineProperty(subArrProto, method, {
set() {},
get() {}
})
})
proxy可以自动监听上面所有的
优势:
Object.defineProperty有一个有点,兼容性好
而Proxy 是新出的 API,兼容性还不够好,不支持 IE 全系列。
1.计算属性与methods
相比于方法,计算属性基于它的依赖缓存,只有当其依赖的值发生变化的时候,才会触发重新计算,而不像普通的方法函数,每当重新渲染的时后,都会重新执行一遍。
2、计算属性与watch
计算属性是基于它们的响应式依赖进行缓存的。只在相关响应式依赖发生改变时它们才会重新求值。
(也就是说只有当依赖的属性发生变化时才才会重新计算这个计算属性)
Computed 与 watch 的区别在于,前者是在依赖的属性发生改变时触发回调函数,后者则是在属性本身发生改变时触发回调函数。
当自己变化 引起他人变化的时候 就用 watch。当他人变化引起自己变化的时候就用 computed;