vue 中directive功能的簡單實(shí)現(xiàn)
2018年首個計劃是學(xué)習(xí)vue源碼,查閱了一番資料之后,決定從第一個commit開始看起,這將是一場持久戰(zhàn)!本篇介紹directive的簡單實(shí)現(xiàn),主要學(xué)習(xí)其實(shí)現(xiàn)的思路及代碼的設(shè)計(directive和filter擴(kuò)展起來非常方便,符合設(shè)計模式中的 開閉原則 )。
構(gòu)思API
<div id="app" sd-on-click="toggle | .button">
<p sd-text="msg | capitalize"></p>
<p sd-class-red="error" sd-text="content"></p>
<button class="button">Toggle class</button>
</div>
var app = Seed.create({
id: 'app',
scope: {
msg: 'hello',
content: 'world',
error: true,
toggle: function() {
app.scope.error = !app.scope.error;
}
}
});
實(shí)現(xiàn)功能夠簡單吧--將scope中的數(shù)據(jù)綁定到app中。
核心邏輯設(shè)計
指令格式
以 sd-text="msg | capitalize" 為例說明:
- sd 為統(tǒng)一的前綴標(biāo)識
- text 為指令名稱
- capitalize 為過濾器名稱
其中 | 后面為過濾器,可以添加多個。 sd-class-red 中的red為參數(shù)。
代碼結(jié)構(gòu)介紹
main.js 入口文件
// Seed構(gòu)造函數(shù)
const Seed = function(opts) {
};
// 對外暴露的API
module.exports = {
create: function(opts) {
return new Seed(opts);
}
};
directives.js
module.exports = {
text: function(el, value) {
el.textContent = value || '';
}
};
filters.js
module.exports = {
capitalize: function(value) {
value = value.toString();
return value.charAt(0).toUpperCase() + value.slice(1);
}
};
就這三個文件,其中directives和filters都是配置文件,很易于擴(kuò)展。
實(shí)現(xiàn)的大致思路如下:
1.在Seed實(shí)例創(chuàng)建的時候會依次解析el容器中node節(jié)點(diǎn)的指令
2.將指令解析結(jié)果封裝為指令對象,結(jié)構(gòu)為:
| 屬性 | 說明 | 類型 |
|---|---|---|
| attr | 原始屬性,如 sd-text |
String |
| key | 對應(yīng)scope對象中的屬性名稱 | String |
| filters | 過濾器名稱列表 | Array |
| definition | 該指令的定義,如text對應(yīng)的函數(shù) | Function |
| argument | 從attr中解析出來的參數(shù)(只支持一個參數(shù)) | String |
| update | 更新directive時調(diào)用 typeof def === 'function' ? def : def.update |
Function |
| bind | 如果directive中定義了bind方法,則在 bindDirective 中會調(diào)用 |
Function |
| el | 存儲當(dāng)前element元素 | Element |
3.想辦法執(zhí)行指令的update方法即可,該插件使用了 Object.defineProperty 來定義scope中的每個屬性,在其setter中觸發(fā)指令的update方法。
核心代碼
const prefix = 'sd';
const Directives = require('./directives');
const Filters = require('./filters');
// 結(jié)果為[sd-text], [sd-class], [sd-on]的字符串
const selector = Object.keys(Directives).map((name) => `[${prefix}-${name}]`).join(',');
const Seed = function(opts) {
const self = this,
root = this.el = document.getElementById(opts.id),
// 篩選出el下所能支持的directive的nodes列表
els = this.el.querySelectorAll(selector),
bindings = {};
this.scope = {};
// 解析節(jié)點(diǎn)
[].forEach.call(els, processNode);
// 解析根節(jié)點(diǎn)
processNode(root);
// 給scope賦值,觸發(fā)setter方法,此時會調(diào)用與其相對應(yīng)的directive的update方法
Object.keys(bindings).forEach((key) => {
this.scope[key] = opts.scope[key];
});
function processNode(el) {
cloneAttributes(el.attributes).forEach((attr) => {
const directive = parseDirective(attr);
if (directive) {
bindDirective(self, el, bindings, directive);
}
});
}
};
可以看到核心方法 processNode 主要做了兩件事一個是 parseDirective ,另一個是 bindDirective 。
先來看看 parseDirective 方法:
function parseDirective(attr) {
if (attr.name.indexOf(prefix) == -1) return;
// 解析屬性名稱獲取directive
const noprefix = attr.name.slice(prefix.length + 1),
argIndex = noprefix.indexOf('-'),
dirname = argIndex === -1 ? noprefix : noprefix.slice(0, argIndex),
arg = argIndex === -1 ? null : noprefix.slice(argIndex + 1),
def = Directives[dirname]
// 解析屬性值獲取filters
const exp = attr.value,
pipeIndex = exp.indexOf('|'),
key = (pipeIndex === -1 ? exp : exp.slice(0, pipeIndex)).trim(),
filters = pipeIndex === -1 ? null : exp.slice(pipeIndex + 1).split('|').map((filterName) => filterName.trim());
return def ? {
attr: attr,
key: key,
filters: filters,
argument: arg,
definition: Directives[dirname],
update: typeof def === 'function' ? def : def.update
} : null;
}
以 sd-on-click="toggle | .button" 為例來說明,其中attr對象的name為 sd-on-click ,value為 toggle | .button ,最終解析結(jié)果為:
{
"attr": attr,
"key": "toggle",
"filters": [".button"],
"argument": "click",
"definition": {"on": {}},
"update": function(){}
}
緊接著調(diào)用 bindDirective 方法
/**
* 數(shù)據(jù)綁定
* @param {Seed} seed Seed實(shí)例對象
* @param {Element} el 當(dāng)前node節(jié)點(diǎn)
* @param {Object} bindings 數(shù)據(jù)綁定存儲對象
* @param {Object} directive 指令解析結(jié)果
*/
function bindDirective(seed, el, bindings, directive) {
// 移除指令屬性
el.removeAttribute(directive.attr.name);
// 數(shù)據(jù)屬性
const key = directive.key;
let binding = bindings[key];
if (!binding) {
bindings[key] = binding = {
value: undefined,
directives: [] // 與該數(shù)據(jù)相關(guān)的指令
};
}
directive.el = el;
binding.directives.push(directive);
if (!seed.scope.hasOwnProperty(key)) {
bindAccessors(seed, key, binding);
}
}
/**
* 重寫scope西鄉(xiāng)屬性的getter和setter
* @param {Seed} seed Seed實(shí)例
* @param {String} key 對象屬性即opts.scope中的屬性
* @param {Object} binding 數(shù)據(jù)綁定關(guān)系對象
*/
function bindAccessors(seed, key, binding) {
Object.defineProperty(seed.scope, key, {
get: function() {
return binding.value;
},
set: function(value) {
binding.value = value;
// 觸發(fā)directive
binding.directives.forEach((directive) => {
// 如果有過濾器則先執(zhí)行過濾器
if (typeof value !== 'undefined' && directive.filters) {
value = applyFilters(value, directive);
}
// 調(diào)用update方法
directive.update(directive.el, value, directive.argument, directive);
});
}
});
}
/**
* 調(diào)用filters依次處理value
* @param {任意類型} value 數(shù)據(jù)值
* @param {Object} directive 解析出來的指令對象
*/
function applyFilters(value, directive) {
if (directive.definition.customFilter) {
return directive.definition.customFilter(value, directive.filters);
} else {
directive.filters.forEach((name) => {
if (Filters[name]) {
value = Filters[name](value);
}
});
return value;
}
}
其中的bindings存放了數(shù)據(jù)和指令的關(guān)系,該對象中的key為opts.scope中的屬性,value為Object,如下:
{
"msg": {
"value": undefined,
"directives": [] // 上面介紹的directive對象
}
}
數(shù)據(jù)與directive建立好關(guān)系之后, bindAccessors 中為seed的scope對象的屬性重新定義了getter和setter,其中setter會調(diào)用指令update方法,到此就已經(jīng)完事具備了。
Seed構(gòu)造函數(shù)在實(shí)例化的最后會迭代bindings中的key,然后從opts.scope找到對應(yīng)的value, 賦值給了scope對象,此時setter中的update就觸發(fā)執(zhí)行了。
下面再看一下 sd-on 指令的定義:
{
on: {
update: function(el, handler, event, directive) {
if (!directive.handlers) {
directive.handlers = {};
}
const handlers = directive.handlers;
if (handlers[event]) {
el.removeEventListener(event, handlers[event]);
}
if (handler) {
handler = handler.bind(el);
el.addEventListener(event, handler);
handlers[event] = handler;
}
},
customFilter: function(handler, selectors) {
return function(e) {
const match = selectors.every((selector) => e.target.matches(selector));
if (match) {
handler.apply(this, arguments);
}
}
}
}
}
發(fā)現(xiàn)它有customFilter,其實(shí)在 applyFilters 中就是針對該指令做的一個單獨(dú)的判斷,其中的selectors就是[".button"],最終返回一個匿名函數(shù)(事件監(jiān)聽函數(shù)),該匿名函數(shù)當(dāng)做value傳遞給update方法,被其handler接收,update方法處理的是事件的綁定。這里其實(shí)實(shí)現(xiàn)的是事件的代理功能,customFilter中將handler包裝一層作為事件的監(jiān)聽函數(shù),同時還實(shí)現(xiàn)事件代理功能,設(shè)計的比較巧妙!
總結(jié)
以上所述是小編給大家介紹的vue 中directive的簡單實(shí)現(xiàn),希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復(fù)大家的。在此也非常感謝大家對腳本之家網(wǎng)站的支持!
相關(guān)文章
vue使用腳手架vue-cli創(chuàng)建并引入自定義組件
這篇文章介紹了vue使用腳手架vue-cli創(chuàng)建并引入自定義組件的方法,文中通過示例代碼介紹的非常詳細(xì)。對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-03-03
Vue利用自定義指令實(shí)現(xiàn)按鈕權(quán)限控制
這篇文章主要為大家詳細(xì)介紹了Vue如何利用自定義指令實(shí)現(xiàn)按鈕權(quán)限控制效果,文中的示例代碼講解詳細(xì),具有一定的學(xué)習(xí)價值,需要的可以參考下2023-05-05
詳解使用vue-admin-template的優(yōu)化歷程
這篇文章主要介紹了詳解使用vue-admin-template的優(yōu)化歷程,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-05-05
vue里input根據(jù)value改變背景色的實(shí)例
今天小編就為大家分享一篇vue里input根據(jù)value改變背景色的實(shí)例,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-09-09
vue如何實(shí)現(xiàn)清空this.$route.query的值
這篇文章主要介紹了vue如何實(shí)現(xiàn)清空this.$route.query的值,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-09-09
使用Vue封裝一個可隨時暫停啟動無需擔(dān)心副作用的定時器
這篇文章主要為大家詳細(xì)介紹了如何使用Vue封裝一個可隨時暫停啟動無需擔(dān)心副作用的定時器,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2024-11-11

