原生js系列之DOM工廠模式

寫在前面

如今,在項目中使用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 = valuereturn this
}export function html (value) {if (!isDef(value)) {return this.node.innerHTML}this.node.innerHTML = valuereturn this
}export function value (val) {if (!isDef(val)) {return this.node.value}this.node.value = valreturn 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.classListlet 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.classListlet 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才返回trueif (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 = nullif (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 = nulllet realReference = getRealNode(reference) || this.node.firstChildif (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 = nullif (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.lengthfunction 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] + upperNameif (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 nodeif (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 || emptyNodethis.__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_TYPEthis.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] = valuereturn 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] = thisFnreturn 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://www.pswp.cn/news/453410.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/453410.shtml
英文地址,請注明出處:http://en.pswp.cn/news/453410.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

武術與軟件設計 - 簡單即是最好

偶然間在公車上看見一個講中國功夫的特輯&#xff0c;說道香港武打片的發展歷程&#xff0c;當然就不得不提起李小龍先生&#xff0c;我們知道他截拳道的威力&#xff0c;這時候我記得在看李小龍傳奇時他所說的一些話&#xff0c;當他和美國一個高手比武后他輸了&#xff0c;最…

matlab的概述,Matlab概述

MATLAB(矩陣實驗室)是數字計算&#xff0c;可視化和編程的第四代高級編程語言和交互式環境。MATLAB是由MathWorks開發的。它允許矩陣操縱&#xff0c;繪制功能和數據; 實現算法; 創建用戶界面; 與其他語言編寫的程序(包括C語言&#xff0c;C&#xff0c;Java和FORTRAN)進行交互…

形參和實參

形參&#xff1a;全稱為“形式參數”是在定義函數名和函數體的時候使用的參數&#xff0c;目的是用來接收調用該函數時傳遞的參數。形參的作用是實現主調函數與被調函數之間的聯系&#xff0c;通常將函數所處理的數據&#xff0c;影響函數功能的因素或者函數處理的結果作為形參…

sizeof和strlen的區別

strlen——get the length of a string.size_t strlen(const char *string);Each ofthese functions returns the number of characters instring, notincluding the terminating null character.//函數返回string里的字符數&#xff0c;不包括終止字符\0sizeofThe sizeof keyw…

位置參數及操作符號

特殊字符對應的處理參數&#xff1a; 參數說明$0當前執行的腳本文件名&#xff0c;若全路徑執行&#xff0c;則顯示腳本路徑$n當前執行腳本的第n個參數值&#xff0c;若n>9&#xff0c;則需寫成${10}$#當前傳參總個數$$腳本運行的當前進程ID號,用例&#xff1a;當一個進程重…

python變量命名可以有特殊符號嗎,和孩子一起學習python之變量命名規則

下面是關于變量名(也稱為標識符)的一些規則必須以一個字母或一個下劃線字符開頭。后面可以使用一個字母、數字或下劃線字符的序列&#xff0c;長度不限。字母可以是大寫或小寫&#xff0c;大小寫是不同的。也就是說&#xff0c;Ax不同于aX。數字可以是從0到9(包括0到9)的任意數…

C語言中*和

(一) 在定義時&#xff0c;* 是一個標識符&#xff0c;聲明該變量是一個指針&#xff0c;比如說int *p; 那p就是一個指向int型的指針&#xff1b; 在調用時&#xff0c; &#xff08;1&#xff09;*p是指指針p指向的那個變量&#xff0c;比如說之前有int a5&#xff1b;int …

IT人的好習慣和不良習慣總結

好習慣&#xff1a; 細節一&#xff1a;在電腦旁放上幾盆植物&#xff0c;傳說仙人掌可以有效地吸收輻射&#xff0c;但是會扎到人&#xff0c;而且有沒效果也沒科學根據&#xff0c;不推薦&#xff1b;其實只要是綠色植物就可以&#xff0c;植物可以讓你多點氧氣&#xff0c;保…

【BZOJ 3326】[Scoi2013]數數 數位dp+矩陣乘法優化

挺好的數位dp……先說一下我個人的做法:經過觀察,發現這題按照以往的思路從后往前遞增,不怎么好推,然后我就大膽猜想,從前往后推,發現很好推啊,維護四個變量,從開始位置到現在有了i個數 f[i]:所有數的所有未包含最后一位的子串的和 s[i]:所有數的所有后綴子串的和 c[i]:所有數的…

zookeeper偽集群(在一臺機器上集群)

2019獨角獸企業重金招聘Python工程師標準>>> 創建一下的目錄結構zookeeper-3.4.10是你下載的zookeeper的解壓包 /zookeeper_cluster----/server_one|---/data|myid(文件)|---/datalog|---/zookeeper-3.4.10|---/bin|---/conf|---zoo.cfg|---..... |---/....----/ser…

mongo的php查詢,使用PHP進行簡單查詢的mongo查詢速度慢

我有一個非常簡單的使用PHP執行的Mongo Query。我相信查詢執行得非常快&#xff0c;因為當我在終端上運行它時&#xff0c;它幾乎可以立即完成&#xff0c;并且當我解釋()時&#xff0c;它表明它正在1-2ms內執行。但是&#xff0c;當我去迭代游標并將內容放入數組時&#xff0c…

順序存儲結構和鏈式存儲結構的優缺點

&#xff08;一&#xff09;順序存儲結構和鏈式存儲結構的優缺點比較&#xff0c;以及使用情況。 1 優缺點 ① 順序存儲時&#xff0c;相鄰數據元素的存放地址也相鄰&#xff08;邏輯與物理統一&#xff09;&#xff1b;要求內存中可用存儲單元的地址必須是連續的。 優點&…

大話軟件開發與開車的共同點

昨天路上開車&#xff0c;突然有了這個想法&#xff0c;做軟件開發與開車&#xff0c;竟然有這么多的相似之處&#xff0c;大致整理了一下思路&#xff0c;和大家分享一下。 一、目的 開車的目的有3個&#xff0c;第一是為了讓自己到底目的地(上班族)&#xff0c;第二是為了兜…

Spring核心接口之Ordered

一、Ordered接口介紹Spring中提供了一個Ordered接口。從單詞意思就知道Ordered接口的作用就是用來排序的。Spring框架是一個大量使用策略設計模式的框架&#xff0c;這意味著有很多相同接口的實現類&#xff0c;那么必定會有優先級的問題。于是Spring就提供了Ordered這個接口&a…

將本地代碼上傳至github

注冊github賬號 https://github.com/ 安裝git工具 https://git-for-windows.github.io 1.在github中創建一個項目 2.填寫相應信息&#xff0c;點擊create Repository name: 倉庫名稱 Description(可選): 倉庫描述介紹 Public, Private : 倉庫權限&#xff08;公開共享&#xff…

禪道 php api,云禪道有API的方式可以獲取數據嗎

api相關手冊&#xff1a;api接口查看&#xff0c;可以本地搭建和云禪道相同版本的禪道&#xff0c;然后admin 后臺 二次開發 api&#xff0c;可以查看接口列表。api調用步驟PATH_INFO方式1、訪問 http://x.com/api-getsessionid.json獲取禪道session信息2、使用上一步獲取的ses…

鏈表的頭結點和尾節點的用處

某些情況下設置尾指針的好處 尾指針是指向終端結點的指針&#xff0c;用它來表示單循環鏈表可以使得查找鏈表的開始結點和終端結點都很方便&#xff0c;設一帶頭結點的單循環鏈表&#xff0c;其尾指針為rear&#xff0c;則開始結點和終端結點的位置分別是rear->next->ne…

經驗從哪里來?從痛苦中來!

1 剛才發博客&#xff0c;寫的幾百字丟失&#xff0c;讓我知道下次一定要在記事本里寫好&#xff0c;再復制過來&#xff0c;避免丟失了 2 程序忘記備份&#xff0c;辛苦一個多月的東西沒有了&#xff0c;只能找到1月前的版本&#xff0c;讓我知道了&#xff0c;重要的東西必須…

oracle 加全文索引,Oracle創建全文索引

1、創建表空間&#xff0c;有必要將物理文件設置大一些2、創建基于這個表空間的用戶3、創建需要建立全文索引的表4、用管理員帳戶為使用這用戶開發ctx_ddl權限grant execute on ctx_ddl to useer;5、創建適合的lexer(解析器)exec ctx_ddl.create_references(my_lexer,basic_le…

機器視覺系統需要考慮的十個問題

為了使用戶在選擇一款機器視覺系統時應該考慮的關鍵的、基本的特性方面提供指導。下面是選擇一款機器視覺系統時要優先考慮的十個方面&#xff1a; 1. 定位器 對象或特征的精確定位是一個檢測系統或由視覺引導的運動系統的重要功能。傳統的物體定位采用的是灰度值校正來識別物體…