摘要:于是在不斷的摸索和思考中,想出了工廠模式這個概念,咱們可以這么理解工廠就是取快遞的地方,不管是從哪里發來的貨品,統一送到這里,然后再由特定的人群來取。
寫在前面
如今,在項目中使用React、Vue等框架作為技術棧已成為一種常態,在享受帶來便利性的同時,也許我們漸漸地遺忘原生js的寫法。
現在,是時候回歸本源,響應原始的召喚了。本文將一步一步帶領大家封裝一套屬于自己的DOM操作庫,我將其命名為qnode。
功能特性qnode吸收了jquery優雅的鏈式寫法,并且融入了我個人的一些經驗和思考:
自定義DOM工廠模式(緩存節點、跨文件操作)
快速優雅地創建新的DOM節點
CSS3樣式前綴自動識別和添加
大家可能會比較疑惑,什么是DOM工廠模式?
實際上是這樣,有一些需要共享的節點、數據和方法,它在某個文件中定義,在另一個文件中調用。我最初的方式是在文件中暴露(export)出這些東西,但是后來發現文件多了,這種方式很難維護,而且需要引入很多的文件,非常麻煩。
于是在不斷的摸索和思考中,想出了DOM工廠模式這個概念,咱們可以這么理解:DOM工廠就是取快遞的地方,不管是從哪里發來的貨品,統一送到這里,然后再由特定的人群來取。
當然,未來還有很長的路要走,我會不斷地探索和改進,融入更多的想法和改進。
目錄結構qnode ├── QNode.js ├── README.md ├── api.js ├── core │?? ├── attr.js │?? ├── find.js │?? ├── index.js │?? ├── klass.js │?? ├── listener.js │?? ├── node.js │?? └── style.js ├── index.js ├── q.js └── tools.js
q.js是DOM操作的集合,融合了所有core的方法
QNode.js是工廠模式,提供節點、數據以及方法的緩存和獲取
core目錄下的文件是DOM操作的具體方法
編寫代碼core/attr.js:內容屬性操作
import { isDef } from "../tools" export function text (value) { if (!isDef(value)) { return this.node.textContent } this.node.textContent = value return this } export function html (value) { if (!isDef(value)) { return this.node.innerHTML } this.node.innerHTML = value return this } export function value (val) { if (!isDef(val)) { return this.node.value } this.node.value = val return this } export function attr (name, value) { if (!isDef(value)) { return this.node.getAttribute(name) } if (value === true) { this.node.setAttribute(name, "") } else if (value === false || value === null) { this.node.removeAttribute(name) } else { this.node.setAttribute(name, value) } return this }
core/find.js:節點信息獲取
export function tagName () { return this.node.tagName } export function current () { return this.node } export function parent () { return this.node.parentNode } export function next () { return this.node.nextSibling } export function prev () { return this.node.previousSibling } export function first () { return this.node.firstChild } export function last () { return this.node.lastChild } export function find (selector) { return this.node.querySelector(selector) }
core/klass.js:class樣式操作
import { isArray } from "../tools" export function addClass (cls) { let classList = this.node.classList let classes = isArray(cls) ? cls : [].slice.call(arguments, 0) classList.add.apply(classList, classes.filter(c => c).join(" ").split(" ")) return this } export function removeClass (cls) { let classList = this.node.classList let classes = isArray(cls) ? cls : [].slice.call(arguments, 0) classList.remove.apply(classList, classes.filter(c => c).join(" ").split(" ")) return this } export function hasClass (cls) { let classList = this.node.classList // 若有空格,則必須滿足所有class才返回true if (cls.indexOf(" ") !== -1) { return cls.split(" ").every(c => classList.contains(c)) } return classList.contains(cls) }
core/listener.js:事件監聽處理
export function on (type, fn, useCapture = false) { this.node.addEventListener(type, fn, useCapture) return this } export function off (type, fn) { this.node.removeEventListener(type, fn) return this }
core/node.js:DOM操作
import { createFragment } from "../api" import { getRealNode, isArray } from "../tools" export function append (child) { let realNode = null if (isArray(child)) { let fragment = createFragment() child.forEach(c => { realNode = getRealNode(c) if (realNode) { fragment.appendChild(realNode) } }) this.node.appendChild(fragment) } else { realNode = getRealNode(child) if (realNode) { this.node.appendChild(realNode) } } return this } export function appendTo (parent) { parent = getRealNode(parent) if (parent) { parent.appendChild(this.node) } return this } export function prepend (child, reference) { let realNode = null let realReference = getRealNode(reference) || this.node.firstChild if (isArray(child)) { let fragment = createFragment() child.forEach(c => { realNode = getRealNode(c) if (realNode) { fragment.appendChild(realNode) } }) this.node.insertBefore(fragment, realReference) } else { realNode = getRealNode(child) if (realNode) { this.node.insertBefore(realNode, realReference) } } return this } export function prependTo (parent, reference) { parent = getRealNode(parent) if (parent) { parent.insertBefore(this.node, getRealNode(reference) || parent.firstChild) } return this } export function remove (child) { // 沒有要移除的子節點則移除本身 if (!child) { if (this.node.parentNode) { this.node.parentNode.removeChild(this.node) } } else { let realNode = null if (isArray(child)) { child.forEach(c => { realNode = getRealNode(c) if (realNode) { this.node.removeChild(realNode) } }) } else { realNode = getRealNode(child) if (realNode) { this.node.removeChild(realNode) } } } return this }
core/style.js:內聯樣式操作
import { isDef, isObject } from "../tools" const htmlStyle = document.documentElement.style const prefixes = ["webkit", "moz", "ms", "o"] const prefixLen = prefixes.length function getRealStyleName (name) { if (name in htmlStyle) { return name } // 首字母大寫 let upperName = name[0].toUpperCase() + name.substr(1) // 前綴判斷 for (let i = 0; i < prefixLen; i++) { let realName = prefixes[i] + upperName if (realName in htmlStyle) { return realName } } // 都不支持則返回原值 return name } export function css (name) { if (!this.computedStyle) { this.computedStyle = window.getComputedStyle(this.node) } if (!isDef(name)) { return this.computedStyle } return this.computedStyle[name] } export function style (a, b) { if (isObject(a)) { Object.keys(a).forEach(name => { name = getRealStyleName(name) this.node.style[name] = a[name] }) } else { a = getRealStyleName(a) if (!isDef(b)) { return this.node.style[a] } this.node.style[a] = b } return this } export function show () { if (this.node.style.display === "none") { this.node.style.display = "" } return this } export function hide () { this.node.style.display = "none" return this } export function width () { return this.node.clientWidth } export function height () { return this.node.clientHeight }
core/index.js:所有操作集合
import * as attr from "./attr" import * as find from "./find" import * as klass from "./klass" import * as listener from "./listener" import * as node from "./node" import * as style from "./style" export default Object.assign({}, attr, find, klass, listener, node, style )
api.js:DOM API
// 創建dom節點 export function createElement (tagName) { return document.createElement(tagName) } // 創建dom節點片段 export function createFragment () { return document.createDocumentFragment() }
tools.js:工具方法
import { createElement } from "./api" export const Q_TYPE = (typeof Symbol === "function" && Symbol("q")) || 0x89bc export const QNODE_TYPE = (typeof Symbol === "function" && Symbol("QNode")) || 0x7b96 // 占位node節點 export const emptyNode = createElement("div") export function isQ (ele) { return ele && ele.node && ele.__type__ === Q_TYPE } /** * 判斷值是否定義 * @param {any} t * @returns {boolean} */ export function isDef (t) { return typeof t !== "undefined" } /** * 判斷是否為字符串 * @param {any} t * @returns {boolean} */ export function isString (t) { return typeof t === "string" } /** * 是否為對象 * @param {any} t * @param {boolean} [includeArray=false] 是否包含數組 * @returns {boolean} */ export function isObject (t) { return t && typeof t === "object" } /** * 判斷是否為數組 * @param {any} t * @returns {boolean} */ export function isArray (t) { if (Array.isArray) { return Array.isArray(t) } return Object.prototype.toString.call(t) === "[object Array]" } // 判斷是否為dom元素 export function isElement (node) { if (isObject(HTMLElement)) { return node instanceof HTMLElement } return node && node.nodeType === 1 && isString(node.nodeName) } export function getRealNode (ele) { if (isElement(ele)) { return ele } else if (isQ(ele)) { return ele.node } return null }
q.js
import { createElement } from "./api" import { Q_TYPE, isElement, isString, emptyNode } from "./tools" import core from "./core" class Q { constructor (selector) { let node if (isElement(selector)) { node = selector } else if (isString(selector)) { if (selector[0] === "$") { node = createElement(selector.substring(1)) } else { node = document.querySelector(selector) } } // node不存在,則創建一個占位node,避免操作dom報錯 this.node = node || emptyNode this.__type__ = Q_TYPE } } // 集合 Object.assign(Q.prototype, core) export default function q (selector) { return new Q(selector) }
QNode.js
import { QNODE_TYPE, isQ, isArray } from "./tools" import q from "./q" export default class QNode { constructor () { this.__type__ = QNODE_TYPE this.qNodes = {} this.store = {} this.methods = {} } q (selector) { return q(selector) } getNode (name) { return this.qNodes[name] } setNode (name, node) { if (isArray(node)) { this.qNodes[name] = node.map(n => isQ(n) ? n : q(n)) } else { this.qNodes[name] = isQ(node) ? node : q(node) } return this.qNodes[name] } getStore (name) { return this.store[name] } setStore (name, value) { this.store[name] = value return value } getMethod (name) { return this.methods[name] } execMethod (name) { let fn = this.methods[name] return fn && fn.apply(this, [].slice.call(arguments, 1)) } setMethod (name, fn) { let thisFn = fn.bind(this) this.methods[name] = thisFn return thisFn } }
index.js
import q from "./q" import QNode from "./QNode" export { q, QNode } export default { q, QNode }
到這里為止,所有代碼已經編寫完成了。
API Reference q(獲取|創建節點)參數:
#id 根據id獲取節點
.class 根據class獲取節點
tagName 根據標簽獲取節點
$tagName 創建新的節點
備注:如果有多個節點,則只獲取第一個
方法:
attr
text: str 【設置文本內容,若無參數則獲取文本內容】
html: str 【設置html,若無參數則獲取html】
value: val 【設置表單值,若無參數則獲取表單值】
attr: name, value 【設置name屬性的值,若value無參數則獲取name的值】
find
tagName 【獲取節點名稱】
current 【獲取節點本身】
parent 【獲取父節點】
next 【獲取后一個節點】
prev 【獲取上一個節點】
first 【獲取第一個子節點】
last 【獲取最后一個子節點】
find: #id | .class | tagName 【找子節點】
class
addClass: str | arr | a, b, ... 【添加樣式class】
removeClass: str | arr | a, b, ... 【移除樣式class】
hasClass: str 【是否含有樣式class】
listener
on: type, fn, useCapture=false 【添加事件監聽】
off: type, fn 【移除事件監聽】
node
append: node | nodeList 【填充子節點到最后】
appendTo: parent 【填充到父節點中最后】
prepend: node | nodeList, reference 【填充子節點到最前或指定節點前】
prependTo: parent, reference【填充到父節點中最前或指定節點前】
remove: child 【移除子節點,若無參數則移除自身】
style
css: name 【獲取css文件中定義的樣式】
style: (name, value) | object 【1.設置或獲取內聯樣式;2.設置一組樣式】
show 【顯示節點】
hide 【隱藏節點】
width 【獲取節點寬度】
height 【獲取節點高度】
QNode(節點倉庫,包括數據和方法)方法:
q: 同上述q
getNode: name 【獲取節點】
setNode: name, node 【設置節點,返回節點】
getStore: name 【獲取數據】
setStore: name, value 【設置數據,返回數據】
getMethod: name 【獲取方法】
setMethod: name, fn 【設置方法,返回方法,this綁定到qnode】
execMethod: name 【執行方法,name后面可以傳入方法需要的參數,this為qnode】
結語本文到這里就要結束了,讀者對文中的代碼感興趣的話,建議自己動手試試,在編程這塊兒,實踐才能出真知。
寫完之后,是不是躍躍欲試呢?下一篇文章我將基于本文封裝的DOM庫來開發無限循環輪播圖,詳細請看下文:原生js系列之無限循環輪播圖。
附:本文源碼
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/112874.html
摘要:于是在不斷的摸索和思考中,想出了工廠模式這個概念,咱們可以這么理解工廠就是取快遞的地方,不管是從哪里發來的貨品,統一送到這里,然后再由特定的人群來取。 寫在前面 如今,在項目中使用React、Vue等框架作為技術棧已成為一種常態,在享受帶來便利性的同時,也許我們漸漸地遺忘原生js的寫法。 現在,是時候回歸本源,響應原始的召喚了。本文將一步一步帶領大家封裝一套屬于自己的DOM操作庫,我將...
摘要:模塊化是隨著前端技術的發展,前端代碼爆炸式增長后,工程化所采取的必然措施。目前模塊化的思想分為和。特別指出,事件不等同于異步,回調也不等同于異步。將會討論安全的類型檢測惰性載入函數凍結對象定時器等話題。 Vue.js 前后端同構方案之準備篇——代碼優化 目前 Vue.js 的火爆不亞于當初的 React,本人對寫代碼有潔癖,代碼也是藝術。此篇是準備篇,工欲善其事,必先利其器。我們先在代...
摘要:大潮來襲前端開發能做些什么去年谷歌和火狐針對提出了的標準,顧名思義,即的體驗方式,我們可以戴著頭顯享受沉浸式的網頁,新的標準讓我們可以使用語言來開發。 VR 大潮來襲 --- 前端開發能做些什么 去年谷歌和火狐針對 WebVR 提出了 WebVR API 的標準,顧名思義,WebVR 即 web + VR 的體驗方式,我們可以戴著頭顯享受沉浸式的網頁,新的 API 標準讓我們可以使用 ...
摘要:沒有看過上一篇文章的話,可以在這里找到原生系列之工廠模式。那么這篇文章,我們將基于上述的,從頭開始寫一個無限循環輪播圖的組件。附無限循環輪播圖示例本文源碼 前情回顧 在上一篇文章中,我們封裝了一個DOM庫(qnode),為了讓大家直觀地感受到其方便友好的自定義工廠模式,于是給大家帶來了這篇文章。 沒有看過上一篇文章的話,可以在這里找到:原生js系列之DOM工廠模式。 那么這篇文章,我們...
閱讀 1630·2023-04-25 18:19
閱讀 2078·2021-10-26 09:48
閱讀 1079·2021-10-09 09:44
閱讀 1731·2021-09-09 11:35
閱讀 3027·2019-08-30 15:54
閱讀 2021·2019-08-30 11:26
閱讀 2285·2019-08-29 17:06
閱讀 884·2019-08-29 16:38