JS如何实现自定义渲染器?渲染的抽象原理是什么

来源:Nodejs社区作者:美园和花头衔:网络博主
导读:本期聚焦于小伙伴创作的《JS如何实现自定义渲染器?渲染的抽象原理是什么》,敬请观看详情,探索知识的价值。以下视频、文章将为您系统阐述其核心内容与价值。如果您觉得《JS如何实现自定义渲染器?渲染的抽象原理是什么》有用,将其分享出去将是对创作者最好的鼓励。

自定义渲染器是前端框架实现跨平台能力的核心,它的本质是将渲染逻辑从具体平台中抽离出来,通过统一的抽象层对接不同的渲染目标。JS实现自定义渲染器的核心在于做好渲染的抽象,让同一套逻辑可以适配浏览器DOM、小程序、Native等不同环境。

JS如何实现自定义渲染器?渲染的抽象原理是什么

什么是渲染的抽象

渲染的抽象指的是把渲染过程中与平台强相关的操作封装成统一的接口,上层渲染逻辑只调用这些接口,不直接操作平台特有的API。比如浏览器中创建元素是document.createElement,而小程序中可能是wx.createView,抽象层会把这两个操作统一成createElement接口,上层逻辑只需要调用createElement即可,不需要关心底层是哪个平台。

渲染抽象通常包含三个核心部分:

  • 节点操作抽象:封装创建、插入、删除、更新节点的方法
  • 属性操作抽象:封装设置、更新、移除节点属性的方法
  • 事件操作抽象:封装事件绑定、解绑的方法

虚拟节点设计

虚拟节点(VNode)是渲染抽象的数据载体,它是对真实节点的JS对象描述,不依赖任何平台。一个基础的虚拟节点结构如下:

// 虚拟节点构造函数
function createVNode(type, props, children) {
  return {
    type, // 节点类型,比如浏览器中是div、span,自定义平台可以是view、text
    props, // 节点属性,包含普通属性和事件
    children, // 子节点,可以是字符串或者虚拟节点数组
    el // 对应的真实节点引用,渲染后赋值
  }
}

实现渲染抽象层

首先我们需要定义渲染抽象层的接口,不同平台只需要实现这些接口即可对接渲染器。这里我们以浏览器和自定义控制台渲染为例,先定义抽象接口:

// 渲染抽象层接口定义
const rendererOptions = {
  // 创建元素
  createElement(type) {
    throw new Error('createElement需要实现')
  },
  // 插入节点
  insert(child, parent, anchor) {
    throw new Error('insert需要实现')
  },
  // 设置属性
  patchProp(el, key, prevValue, nextValue) {
    throw new Error('patchProp需要实现')
  },
  // 删除节点
  remove(el) {
    throw new Error('remove需要实现')
  },
  // 创建文本节点
  createText(text) {
    throw new Error('createText需要实现')
  },
  // 设置文本节点内容
  setText(el, text) {
    throw new Error('setText需要实现')
  }
}

浏览器平台的抽象实现

针对浏览器DOM环境,实现上述抽象接口:

// 浏览器环境的渲染抽象实现
const browserOptions = {
  createElement(type) {
    return document.createElement(type)
  },
  insert(child, parent, anchor) {
    // anchor为null时插入到末尾
    parent.insertBefore(child, anchor || null)
  },
  patchProp(el, key, prevValue, nextValue) {
    if (key.startsWith('on')) {
      // 事件处理
      const eventName = key.slice(2).toLowerCase()
      if (prevValue) {
        el.removeEventListener(eventName, prevValue)
      }
      if (nextValue) {
        el.addEventListener(eventName, nextValue)
      }
    } else {
      // 普通属性处理
      if (nextValue == null) {
        el.removeAttribute(key)
      } else {
        el.setAttribute(key, nextValue)
      }
    }
  },
  remove(el) {
    const parent = el.parentNode
    if (parent) {
      parent.removeChild(el)
    }
  },
  createText(text) {
    return document.createTextNode(text)
  },
  setText(el, text) {
    el.nodeValue = text
  }
}

实现核心渲染器逻辑

有了抽象层之后,我们就可以实现不依赖具体平台的渲染器核心逻辑,包括挂载和更新两个主要流程。

渲染器挂载逻辑

挂载指的是将虚拟节点转换成真实节点并插入到容器中:

// 渲染器核心函数
function createRenderer(options) {
  const {
    createElement,
    insert,
    patchProp,
    remove,
    createText,
    setText
  } = options

  // 处理子节点
  function mountChildren(children, container, anchor) {
    if (typeof children === 'string') {
      // 文本子节点
      const textNode = createText(children)
      insert(textNode, container, anchor)
    } else if (Array.isArray(children)) {
      // 数组子节点,递归挂载
      children.forEach(child => {
        patch(null, child, container, anchor)
      })
    }
  }

  // 挂载元素节点
  function mountElement(vnode, container, anchor) {
    // 创建真实元素
    const el = createElement(vnode.type)
    vnode.el = el
    // 处理属性
    if (vnode.props) {
      for (const key in vnode.props) {
        patchProp(el, key, null, vnode.props[key])
      }
    }
    // 处理子节点
    if (vnode.children) {
      mountChildren(vnode.children, el, null)
    }
    // 插入到容器
    insert(el, container, anchor)
  }

  // 核心patch函数,负责对比新旧节点,执行挂载或更新
  function patch(n1, n2, container, anchor) {
    if (!n1) {
      // 没有旧节点,执行挂载
      mountElement(n2, container, anchor)
    } else {
      // 有旧节点,执行更新逻辑,这里简化只处理同类型节点更新
      if (n1.type !== n2.type) {
        // 类型不同,先删除旧节点再挂载新节点
        remove(n1.el)
        mountElement(n2, container, anchor)
      } else {
        // 类型相同,更新属性和子节点
        const el = n2.el = n1.el
        // 更新属性
        const oldProps = n1.props || {}
        const newProps = n2.props || {}
        for (const key in newProps) {
          if (newProps[key] !== oldProps[key]) {
            patchProp(el, key, oldProps[key], newProps[key])
          }
        }
        for (const key in oldProps) {
          if (!(key in newProps)) {
            patchProp(el, key, oldProps[key], null)
          }
        }
        // 更新子节点,简化逻辑只处理文本子节点更新
        if (typeof n2.children === 'string') {
          if (typeof n1.children === 'string') {
            if (n1.children !== n2.children) {
              setText(el, n2.children)
            }
          } else {
            // 旧子节点是数组,清空后设置文本
            el.innerHTML = ''
            const textNode = createText(n2.children)
            insert(textNode, el, null)
          }
        }
      }
    }
  }

  // 对外暴露的render函数
  function render(vnode, container) {
    if (vnode) {
      // 有新节点,执行patch
      patch(container._vnode, vnode, container, null)
      container._vnode = vnode
    } else {
      // 没有新节点,清空容器
      if (container._vnode) {
        remove(container._vnode.el)
        container._vnode = null
      }
    }
  }

  return {
    render
  }
}

使用自定义渲染器

基于浏览器抽象实现,我们可以创建一个浏览器渲染器并使用它:

// 创建浏览器渲染器
const browserRenderer = createRenderer(browserOptions)
// 创建虚拟节点
const vnode = createVNode('div', {
  class: 'container',
  onClick: () => console.log('容器被点击')
}, [
  createVNode('h1', null, '自定义渲染器示例'),
  createVNode('p', null, '这是通过自定义渲染器渲染的内容')
])
// 挂载到页面容器
const container = document.getElementById('app')
browserRenderer.render(vnode, container)

扩展其他平台的渲染器

如果需要适配其他平台,比如自定义的控制台渲染,只需要实现对应的抽象层接口即可,不需要修改渲染器核心逻辑。以下是一个简单的控制台渲染抽象实现:

// 控制台环境的渲染抽象实现
const consoleOptions = {
  createElement(type) {
    return { type, children: [], props: {} }
  },
  insert(child, parent) {
    parent.children.push(child)
  },
  patchProp(el, key, prevValue, nextValue) {
    el.props[key] = nextValue
  },
  remove(el) {
    // 控制台环境简化删除逻辑
    console.log('删除节点', el.type)
  },
  createText(text) {
    return { type: 'text', content: text }
  },
  setText(el, text) {
    el.content = text
  }
}

// 创建控制台渲染器
const consoleRenderer = createRenderer(consoleOptions)
// 创建虚拟节点
const consoleVNode = createVNode('view', { class: 'page' }, [
  createVNode('text', null, '控制台渲染内容'),
  createVNode('text', null, '第二个文本节点')
])
// 自定义容器对象
const consoleContainer = { _vnode: null, children: [] }
// 执行渲染
consoleRenderer.render(consoleVNode, consoleContainer)
console.log('控制台渲染结果', consoleContainer.children)

总结

JS实现自定义渲染器的核心是做好渲染的抽象,通过封装平台相关的操作接口,让渲染核心逻辑可以跨平台复用。整个流程分为四步:设计统一的虚拟节点结构、定义渲染抽象接口、实现不同平台的抽象接口、编写不依赖平台的渲染核心逻辑。这种方式也是React、Vue等框架实现多端渲染的基础思路,理解这个过程可以帮助开发者更深入掌握前端框架的底层工作原理。

JavaScript自定义渲染器渲染抽象虚拟DOM渲染器原理修改时间:2026-07-19 12:57:37

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。