写过自定义指令吗?原理是什么
答复范例
Vue
有一组默认指令,比方v-model
或v-for
,同时Vue
也容许用户注册自定义指令来扩大 Vue 能力- 自定义指令次要实现一些可复用低层级
DOM
操作 - 应用自定义指令分为定义、注册和应用三步:
- 定义自定义指令有两种形式:对象和函数模式,前者相似组件定义,有各种生命周期;后者只会在
mounte
d 和updated
时执行
- 注册自定义指令相似组件,能够应用
app.directive()
全局注册,应用{directives:{xxx}}
部分注册 - 应用时在注册名称前加上
v-
即可,比方v-focus
- 我在我的项目中罕用到一些自定义指令,例如:
- 复制粘贴
v-copy
- 长按
v-longpress
- 防抖
v-debounce
- 图片懒加载
v-lazy
- 按钮权限
v-premission
- 页面水印
v-waterMarker
- 拖拽指令
v-draggable
vue3
中指令定义产生了比拟大的变动,次要是钩子的名称放弃和组件统一,这样开发人员容易记忆,不易犯错。另外在v3.2
之后,能够在setup
中以一个小写v
结尾不便的定义自定义指令,更简略了
根本应用
当 Vue 中的外围内置指令不可能满足咱们的需要时,咱们能够定制自定义的指令用来满足开发的需要
咱们看到的 v-
结尾的行内属性,都是指令,不同的指令能够实现或实现不同的性能,对一般 DOM 元素进行底层操作,这时候就会用到自定义指令。除了外围性能默认内置的指令 (v-model
和 v-show
),Vue
也容许注册自定义指令
// 指令应用的几种形式:// 会实例化一个指令,但这个指令没有参数
`v-xxx`
// -- 将值传到指令中
`v-xxx="value"`
// -- 将字符串传入到指令中,如 `v-html="'<p> 内容 </p>'"`
`v-xxx="'string'"`
// -- 传参数(`arg`),如 `v-bind:class="className"`
`v-xxx:arg="value"`
// -- 应用修饰符(`modifier`)`v-xxx:arg.modifier="value"`
注册一个自定义指令有全局注册与部分注册
// 全局注册注册次要是用过 Vue.directive 办法进行注册
// Vue.directive 第一个参数是指令的名字(不须要写上 v - 前缀),第二个参数能够是对象数据,也能够是一个指令函数
// 注册一个全局自定义指令 `v-focus`
Vue.directive('focus', {
// 当被绑定的元素插入到 DOM 中时……
inserted: function (el) {
// 聚焦元素
el.focus() // 页面加载实现之后主动让输入框获取到焦点的小性能}
})
// 部分注册通过在组件 options 选项中设置 directive 属性
directives: {
focus: {
// 指令的定义
inserted: function (el) {el.focus() // 页面加载实现之后主动让输入框获取到焦点的小性能
}
}
}
// 而后你能够在模板中任何元素上应用新的 v-focus property,如下:<input v-focus />
钩子函数
bind
:只调用一次,指令第一次绑定到元素时调用。在这里能够进行一次性的初始化设置。inserted
:被绑定元素插入父节点时调用 (仅保障父节点存在,但不肯定已被插入文档中)。update
:被绑定于元素所在的模板更新时调用,而无论绑定值是否变动。通过比拟更新前后的绑定值,能够疏忽不必要的模板更新。componentUpdated
:被绑定元素所在模板实现一次更新周期时调用。unbind
:只调用一次,指令与元素解绑时调用。
所有的钩子函数的参数都有以下:
el
:指令所绑定的元素,能够用来间接操作 DOM-
binding
:一个对象,蕴含以下property
:name
:指令名,不包含v-
前缀。value
:指令的绑定值,例如:v-my-directive="1 + 1"
中,绑定值为2
。oldValue
:指令绑定的前一个值,仅在update
和componentUpdated
钩子中可用。无论值是否扭转都可用。expression
:字符串模式的指令表达式。例如v-my-directive="1 + 1"
中,表达式为"1 + 1"
。arg
:传给指令的参数,可选。例如v-my-directive:foo
中,参数为"foo"
。modifiers
:一个蕴含修饰符的对象。例如:v-my-directive.foo.bar
中,修饰符对象为{foo: true, bar: true}
vnode
:Vue
编译生成的虚构节点oldVnode
:上一个虚构节点,仅在update
和componentUpdated
钩子中可用
除了 el
之外,其它参数都应该是只读的,切勿进行批改。如果须要在钩子之间共享数据,倡议通过元素的 dataset
来进行
<div v-demo="{color:'white', text:'hello!'}"></div>
<script>
Vue.directive('demo', function (el, binding) {console.log(binding.value.color) // "white"
console.log(binding.value.text) // "hello!"
})
</script>
利用场景
应用自定义组件组件能够满足咱们日常一些场景,这里给出几个自定义组件的案例:
- 防抖
// 1. 设置 v -throttle 自定义指令
Vue.directive('throttle', {bind: (el, binding) => {
let throttleTime = binding.value; // 防抖工夫
if (!throttleTime) { // 用户若不设置防抖工夫,则默认 2s
throttleTime = 2000;
}
let cbFun;
el.addEventListener('click', event => {if (!cbFun) { // 第一次执行
cbFun = setTimeout(() => {cbFun = null;}, throttleTime);
} else {event && event.stopImmediatePropagation();
}
}, true);
},
});
// 2. 为 button 标签设置 v -throttle 自定义指令
<button @click="sayHello" v-throttle> 提交 </button>
- 图片懒加载
设置一个 v-lazy
自定义组件实现图片懒加载
const LazyLoad = {
// install 办法
install(Vue,options){
// 代替图片的 loading 图
let defaultSrc = options.default;
Vue.directive('lazy',{bind(el,binding){LazyLoad.init(el,binding.value,defaultSrc);
},
inserted(el){
// 兼容解决
if('InterpObserver' in window){LazyLoad.observe(el);
}else{LazyLoad.listenerScroll(el);
}
},
})
},
// 初始化
init(el,val,def){
// src 贮存实在 src
el.setAttribute('src',val);
// 设置 src 为 loading 图
el.setAttribute('src',def);
},
// 利用 InterpObserver 监听 el
observe(el){
let io = new InterpObserver(entries => {
let realSrc = el.dataset.src;
if(entries[0].isIntersecting){if(realSrc){
el.src = realSrc;
el.removeAttribute('src');
}
}
});
io.observe(el);
},
// 监听 scroll 事件
listenerScroll(el){let handler = LazyLoad.throttle(LazyLoad.load,300);
LazyLoad.load(el);
window.addEventListener('scroll',() => {handler(el);
});
},
// 加载实在图片
load(el){
let windowHeight = document.documentElement.clientHeight
let elTop = el.getBoundingClientRect().top;
let elBtm = el.getBoundingClientRect().bottom;
let realSrc = el.dataset.src;
if(elTop - windowHeight<0&&elBtm > 0){if(realSrc){
el.src = realSrc;
el.removeAttribute('src');
}
}
},
// 节流
throttle(fn,delay){
let timer;
let prevTime;
return function(...args){let currTime = Date.now();
let context = this;
if(!prevTime) prevTime = currTime;
clearTimeout(timer);
if(currTime - prevTime > delay){
prevTime = currTime;
fn.apply(context,args);
clearTimeout(timer);
return;
}
timer = setTimeout(function(){prevTime = Date.now();
timer = null;
fn.apply(context,args);
},delay);
}
}
}
export default LazyLoad;
- 一键 Copy 的性能
import {Message} from 'ant-design-vue';
const vCopy = { //
/*
bind 钩子函数,第一次绑定时调用,能够在这里做初始化设置
el: 作用的 dom 对象
value: 传给指令的值,也就是咱们要 copy 的值
*/
bind(el, { value}) {
el.$value = value; // 用一个全局属性来存传进来的值,因为这个值在别的钩子函数里还会用到
el.handler = () => {if (!el.$value) {
// 值为空的时候,给出提醒,我这里的提醒是用的 ant-design-vue 的提醒,你们随便
Message.warning('无复制内容');
return;
}
// 动态创建 textarea 标签
const textarea = document.createElement('textarea');
// 将该 textarea 设为 readonly 避免 iOS 下主动唤起键盘,同时将 textarea 移出可视区域
textarea.readOnly = 'readonly';
textarea.style.position = 'absolute';
textarea.style.left = '-9999px';
// 将要 copy 的值赋给 textarea 标签的 value 属性
textarea.value = el.$value;
// 将 textarea 插入到 body 中
document.body.appendChild(textarea);
// 选中值并复制
textarea.select();
// textarea.setSelectionRange(0, textarea.value.length);
const result = document.execCommand('Copy');
if (result) {Message.success('复制胜利');
}
document.body.removeChild(textarea);
};
// 绑定点击事件,就是所谓的一键 copy 啦
el.addEventListener('click', el.handler);
},
// 当传进来的值更新的时候触发
componentUpdated(el, { value}) {el.$value = value;},
// 指令与元素解绑的时候,移除事件绑定
unbind(el) {el.removeEventListener('click', el.handler);
},
};
export default vCopy;
- 拖拽
<div ref="a" id="bg" v-drag></div>
directives: {
drag: {bind() {},
inserted(el) {el.onmousedown = (e) => {
let x = e.clientX - el.offsetLeft;
let y = e.clientY - el.offsetTop;
document.onmousemove = (e) => {
let xx = e.clientX - x + "px";
let yy = e.clientY - y + "px";
el.style.left = xx;
el.style.top = yy;
};
el.onmouseup = (e) => {document.onmousemove = null;};
};
},
},
}
原理
- 指令实质上是装璜器,是
vue
对HTML
元素的扩大,给HTML
元素减少自定义性能。vue
编译DOM
时,会找到指令对象,执行指令的相干办法。 - 自定义指令有五个生命周期(也叫钩子函数),别离是
bind
、inserted
、update
、componentUpdated
、unbind
原理
- 在生成
ast
语法树时,遇到指令会给以后元素增加directives
属性 - 通过
genDirectives
生成指令代码 - 在
patch
前将指令的钩子提取到cbs
中, 在patch
过程中调用对应的钩子 - 当执行指令对应钩子函数时,调用对应指令定义的办法
说一下 Vue 的生命周期
Vue 实例有⼀个残缺的⽣命周期,也就是从开始创立、初始化数据、编译模版、挂载 Dom -> 渲染、更新 -> 渲染、卸载 等⼀系列过程,称这是 Vue 的⽣命周期。
- beforeCreate(创立前):数据观测和初始化事件还未开始,此时 data 的响应式追踪、event/watcher 都还没有被设置,也就是说不能拜访到 data、computed、watch、methods 上的办法和数据。
- created(创立后):实例创立实现,实例上配置的 options 包含 data、computed、watch、methods 等都配置实现,然而此时渲染得节点还未挂载到 DOM,所以不能拜访到
$el
属性。 - beforeMount(挂载前):在挂载开始之前被调用,相干的 render 函数首次被调用。实例已实现以下的配置:编译模板,把 data 外面的数据和模板生成 html。此时还没有挂载 html 到页面上。
- mounted(挂载后):在 el 被新创建的 vm.$el 替换,并挂载到实例下来之后调用。实例已实现以下的配置:用下面编译好的 html 内容替换 el 属性指向的 DOM 对象。实现模板中的 html 渲染到 html 页面中。此过程中进行 ajax 交互。
- beforeUpdate(更新前):响应式数据更新时调用,此时尽管响应式数据更新了,然而对应的实在 DOM 还没有被渲染。
- updated(更新后):在因为数据更改导致的虚构 DOM 从新渲染和打补丁之后调用。此时 DOM 曾经依据响应式数据的变动更新了。调用时,组件 DOM 曾经更新,所以能够执行依赖于 DOM 的操作。然而在大多数状况下,应该防止在此期间更改状态,因为这可能会导致更新有限循环。该钩子在服务器端渲染期间不被调用。
- beforeDestroy(销毁前):实例销毁之前调用。这一步,实例依然齐全可用,
this
仍能获取到实例。 - destroyed(销毁后):实例销毁后调用,调用后,Vue 实例批示的所有货色都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。该钩子在服务端渲染期间不被调用。
另外还有 keep-alive
独有的生命周期,别离为 activated
和 deactivated
。用 keep-alive
包裹的组件在切换时不会进行销毁,而是缓存到内存中并执行 deactivated
钩子函数,命中缓存渲染后会执行 activated
钩子函数。
Vue 中 computed 和 watch 有什么区别?
计算属性 computed:
(1)** 反对缓存 **,只有依赖数据发生变化时,才会从新进行计算函数;(2)计算属性内 ** 不反对异步操作 **;(3)计算属性的函数中 ** 都有一个 get**(默认具备,获取计算属性)** 和 set**(手动增加,设置计算属性)办法;(4)计算属性是主动监听依赖值的变动,从而动静返回内容。
侦听属性 watch:
(1)** 不反对缓存 **,只有数据发生变化,就会执行侦听函数;(2)侦听属性内 ** 反对异步操作 **;(3)侦听属性的值 ** 能够是一个对象,接管 handler 回调,deep,immediate 三个属性 **;(3)监听是一个过程,在监听的值变动时,能够触发一个回调,并 ** 做一些其余事件 **。
v-if 和 v -show 的区别
- 伎俩:v-if 是动静的向 DOM 树内增加或者删除 DOM 元素;v-show 是通过设置 DOM 元素的 display 款式属性管制显隐;
- 编译过程:v-if 切换有一个部分编译 / 卸载的过程,切换过程中适合地销毁和重建外部的事件监听和子组件;v-show 只是简略的基于 css 切换;
- 编译条件:v-if 是惰性的,如果初始条件为假,则什么也不做;只有在条件第一次变为真时才开始部分编译; v-show 是在任何条件下,无论首次条件是否为真,都被编译,而后被缓存,而且 DOM 元素保留;
- 性能耗费:v-if 有更高的切换耗费;v-show 有更高的初始渲染耗费;
- 应用场景:v-if 适宜经营条件不大可能扭转;v-show 适宜频繁切换。
对 React 和 Vue 的了解,它们的异同
相似之处:
- 都将注意力集中放弃在外围库,而将其余性能如路由和全局状态治理交给相干的库;
- 都有本人的构建工具,能让你失去一个依据最佳实际设置的我的项目模板;
- 都应用了 Virtual DOM(虚构 DOM)进步重绘性能;
- 都有 props 的概念,容许组件间的数据传递;
- 都激励组件化利用,将利用分拆成一个个性能明确的模块,进步复用性。
不同之处:
1)数据流
Vue 默认反对数据双向绑定,而 React 始终提倡单向数据流
2)虚构 DOM
Vue2.x 开始引入 ”Virtual DOM”,打消了和 React 在这方面的差别,然而在具体的细节还是有各自的特点。
- Vue 声称能够更快地计算出 Virtual DOM 的差别,这是因为它在渲染过程中,会跟踪每一个组件的依赖关系,不须要从新渲染整个组件树。
- 对于 React 而言,每当利用的状态被扭转时,全副子组件都会从新渲染。当然,这能够通过 PureComponent/shouldComponentUpdate 这个生命周期办法来进行管制,但 Vue 将此视为默认的优化。
3)组件化
React 与 Vue 最大的不同是模板的编写。
- Vue 激励写近似惯例 HTML 的模板。写起来很靠近规范 HTML 元素,只是多了一些属性。
- React 举荐你所有的模板通用 JavaScript 的语法扩大——JSX 书写。
具体来讲:React 中 render 函数是反对闭包个性的,所以 import 的组件在 render 中能够间接调用。然而在 Vue 中,因为模板中应用的数据都必须挂在 this 上进行一次直达,所以 import 一个组件完了之后,还须要在 components 中再申明下。4)监听数据变动的实现原理不同
- Vue 通过 getter/setter 以及一些函数的劫持,能准确晓得数据变动,不须要特地的优化就能达到很好的性能
- React 默认是通过比拟援用的形式进行的,如果不优化(PureComponent/shouldComponentUpdate)可能导致大量不必要的 vDOM 的从新渲染。这是因为 Vue 应用的是可变数据,而 React 更强调数据的不可变。
5)高阶组件
react 能够通过高阶组件(HOC)来扩大,而 Vue 须要通过 mixins 来扩大。
高阶组件就是高阶函数,而 React 的组件自身就是纯正的函数,所以高阶函数对 React 来说大海捞针。相同 Vue.js 应用 HTML 模板创立视图组件,这时模板无奈无效的编译,因而 Vue 不能采纳 HOC 来实现。
6)构建工具
两者都有本人的构建工具:
- React ==> Create React APP
- Vue ==> vue-cli
7)跨平台
- React ==> React Native
- Vue ==> Weex
如何从实在 DOM 到虚构 DOM
波及到 Vue 中的模板编译原理,次要过程:
- 将模板转换成
ast
树,ast
用对象来形容实在的 JS 语法(将实在 DOM 转换成虚构 DOM) - 优化树
- 将
ast
树生成代码
参考 前端进阶面试题具体解答
过滤器的作用,如何实现一个过滤器
依据过滤器的名称,过滤器是用来过滤数据的,在 Vue 中应用 filters
来过滤数据,filters
不会批改数据,而是过滤数据,扭转用户看到的输入(计算属性 computed
,办法 methods
都是通过批改数据来解决数据格式的输入显示)。
应用场景:
- 须要格式化数据的状况,比方须要解决工夫、价格等数据格式的输入 / 显示。
- 比方后端返回一个 年月日的日期字符串 ,前端须要展现为 多少天前 的数据格式,此时就能够用
fliters
过滤器来解决数据。
过滤器是一个函数,它会把表达式中的值始终当作函数的第一个参数。过滤器用在 插值表达式 {{}}
和 v-bind
表达式 中,而后放在操作符“|
”前面进行批示。
例如,在显示金额,给商品价格增加单位:
<li> 商品价格:{{item.price | filterPrice}}</li>
filters: {filterPrice (price) {return price ? ('¥' + price) : '--'
}
}
Vue 中封装的数组办法有哪些,其如何实现页面更新
在 Vue 中,对响应式解决利用的是 Object.defineProperty 对数据进行拦挡,而这个办法并不能监听到数组外部变动,数组长度变动,数组的截取变动等,所以须要对这些操作进行 hack,让 Vue 能监听到其中的变动。那 Vue 是如何实现让这些数组办法实现元素的实时更新的呢,上面是 Vue 中对这些办法的封装:
// 缓存数组原型
const arrayProto = Array.prototype;
// 实现 arrayMethods.__proto__ === Array.prototype
export const arrayMethods = Object.create(arrayProto);
// 须要进行性能拓展的办法
const methodsToPatch = [
"push",
"pop",
"shift",
"unshift",
"splice",
"sort",
"reverse"
];
/** * Intercept mutating methods and emit events */
methodsToPatch.forEach(function(method) {
// 缓存原生数组办法
const original = arrayProto[method];
def(arrayMethods, method, function mutator(...args) {
// 执行并缓存原生数组性能
const result = original.apply(this, args);
// 响应式解决
const ob = this.__ob__;
let inserted;
switch (method) {
// push、unshift 会新增索引,所以要手动 observer
case "push":
case "unshift":
inserted = args;
break;
// splice 办法,如果传入了第三个参数,也会有索引退出,也要手动 observer。case "splice":
inserted = args.slice(2);
break;
}
//
if (inserted) ob.observeArray(inserted);// 获取插入的值,并设置响应式监听
// notify change
ob.dep.notify();// 告诉依赖更新
// 返回原生数组办法的执行后果
return result;
});
});
简略来说就是,重写了数组中的那些原生办法,首先获取到这个数组的__ob__,也就是它的 Observer 对象,如果有新的值,就调用 observeArray 持续对新的值察看变动(也就是通过 target__proto__ == arrayMethods
来扭转了数组实例的型),而后手动调用 notify,告诉渲染 watcher,执行 update。
slot 是什么?有什么作用?原理是什么?
slot 又名插槽,是 Vue 的内容散发机制,组件外部的模板引擎应用 slot 元素作为承载散发内容的进口。插槽 slot 是子组件的一个模板标签元素,而这一个标签元素是否显示,以及怎么显示是由父组件决定的。slot 又分三类,默认插槽,具名插槽和作用域插槽。
- 默认插槽:又名匿名查抄,当 slot 没有指定 name 属性值的时候一个默认显示插槽,一个组件内只有有一个匿名插槽。
- 具名插槽:带有具体名字的插槽,也就是带有 name 属性的 slot,一个组件能够呈现多个具名插槽。
- 作用域插槽:默认插槽、具名插槽的一个变体,能够是匿名插槽,也能够是具名插槽,该插槽的不同点是在子组件渲染作用域插槽时,能够将子组件外部的数据传递给父组件,让父组件依据子组件的传递过去的数据决定如何渲染该插槽。
实现原理:当子组件 vm 实例化时,获取到父组件传入的 slot 标签的内容,寄存在 vm.$slot
中,默认插槽为 vm.$slot.default
,具名插槽为vm.$slot.xxx
,xxx 为插槽名,当组件执行渲染函数时候,遇到 slot 标签,应用$slot
中的内容进行替换,此时能够为插槽传递数据,若存在数据,则可称该插槽为作用域插槽。
Vue 单页利用与多页利用的区别
概念:
- SPA 单页面利用(SinglePage Web Application),指只有一个主页面的利用,一开始只须要加载一次 js、css 等相干资源。所有内容都蕴含在主页面,对每一个功能模块组件化。单页利用跳转,就是切换相干组件,仅仅刷新部分资源。
- MPA 多页面利用(MultiPage Application),指有多个独立页面的利用,每个页面必须反复加载 js、css 等相干资源。多页利用跳转,须要整页资源刷新。
说说 Vue 的生命周期吧
什么时候被调用?
- beforeCreate:实例初始化之后,数据观测之前调用
- created:实例创立万之后调用。实例实现:数据观测、属性和办法的运算、
watch/event
事件回调。无$el
. - beforeMount:在挂载之前调用,相干
render
函数首次被调用 - mounted:了被新创建的
vm.$el
替换,并挂载到实例下来之后调用改钩子。 - beforeUpdate:数据更新前调用,产生在虚构 DOM 从新渲染和打补丁,在这之后会调用改钩子。
- updated:因为数据更改导致的虚构 DOM 从新渲染和打补丁,在这之后会调用改钩子。
- beforeDestroy:实例销毁前调用,实例依然可用。
- destroyed:实例销毁之后调用,调用后,Vue 实例批示的所有货色都会解绑,所有事件监听器和所有子实例都会被移除
每个生命周期外部能够做什么?
- created:实例曾经创立实现,因为他是最早触发的,所以能够进行一些数据、资源的申请。
- mounted:实例曾经挂载实现,能够进行一些 DOM 操作。
- beforeUpdate:能够在这个钩子中进一步的更改状态,不会触发重渲染。
- updated:能够执行依赖于 DOM 的操作,然而要防止更改状态,可能会导致更新无线循环。
- destroyed:能够执行一些优化操作,清空计时器,解除绑定事件。
ajax 放在哪个生命周期?:个别放在 mounted
中,保障逻辑统一性,因为生命周期是同步执行的, ajax
是异步执行的。复数服务端渲染 ssr
同一放在 created
中,因为服务端渲染不反对 mounted
办法。 什么时候应用 beforeDestroy?:以后页面应用 $on
,须要解绑事件。分明定时器。解除事件绑定, scroll mousemove
。
MVC 和 MVVM 区别
MVC
MVC 全名是 Model View Controller,是模型 (model)-视图(view)-控制器(controller) 的缩写,一种软件设计榜样
- Model(模型):是应用程序中用于解决应用程序数据逻辑的局部。通常模型对象负责在数据库中存取数据
- View(视图):是应用程序中解决数据显示的局部。通常视图是根据模型数据创立的
- Controller(控制器):是应用程序中解决用户交互的局部。通常控制器负责从视图读取数据,管制用户输出,并向模型发送数据
MVC 的思维:一句话形容就是 Controller 负责将 Model 的数据用 View 显示进去,换句话说就是在 Controller 外面把 Model 的数据赋值给 View。
MVVM
MVVM 新增了 VM 类
- ViewModel 层:做了两件事达到了数据的双向绑定 一是将【模型】转化成【视图】,行将后端传递的数据转化成所看到的页面。实现的形式是:数据绑定。二是将【视图】转化成【模型】,行将所看到的页面转化成后端的数据。实现的形式是:DOM 事件监听。
MVVM 与 MVC 最大的区别就是:它实现了 View 和 Model 的主动同步,也就是当 Model 的属性扭转时,咱们不必再本人手动操作 Dom 元素,来扭转 View 的显示,而是扭转属性后该属性对应 View 层显示会主动扭转(对应 Vue 数据驱动的思维)
整体看来,MVVM 比 MVC 精简很多,不仅简化了业务与界面的依赖,还解决了数据频繁更新的问题,不必再用选择器操作 DOM 元素。因为在 MVVM 中,View 不晓得 Model 的存在,Model 和 ViewModel 也察看不到 View,这种低耦合模式进步代码的可重用性
留神:Vue 并没有齐全遵循 MVVM 的思维 这一点官网本人也有阐明
那么问题来了 为什么官网要说 Vue 没有齐全遵循 MVVM 思维呢?
- 严格的 MVVM 要求 View 不能和 Model 间接通信,而 Vue 提供了 $refs 这个属性,让 Model 能够间接操作 View,违反了这一规定,所以说 Vue 没有齐全遵循 MVVM。
$nextTick 是什么?
Vue 实现响应式并不是在数据产生后立刻更新 DOM,应用 vm.$nextTick
是在下次 DOM 更新循环完结之后立刻执行提早回调。在批改数据之后应用,则 能够在回调中获取更新后的 DOM。
那 vue 中是如何检测数组变动的呢?
数组就是应用 object.defineProperty
从新定义数组的每一项,那能引起数组变动的办法咱们都是晓得的, pop
、 push
、 shift
、 unshift
、 splice
、 sort
、 reverse
这七种,只有这些办法执行改了数组内容,我就更新内容就好了,是不是很好了解。
- 是用来函数劫持的形式,重写了数组办法,具体呢就是更改了数组的原型,更改成本人的,用户调数组的一些办法的时候,走的就是本人的办法,而后告诉视图去更新。
- 数组里每一项可能是对象,那么我就是会对数组的每一项进行观测,(且只有数组里的对象能力进行观测,观测过的也不会进行观测)
vue3:改用 proxy
,可间接监听对象数组的变动。
vue 如何监听对象或者数组某个属性的变动
当在我的项目中间接设置数组的某一项的值,或者间接设置对象的某个属性值,这个时候,你会发现页面并没有更新。这是因为 Object.defineProperty()限度,监听不到变动。
解决形式:
- this.$set(你要扭转的数组 / 对象,你要扭转的地位 /key,你要改成什么 value)
this.$set(this.arr, 0, "OBKoro1"); // 扭转数组 this.$set(this.obj, "c", "OBKoro1"); // 扭转对象
- 调用以下几个数组的办法
splice()、push()、pop()、shift()、unshift()、sort()、reverse()
vue 源码里缓存了 array 的原型链,而后重写了这几个办法,触发这几个办法的时候会 observer 数据,意思是应用这些办法不必再进行额定的操作,视图主动进行更新。举荐应用 splice 办法会比拟好自定义, 因为 splice 能够在数组的任何地位进行删除 / 增加操作
vm.$set
的实现原理是:
- 如果指标是数组,间接应用数组的 splice 办法触发相应式;
- 如果指标是对象,会先判读属性是否存在、对象是否是响应式,最终如果要对属性进行响应式解决,则是通过调用 defineReactive 办法进行响应式解决(defineReactive 办法就是 Vue 在初始化对象时,给对象属性采纳 Object.defineProperty 动静增加 getter 和 setter 的性能所调用的办法)
v-model 能够被用在自定义组件上吗?如果能够,如何应用?
能够。v-model 实际上是一个语法糖,如:
<input v-model="searchText">
实际上相当于:
<input
v-bind:value="searchText"
v-on:input="searchText = $event.target.value"
>
用在自定义组件上也是同理:
<custom-input v-model="searchText">
相当于:
<custom-input
v-bind:value="searchText"
v-on:input="searchText = $event"
></custom-input>
显然,custom-input 与父组件的交互如下:
- 父组件将
searchText
变量传入 custom-input 组件,应用的 prop 名为value
; - custom-input 组件向父组件传出名为
input
的事件,父组件将接管到的值赋值给searchText
;
所以,custom-input 组件的实现应该相似于这样:
Vue.component('custom-input', {props: ['value'],
template: ` <input v-bind:value="value" v-on:input="$emit('input', $event.target.value)" > `
})
Vue 的基本原理
当一个 Vue 实例创立时,Vue 会遍历 data 中的属性,用 Object.defineProperty(vue3.0 应用 proxy)将它们转为 getter/setter,并且在外部追踪相干依赖,在属性被拜访和批改时告诉变动。每个组件实例都有相应的 watcher 程序实例,它会在组件渲染的过程中把属性记录为依赖,之后当依赖项的 setter 被调用时,会告诉 watcher 从新计算,从而以致它关联的组件得以更新。
形容下 Vue 自定义指令
在 Vue2.0 中,代码复用和形象的次要模式是组件。然而,有的状况下,你依然须要对一般 DOM 元素进行底层操作,这时候就会用到自定义指令。
个别须要对 DOM 元素进行底层操作时应用,尽量只用来操作 DOM 展现,不批改外部的值。当应用自定义指令间接批改 value 值时绑定 v -model 的值也不会同步更新;如必须批改能够在自定义指令中应用 keydown 事件,在 vue 组件中应用 change 事件,回调中批改 vue 数据;
(1)自定义指令根本内容
- 全局定义:
Vue.directive("focus",{})
- 部分定义:
directives:{focus:{}}
-
钩子函数:指令定义对象提供钩子函数
o bind:只调用一次,指令第一次绑定到元素时调用。在这里能够进行一次性的初始化设置。
o inSerted:被绑定元素插入父节点时调用(仅保障父节点存在,但不肯定已被插入文档中)。
o update:所在组件的 VNode 更新时调用,然而可能产生在其子 VNode 更新之前调用。指令的值可能产生了扭转,也可能没有。然而能够通过比拟更新前后的值来疏忽不必要的模板更新。
o ComponentUpdate:指令所在组件的 VNode 及其子 VNode 全副更新后调用。
o unbind:只调用一次,指令与元素解绑时调用。
-
钩子函数参数
o el:绑定元素o bing:指令外围对象,形容指令全副信息属性
o name
o value
o oldValue
o expression
o arg
o modifers
o vnode 虚构节点
o oldVnode:上一个虚构节点(更新钩子函数中才有用)
(2)应用场景
- 一般 DOM 元素进行底层操作的时候,能够应用自定义指令
- 自定义指令是用来操作 DOM 的。只管 Vue 推崇数据驱动视图的理念,但并非所有状况都适宜数据驱动。自定义指令就是一种无效的补充和扩大,不仅可用于定义任何的 DOM 操作,并且是可复用的。
(3)应用案例
高级利用:
- 鼠标聚焦
- 下拉菜单
- 绝对工夫转换
- 滚动动画
高级利用:
- 自定义指令实现图片懒加载
- 自定义指令集成第三方插件
Vue 为什么没有相似于 React 中 shouldComponentUpdate 的生命周期?
考点: Vue 的变动侦测原理
前置常识: 依赖收集、虚构 DOM、响应式零碎
根本原因是 Vue 与 React 的变动侦测形式有所不同
React 是 pull 的形式侦测变动, 当 React 晓得发生变化后, 会应用 Virtual Dom Diff 进行差别检测, 然而很多组件实际上是必定不会发生变化的, 这个时候须要用 shouldComponentUpdate 进行手动操作来缩小 diff, 从而进步程序整体的性能.
Vue 是 pull+push 的形式侦测变动的, 在一开始就晓得那个组件产生了变动, 因而在 push 的阶段并不需要手动管制 diff, 而组件外部采纳的 diff 形式实际上是能够引入相似于 shouldComponentUpdate 相干生命周期的, 然而通常正当大小的组件不会有适量的 diff, 手动优化的价值无限, 因而目前 Vue 并没有思考引入 shouldComponentUpdate 这种手动优化的生命周期.
父子组件生命周期调用程序(简略)
渲染程序:先父后子,实现程序:先子后父
更新程序:父更新导致子更新,子更新实现后父
销毁程序:先父后子,实现程序:先子后父