原生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.mzph.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. 定位器 对象或特征的精确定位是一个检测系统或由视觉引导的运动系统的重要功能。传统的物体定位采用的是灰度值校正来识别物体…