// 掌握好 2/8 原则
// vDOM 模版渲染
// 考察整体流程是否全面,热门技术是否有深度。
// 题目
// 描述 组件渲染和更新的 过程
// 描述 双向数据绑定 v-model 的实现原理
// Vue原理
// 组件化
// 响应式
// vdom 和 diff
// 模版编译
// 组件渲染过程
// 前端路由
// 如何理解 MVVM?
// 组件化基础
// asp jsp php 已经有组件化了
// nodejs 也有组件化了~
// 数据驱动视图 (MVVM setState)
// 传统组件 - 只是静态渲染,更新还要依赖于操作DOM
// 数据驱动视图 - React setState
// 数据驱动视图 - Vue MVVM - 只需要修改数据 就可以把视图内容修改了~ 不再是频繁操作 DOM 了
// - View <-> ViewMdeol(Vue就是 提供 链接,中间并处理的一个能力 ) <-> Model
// Model view viewModel
// Vue 响应式
// 组件 data 的数据一旦变化,立刻触发视图的更新。
// 实现 数据驱动视图的 第一步
// Object.defineProperty
Object.defineProperty(data, 'name', {
get: function() {return name},
set: function(newVal) {name = newVal}
})
// - 监听对象 监听数组
// - 复杂对象 深度监听
// - 具体可看 observe-demo 文件夹~
// Object.defineProperty 是不具备 监听数组的能力的
// - 缺点
// 深度监听, 需要递归到底, 一次性计算量大
// ⚠️ - 所以需要一次性 递归完吗? 可不可以 什么时候用到什么时候监听呢?
// ⚠️ - 如果对 data 新增一个属性 就监听不到 所以需要有 Vue.set
// ⚠️ - 如果对 data 删除一个属性 就监听不到 所以需要有 Vue.delete
// ⚠️ - 无法原生 监听数组, 需要特殊处理。
// Object.defineProperty 的一些缺点 (Vue3.0 启动 Proxy)
// Proxy 有兼容性问题 而且无法 polyfill - ⚠️
// 虚拟DOM (Virtual DOM) 和 diff -
// vdom 是实现 vue 和 React 的重要基石
// diff 算法是 vdom 中最核心,最关键的部分。
// vdom 是一个热门话题 也是面试中的热门问题
// DOM操作非常耗费性能 - ⚠️⚠️
// 用 jQ 的时候可以自行操作 DOM 手动调整
// Vue React 是数据驱动视图, 如何有效控制 DOM 操作?
// vdom~
// 有了一定的复杂度,想减少计算次数就比较难
// 能不能吧计算 更多的转移为 Js 计算呢? 因为 Js 的执行速度很快~
// vdom 用 Js 模拟 DOM 结构,计算出最小的变更,操作DOM~
// 用 Js 模拟 DOM 结构 -
// HTML 可以说是 XML 的一个特别版本~
vdom
- a
{
tag: 'div', // 目标元素~
props: { // 属性 样式 事件啥的~ 子元素
id: '1',
className: '2',
},
children: [
{
tag: 'p' // text文字也是 子节点
children: 'vdom'
},
{
tag: 'ul',
props: {
style: 'font-size: 20px';
},
children: [
{
tag: 'li',
children: 'a'
}
]
}
]
}
// 通过 snabbdom 学习 dom - (Vue 参考它的实现的vdom 和 diff)
// - diff 算法 / diff算法能在日常使用 vue React 中体现出来 (例如 key)
// 两棵树做 diff, 如这里的 vdom diff~
// - 树的 diff算法的 事件复杂度 (O^3) - 1. 遍历tree1 2. 遍历tree2 3.排序
// 如果有 1000 个节点, 要计算1亿次 算法不可用。
// - 优化时间复杂度到 O(n)
// - 只比较统一层级, 不跨级比较
// - tag 不相同,则直接删掉重建, 不再深度比较
// - tag 和 key,两者都相同, 则认为是相同节点, 不再深度比较。
// snabbdom 源码解读~
// [cbs 其实就是 callbacks] [cbs.pre 就是执行 pre-hook~] [hook 其实就是 类似生命周期]
// h函数 - 返回的是一个 vnode(vnode 返回的是一个对象)
// patch函数解析
// 第一个参数不是 vnode~ 那就创建一个 空的vnode 关联到这个 DOM 元素
// 相同的 vnode~ (判断相同vnode的条件 - key 和 tag 都相等 就是相同的)
// - 都不传 key 那 undefined === undefined // true 都不传递 key 是不再循环体内的
// 不同的 vnode~ (如果不相同 那就 对比同一层级 然后进行删掉 销毁 重建~)
// - patchVnode
// 1 - 执行 prepatch hook~
// 2 - 设置 vnode.elem (将旧的vnode对应的 elem 元素 对应的 赋值给 新的vnode 这样才可以知道 之后更新或者替换哪个 DOM元素)
// 3 - 获取 旧的children 和 新的children
// 4 - 判断 新的 text 等于 undefined (那就意味新的 children 一般有值) ~
// 4.1 - 也是对比新旧 (- 新的有旧的没就赋值 - 旧的有新的没就删除 - 新旧都有就更新(updateChildren))
// 5 - 4否则 然后做处理
// 6 - 在判断 新的text 和 旧的text 对比~ (然后对比之后 不一样的话执行 删掉销毁重建(设置新的text)啥的)
// 更新节点操作 - updateChildren
// - 定义了 oldStartIdx oldEndIdx newStartIdx newEndIdx
// 然后 开始累加 结尾递减 在一边对比~ (往中间 碰头~)
// 然后新旧 开始和开始做对比 else 结尾和结尾做对比 else 开始和结尾 else 结尾和开始 (交叉对比) 这个只是 snabbdom 自己的对比~
// 如果命中了就会走 patchVnode 函数 然后有子节点的话 再递归对比 子节点
// 然后 else 以上四个都没有命中
// - 拿新节点的 key 能否对应上 oldChildren 中的某个节点的key (这个就是 我拿新的一个节点去找 旧的当中任意一个 去查看对比)
// - 没有对应上 - 直接重建(createElem) 就是插入 insertBefore()~
// - 对应上了的话 查看 key 和 sel(就是tag) 看看是否相等 - 相等就是 patchVnode 不想等就是 - 直接重建(createElem) 就是插入 insertBefore()~
// ⚠️ - 不使用key 和 使用key的对比 - 例如只是 顺序改变了
// 如果不用key 就判断不一样直接删除了~ // 如果使用key 那就只是改变一下顺序
// ⚠️ - 所以 key 不能使用随机数或者index 要和遍历Item有关的唯一标示的val 做key
// vdom和diff算法总结 - vdom核心改变很重要 h vnode patch diff key 等
// vdom 存在的价格更加重要 - 数据驱动视图 控制DOM操作 -
// 模版编译~
// 模版是 vue 开发中最常用的部分, 他不是 html, 它有指令,插值,Js表达式,能实现判断,循环啥的。
// html 是标签语言, 只有 Js 才能实现判断,循环(图灵完备的语言)
// 因此 模版一定是转换为 某种Js代码 即编译模版~
// 具体查看 ./vue-template-compiler-demo 文件夹
// 组件渲染和更新过程
// vue template complier 将模版编译成 render 函数
// 执行 render 函数生成 vnode (vnode 在渲染到 DOM上~)
// 基于 vnode 在执行 patch 和 diff
// 监听属性变化(有监听属性 和触发更新视图的方法) 生成一个新的属性 生成一个新的vnode 然后再对应的渲染到DOM上
// 如果使用 webpack vue-loader, 会在 开发环境下 编译模版~( 如果在运行时编译就比较慢⚠️)
// -
// Js 的 with 语法~ 不常用
// 使用 with,能改变 {} 内自由变量的查找方式。
// 将 {} 内自由变量, 当作 obj 的属性来查找。
// ⚠️ - with 要慎用, 他打破了作用域规则, 易读性变差~
const obj = { a: 1 };
with(obj) {
console.log(a); // 1
console.log(b); // 报错
}
console.log(obj.b); // 这样会打印 undefined
// Vue 组件中使用 render 代替 template -
Vue.component('heading', {
// template: 'xxx', 第一种方式 template 执行
render: function(createElement) { // 第二种方式, rander 函数执行
return createElement(
'h' + this.level, // 这里的 h 就是简单的字符串拼接就是 h1 h2啥的
// 第二个参数可以是属性
[ // 第三个参数 就是 子元素
createElement('a', {
attrs: {
name: 'headerId',
href: '#' + 'headerId'
}
}, 'this is a tag')
]
)
}
})
// 在有些复杂情况下, 不能用 template,就考虑使用 render~
// React 一直都在用 render(没有模版) 和这里一样
// 模版到 render 再到vnode 再到渲染和更新 -
// 组件渲染和更新过程 - - 考察对流程了解的全面程度
// - 一个组件渲染到页面,修改data触发更新(数据驱动视图)
// 知识点
// - zs - 响应式: 监听 data属性, getter setter 包括数组
// - zs - 模版编译: 模版到 render 函数,再到 vnode
// - zs - vdom: `patch(elem, vnode)`(把 vnode 渲染到空的 elem 上) 和 `patch(vnode, newVnode)` (新的vnode 去更新 旧的vnode)
// 初次渲染过程
// 解析模版为 render 函数 (这个vue-loader 使用webpack 的话 在编译或者打包的时候已经完成了)
// 触发响应式 监听 data 属性的 getter setter
// with(this){return _c('p',[_v(_s(message))])}
// 就获取到了 this.message ⚠️⚠️⚠️⚠️
// {{ meaage }}
// data() {
// return {
// message: 'hello', // 会触发get
// city: '北京' // 不会触发get 因为模版没有用到 和视图没有关系
// }
// }
// 执行 render 函数, 生成vnode, patch(elem, vnode)
// 更新过程
// 修改 data 触发 setter (此前 getter 中已被监听)
// 重新执行 render 函数, 生成 newVnode
// patch(vnode, newVnode) - 对比差异 更新
// render 会生成 vnode~
// render 过程中触发 data (getter setter)
// 然后中间会监听 然后触发依赖 收集依赖
// 异步渲染 -
// 回顾 $nextTick ($nextTick 会等待 DOM 渲染完再回调)
// 汇总 data 的修改, 一次性 更新 视图 (多次 data 只更新一次~)
// 减少 DOM 操作次数,提高性能
// Vue-router - - 前端路由的原理
// hash - window.onhashchage
// hash - #后面的部分 - 通过监听 hash的变化 来触发路由的变化
// hash 变化会触发网页跳转 (即浏览器的前进 后退)
// hash 变化不会刷新页面 SPA必需的特点 (router-view 前端自己控制)
// hash 永远不会提交到 server 端 (完全属于前端的东西)
// window.onhashchage = (event) => {
// event.oldURL - 变化前的url
// event.newURL - 新的url
// location.hash - 获取hash值
// }
// // 页面初次加载,获取 hash
// document.addEventListener('DOMContentLoaded', () => {
// console.log('hash:', location.hash);
// })
// // hash的变化包括 - Js修改URL - 手动修改url的hash(如果修改其他 可能会触发页面刷新) - 浏览器的前进,后退
// // Js修改 url
// btn.onclick(() => {
// location.href = '#/user';
// })
// history (H5)
// 用 URL 规范的路由, 但跳转的时候不刷新页面(是 SPA 必需的)
// histoty.pushState
// window.onpopstate
// 正常页面浏览
// - https://gighub.com/yyy - https://gighub.com/yyy/aaa - https://gighub.com/yyy/aaa/bbb 这三种都会刷新页面
// 改造成 H5-history 模式
// - https://gighub.com/yyy(第一次访问刷新页面) - https://gighub.com/yyy/aaa - https://gighub.com/yyy/aaa/bbb 后两次都前端跳转,不刷新页面
// document.addEventListener('DOMContentLoaded', () => {
// console.log('path:', location.pathname); // 页面初次加载 获取path
// })
// // 打开一个新的路由 【⚠️注意】用pushState方式, 浏览器不会刷新页面
// btn.onclick(() => {
// const state = { name: 'page1' };
// console.log('切换路由到 page1'); // todo code this
// histoty.pushState(state, '', 'page1'); // 重要
// })
// // 监听浏览器的前进 后退
// window.onpopstate = (event) => { // 重要
// // state 很有可能就是上面定义的 state(只要定义了 state)
// // location.pathname 可以监听到 path
// console.log('onpopstate', event.state, location.pathname);
// }
// // 需要 server 端配合~ 无论访问什么样的路由 都回去返回一个 index.html~
// 无论前端怎么搞 后端只返回 前端的一个主文件即可~
// to B系统推荐 hash 简单易用 对url规范不敏感。 -
// to C系统 可以考虑 H5 history, 但需要服务端支持 - 如果需要做 搜索引擎优化的话 就需要考虑 H5 history -
// 总结~