vue笔记待整理

  1. computed会缓存。vue.use()参数如果是函数直接执行,是对象则调用。会给vue绑定$route、$router。
{
  path: 'detail/:id',
  name: 'Detail',
  props: true, // 开启props会把url参数传给组件(推荐),也可以用$route.params.id获取
  component: () => import("../views/Detail.vue"), // 需要才加载
}
  1. hash路由有#,history没有#需要服务端支持。history原理是h5的history api,history.pushState()IE10才支持,不会发起请求只会改地址并记录到历史记录。
  2. 由于服务端匹配不到对应页面会返回404,需要设置如果不是静态资源则全都返回index.html。{path: '*', name: '404', component: '404.vue'},然后new VueRouter({mode: 'history'})。部署到服务器上后刷新页面会404。
  3. nginx存放的目录不能有中文,start nginx启动,修改配置需要重启nginx -s reload,停止nginx -s stop。需要将打包的文件放在nginx安装目录里的html文件夹里。nginx默认启用80端口,nginx.conf可以修改server端口。修改后记得重启。
location / {
  root html; // 根文件夹
  index index.html index.html; // 默认访问页面
  try_files $uri $uri/ /index.html; // 尝试去访问输入的url,如果有直接返回,没有访问index.html
}
  1. hash模式监听hashchange。history监听popstate,当调用push和replace不会触发,当调用back和forward或者点击前进后退的时候能触发。
  2. Vue的强大在于它的插件,可以通过Vue.use()传入对象就执行对象的install方法。
    所以Vue-router是个类有该方法,而且能new VueRouter所以他的构造函数能传入一些规则。有以下字段options、data、routeMap,和方法Constructor(Options)、_install(Vue)、init()、initEvent()、createRouteMap()、initComponents(Vue)
  3. Vue的构建版本:运行时版不支持template模板,需要打包的时候提前编译。完整版包含运行时和编译器,体积比运行时版本大10K左右,程序运行的时候把template转换成render函数(创建虚拟Dom)。vue-cli默认时创建用的运行时。
  4. 新增vue.config.js增加module exports = {runtimeCompiler: true},默认是false不带编译器,改成true则当编译器不用自己写render。
  5. 数据驱动:数据响应式(改数据dom自动刷新)、双向绑定(视图改变数据改变,反之亦然)。vue2响应式原理:当访问和设置的时候,用Object.defineProperty(vm, key, {})改set/get。vue3响应式原理:let vm = new Proxy(data, {get(targetObj, key), set(targetObj, key, newValue)})es6新增,ie不支持。proxy可以监听整个对象的属性,不需要像defineProperty要去循环遍历。
  6. 发布订阅模式:将事情发布给事件中心触发,订阅者收到做相应处理;vm.$on('eventName', ()=>{}), vm.$emit('eventName', () =>{})。观察者模式:直接存储所有观察者(订阅者),当事件发生的时候调用所有观察者的update方法。前者用事件中心隔离了两者,后者没有事件中心相互依赖。
  7. Vue处理->Observer数据劫持(监听变化)->Dep发布者->调用Watcher观察者里的update(渲染视图);Vue->Compiler解析指令和插值表达式->Watcher处理。
  8. Vue类要做的事是负责接受参数选项,把data中的属性注入到Vue实例转换成setter和getter,调用observer监听data所有属性变化,调用compiler解析指令和差值表达式。
  9. compiler负责编译模板,解析指令/差值表达式;负责页面的首次渲染;当数据变化后重新渲染视图。
compile(el) {
  let childNodes = el.childNodes // children是子元素,childNodes是子节点包括文本
  Array.from(childNodes).forEach(node => { // 类数组对象需要转换为数组才能调用数组方法
    if (this.isTextNode(node)) {
      this.compileText(node)
    } else if (this.isElementNode(node)) {
      this.compileElement(node)
    }

    // 如果还有子节点,要递归遍历
    if (node.childNodes && node.childNodes.length) {
      this.compile(node)
    }
  })
}

compileText(node) {
  console.dir(node) // 将节点以对象形式展示
  // 处理 {{ msg }}
  let reg = /\{\{(.+?)\}\}/
  let value = node.textContent
  if (reg.test(value)) {
    let key = RegExp.$1.trim()
    node.textContent = value.replace(reg, this.vm[key])
  }
}
  1. 数据改变修改视图:Dep类的getter会收集依赖观察者,即收集所有依赖该属性的地方。setter通知依赖的地方变更触发观察者update。数据变更的时候要变更视图,所以要在视图变更的地方即操作dom的地方去创建watcher。
  2. 真实dom的对象特别多,可以创建一个对象来模拟某些必要属性。虚拟dom可以维护程序的状态,跟踪上次状态,对比状态差异只更新发生变化的dom。而以前是直接整个dom做替换。作用是:(1)维护视图和状态的关系(2)复杂视图下可以提升渲染性能,简单视图需要转化为虚拟dom对象可能更耗时(3)跨平台,因为虚拟dom是对象而不是dom,所以方便在不同平台上做处理(如小程序、原生应用,服务端渲染ssr、浏览器渲染)
  3. Snabbdom是虚拟dom开源库,代码只有200行易扩展易懂。npm install parcel-bundler -D,然后配置scripts,新建src/basicusage.js导入init和h(注意node12/webpack5之后才支持exports所以路径引用需要自行修改为真实路径)
"scripts": {
  "dev": "parcel index.html --open", // 自动打开浏览器
  "build": "parcel build index.html"
}
const {h} = require('snabbdom/build/package/h')
const {init} = require('snabbdom/build/package/init')

const patch = init([])
// h函数和vue中的一样,第一个参数是标签+选择器(可用于更改类名),第二个参数是文本(字符串)/子元素(数组)
let vnode = h('div#container.cls', 'hello world')
let app = document.querySelector('#app')
// 第一个参数旧的vnode或者dom元素,第二个参数是新的vnode,返回一个新的vnode
let oldVnode = patch(app, vnode)
vnode = h('div#container.xxx', 'Hello Snabbdom')
/*
vnode = h('div#container', [
  h('h1', 'hello world'),
  h('p', 'hello p')
])
*/
patch(oldVnode, vnode) // 对比差异,更新到真实dom上
patch(oldVnode, h('!')) // 清除元素,!是空的注释节点
  1. snabbdom的核心库并不能处理dom的属性/样式/事件等,可以通过注册Snabbdom默认提供的模块来实现。模块可以用于扩展snabbdom的功能,是通过注册全局钩子函数来实现的。a.导入模块;b.init注册模块;c. h函数第二个参数(对象)处使用模块。
import {init} from 'snabbdom/build/init'
import {h} from 'snabbdom/build/h'

import {styleModule} from 'snabbdom/build/modules/style'
import {eventListenersModule} from 'snabbdom/build/modules/eventlisteners'

const patch = init([
  styleModule,
  eventListenersModule
])

let vnode = h('div', [
  h('h1', {style: {backgroundColor: 'red'}}, 'hello world'),
  h('p', {on: {click: eventHandle}}, 'click me')
])

function eventHandle() {
  alert('你很棒')
}

const app = document.querySelector('#app')
patch(app, vnode)
  1. 看源码要带着问题去看,了解主线再看细节。Snabbdom的核心:
  • init()设置模块,创建patch()函数
  • 使用h()函数创建js对象(VNode)描述真实DOM
  • patch()比较新旧两个VNode
  • 把变化的内容更新到真实DOM树
  1. js没有重载,后面声明的会覆盖前面的函数。ts有重载,参数个数或者类型不同(即使名字相同)则认为两个不同函数。snabbdom的h函数定义了四个h函数和一个真正的h函数,真正的h函数通过判断参数个数和类型去决定调用前面四个哪个h函数。
  2. 按F12或者ctrl+鼠标左键可以跳转到定义位置;alt+←可以后退到上一页,alt+→可以前进。
  3. patch(oldVnode, newVnode)把新旧节点中变化的内容渲染到真实DOM,最后返回新节点作为下一次处理的旧节点。
  • 先判断新旧VNode是否相同节点(节点的key和sel选择器是否相同)。
  • 如果不是相同节点,删除之前的内容,重新渲染。
  • 如果是相同节点,再判断新VNode是否有text,如果有并且和oldVNode的text不同,直接更新文本内容。
  • 如果新的VNode有children,判断子节点是否有变化。
  1. init(modules, api)第二个参数可以指定VNode转换为对应平台的api,默认是转成DOM。第一个是钩子函数数组。返回一个patch函数,主要是先缓存了前两个配置项,这样后续用patch不用老是传。
  2. patch先看传入是否VNode,不是先生成VNode。ts变量加!表示一定有值,obj.data!.hook!.fn()表示有值才调用后面方法。obj?.fn如果有obj返回obj.fn否则返回undefined。控制台点击行号打断点然后F5刷新运行,F11一句句运行,F10跳过某个方法。
  3. diff算法,虚拟dom不触发浏览器重绘重排,直接对比js描述的节点信息。最麻烦的是每个节点对比,snabbdom做了优化只对比树的同级节点,因为dom操作很少跨级别操作节点。
  4. 先比较oldStart和newStart,直到不相同;比较oldEnd和newEnd;再比较oldStart和newEnd;再比较oldEnd和newStart;都不同相同则遍历。当老节点或新节点的所有子节点全部遍历完(oldStart>oldEnd或者newStart>newEnd),则循环结束。
  • 新旧开始节点如果是sameVnode(key和sel相同),会重用旧的dom节点只是修改内容,调用patchVnode()对比和更新节点。把旧开始和新开始索引往后移动。
  • 旧开始和新结束如果相同,调用patchVnode()对比和更新,把oldStart对应的dom元素移动到oldEnd之后(因为旧开始和新结束是相同节点),并且oldStart++,newEnd--。
  • 旧结束和新开始,调用patchVnode()对比和更新,把oldEnd对应的dom元素移动到oldStart之前,并且oldEnd--,newStart++。
  • 如果都不相同,则遍历旧节点中是否有newStart相同节点,有的话对比更新,并把对应节点移到oldStart之前,清空旧节点索引;如果没有,则直接在oldStart之前创建新的节点。

全部遍历完之后若老节点有剩余则删除,若新节点有剩余则把新节点加入到老节点中。

  1. 不设置key,那么所有节点key都是undefined是相同的,会直接替换节点内容(sel也相同的前提下)。设置了key那么只有key才会对比和移动元素。如果不设置key可能会导致渲染错误,因为只是替换了节点的内容,但绑在节点上的属性数据都没变,如果checkbox的话那旧节点选中也会导致新节点选中。如果有key则认为不是相同节点会重新创建。

// 为啥watcher里不直接addSub而要通过Dep,compileText里每次都新建watcher吗,差值表达式如何支持表达式

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 第二次来梳理Vue源码逻辑了。第一次因为不熟悉,梳理的很细致才弄懂。第二次就更有大局观一些了,这次我主要抓住流程的...
    LoveBugs_King阅读 5,781评论 1 5
  • Vue原理 未经允许 禁止转载 MVVM 数据驱动视图 传统组件只是静态渲染,更新还要依赖于操作DOM vue M...
    吾名刘斩仙阅读 1,417评论 0 0
  • 一、对于MVVM的理解 MVVM是Model-View -ViewModel的缩写 Model:数据模型,定义数据...
    kallenqi阅读 1,774评论 1 2
  • 互联网寒冬的来袭,如何在面试之前更高效的做好面试准备,是程序员共同关注的问题。 现在面试门槛越来越高,很多开发者对...
    you的日常阅读 4,601评论 0 7
  • Vue原理 组件化和MVVM 数据驱动视图 传统组件,只是静态渲染,更新还要依赖操作DOM 数据驱动视图 - vu...
    中原丶吴彦祖阅读 708评论 0 0