博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
原生js系列之DOM工厂模式
阅读量:6717 次
发布时间:2019-06-25

本文共 10859 字,大约阅读时间需要 36 分钟。

写在前面

如今,在项目中使用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.styleconst 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] + 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')) || 0x89bcexport 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库来开发无限循环轮播图,详细请看下文:。

附:

转载地址:http://gnumo.baihongyu.com/

你可能感兴趣的文章
《途客圈创业记:不疯魔,不成活》一一1.3 iWeekend创业周末
查看>>
《精通SNMP》——2.4 标签类型和子类型
查看>>
《云数据中心构建实战:核心技术、运维管理、安全与高可用》——导读
查看>>
《Python自动化运维:技术与最佳实践》一2.4 探测Web服务质量方法
查看>>
《Android UI基础教程》——2.4节显示列表
查看>>
《Scala机器学习》一一1.5 使用Scala和Spark的Notebook工作
查看>>
Fast-FrameWork v0.1.1,JDK 8 MVC 框架
查看>>
《IP组播(第1卷)》一导读
查看>>
《高效能程序员的修炼》一学会读源代码
查看>>
3大军团、266个项目,菜鸟技术如何玩转双11项目管理?
查看>>
魅族隔空回应雷军:开放 Flyme 对抗 MIUI
查看>>
成为阿里云大使的笔记
查看>>
《深入解析IPv6(第3版)》——2.10 参考文献
查看>>
《Adobe Illustrator CC经典教程》—第0课0.16节使用文字
查看>>
企业安全:从触觉时代到视觉时代
查看>>
Oracle Dataguard在阿里云ecs上的测试
查看>>
《Python数据科学实践指南》——0.3 为什么是Python
查看>>
《混合云计算》——2.4 检查云集成的需求
查看>>
《Axure RP8产品原型设计快速上手指南》一1.7 大纲面板
查看>>
《机器学习与R语言(原书第2版)》一第3章 懒惰学习——使用近邻分类
查看>>