「Vue2.0」directive.js
Vue.directive('xxx', {
// 指令第一次绑定到元素时「初始化」……
bind: function (el, binding, vnode, oldVnode) {
// el:绑定指令的DOM元素对象
// binding:数据对象
// + name:不带“v-”的指令名字
// + rawName:带“v-”的指令名字「含值和修饰符」
// + value:指令绑定的值 v-xxx="1+1" -> value:2
// + expression:指令表达式 v-xxx="1+1" -> expression:"1+1"
// + arg:传给指令的参数 v-xxx:n -> arg:"n"
// + modifiers:修饰符对象 v-xxx.stop.finish -> modifiers:{stop:true,finish:true}
// vnode:Vue编译生成的虚拟节点
// oldVnode:上一个虚拟节点
},
// 被绑定元素插入父节点时「父节点不一定非要插入到文档中,一般是插入到DOM中」……
inserted: function () {},
// 所在组件的VNode更新时「指令的值可能发生了改变,也可能没有」……
update: function (el, binding, vnode, oldVnode) {
// binding:数据对象
// + oldArg
// + oldValue
// + ...
},
// 所在组件的VNode及其子VNode全部更新后……
componentUpdated: function () {},
// 指令与元素解绑时……
unbind: function () {}
});
// 使用自定义指令
<span v-xxx:100.stop="200"></span>
「Vue3.0」directive.js
export default function directive(app) {
app.directive('xxx', {
// 指令首次绑定到元素且在安装父组件之前...「等同于bind」
beforeMount(el, binding, vnode, prevVnode) {
// binding:数据对象
// + arg:传给指令的参数 v-xxx:n -> arg:"n"
// + modifiers:修饰符对象 v-xxx.stop -> modifiers:{stop:true}
// + value:指令绑定的值 v-xxx="1+1" -> value:2
// + oldValue:之前绑定的值
},
// 安装绑定元素的父组件时...「等同于inserted」
mounted() {},
// 在包含组件的VNode更新之前...
beforeUpdate() {},
// 在包含组件的VNode及其子VNode更新后...「等同于componentUpdated」
updated() {},
// 在卸载绑定元素的父组件之前...
beforeUnmount() {},
// 指令与元素解除绑定且父组件已卸载时...「等同于unbind」
unmounted() {}
});
};
// main.js
import {
createApp
} from 'vue';
import App from './App.vue';
import directive from './directive';
const app = createApp(App);
directive(app);
app.mount('#app');
文本处理的两大自定义指令:v-copy / v-emoji
document.execCommand(“Copy”) 将选中文本复制到剪切板「只对input和textarea有效」\ select() 选择文本
v-copy
Vue.directive('copy', {
bind(el, binding) {
// 初始化:点击元素实现文本复制
el.$value = binding.value;
el.$handle = () => {
let text = el.$value,
textarea = null,
copyText = '';
if (!text) {
alert('亲,需要复制的内容不能为空哦~~');
return;
}
// 创建textarea
textarea = document.createElement('textarea');
textarea.value = text;
textarea.readOnly = 'readonly';
// textarea.style.display = 'none'; //=>不能用这个隐藏
textarea.style.cssText = "opacity:0;position:fixed;left:-9999px";
document.body.appendChild(textarea);
// 选中textarea的内容且复制
textarea.select();
copyText = document.execCommand('Copy');
if (copyText) {
alert('亲,恭喜您拷贝成功!');
}
document.body.removeChild(textarea);
};
el.addEventListener('click', el.$handle);
},
componentUpdated(el, binding) {
// 组件更新时,同步更新需要复制的内容「可能内容没有更新」
el.$value = binding.value;
},
unbind(el) {
// 指令与元素解绑时「例如卸载组件」,移除事件绑定
el.removeEventListener('click', el.$handle);
}
});
v-emoji
const trigger = (el, type) => {
const ev = document.createEvent('HTMLEvents');
ev.initEvent(type, true, true);
el.dispatchEvent(ev);
};
Vue.directive('emoji', {
bind(el, binding) {
el.$handle = ev => {
let val = el.value,
reg = /[^0-9a-zA-Z]/g;
el.value = val.replace(reg, '');
// 同步v-modal的数据
trigger(el, 'input');
};
el.$handle();
el.addEventListener('keyup', el.$handle);
},
unbind(el) {
el.removeEventListener('keyup', el.$handle);
}
});
//使用
<input type="text" v-model="inpText" v-emoji />
必用指令:v-debounce「防抖」和 v-throttle「截流」
防抖(300ms内重新触发,都会重新计时)和节流(把频率降低,300ms一次)
const throttle = function throttle(func, wait) {
if (typeof func !== "function") throw new TypeError('func must be a function!');
wait = +wait;
if (isNaN(wait)) wait = 300;
let timer = null,
previous = 0,
result;
return function proxy(...params) {
let now = +new Date,
remaining = wait - (now - previous),
self = this;
if (remaining <= 0) {
if (timer) {
clearTimeout(timer);
timer = null;
}
previous = now;
result = func.apply(self, params);
return result;
}
if (!timer) {
timer = setTimeout(() => {
clearTimeout(timer);
timer = null;
previous = +new Date;
result = func.apply(self, params);
}, remaining);
}
return result;
};
};
const debounce = function debounce(func, wait, immediate) {
if (typeof func !== "function") throw new TypeError('func must be a function!');
if (typeof wait === "undefined") {
wait = 500;
immediate = false;
}
if (typeof wait === "boolean") {
immediate = wait;
wait = 500;
}
if (typeof immediate === "undefined") {
immediate = false;
}
if (typeof wait !== "number") throw new TypeError('wait must be a number!');
if (typeof immediate !== "boolean") throw new TypeError('immediate must be a boolean!');
let timer = null,
result;
return function proxy(...params) {
let self = this,
callNow = !timer && immediate;
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
clearTimeout(timer);
timer = null;
if (!immediate) result = func.apply(self, params);
}, wait);
if (callNow) result = func.apply(self, params);
return result;
};
}
const common_config = {
bind(el, binding) {
let {
func,
wait = 300,
immediate = true,
params = [],
type = 'click'
} = binding.value;
const handle = binding.name === 'debounce' ? debounce : throttle,
proxy = function proxy(...args) {
return func.call(this, ...params.concat(args));
};
el.$type = type;
el.$handle = handle(proxy, wait, immediate);
el.addEventListener(el.$type, el.$handle);
},
unbind(el) {
el.removeEventListener(el.$type, el.$handle);
}
};
Vue.directive('debounce', common_config);
Vue.directive('throttle', common_config);
//使用
<button
class="submit"
v-throttle="{
func: submitHandle,
wait: 1000,
immediate: true,
params: [10, 20],
type: 'click',
}"
>
点我啊!!
</button>
基于 v-permission /[pəˈmɪʃn]/ 指令实现权限的校验及管理
Vue.directive('permission', {
inserted(el, binding) {
// 获取从服务器获取的用户权限列表 Vuex|Storage 「格式:xxx|xxx...」
let permission = localStorage.getItem('permission'),
permissionList = [];
if (!permission) permission = "";
permissionList = permission.split('|');
// 获取用户传递的权限校验标识 「格式:xxx|xxx...」
let passText = binding.value,
passTextArr = [];
if (!passText) passText = "";
passTextArr = passText.split('|');
// 循环校验是否有权限
let flag = false,
i = 0,
len = passTextArr.length;
for (; i < len; i++) {
if (permissionList.includes(passTextArr[i])) {
flag = true;
break;
}
}
// 控制元素显示隐藏
if (!flag) el.parentNode && el.parentNode.removeChild(el);
}
});
滴滴面试题:基于v-LazyLoad实现图片懒加载
<https://developer.mozilla.org/zh-CN/docs/Web/API/IntersectionObserver>
const ob_config = {
threshold: [1]
};
const ob = new IntersectionObserver((entries) => {
entries.forEach(item => {
let {
target,
isIntersecting
} = item;
if (!isIntersecting) return;
let imgBox = target.querySelector('img');
if (!imgBox) return;
imgBox.src = target.$src;
imgBox.style.opacity = 1;
ob.unobserve(target);
});
}, ob_config);
Vue.directive('lazyload', {
inserted(el, binding) {
let imgBox = el.querySelector('img');
if (!imgBox) return;
imgBox.src = '';
imgBox.style.opacity = 0;
imgBox.style.transition = 'opacity .3s';
el.$src = binding.value;
ob.observe(el);
}
});