1、vue-router原理实现

1、动态路由

  1. const routes = [{
  2. path:'/detail:id',
  3. name:'Detial',
  4. props:true,
  5. component:()=>('../views/Detail.vue')
  6. }]
  7. //接收时推荐使用props:['id']来接收参数,路由中需要开启props。

2、嵌套路由

  1. const routes = [{
  2. path:'/',
  3. component:Layout,
  4. children:[
  5. {
  6. name:'index',
  7. path:'',
  8. component:Index
  9. },
  10. {
  11. name:'detail',
  12. path:'detail/:id',
  13. props:true,
  14. component:()=>('../views/Detail.vue')
  15. }
  16. ]
  17. }]

3、编程式导航

  1. this.$router.replace('/login')
  2. this.$router.push('/')
  3. this.$router.push(name:'Detail',params:{id:1})
  4. this.$router.go(-2)
  5. this.$router.back()

4、Hash模式和History模式的区别

区别:

  1. Hash模式是基于锚点,以及onhashchange事件
  2. History模式是基于HTML5中的HistoryAPI
    • history.pushState() IE10以后才支持
    • history.repalceState()

5、History模式

  1. History需要服务器的支持
  2. 单页应用中,服务端不存在http://www.testurl.com/login这样的地址会返回找不到该页面
  3. 在服务端应该除了静态资源外都返回单页应用的index.html

6、VueRouter实现原理

Hash模式:

  1. URL中#后面的内容作为路径地址
  2. 监听hashchange事件
  3. 根据当前路由地址找到对应的组件重新渲染

History模式

  1. 通过history.pushState()方法改变地址栏
  2. 监听popstate事件
  3. 根据当前路由地址找到对应组件重新渲染

7、VueRouter-install

  1. let _Vue = null
  2. export default class VueRouter{
  3. static install (Vue){
  4. //1.判断当前插件是否已经被安装
  5. if(VueRouter.inatall.installed){
  6. return
  7. }
  8. VueRouter.install.installed = true
  9. //2.把vue构造函数记录到全局变量
  10. _Vue = Vue
  11. //3.把创建Vue实例时候传入的router对象注入到Vue实例上
  12. //混入
  13. _Vue.mixin({
  14. beforeCreate(){
  15. if(this.$options.router){
  16. _Vue.prototype.$router = this.$options.router
  17. }
  18. }
  19. })
  20. }
  21. }

8、VueRouter-构造函数

  1. let _Vue = null
  2. export default class VueRouter{
  3. static install (Vue){
  4. //1.判断当前插件是否已经被安装
  5. if(VueRouter.inatall.installed){
  6. return
  7. }
  8. VueRouter.install.installed = true
  9. //2.把vue构造函数记录到全局变量
  10. _Vue = Vue
  11. //3.把创建Vue实例时候传入的router对象注入到Vue实例上
  12. //混入
  13. _Vue.mixin({
  14. beforeCreate(){
  15. if(this.$options.router){
  16. _Vue.prototype.$router = this.$options.router
  17. }
  18. }
  19. })
  20. }
  21. constructor(options){
  22. this.options = options
  23. this.routeMap = {}
  24. this.data = _Vue.observable({
  25. current:'/'
  26. })
  27. }
  28. }

9、VueRouter-createRounteMap

  1. let _Vue = null
  2. export default class VueRouter{
  3. static install (Vue){
  4. //1.判断当前插件是否已经被安装
  5. if(VueRouter.inatall.installed){
  6. return
  7. }
  8. VueRouter.install.installed = true
  9. //2.把vue构造函数记录到全局变量
  10. _Vue = Vue
  11. //3.把创建Vue实例时候传入的router对象注入到Vue实例上
  12. //混入
  13. _Vue.mixin({
  14. beforeCreate(){
  15. if(this.$options.router){
  16. _Vue.prototype.$router = this.$options.router
  17. }
  18. }
  19. })
  20. }
  21. constructor(options){
  22. this.options = options
  23. this.routeMap = {}
  24. this.data = _Vue.observable({
  25. current:'/'
  26. })
  27. }
  28. createRouteMap(){
  29. //遍历所有的路由规则,把路由规则解析成键值对的形式 存储到routeMap中
  30. this.options.routes.forEach(route=>{
  31. this.routeMap[route.path] = route.component
  32. })
  33. }
  34. }

10、VueRouter-router-link

vue的构建版本

  1. 运行时版本:不支持template模板,需要打包的时候提前编译
  2. 完整版:包含运行时和编译器,体积比运行时版大10k左右,程序运行的时候把模板转换成render函数
  1. let _Vue = null
  2. export default class VueRouter{
  3. static install (Vue){
  4. //1.判断当前插件是否已经被安装
  5. if(VueRouter.inatall.installed){
  6. return
  7. }
  8. VueRouter.install.installed = true
  9. //2.把vue构造函数记录到全局变量
  10. _Vue = Vue
  11. //3.把创建Vue实例时候传入的router对象注入到Vue实例上
  12. //混入
  13. _Vue.mixin({
  14. beforeCreate(){
  15. if(this.$options.router){
  16. _Vue.prototype.$router = this.$options.router
  17. this.$options.router.init()
  18. }
  19. }
  20. })
  21. }
  22. constructor(options){
  23. this.options = options
  24. this.routeMap = {}
  25. this.data = _Vue.observable({
  26. current:'/'
  27. })
  28. }
  29. init(){
  30. this.createRouterMap()
  31. this.initComponents(_Vue)
  32. }
  33. createRouteMap(){
  34. //遍历所有的路由规则,把路由规则解析成键值对的形式 存储到routeMap中
  35. this.options.routes.forEach(route=>{
  36. this.routeMap[route.path] = route.component
  37. })
  38. }
  39. initComponents(Vue){
  40. Vue.component('router-link',{
  41. props:{
  42. ro:String
  43. },
  44. template:'<a :href="to"><slot></slot></a>'
  45. })
  46. }
  47. }

11、VueRouter完整版的vue

开启完整版本的vue

  1. module.exports={
  2. runtimeCompiler:true
  3. }

12、VueRouter-render

  1. let _Vue = null
  2. export default class VueRouter{
  3. static install (Vue){
  4. //1.判断当前插件是否已经被安装
  5. if(VueRouter.inatall.installed){
  6. return
  7. }
  8. VueRouter.install.installed = true
  9. //2.把vue构造函数记录到全局变量
  10. _Vue = Vue
  11. //3.把创建Vue实例时候传入的router对象注入到Vue实例上
  12. //混入
  13. _Vue.mixin({
  14. beforeCreate(){
  15. if(this.$options.router){
  16. _Vue.prototype.$router = this.$options.router
  17. this.$options.router.init()
  18. }
  19. }
  20. })
  21. }
  22. constructor(options){
  23. this.options = options
  24. this.routeMap = {}
  25. this.data = _Vue.observable({
  26. current:'/'
  27. })
  28. }
  29. init(){
  30. this.createRouterMap()
  31. this.initComponents(_Vue)
  32. }
  33. createRouteMap(){
  34. //遍历所有的路由规则,把路由规则解析成键值对的形式 存储到routeMap中
  35. this.options.routes.forEach(route=>{
  36. this.routeMap[route.path] = route.component
  37. })
  38. }
  39. initComponents(Vue){
  40. Vue.component('router-link',{
  41. props:{
  42. ro:String
  43. },
  44. render(h){
  45. return h('a',{
  46. arrts:{
  47. href:this.to
  48. }
  49. },[this.$slots.default])
  50. }
  51. })
  52. }
  53. }

13、VueRouter-router-view

  1. let _Vue = null
  2. export default class VueRouter{
  3. static install (Vue){
  4. //1.判断当前插件是否已经被安装
  5. if(VueRouter.inatall.installed){
  6. return
  7. }
  8. VueRouter.install.installed = true
  9. //2.把vue构造函数记录到全局变量
  10. _Vue = Vue
  11. //3.把创建Vue实例时候传入的router对象注入到Vue实例上
  12. //混入
  13. _Vue.mixin({
  14. beforeCreate(){
  15. if(this.$options.router){
  16. _Vue.prototype.$router = this.$options.router
  17. this.$options.router.init()
  18. }
  19. }
  20. })
  21. }
  22. constructor(options){
  23. this.options = options
  24. this.routeMap = {}
  25. this.data = _Vue.observable({
  26. current:'/'
  27. })
  28. }
  29. init(){
  30. this.createRouterMap()
  31. this.initComponents(_Vue)
  32. }
  33. createRouteMap(){
  34. //遍历所有的路由规则,把路由规则解析成键值对的形式 存储到routeMap中
  35. this.options.routes.forEach(route=>{
  36. this.routeMap[route.path] = route.component
  37. })
  38. }
  39. initComponents(Vue){
  40. Vue.component('router-link',{
  41. props:{
  42. ro:String
  43. },
  44. render(h){
  45. return h('a',{
  46. arrts:{
  47. href:this.to
  48. },
  49. on:{
  50. click:this.chickHandler
  51. }
  52. },[this.$slots.default])
  53. }
  54. })
  55. methods:{
  56. chickHandler(e){
  57. history.pushState({},'',this.to)
  58. this.$router.data.current = this.to
  59. e.preventDefault()
  60. }
  61. }
  62. }
  63. const self = this
  64. Vue.component('router-view',{
  65. render(h){
  66. const component = self.routeMap[self.data.current]
  67. return h(component)
  68. }
  69. }
  70. }

14、VueRouter-initEvent

  1. let _Vue = null
  2. export default class VueRouter{
  3. static install (Vue){
  4. //1.判断当前插件是否已经被安装
  5. if(VueRouter.inatall.installed){
  6. return
  7. }
  8. VueRouter.install.installed = true
  9. //2.把vue构造函数记录到全局变量
  10. _Vue = Vue
  11. //3.把创建Vue实例时候传入的router对象注入到Vue实例上
  12. //混入
  13. _Vue.mixin({
  14. beforeCreate(){
  15. if(this.$options.router){
  16. _Vue.prototype.$router = this.$options.router
  17. this.$options.router.init()
  18. }
  19. }
  20. })
  21. }
  22. constructor(options){
  23. this.options = options
  24. this.routeMap = {}
  25. this.data = _Vue.observable({
  26. current:'/'
  27. })
  28. }
  29. init(){
  30. this.createRouterMap()
  31. this.initComponents(_Vue)
  32. this.initEvent()
  33. }
  34. createRouteMap(){
  35. //遍历所有的路由规则,把路由规则解析成键值对的形式 存储到routeMap中
  36. this.options.routes.forEach(route=>{
  37. this.routeMap[route.path] = route.component
  38. })
  39. }
  40. initEvent(){
  41. window.addEventListener('popstate',()=>{
  42. this.data.current = window.location.pathname
  43. })
  44. }
  45. initComponents(Vue){
  46. Vue.component('router-link',{
  47. props:{
  48. ro:String
  49. },
  50. render(h){
  51. return h('a',{
  52. arrts:{
  53. href:this.to
  54. },
  55. on:{
  56. click:this.chickHandler
  57. }
  58. },[this.$slots.default])
  59. }
  60. })
  61. methods:{
  62. chickHandler(e){
  63. history.pushState({},'',this.to)
  64. this.$router.data.current = this.to
  65. e.preventDefault()
  66. }
  67. }
  68. }
  69. const self = this
  70. Vue.component('router-view',{
  71. render(h){
  72. const component = self.routeMap[self.data.current]
  73. return h(component)
  74. }
  75. }
  76. }

2、模拟vue.js响应式原理

1、数据驱动

数据响应式:

  1. 数据模型仅仅是普通的javascript对象,而当我们修改数据时,视图会进行更新,避免了繁琐的DOM操作,提高开发效率

双向绑定

  1. 数据改变,视图改变,视图改变,数据也随之改变
  2. 我们可以使用v-model在表单元素上创建双向数据绑定

数据驱动是Vue最独特的特性之一

  1. 开发过程中仅需要关注数据本身,不需要关心数据是如何渲染到视图的

2、数据响应式核心原理Vue2

  1. function porxyData(data){
  2. Object.key(data).forEach(key =>{
  3. Object.defineProperty(vm,key,{
  4. get(){
  5. return data[key]
  6. },
  7. set(newValue){
  8. if(newValue === data[key]){
  9. return
  10. }
  11. data[key] = newValue
  12. document.querySelector('#app').textContent = data[key]
  13. }
  14. })
  15. })
  16. }

3、数据响应式核心原理Vue3

  1. let vm = new Proxy(data,{
  2. get(target,key){
  3. return target[key]
  4. }
  5. set(target,key,newValue){
  6. if(taeget[key]===newValue){
  7. return
  8. }
  9. target[key] = newValue
  10. document.querySelector('#app').textContent = target[key]
  11. }
  12. })

4、发布订阅模式

发布/订阅模式

  1. 订阅者
  2. 发布者
  3. 信号中心
  1. //事件中心
  2. let eventHub = new Vue()
  3. //ComponentA.vue
  4. //发布者
  5. addTodo:function(){
  6. eventHub.$emit('add-todo',{text:this.newTodoText})
  7. this.newTodoText = ''
  8. }
  9. //ComponentB.vue
  10. create:function(){
  11. //订阅者(实现)
  12. eventHub.$on('add-todo',this.addTodos)
  13. }
  1. class EventEmitter {
  2. constructor(){
  3. this.subs = Object.create(null)
  4. }
  5. $on(eventType,handler){
  6. this.subs[eventType] = this.subs[eventType] || []
  7. this.subs[eventType].push(handler)
  8. }
  9. $emit(eventType){
  10. if(this.subs[eventType]){
  11. this.subs[eventType].forEach(handler =>{
  12. handler()
  13. })
  14. }
  15. }
  16. }

5、观察者模式

  • 观察者(订阅者)—watcher
    • update():当事件发生时,具体要做的事情
  • 目标(发布者)—Dep
    • subs数组:存储所有的观察者
    • addSub():添加观察者
    • notify():当事件发生,调用所有观察者的update()方法
  • 没有事件中心
  1. //发布者-目标
  2. class Dep{
  3. constructor(){
  4. //记录所有的订阅者
  5. this.subs = []
  6. }
  7. addSub(sub){
  8. if(sub && sub.upadte){
  9. this.subs.push(sub)
  10. }
  11. }
  12. notify(){
  13. this.subs.forEach(sub =>{
  14. sub.update()
  15. })
  16. }
  17. }
  18. //订阅者-观察者
  19. class Wather {
  20. update(){
  21. console.log('update')
  22. }
  23. }
  24. //测试
  25. let dep = new Dep()
  26. let watcher = new Watcher()
  27. dep.addSub(watcher)
  28. dep.notify()

6、Vue

功能:

  1. 负责接收初始化的参数(选项)
  2. 负责把data中的属性注入到Vue实例,转换成getter/setter
  3. 负责调用observe监听data中所有属性的变化
  4. 负责调用compiler解析指令/差值表达式
  1. class Vue{
  2. constructor(options){
  3. //1.通过属性保存选项的数据
  4. this.$options = options || {}
  5. this.$data = options.data || {}
  6. this.$el = typeof options.el === 'string'?documnet.querySelector(options.el):options.el
  7. //2.把data中的成员转换成getter和setter注入到Vue实例中
  8. this._proxyData(this.$data)
  9. //3.调用observe监听data中所有属性的变化
  10. //4.调用compiler解析指令/差值表达式
  11. }
  12. _proxyData(data){
  13. //遍历Data中的所有属性
  14. Object.keys(data).forEach(key =>{
  15. //把data的属性注入到vue实例中
  16. Object.defineProperty(this,key,{
  17. enumerable:true,
  18. configurable:true,
  19. get(){
  20. return data[key]
  21. }
  22. set(newValue){
  23. if(newValue == data[key]){
  24. return
  25. }
  26. data[key] = newValue
  27. }
  28. })
  29. })
  30. }
  31. }

7、Observer

功能:

  1. 负责把data选项中的属性转换成响应式数据
  2. data中的某个属性也是对象,把该属性转换成响应式数据
  3. 数据变化发送通知
  1. class Observer{
  2. constructor(data){
  3. this.walk(data)
  4. }
  5. walk(data){
  6. //1.判断data是否是对象
  7. if(!data || typeof !== 'object'){
  8. return
  9. }
  10. //2.遍历data对象所有属性
  11. Object.key(data).forEach(key=>{
  12. this.defineReactive(data,key,data[key])
  13. })
  14. }
  15. defineReactive(obj,key,val){
  16. let that = this
  17. this.walk(val)
  18. Object.defineProperty(obj,key,{
  19. enumerable:true,
  20. configurable:true,
  21. get(){
  22. return val
  23. }
  24. set(newValue){
  25. if(newValue = val){
  26. return
  27. }
  28. val = newValue
  29. that.walk(newValue)
  30. }
  31. })
  32. }
  33. }
  1. class Vue{
  2. constructor(options){
  3. //1.通过属性保存选项的数据
  4. this.$options = options || {}
  5. this.$data = options.data || {}
  6. this.$el = typeof options.el === 'string'?documnet.querySelector(options.el):options.el
  7. //2.把data中的成员转换成getter和setter注入到Vue实例中
  8. this._proxyData(this.$data)
  9. //3.调用observe监听data中所有属性的变化
  10. new Observer(this.$data)
  11. //4.调用compiler解析指令/差值表达式
  12. }
  13. _proxyData(data){
  14. //遍历Data中的所有属性
  15. Object.keys(data).forEach(key =>{
  16. //把data的属性注入到vue实例中
  17. Object.defineProperty(this,key,{
  18. enumerable:true,
  19. configurable:true,
  20. get(){
  21. return data[key]
  22. }
  23. set(newValue){
  24. if(newValue == data[key]){
  25. return
  26. }
  27. data[key] = newValue
  28. }
  29. })
  30. })
  31. }
  32. }

8、Compiler

功能:

  1. 负责编译模板,解析指令/差值表达式
  2. 负责页面的首次渲染
  3. 当数据变化后重新渲染视图
  1. class Compiler{
  2. constructor(vm){
  3. this.el = vm.$el
  4. this.vm = vm
  5. this.compile(this.el)
  6. }
  7. //编译模板,处理文本节点和元素节点
  8. compile(el){
  9. let el.childNodes = el.childNodes
  10. Array.from(childNodes).forEach(node =>{
  11. if(this.isTextNode(node){
  12. this.compileText(node)
  13. })else if(this.isElementNode(node)){
  14. this.compileElement(node)
  15. }
  16. //判断node节点是否有子节点,如果有子节点,要递归调用compile
  17. if(node.childNodes && node.childNodes.length){
  18. this.compile(node)
  19. }
  20. })
  21. }
  22. //编译元素节点,处理指令
  23. compileElement(node){
  24. Array.from(node.attributes).forEach(attr =>{
  25. //判断是否是指令
  26. let arrtName = attr.name
  27. if(this.isDirective(attrName)){
  28. attrName = attrName.substr(2)
  29. let key = attr.value
  30. this.update(node,key,attrName)
  31. }
  32. })
  33. }
  34. update(node,key,attrName){
  35. let updateFn = this[attrName + 'Updater']
  36. updateFn && updateFn(node,this.vm[key])
  37. }
  38. //处理v-text指令
  39. textUpdater(node,value){
  40. node.textContent = value
  41. }
  42. //处理v-model
  43. modelUpdater(node,value){
  44. node.value = value
  45. }
  46. //编译文本节点,处理差值表达式
  47. compileText(node){
  48. //{{ msg }}
  49. let reg = /\{\{(.+?)\}\}/
  50. let value = node.textContent
  51. if(reg.test(value)){
  52. let key = RegExp.$1.trim()
  53. node.textContent = value.replace(reg,this.vm[key])
  54. }
  55. }
  56. //判断元素属性是否是指令
  57. isDirective(attrName){
  58. return attrName.startWith('v-')
  59. }
  60. //判断节点是否文本节点
  61. isTextNode(node){
  62. return node.nodeType === 3
  63. }
  64. //判断节点是否是元素节点
  65. isElementNode(node){
  66. return node.nodeType === 1
  67. }
  68. }
  1. class Vue{
  2. constructor(options){
  3. //1.通过属性保存选项的数据
  4. this.$options = options || {}
  5. this.$data = options.data || {}
  6. this.$el = typeof options.el === 'string'?documnet.querySelector(options.el):options.el
  7. //2.把data中的成员转换成getter和setter注入到Vue实例中
  8. this._proxyData(this.$data)
  9. //3.调用observe监听data中所有属性的变化
  10. new Observer(this.$data)
  11. //4.调用compiler解析指令/差值表达式
  12. new Compiler(this)
  13. }
  14. _proxyData(data){
  15. //遍历Data中的所有属性
  16. Object.keys(data).forEach(key =>{
  17. //把data的属性注入到vue实例中
  18. Object.defineProperty(this,key,{
  19. enumerable:true,
  20. configurable:true,
  21. get(){
  22. return data[key]
  23. }
  24. set(newValue){
  25. if(newValue == data[key]){
  26. return
  27. }
  28. data[key] = newValue
  29. }
  30. })
  31. })
  32. }
  33. }

9、Dep

功能:

  1. 收集依赖,添加观察者
  2. 通知所有观察者
  1. class Dep{
  2. constructor(){
  3. //存储所有的观察者
  4. this.subs = []
  5. }
  6. //添加观察者
  7. addSub(sub){
  8. if(sub && sub.update){
  9. this.subs.push(sub)
  10. }
  11. }
  12. //发送通知
  13. notify(){
  14. this.subs.forEach(sub =>{
  15. sub.update()
  16. })
  17. }
  18. }
  1. class Observer{
  2. constructor(data){
  3. this.walk(data)
  4. }
  5. walk(data){
  6. //1.判断data是否是对象
  7. if(!data || typeof !== 'object'){
  8. return
  9. }
  10. //2.遍历data对象所有属性
  11. Object.key(data).forEach(key=>{
  12. this.defineReactive(data,key,data[key])
  13. })
  14. }
  15. defineReactive(obj,key,val){
  16. let that = this
  17. //负责收集依赖,并发送通知
  18. let dep = new Dep()
  19. this.walk(val)
  20. Object.defineProperty(obj,key,{
  21. enumerable:true,
  22. configurable:true,
  23. get(){
  24. //收集依赖
  25. Dep.target && dep.addSub(Dep.target)
  26. return val
  27. }
  28. set(newValue){
  29. if(newValue = val){
  30. return
  31. }
  32. val = newValue
  33. that.walk(newValue)
  34. dep.notify()
  35. }
  36. })
  37. }
  38. }

10、watcher

功能:

  1. 当数据变化触发依赖,dep通知所有的watcher实例更新视图
  2. 自身实例化的时候往dep对象添加自己
  1. class Watcher{
  2. constructor(vm,key,cb){
  3. this.vm = vm
  4. //data中的属性名称
  5. this.key = key
  6. //回调函数负责更新视图
  7. this.cb = cb
  8. //把watcher对象记录到Dep类的静态属性target中
  9. Dep.targrt = this
  10. //触发get方法,在get方法中调用addSub
  11. this.oldValue = vm[key]
  12. Dep.target = null
  13. }
  14. //当数据发生变化的时候更新视图
  15. update(){
  16. let newValue = this.vm[this.key]
  17. if(this.oldValue === newValue){
  18. return
  19. }
  20. this.cb(newValue)
  21. }
  22. }
  1. class Compiler{
  2. constructor(vm){
  3. this.el = vm.$el
  4. this.vm = vm
  5. this.compile(this.el)
  6. }
  7. //编译模板,处理文本节点和元素节点
  8. compile(el){
  9. let el.childNodes = el.childNodes
  10. Array.from(childNodes).forEach(node =>{
  11. if(this.isTextNode(node){
  12. this.compileText(node)
  13. })else if(this.isElementNode(node)){
  14. this.compileElement(node)
  15. }
  16. //判断node节点是否有子节点,如果有子节点,要递归调用compile
  17. if(node.childNodes && node.childNodes.length){
  18. this.compile(node)
  19. }
  20. })
  21. }
  22. //编译元素节点,处理指令
  23. compileElement(node){
  24. Array.from(node.attributes).forEach(attr =>{
  25. //判断是否是指令
  26. let arrtName = attr.name
  27. if(this.isDirective(attrName)){
  28. attrName = attrName.substr(2)
  29. let key = attr.value
  30. this.update(node,key,attrName)
  31. }
  32. })
  33. }
  34. update(node,key,attrName){
  35. let updateFn = this[attrName + 'Updater']
  36. updateFn && updateFn.call(this,node,this.vm[key],key)
  37. }
  38. //处理v-text指令
  39. textUpdater(node,value,key){
  40. node.textContent = value
  41. new Watcher(this.vm,key,(newValue)=>{
  42. node.textContent = newValue
  43. })
  44. }
  45. //处理v-model
  46. modelUpdater(node,value,key){
  47. node.value = value
  48. new Watcher(this.vm,key,(newValue)=>{
  49. node.value = newValue
  50. })
  51. }
  52. //编译文本节点,处理差值表达式
  53. compileText(node){
  54. //{{ msg }}
  55. let reg = /\{\{(.+?)\}\}/
  56. let value = node.textContent
  57. if(reg.test(value)){
  58. let key = RegExp.$1.trim()
  59. node.textContent = value.replace(reg,this.vm[key])
  60. //创建watcher,当数据改变的时候更新视图
  61. new Watcher(this.vm,key,(newValue)=>{
  62. node.textContent = newValue
  63. })
  64. }
  65. }
  66. //判断元素属性是否是指令
  67. isDirective(attrName){
  68. return attrName.startWith('v-')
  69. }
  70. //判断节点是否文本节点
  71. isTextNode(node){
  72. return node.nodeType === 3
  73. }
  74. //判断节点是否是元素节点
  75. isElementNode(node){
  76. return node.nodeType === 1
  77. }
  78. }

11、双向绑定

  1. class Compiler{
  2. constructor(vm){
  3. this.el = vm.$el
  4. this.vm = vm
  5. this.compile(this.el)
  6. }
  7. //编译模板,处理文本节点和元素节点
  8. compile(el){
  9. let el.childNodes = el.childNodes
  10. Array.from(childNodes).forEach(node =>{
  11. if(this.isTextNode(node){
  12. this.compileText(node)
  13. })else if(this.isElementNode(node)){
  14. this.compileElement(node)
  15. }
  16. //判断node节点是否有子节点,如果有子节点,要递归调用compile
  17. if(node.childNodes && node.childNodes.length){
  18. this.compile(node)
  19. }
  20. })
  21. }
  22. //编译元素节点,处理指令
  23. compileElement(node){
  24. Array.from(node.attributes).forEach(attr =>{
  25. //判断是否是指令
  26. let arrtName = attr.name
  27. if(this.isDirective(attrName)){
  28. attrName = attrName.substr(2)
  29. let key = attr.value
  30. this.update(node,key,attrName)
  31. }
  32. })
  33. }
  34. update(node,key,attrName){
  35. let updateFn = this[attrName + 'Updater']
  36. updateFn && updateFn.call(this,node,this.vm[key],key)
  37. }
  38. //处理v-text指令
  39. textUpdater(node,value,key){
  40. node.textContent = value
  41. new Watcher(this.vm,key,(newValue)=>{
  42. node.textContent = newValue
  43. })
  44. }
  45. //处理v-model
  46. modelUpdater(node,value,key){
  47. node.value = value
  48. new Watcher(this.vm,key,(newValue)=>{
  49. node.value = newValue
  50. })
  51. //双向绑定
  52. node.addEventListener('input',()=>{
  53. this.vm[key] = node.value
  54. })
  55. }
  56. //编译文本节点,处理差值表达式
  57. compileText(node){
  58. //{{ msg }}
  59. let reg = /\{\{(.+?)\}\}/
  60. let value = node.textContent
  61. if(reg.test(value)){
  62. let key = RegExp.$1.trim()
  63. node.textContent = value.replace(reg,this.vm[key])
  64. //创建watcher,当数据改变的时候更新视图
  65. new Watcher(this.vm,key,(newValue)=>{
  66. node.textContent = newValue
  67. })
  68. }
  69. }
  70. //判断元素属性是否是指令
  71. isDirective(attrName){
  72. return attrName.startWith('v-')
  73. }
  74. //判断节点是否文本节点
  75. isTextNode(node){
  76. return node.nodeType === 3
  77. }
  78. //判断节点是否是元素节点
  79. isElementNode(node){
  80. return node.nodeType === 1
  81. }
  82. }

3、Virtual DOM

1、什么是虚拟DOM

虚拟Dom就是一个普通的javascript对象

2、为什么使用虚拟DOM

  1. 手动操作DOM比较麻烦,还需要考虑浏览器兼容性问题,虽然有jQuery等库简化DOM操作,但随着项目的复杂DOM操作复杂提升
  2. 为了简化DOM的复杂操作于是出现了各种MVVM框架,MVVM框架解决了视图和状态的同步问题
  3. 为了简化视图的操作我么使用模板引擎,但是模板引擎没有解决跟踪状态变化的问题,于是Virtual DOM出现了
  4. Virtual DOM的好处是当状态改变时不需要立即更新DOM,只需要创建一个虚拟树来描述DOM,Virtual DOM内部将农村清除如何有效(diff)更新DOM

3、虚拟DOM的作用和虚拟DOM库

只有在DOM操作比较复杂的时候使用虚拟DOM才能提高性能,若DOM操作比较简单,则使用虚拟DOM并不能提高性能

4、创建项目

  1. //md snabbdom-demo
  2. //cd snabbdom-dom
  3. //yarn init -y
  4. //yarn add parcel-bundler
  5. //package.json
  6. "script":{
  7. "dev":"parcel index.html --open"
  8. "build":"parcel build index.html"
  9. }

5、导入Snabbdom

  1. //yarn add snabbdom
  2. import {h,thunk,init} from 'snabbdom'

6、snabbdom的基本用法

  1. import {h,init} from 'snabbdom'
  2. //1.hello world
  3. //参数:数组,模块
  4. //返回值:patch函数,作用对比两个vnode差异更新到真是DOM
  5. let patch = init([])
  6. //第一个参数:标签+选择器
  7. //第二个参数:如果是字符串的话就是标签中的内容
  8. let vnode = h('div#container.cls','Hello World')
  9. let app = document.querySelector('#app')
  10. //第一个参数:可以使DOM元素,内部会把DOM元素转换成VNode
  11. //第二个参数:VNode
  12. //返回值:VNode
  13. let oldVnode = patch(app,vnode)
  14. //假设的时刻
  15. vnode = h('div','Hello Snabbdom')
  16. patch(oldVnode,vnode)
  1. //2.div中放置子元素h1,p
  2. import {h,init} from 'snabbdom'
  3. let patch = init([])
  4. let vnode = h('div#container',[
  5. h('h1','Hello Snabbdom'),
  6. h('p','这是一个P标签')
  7. ])
  8. let app = documnet.querySelector('#app')
  9. let oldVnode = patch(app,vnode)
  10. setTimeout(()=>{
  11. vnode = h('div#container',[
  12. h('h1','Hello World'),
  13. h('p','Hello p')
  14. patch(oldVnode,vnode)
  15. //清空页面元素
  16. patch(oldVnode,h('!'))
  17. ])
  18. },2000)

7、模块

Snabbdom的核心库并不能处理元素属性/样式/事件,如果需要处理的话可以使用模块

常用的模块(官方提供了6个模块)

  1. attributes
  2. props
  3. class
  4. dataset
  5. eventlisteners
  6. style

模块的使用

  1. 导入需要的模块
  2. init()中注册模块
  3. 使用h()函数创建VNode的时候,可以把第二个参数设置为对象,其他参数往后移
  1. import {init,h} from 'snabbdom'
  2. import style from 'snabbdom/modules/style'
  3. import evenlisters from 'snabbdom/modules/evenlisters'
  4. let patch = init([
  5. style,
  6. evenlisters
  7. ])
  8. let vnode = h('div',{
  9. style:{
  10. backgroundColor:'red'
  11. }
  12. on:{
  13. click: eventHandler
  14. }
  15. },[
  16. h('h1','Hello Snabbdom'),
  17. h('p','这是个P标签')
  18. ])
  19. function eventHandler(){
  20. console.log('点击我了')
  21. }
  22. let app = documnet.querySelector('#app')
  23. patch(app,vnode)

8、snabbdom源码解析

如何学习源码:

  1. 先宏观了解
  2. 带着目标源码
  3. 看源码的工程要不求甚解
  4. 调试
  5. 参考资料

snabbdom的核心

  1. 使用h()函数创建javascript对象(VNode)描述真实DOM
  2. init()设置模块,创建patch()
  3. patch()比较新旧两个vnode
  4. 把变化的内容更新到真实DOM树上

9、h函数

  1. h()函数最早见于Hyperscript,使用javaScript创建超文本
  2. Snabbdom中的h()函数不是用来创建超文本,而是创建vnode

函数重载:

概念:

  1. 1. 参数个数或类型不同的函数
  2. 2. javaScript中没有重载的概念
  3. 3. TypeScript中有重载,不过重载的实现还是通过代码调整参数

核心:调用vnode函数返回一个虚拟节点

10、必备快捷键

  1. 光标移动到is,按f2,跳到定义处
  2. alt+方向键左,返回
  3. ctrl+鼠标左键,跳到定义处

11、vnode

vnode函数就是接受一些参数并返回一个Vnode

12、patch的整体过程

  1. patch(oldVnode,newVnode)
  2. 打补丁,把心节点中变化的内容渲染到真实DOM,最后返回新节点作为下一次处理得旧节点
  3. 对比新旧vNode是否相同节点(节点的key和sel相同)
  4. 如果不是相同的节点,删除之前的内容,重新渲染
  5. 如果是相同的节点,再判断新的VNode是否有text,如果有并且和oldValue的text不同,直接更新文本内容
  6. 如果新的VNode有children,判断子节点是否有变化,判断子节点的过程使用的就是diff算法
  7. diff过程值进行同层级比较

13、init

接收参数modules和Domapi,并把所有的模块的钩子函数统一存储到cbs对象中,并返回一个patch函数,init为一个高阶函数

14、patch

对比新老节点直接的差异,并把差异渲染到页面上,同时触发钩子函数

15、createElm

用于创建DOM元素,不会把DOM元素挂载到页面上

16、updateChildren

updatechildren是diff算法的核心,对比新旧节点的children,更新DOM