滲透測試-28:清理痕跡
一、 什麼是自定義指令
我們看到的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:所在元件的 VNode 更新時呼叫,但是可能發生在其子 VNode更新之前。指令的值可能發生了改變,也可能沒有。但是你可以通過比較更新前後的值來忽略不必要的模板更新
componentUpdated:指令所在元件的 VNode 及其子 VNode 全部更新後呼叫
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>
三、應用場景
使用自定義元件元件可以滿足我們日常一些場景,這裡給出幾個自定義元件的案例:
防抖
圖片懶載入
一鍵 Copy的功能
輸入框防抖
防抖這種情況設定一個v-throttle自定義指令來實現
// 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自定義指令的應用場景
使用自定義指令背景
程式碼複用和抽象的主要形式是元件。
當需要對普通 DOM 元素進行底層操作,此時就會用到自定義指令
但是,對於大幅度的 DOM 變動,還是應該使用元件
常用案例
1、 輸入框自動聚焦
// 註冊一個全域性自定義指令 `v-focus`
Vue.directive('focus', {
// 當被繫結的元素插入到 DOM 中時
inserted: function (el) {
// 聚焦元素
el.focus()
}
})
//<input v-focus>
2、下拉選單
點選下拉選單本身不會隱藏選單
點選下拉選單以外的區域隱藏選單
<script>
Vue.directive('clickoutside', {
bind(el, binding) {
function documentHandler(e) {
if (el.contains(e.target)) {
return false
}
if (binding.expression) {
binding.value(e)
}
}
el.__vueMenuHandler__ = documentHandler
document.addEventListener('click', el.__vueMenuHandler__)
},
unbind(el) {
document.removeEventListener('click', el.__vueMenuHandler__)
delete el.__vueMenuHandler__
}
})
new Vue({
el: '#app',
data: {
show: false
},
methods: {
handleHide() {
this.show = false
}
}
})
</script>
<div class="main" v-menu="handleHide">
<button @click="show = !show">點選顯示下拉選單</button>
<div class="dropdown" v-show="show">
<div class="item"><a href="#">選項 1</a></div>
<div class="item"><a href="#">選項 2</a></div>
<div class="item"><a href="#">選項 3</a></div>
</div>
</div>
3、相對時間轉換
類似微博、朋友圈釋出動態後的相對時間,比如剛剛、兩分鐘前等等
<span v-relativeTime="time"></span>
<script>
new Vue({
el: '#app',
data: {
time: 1565753400000
}
})
Vue.directive('relativeTime', {
bind(el, binding) {
// Time.getFormatTime() 方法,自行補充
el.innerHTML = Time.getFormatTime(binding.value)
el.__timeout__ = setInterval(() => {
el.innerHTML = Time.getFormatTime(binding.value)
}, 6000)
},
unbind(el) {
clearInterval(el.innerHTML)
delete el.__timeout__
}
})
</script>
理論:
vue中的自定義指令:
vue中除了核心功能內建的指令外,也允許註冊自定義指令。自定義指令又分為全域性的自定義指令和區域性自定義指令。
全域性自定義指令是通過Vue.directive(‘第一個引數是指令的名稱’,{第二個引數是一個物件,這個物件上有鉤子函式})
Vue.directive('focus', {
// el:指令所繫結的元素,可以用來直接操作 DOM。
//binding:一個物件,包含以下 property:
inserted: function (el) { vNode引數
el.focus();
}
});
區域性自定義指令:
是定義在元件內部的,只能在當前元件中使用
directives: {
// 指令名稱
dir1: {
inserted(el) { / inserted 表示被繫結元素插入父節點時呼叫
// 指令中第一個引數是當前使用指令的DOM
console.log(el);
console.log(arguments);
// 對DOM進行操作
el.style.width = '200px';
el.style.height = '200px';
el.style.background = '#000';
}
},
color: { // 為元素設定指定的字型顏色
bind(el, binding) {
el.style.color = binding.value;
}
}
}
鉤子函式:
一個指令定義物件可以提供如下幾個鉤子函式 (均為可選):
inserted:被繫結元素插入父節點時呼叫 (僅保證父節點存在,但不一定已被插入文件中)。
bind:只調用一次,指令第一次繫結到元素時呼叫。
update:所在元件的 VNode 更新時呼叫,但是可能發生在其子 VNode 更新之前
componentUpdated:指令所在元件的 VNode 及其子 VNode 全部更新後呼叫。
unbind:只調用一次,指令與元素解綁時呼叫。
————————————————
原文連結:https://blog.csdn.net/weixin_58032613/article/details/122759818