IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> JavaScript知识库 -> part3-m5 -> 正文阅读

[JavaScript知识库]part3-m5

  • 本阶段围绕当下国内最主流的前端核心框架 Vue.js 展开,深入框架内部,通过解读源码或者手写实现的方式,剖析 Vue.js 框架的内部实现原理,让你做到知其所以然。同时我们还会介绍 Vue.js 的进阶用法、周边生态以及性能优化,让你轻松应对更加复杂的项目业务需求。

模块五 Vue.js 3.0 Composition APIs 及 3.0 原理剖析

  • 本模块中围绕 Vue.js 3.0,重点介绍 Vue.js 3.0 的设计初衷以及 Vue.js 3.0 的新特性,Vue.js 3.0 和 Vue.js 2.0 的对比,Composition APIs 快速入门,Vue.js 3.0 源码解析。让你可以更快上手,把握 Vue.js 3.0 的新特性。

任务一:Vue 3.0介绍

  1. Vue.js 3.0 源码组织方式
  • 源码组织方式的变化
    • Vue3全部采用TypeScript重写,使用Monorepo的方式来组织代码结构,把独立的功能模块都提取到不同的包中。90%以上API都兼容2.x
  • Composition API 根据社区反馈新增了组合API,是为了解决V2.x 开发大型项目时遇到超大组件,options API 不好拆分和重用的问题
  • 性能方面
    • 使用proxy代理对象重写了响应式代码,并且对编译器做了优化,重写了虚拟DOM,从而让渲染和update的性能都有了大幅度提升
    • 官方介绍,服务端渲染的性能也提高了2-3倍
  • Vite
    • 在开发阶段,测试项目的时候,不需要打包,可以直接去运行项目
  • packages 目录结构
    • compiler-core 和平台无关的编译器
    • compiler-dom 浏览器平台下的编译器,依赖前者
    • compiler-sfc single-file-component 用来编译单文件组件,依赖前两者
    • compiler-ssr 服务端渲染编译器,依赖于compiler-dom
    • reactivity 数据响应式系统,可以独立运行
    • runtime-core 和平台无关的运行时
    • runtime-dom 针对浏览器的运行时
    • runtime-test
      • 专门为测试编写的轻量级运行时,这个运行时渲染出来的DOM树,其实是一个js对象,所以这个运行时可以运行在任何js环境中,可以测试渲染是否正确
      • 还可以用于序列化DOM,触发DOM事件,以及记录某次DOM中的更新操作
    • server-renderer 用于服务端渲染
    • shared 是Vue内部使用的一些公共的API
    • size-check 私有的包,不会发布到NPM,在tree sharking 后检查包的大小
    • template-explorer 在浏览器运行的时时编译组件,它会输出render函数
    • vue 构建完整版vue
  1. 不同的构建版本
  • 构建版本
  • packages/vue
  • 分为四类
    • cjs commonJs
      • vue.cjs.js 开发版,代码没有被压缩
      • vue.cjs.prod.js 生成版,被压缩
      • 都是完整版,包含运行时和编译器
    • global
      • vue.global.js
      • vue.global.prod.js
      • vue.runtime.global.js
      • vue.runtime.global.prod.js
      • 前两个完整版vue,包含编译器和运行时,后面两个是只包含运行时的构建版本
      • 这四个都可以在浏览器中通过script标签直接导入,导入js后,会增加一个全局vue对象
    • browser
      • vue.esm-browser.js
      • vue.esm-browser.prod.js
      • vue.runtime.esm-browser.js
      • vue.runtime.esm-browser.prod.js
      • 都包含ESM,浏览器原生模块化方式,在浏览器中可以直接通过script type=module的方式导入这些模块
    • bundler
      • vue.esm-bundler.js
      • vue-runtime.esm-bundler.js
      • 没有打包所有的代码,要配合打包工具来使用,使用ESM模块化的方式
      • 使用脚手架创建的项目中,默认导入第二个,这个文件只有运行时,是vue的最小版本
      • 在项目开发完毕,重新打包的时候,只会打包使用到的代码,可以让vue体积更小
  1. Composition API 设计动机
  • RFC(Request For Comments)
    • 首先官方给出一些提案,然后收集社区反馈并讨论,最后确认
    • http; rfc仓库
  • Composition API RFC 文档,介绍Composition的使用
  • 设计动机
    • Options API
      • 包含一个描述组件选项(data、methods、props等)的对象
      • Options API 开发复杂组件,同一个功能逻辑的代码被拆分到不同选项
      • Options API Demo
        [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8iJX8lCa-1628175828313)(./img/1627893477037.jpg)]
      • 需要在多个选项中增加代码,如果代码比较多,就需要上下拖动滚动条
      • 同一逻辑的代码,被拆分到不同位置,不方便提取重用代码
      • 使用Options API,还难以提取组件中可重用的逻辑,输入Vue2有mixin混入机制,可以把组件中重复的代码提取并重用,但是mixin使用也有问题,比如命名冲突,或者数据来源不清晰
  • Composition API
    • Vue.js 3.0 新增的一组API
    • 一组基于函数的API
    • 可以更灵活的组织组件的逻辑
      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kaPoh3uD-1628175828315)(./img/1627893931881.jpg)]
    • 相比于Options API,这样的好处在于,查看某个逻辑的时候,只需要关注具体的函数即可,当前的逻辑代码,都封装在函数内部
    • 开发的组件中,需要提取可复用的逻辑,这个时候可以选择Composition API
  1. 性能提升
  • 响应式系统升级
    • 使用proxy对象,重写响应式系统
    • Vue.js 2.x 中响应式系统的核心defineProperty
    • Vue.js 3.0 中使用Proxy对象重写响应式系统
      • 可以监听动态新增的属性
      • 可以监听删除的属性
      • 可以监听数组的索引和length属性
    • proxy返回的是一个新对象,可以只操作新对象达到目的,不需要初始化的时候深度遍历监听,性能高于Object.defineProperty;而Object.defineProperty只能遍历对象属性直接修改。有多层属性嵌套,只有访问这属性的时候,才会递归处理下一级的属性。
    • 使用proyx默认就可以监听到动态添加的属性,而Vue2 需要调用Vue.set 方法来处理,
  • 编译优化
    • 通过重写编译过程和优化虚拟DOM,提升渲染性能
    • vue2中渲染的最小单位是组件
    • Vue.js 2.x 中通过标记静态根节点,优化diff的过程,静态节点还需要diff,这个过程没有被优化
    • Vue.js 3.0 中标记和提升所有的静态根节点,diff 的时候只需要对比动态节点内容
      • Fragments(vscode中升级vetur插件,不然模板中没有唯一根节点,提示错误) 片段的特性,模版中不需要创建唯一的一个根节点,模版中可以直接放文本内容或者同级标签
      • 静态提升 静态节点都会被提升到render的外层,只有在初始化的时候才会被创建一次,当再次调用render的时候不需要再次创建静态节点,直接重用上一次创建的vnode
      • Patch flag(标记动态节点) 值为1,表示只有文本内容是动态的,变化时,只会比较文本内容。9代表 text和 props是动态内容
      • 缓存事件处理函数(减少了不必要的更新操作) 首次渲染会生成一个新的函数,会把新的函数缓存到_cache中,缓存的函数,永远不会发生变化,绑定不会发生变化,但是运行这个函数的时候,会去获取最新的 handler,避免来不必要的更新
        [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EE4h5Hlu-1628175828316)(./img/1627896757018.jpg)]
  • 源码体积的优化
    • Vue.js 3.0 中移除了一些不常用的API
      • 例如:inline-template、filer等
    • 通过优化源码体积和更好tree shaking 的支持,减少打包的体积,依赖ESM,模块化的静态语法结构,通过编译阶段的静态分析,找到没有引入的模块,在打包的时候,直接过滤掉。内置的指令如keep-alive 都是按需引入,很多api都是支持tree shaking,一些新增的api,如果没有使用,是不会打包的,默认vue的核心模块都会被打包
  1. Vite (来自于法语,意思是快)
  • Vue 作者开发的构建工具,意味着比过去基于webpack的vue-cli更快
  • ES Module
    • 现代浏览器都支持ESModule (IE不支持)
    • 通过下面的方式加载模块
    • 支持模块的script默认延迟加载
      • 类似于script标签设置defer
      • 在文档解析完成后,触发DOMContentLoaded 事件前执行
    • index.js
    const app = document.querySelector('#app')
    console.log(app.innerHTML)
    
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mtppBeY4-1628175828317)(./img/1627898077785.jpg)]
  1. Vite
  • Vite as Vue-cli
    • Vite 在开发模式下不需要打包可以直接运行,因为在开发模式下使用浏览器原生支持的ESM加载模块。因为不需要打包代码,在开发模式下秒开
      • Vite 会开启一个测试的服务器,会拦截浏览器发送的请求。浏览器会向服务器发送请求,获取相应的模块,vite会对浏览器不识别的模块进行处理
      • 比如import单文件组件的时候,后缀为.vue,会在服务器上,对.vue文件进行编译,把编译结果返回给浏览器
      • .vue的会修改响应头的content-type:application/javascript,Response 已经是编译好的js代码
      • 如果是单文件组件,会调用compiler-sfc编译单文件组件,并把编译的结果返回给浏览器
    • Vite 特点
      • 快速冷启动
      • 按需编译
      • 模块热更新 模块热更新的性能与总数无关
    • Vite在生产环境下使用Rollup打包
      • 基于 ESModule的方式打包,不需要使用babel 把inport转换为require以及一些辅助函数,因此打包体积更小
    • Vue-CLI 开发模式下必须对项目打包才可以运行
      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3Go8kztH-1628175828318)(./img/1627898902125.jpg)]

任务二:Composition API

  1. Composition API

  2. 生命周期钩子函数

<div id="app">
    {{ x }}
    {{ y }}
</div>
<script type="module">
    // options api 生命周期的钩子函数转换到 setup 中的写法,只需要在钩子面前加个on,然后首字母大写
    import { createApp, reactive, onMounted, onUnmounted, toRefs } from './node_modules/vue/dist/vue.esm-browser.js'
    function useMousePosition() {
        const position = reactive({  // 创建响应式对象
            x: 0,
            y: 0
        })
        const update = e => {
            position.x = e.pageX
            position.y = e.pageY
        }
        onMounted(() => {
            window.addEventListener('mousemove', update)
        })
        onUnmounted(() => {
            window.removeEventListener('mousemove', update)
        })
        // return position
        return toRefs(position)
    }
    const app = createApp({ // 创建vue实例
        // 第一个参数 props
        // 第二个参数 context,attrs、emit、slots
        setup() { // Composition API 入口
            // const position = useMousePosition()
            const { x, y } = useMousePosition()
            return {
                x,
                y
            }
        }
    })
    app.mount('#app')
</script>
  1. reactive-toRefs-ref
  • reactive 通过proxy把一个对象包装为响应式对象
  • toRefs 可以把一个响应式对象中的所有属性都转换为响应式的带有value属性的对象
    • 要求传入的必须是一个代理对象
    • 内部会创建一个新的对象,然后遍历这个代理对象的所有属性,把所有属性的值都转换为响应式对象,挂载到新创建的对象上,最后返回新创建的对象
    • 内部会为代理对象的每一个属性创建一个具有value属性的对象,该对象是响应式的,vulue属性有get和set
    • 通过 toRefs 处理 reactive 返回对象,可以进行解构操作
  • ref 能把基本类型的数据包装成响应式对象
    • ref 有一个vulue属性,如果在模版中使用可以省略。在Composition API必须要。
    • 如果传入是个对象,调用reactive方法,如果基本类型,则创建一个只有value属性的对象,在value属性的get,set方法中处理值
    const count = ref(0)
    count.value++
    
  1. computed
  • 第一种用法
computed(() => count.value + 1)
  • 第二种用法
const count = ref (1)
const plusOne = computed({
    get: () => count.value + 1,
    set: val => {
        count.value = val - 1
    }
})
  1. Watch
  • Watch 的三个参数
    • 第一个参数:要监听的数据 是 ref或者reactive返回的对象
    • 第二个参数:监听到数据变化后执行的函数,这个函数有两个值分别是新值和旧值
    • 第三个参数:选项对象,deep 和immediate
  • Watch 的返回值
    • 取消监听的函数
  1. watchEffect
  • 是watch函数的简化版本,也用来监视数据的变化
  • 接收一个函数作为参数,监听函数内响应式数据的变化,也会返回一个取消监听的函数
  1. todolist-功能演示
  • 添加待办事项
  • 删除待办事项
  • 编辑待办事项
  • 切换待办事项
  • 存储待办事项
  1. todolist-项目结构
  • 使用vue-cli创建vue3项目(需4.5以上版本)vue create my-project
  1. todolist-添加待办事项

  2. todolist-删除待办事项

  3. todolist-编辑待办事项

  • 编辑待办事项
    • 双击待办项,展示编辑文本框
    • 按回车或者编辑文本框失去焦点,修改数据
    • 按ese取消编辑
    • 把编辑文本框清空按回车,删除这一项
    • 显示编辑文本框的时候获取焦点
  1. todolist-编辑待办事项-编辑文本框获取焦点

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BHo8ZQNK-1628175828320)(./img/1628037206241.jpg)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3N2UFnJE-1628175828320)(./img/1628037282688.jpg)]

  • 第二个参数是函数的时候用法vue2和vue3是一样的,vue3这个函数是在mounted 和uodated 之间去执行,和vue2执行时机一样,vue2这个函数是在bind和update的时候执行,el是指令绑定的元素,binding这个元素可以获取指令对应的值
  1. todolist-切换待办事项-演示效果
  • 切换待办事项状态
    • 点击checkbox,改变所有待办项状态
    • All/Active/Completed
    • 其它
      • 显示未完成待办项个数
      • 移除所有完成的项目
      • 如果没有待办项,隐藏main 和footer
  1. todolist-切换待办事项-改变待办事项完成状态

  2. todolist-切换待办事项-切换状态

  3. todolist-切换待办事项-其它

  4. todolist-存储待办事项

<template>
  <section id="app" class="todoapp">
    <header class="header">
      <h1>todos</h1>
      <input
        class="new-todo"
        placeholder="What needs to be done?"
        autocomplete="off"
        autofocus
        v-model="input"
        @keyup.enter="addTodo"
        >
    </header>
    <section class="main" v-show="count">
      <input id="toggle-all" class="toggle-all" v-model="allDone" type="checkbox">
      <label for="toggle-all">Mark all as complete</label>
      <ul class="todo-list">
        <li
          v-for="todo in filteredTodos"
          :key="todo"
          :class="{ editing: todo === editingTodo, completed: todo.completed }"
        >
          <div class="view">
            <input class="toggle" type="checkbox" v-model="todo.completed">
            <label @dblclick="editTodo(todo)">{{ todo.text }}</label>
            <button class="destroy" @click="remove(todo)"></button>
          </div>
          <input
            class="edit"
            type="text"
            v-editing-focus="todo === editingTodo"
            v-model="todo.text"
            @keyup.enter="doneEdit(todo)"
            @blur="doneEdit(todo)"
            @keyup.esc="cancelEdit(todo)"
            >
        </li>
      </ul>
    </section>
    <footer class="footer" v-show="count">
      <span class="todo-count">
        <strong>{{ remainingCount }}</strong> {{ remainingCount > 1 ? 'items' : 'item' }} left
      </span>
      <ul class="filters">
        <li><a href="#/all">All</a></li>
        <li><a href="#/active">Active</a></li>
        <li><a href="#/completed">Completed</a></li>
      </ul>
      <button class="clear-completed" @click="removeCompleted" v-show="count > remainingCount">
        Clear completed
      </button>
    </footer>
  </section>
  <footer class="info">
    <p>Double-click to edit a todo</p>
    <!-- Remove the below line ↓ -->
    <p>Template by <a href="http://sindresorhus.com">Sindre Sorhus</a></p>
    <!-- Change this out with your name and url ↓ -->
    <p>Created by <a href="https://www.lagou.com">教瘦</a></p>
    <p>Part of <a href="http://todomvc.com">TodoMVC</a></p>
  </footer>
</template>
<script>
import './assets/index.css'
import useLocalStorage from './utils/useLocalStorage'
import { ref, computed, onMounted, onUnmounted, watchEffect } from 'vue'

const storage = useLocalStorage()

// 1. 添加待办事项
const useAdd = todos => {
  const input = ref('')
  const addTodo = () => {
    const text = input.value && input.value.trim()
    if (text.length === 0) return
    todos.value.unshift({
      text,
      completed: false
    })
    input.value = ''
  }
  return {
    input,
    addTodo
  }
}

// 2. 删除待办事项
const useRemove = todos => {
  const remove = todo => {
    const index = todos.value.indexOf(todo)
    todos.value.splice(index, 1)
  }
  const removeCompleted = () => {
    todos.value = todos.value.filter(todo => !todo.completed)
  }
  return {
    remove,
    removeCompleted
  }
}

// 3. 编辑待办项
const useEdit = remove => {
  let beforeEditingText = ''
  const editingTodo = ref(null)

  const editTodo = todo => {
    beforeEditingText = todo.text
    editingTodo.value = todo
  }
  const doneEdit = todo => {
    if (!editingTodo.value) return
    todo.text = todo.text.trim()
    todo.text || remove(todo)
    editingTodo.value = null
  }
  const cancelEdit = todo => {
    editingTodo.value = null
    todo.text = beforeEditingText
  }
  return {
    editingTodo,
    editTodo,
    doneEdit,
    cancelEdit
  }
}

// 4. 切换待办项完成状态
const useFilter = todos => {
  const allDone = computed({
    get () {
      return !todos.value.filter(todo => !todo.completed).length
    },
    set (value) {
      todos.value.forEach(todo => {
        todo.completed = value
      })
    }
  })

  const filter = {
    all: list => list,
    active: list => list.filter(todo => !todo.completed),
    completed: list => list.filter(todo => todo.completed)
  }
  const type = ref('all')
  const filteredTodos = computed(() => filter[type.value](todos.value))
  const remainingCount = computed(() => filter.active(todos.value).length)
  const count = computed(() => todos.value.length)

  const onHashChange = () => {
    const hash = window.location.hash.replace('#/', '')
    if (filter[hash]) {
      type.value = hash
    } else {
      type.value = 'all'
      window.location.hash = ''
    }
  }

  onMounted(() => {
    window.addEventListener('hashchange', onHashChange)
    onHashChange()
  })

  onUnmounted(() => {
    window.removeEventListener('hashchange', onHashChange)
  })

  return {
    allDone,
    count,
    filteredTodos,
    remainingCount
  }
}

// 5. 存储待办事项
const useStorage = () => {
  const KEY = 'TODOKEYS'
  const todos = ref(storage.getItem(KEY) || [])
  watchEffect(() => {
    storage.setItem(KEY, todos.value)
  })
  return todos
}

export default {
  name: 'App',
  setup () {
    const todos = useStorage()

    const { remove, removeCompleted } = useRemove(todos)

    return {
      todos,
      remove,
      removeCompleted,
      ...useAdd(todos),
      ...useEdit(remove),
      ...useFilter(todos)
    }
  },
  directives: {
    editingFocus: (el, binding) => {
      binding.value && el.focus()
    }
  }
}
</script>

<style>
</style>

任务三:Vue.js 3.0 响应式系统原理

  1. 响应式系统原理-介绍
  • Vue.js 响应式回顾
    • proxy 对象实现属性监听
    • 多层属性嵌套,在访问属性过程中处理下一级属性
    • 默认监听动态添加的属性
    • 默认监听属性的删除操作
    • 默认监听数组索引和length属性
    • 可以作为单独的模块使用
  • 核心函数
    • reactive/ref/toRefs/computed
    • effect
    • track
    • trigger
  1. 响应式系统原理-Proxy对象回顾
'use strict'
// 问题1: set 和 deleteProperty 中需要返回布尔类型的值
//        在严格模式下,如果返回 false 的话会出现 Type Error 的异常
const target = {
  foo: 'xxx',
  bar: 'yyy'
}
// Reflect.getPrototypeOf()
// Object.getPrototypeOf()
const proxy = new Proxy(target, {
  get (target, key, receiver) {
    // return target[key]
    return Reflect.get(target, key, receiver)
  },
  set (target, key, value, receiver) {
    // target[key] = value
    return Reflect.set(target, key, value, receiver)
  },
  deleteProperty (target, key) {
    // delete target[key]
    return Reflect.deleteProperty(target, key)
  }
})
proxy.foo = 'zzz'
// delete proxy.foo
// 问题2:Proxy 和 Reflect 中使用的 receiver
// Proxy 中 receiver:Proxy 或者继承 Proxy 的对象
// Reflect 中 receiver:如果 target 对象中设置了 getter,getter 中的 this 指向 receiver
const obj = {
  get foo() {
    console.log(this)
    return this.bar
  }
}
const proxy = new Proxy(obj, {
  get (target, key, receiver) {
    if (key === 'bar') { 
      return 'value - bar' 
    }
    return Reflect.get(target, key, receiver)
  }
})
console.log(proxy.foo)
  1. 响应式系统原理-reactive
  • reactive
    • 接收一个参数,判断这个参数是否是对象
    • 创建拦截器对象handler,设置get/set/deleteProperty
    • 返回这个对象
  1. 响应式系统原理-收集依赖

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Zinxg7NB-1628175828321)(./img/1628135478749.jpg)]

  1. 响应式系统原理-effect-track
<script type="module">
  import { reactive, effect } from './reactivity/index.js'

  const product = reactive({
    name: 'iPhone',
    price: 5000,
    count: 3
  })
  let total = 0 
  effect(() => {
    total = product.price * product.count
  })
  console.log(total)

  product.price = 4000
  console.log(total)

  product.count = 1
  console.log(total)

</script>
  1. 响应式系统原理-trigger

  2. 响应式系统原理-ref

  • reactive vs ref
    • ref 可以把基本数据类型数据,转换为响应式对象
    • ref 返回的对象,重新赋值为对象也是响应式的
    • reactive 返回的对象,重新赋值丢失响应式
    • reactive 返回的对象不可以解构
    • 如果一个对象成员比较多,使用ref响应式并不方便
<script type="module">
  import { reactive, effect, ref } from './reactivity/index.js'

  const price = ref(5000)
  const count = ref(3)
 
  let total = 0 
  effect(() => {
    total = price.value * count.value
  })
  console.log(total)

  price.value = 4000
  console.log(total)

  count.value = 1
  console.log(total)

</script>
  1. 响应式系统原理-toRefs
  • toRefs 的作用reactive对象的每一个属性转换为类似ref返回的对象,这样就可以对reactive对象进行解构
<script type="module">
  import { reactive, effect, toRefs } from './reactivity/index.js'

  function useProduct () {
    const product = reactive({
      name: 'iPhone',
      price: 5000,
      count: 3
    })
    
    return toRefs(product)
  }

  const { price, count } = useProduct()

  let total = 0 
  effect(() => {
    total = price.value * count.value
  })
  console.log(total)

  price.value = 4000
  console.log(total)

  count.value = 1
  console.log(total)

</script>
  1. 响应式系统原理-computed
<script type="module">
  import { reactive, effect, computed } from './reactivity/index.js'

  const product = reactive({
    name: 'iPhone',
    price: 5000,
    count: 3
  })
  let total = computed(() => {
    return product.price * product.count
  })
 
  console.log(total.value)

  product.price = 4000
  console.log(total.value)

  product.count = 1
  console.log(total.value)

</script>
// ./reactivity/index.js
const isObject = val => val !== null && typeof val === 'object'
const convert = target => isObject(target) ? reactive(target) : target
const hasOwnProperty = Object.prototype.hasOwnProperty
const hasOwn = (target, key) => hasOwnProperty.call(target, key)

export function reactive (target) {
  if (!isObject(target)) return target

  const handler = {
    get (target, key, receiver) {
      // 收集依赖
      track(target, key)
      const result = Reflect.get(target, key, receiver)
      return convert(result)
    },
    set (target, key, value, receiver) {
      const oldValue = Reflect.get(target, key, receiver)
      let result = true
      if (oldValue !== value) {
        result = Reflect.set(target, key, value, receiver)
        // 触发更新
        trigger(target, key)
      }
      return result
    },
    deleteProperty (target, key) {
      const hadKey = hasOwn(target, key)
      const result = Reflect.deleteProperty(target, key)
      if (hadKey && result) {
        // 触发更新
        trigger(target, key)
      }
      return result
    }
  }

  return new Proxy(target, handler)
}

let activeEffect = null
export function effect (callback) {
  activeEffect = callback
  callback() // 访问响应式对象属性,去收集依赖
  activeEffect = null
}

let targetMap = new WeakMap()

export function track (target, key) {
  if (!activeEffect) return
  let depsMap = targetMap.get(target)
  if (!depsMap) {
    targetMap.set(target, (depsMap = new Map()))
  }
  let dep = depsMap.get(key)
  if (!dep) {
    depsMap.set(key, (dep = new Set()))
  }
  dep.add(activeEffect)
}

export function trigger (target, key) {
  const depsMap = targetMap.get(target)
  if (!depsMap) return
  const dep = depsMap.get(key)
  if (dep) {
    dep.forEach(effect => {
      effect()
    })
  }
}

export function ref (raw) {
  // 判断 raw 是否是ref 创建的对象,如果是的话直接返回
  if (isObject(raw) && raw.__v_isRef) {
    return
  }
  let value = convert(raw)
  const r = {
    __v_isRef: true,
    get value () {
      track(r, 'value')
      return value
    },
    set value (newValue) {
      if (newValue !== value) {
        raw = newValue
        value = convert(raw)
        trigger(r, 'value')
      }
    }
  }
  return r
}

export function toRefs (proxy) {
  const ret = proxy instanceof Array ? new Array(proxy.length) : {}

  for (const key in proxy) {
    ret[key] = toProxyRef(proxy, key)
  }

  return ret
}

function toProxyRef (proxy, key) {
  const r = {
    __v_isRef: true,
    get value () {
      return proxy[key]
    },
    set value (newValue) {
      proxy[key] = newValue
    }
  }
  return r
}

export function computed (getter) {
  const result = ref()

  effect(() => (result.value = getter()))

  return result
}

任务四:Vite 实现原理

  1. Vite
  • Vite 概念
    • Vite 是一个面向现代浏览器的一个更轻、更快的web应用开发工具
    • 它基于ECMAScript 标准原生模块系统 (ES Module)实现
    • 它的出现是为了解决wabpack在开发阶段使用webpack devserver冷启动时间过长,另外,webpack HMR热更新速度慢的问题
    • 使用 Vite创建的项目,就是一个普通的Vue3应用,相比与vue-cli创建的项目,也少了很多配置和依赖
  • Vite 项目依赖
    • Vite 命令行工具
    • @vue/compiler-sfc 用来编译.vue结尾的单文件组件,vue2使用的是vue-template-compiler
    • 只支持vue3,也可以通过模版创建其它类型项目
  • 基础使用
    • vite serve
    • vite build
      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-e5b09BQo-1628175828322)(./img/1628154064066.jpg)]
      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-neynugIs-1628175828323)(./img/1628154099071.jpg)]
  • HMR
    • Vite HMR 立即编译当前所修改的文件
    • webpack HMR
      • 会自动以这个文件为入口重写build一次,所有的涉及到的依赖也都会被加载一遍
  • Build
    • vite build
      • Rollup 最终还是会把文件打包编译到一起,对于代码切割的需求,Vite采用的是原生的动态导入的特性实现的,打包结果只支持现代浏览器,动态导入有相应的Polyfill
      • Dynamic import(动态导入)
        • Polyfill
  • 打包 or 不打包
    • 使用 Webpack 打包的两个原因
      • 浏览器环境并不支持模块化 (IE11不支持)
      • 零散的模块文件会产生大量的HTTP请求 (http2 已经解决,可以复用链接)
  • 开箱即用
    • TypeScript 内置支持
    • less/sass/stylus/postcss- 内置支持(需要单独安装)
    • jsx
    • Web Assembly
  • Vite 特性
    • 快速冷启动
    • 模块热更新
    • 按需编译
    • 开箱即用
  1. Vite 实现原理-静态Web服务器
  • Vite 核心功能
    • 静态Web服务器
    • 编译单文件组件
      • 拦截浏览器不识别模块,并处理
    • HMR
  • 步骤
    1. 安装依赖 koa koa-send
    2. 配置 package.json
    {
      "name": "vite-cli",
      "bin": "index.js"
    }
    
    1. 编写 index.js
    2. 把写好的包链接到npm npm link
    3. 到需要开启静态web服务器项目的根目录下执行 vite-cli
  1. Vite 实现原理-修改第三方模块的路径

  2. Vite 实现原理-加载第三方模块

  3. Vite 实现原理-编译单文件组件

  • npm i @vue/compiler-sfc
  1. Vite 实现原理-编译单文件组件
#!/usr/bin/env node
const path = require('path')
const { Readable } = require('stream')
const Koa = require('koa')
const send = require('koa-send')
const compilerSFC = require('@vue/compiler-sfc')

const app = new Koa()

const streamToString = stream => new Promise((resolve, reject) => {
  const chunks = []
  stream.on('data', chunk => chunks.push(chunk))
  stream.on('end', () => resolve(Buffer.concat(chunks).toString('utf-8')))
  stream.on('error', reject)
})

const stringToStream = text => {
  const stream = new Readable()
  stream.push(text)
  stream.push(null)
  return stream
}

// 3. 加载第三方模块
app.use(async (ctx, next) => {
  // ctx.path --> /@modules/vue
  if (ctx.path.startsWith('/@modules/')) {
    const moduleName = ctx.path.substr(10)
    const pkgPath = path.join(process.cwd(), 'node_modules', moduleName, 'package.json')
    const pkg = require(pkgPath)
    ctx.path = path.join('/node_modules', moduleName, pkg.module)
  }
  await next()
})

// 1. 静态文件服务器
app.use(async (ctx, next) => {
  await send(ctx, ctx.path, { root: process.cwd(), index: 'index.html' })
  await next()
})

// 4. 处理单文件组件
app.use(async (ctx, next) => {
  if (ctx.path.endsWith('.vue')) {
    const contents = await streamToString(ctx.body)
    const { descriptor } = compilerSFC.parse(contents)
    let code
    if (!ctx.query.type) {
      code = descriptor.script.content
      // console.log(code)
      code = code.replace(/export\s+default\s+/g, 'const __script = ')
      code += `
      import { render as __render } from "${ctx.path}?type=template"
      __script.render = __render
      export default __script
      `
    } else if (ctx.query.type === 'template') {
      const templateRender = compilerSFC.compileTemplate({ source: descriptor.template.content })
      code = templateRender.code
    }
    ctx.type = 'application/javascript'
    ctx.body = stringToStream(code)
  }
  await next()
})

// 2. 修改第三方模块的路径
app.use(async (ctx, next) => {
  if (ctx.type === 'application/javascript') {
    const contents = await streamToString(ctx.body)
    // import vue from 'vue'
    // import App from './App.vue'
    ctx.body = contents
      .replace(/(from\s+['"])(?![\.\/])/g, '$1/@modules/')
      .replace(/process\.env\.NODE_ENV/g, '"development"')
  }
})

app.listen(3000)
console.log('Server running @ http://localhost:3000')
  JavaScript知识库 最新文章
ES6的相关知识点
react 函数式组件 & react其他一些总结
Vue基础超详细
前端JS也可以连点成线(Vue中运用 AntVG6)
Vue事件处理的基本使用
Vue后台项目的记录 (一)
前后端分离vue跨域,devServer配置proxy代理
TypeScript
初识vuex
vue项目安装包指令收集
上一篇文章      下一篇文章      查看所有文章
加:2021-08-06 09:31:55  更:2021-08-06 09:33:07 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/17 11:51:01-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码