实现过程
- 编译器将模板编译成渲染函数
- 渲染函数返回一个虚拟DOM
- 渲染器将虚拟DOM渲染成页面
渲染器
渲染器实现
function renderer(vnode, container) {
// 使用 vnode.tag 作为标签名称创建 DOM 元素
const el = document.createElement(vnode.tag)
// 遍历 vnode.props,将属性、事件添加到 DOM 元素
for (const key in vnode.props) {
if (/^on/.test(key)) {
/*target.addEventListener(type, listener[, options]);
target: 必须,需要为其添加事件监听器的DOM元素、Window对象或Document对象。
type: 必须,字符串类型,表示要监听的事件类型,如"click", "mouseover", "load"等。
listener: 必须,事件处理函数,当指定的事件在目标元素上触发时,该函数会被调用。
options: 可选,一个可选的对象,用来指定添加事件监听器的一些额外选项,如:
capture: 布尔值,表示事件是否在捕获阶段触发,默认为 false,即冒泡阶段触发。
once: 布尔值,表示事件处理函数是否只执行一次,之后自动移除,默认为false。
passive: 布尔值,用于改善滚动性能,默认为false,设置为true表示浏览器在滚动期间不会阻止对scroll事件的默认行为。*/
// 如果 key 以 on 开头,说明它是事件
el.addEventListener(
key.substr(2).toLowerCase(), // 事件名称 onClick ---> click
vnode.props[key] // 事件处理函数
)
}
}
// 处理 children
if (typeof vnode.children === 'string') {
// 如果 children 是字符串,说明它是元素的文本子节点
el.appendChild(document.createTextNode(vnode.children))
} else if (Array.isArray(vnode.children)) {
// 递归地调用 renderer 函数渲染子节点,使用当前元素 el 作为挂载点
vnode.children.forEach(child => renderer(child, el))
}
// 将元素添加到挂载点下
container.appendChild(el)
}
虚拟dom
const vnode = {
tag: 'div',
children: [
{
tag: 'div',
children: 'click me1',
props: {
onClick: () => alert('hello')
}
}
]
}
调用
renderer(vnode, document.body)
扩展
命令式编程
命令式编程关注怎么做,强调一步一步改变程序状态达到预期效果
声明式编程
声明式编程关注做什么,专注于描述预期结果和约束条件,而不关注过程,程序更抽象,通过调用表达式、函数、规则和查询等方式描述数据转换和计算逻辑
我的理解
Vue用了声明式来描述UI,用户不用关注过程,但是命令式编程是不可避免的,内部的实现也是使用了命令式,只是用户不需要关注而已,他们没有孰轻孰重,只有适不适合
扩展例子
下面是一个分别用命令式和声明式实现数组相加的例子
- 命令式
// 命令式编程实现数组求和
function imperativeSum(array) {
let sum = 0;
for (let i = 0; i < array.length; i++) {
sum += array[i];
}
return sum;
}
let numbers = [1, 2, 3, 4, 5];
console.log(imperativeSum(numbers)); // 输出:15
- 声明式
// 声明式编程实现数组求和(此处使用JavaScript中的Array.prototype.reduce方法)
function declarativeSum(array) {
return array.reduce((accumulator, currentValue) => accumulator + currentValue, 0);
}
let numbers = [1, 2, 3, 4, 5];
console.log(declarativeSum(numbers)); // 输出:15
备注:部分例子来源于《Vue.js的设计与实现》