✨
blog
  • Blog
  • Element-UI
    • 2019-09-04
  • JS
    • ES6 之 Set 和 Map
    • let 和 const 声明常见概念
    • 元编程
    • ES6之字符串的扩展
    • ES6 之异步流程的前世今生(上)
    • ES6之异步流程的前世今生(下)
    • ES6 之模块你知吗
    • ES6 之解构赋值与箭头函数的妙用
    • 迭代器
    • ES5 之原型(一)
    • ES6之类(二)
    • es7之装饰器
    • es6之数组详解
    • js之this指向
    • 对象
    • vue项目配合使用canvas联动
    • 本文解决痛点:对象里面是否有值
  • MAC
    • vue源码之method
    • Mac的使用技巧
    • 前文
    • Mac常用软件(二)
    • 如何查看 Mac 端口号以及占用情况
  • Node
    • Node之Buffer详解
    • 浏览器与 node 的事件循环(event loop)有何区别
    • Node之多线程
    • node之模块解析(一)
    • 错误捕获与内存告警
  • TS
    • Record
    • 使用方法
    • 工具泛型
    • 类型体操
    • 泛型
  • chrome
    • v8 引擎
    • v8 垃圾回收机制
    • 浏览器的知识
  • flutter
    • 路由
    • 页面布局
  • go
    • index
  • html&css
    • 两栏布局
    • ES5和ES6的区别
    • ES5 和 ES6 的区别
    • HTTP详解
    • TCP 与 UDP 的区别
    • MDN
    • css modules 使用教程
    • css 居中
    • 拖拽
    • flex布局
    • h5 新增特性 html5
    • history 与 hash 路由策略
    • position 定位方式
    • rem布局
    • svg
    • web性能优化
    • 事件循环
    • 从输入网址后发生了什么
    • 前端状态管理
    • 圣杯布局与双飞翼
    • 性能优化 页面的性能统计指标
    • 本地存储的几种对比
    • 浏览器的渲染进程
    • 浏览器缓存策略详解
    • 盒模型
    • 为什么要移动端适配
    • 跨域的 N 种实现方式
  • web3
    • 常见概念
    • vue项目配合使用canvas联动
  • webgl
    • Mac使用技巧(二)
    • Node之模块解析path
  • 代码库
    • documeng的一些常见操作
    • eventBus事件
    • jquery提交
    • jquery的一些常见操作
    • 常见操作
    • 数组polyfill
    • TS代码片段
      • 面试官眼中的test unit
  • 全年安排
    • AfterShip
    • 大企业
  • 函数编程题
    • Promise问题
    • 继承
  • 前端早早聊
    • vue生态
    • 开发一款VScode语言插件
    • 简历回顾和进行复盘
    • 重新认知性能优化及其度量方法
    • 2022-09-17-音视频专场.md
      • 2022-09-17-音视频专场
    • 前端晋升专场
      • 成长的诀窍是靠自己
      • 销销帮
    • 前端监控专场
      • 字节前端监控实践
      • 李港:大前端-从无到有搭建自研前端监控系统
    • 前端跳槽
      • 50个面试官在线招聘
      • 如何识别优秀的前端猎头来跳槽大厂
      • 面试套路
    • 支付宝
      • 面试
    • 管理专场
      • 芋头:管理者眼中的web技术发展前沿
    • 组件专场
      • 基于webCompents的跨技术组件库实践
    • 面试
      • 面试辅导问题
      • 早早聊面试
      • 前端沙箱是什么? 怎么实现沙箱环境?
  • 常见总结
    • 2018年终总结-年底了,你总结了吗?我先来
    • 在逆境中成长
    • 2021年终总结
    • 2024年全年总结
    • 项目
    • Tell2.0 前端复盘
    • 复盘
    • 前端工程师素养
    • 学习方法论
    • 希望与破晓| 2022 年终总结
    • 新起点, 新征途 | 掘金年度征文
    • 稳定| 2023 年终总结
    • 趁着有风快飞翔 | 2019 年终总结
    • AfterShip
      • Emotion:用 JavaScript 编写 CSS 的强大工具
      • 个人中长期目标
      • 事故复盘
      • 时间解析
      • 国内外区别
      • 独立站建设
    • MEIZU
      • NativeApp与H5通信原理
      • SSR 原理
      • SSR的常见问题
      • CLI
      • electron 应用发布流程
      • electron
      • electron 面试
      • 数据结构与算法之美
      • mgc 一期复盘
      • 架构原理
      • 喵币管理
      • 三期复盘总结
      • 异常监控之 sentry 实践
      • 微前端
      • qiankun 原理解析
      • 快游戏一期
      • 游戏中心复盘
    • 个人准则
      • index
    • 编程猫
      • pc 接入 micro bit 方案
      • prompt engineer
      • web work 跨域解析与解决方式
      • web 中的 ai
      • 低版本 node 环境下 ffmpeg 的使用
      • 关于 taobao 源 https 过期
      • 加密 json
      • 安卓 5 和 6 的白屏解决
      • 性能排查与优化实践
      • 探月接入
      • 接入硬件
      • 新生态下的state
      • monorepo 包管理方式
      • 自修复 npm 库
      • 音频的绘制
    • 谨启
      • 音视频
      • 小程序
        • taro 规范
        • 结合 mobx 在跳转前预请求
        • Taro 浅析用法与原理
        • 前文
        • 小程序优化指南
        • 小程序内部实现原理
        • 支付相关
    • tencent
      • TAPD
        • MathJax的食用
        • canvas渲染优化策略
        • 为什么 JavaScript 是单线程的呢?
        • svg 总是不对
        • 前端库
        • 原生端和js端如何通信
        • 在旧项目中复用vue代码
        • 提升自我
        • 批量编辑优化
        • 插入业务对象
        • 编辑器
        • 挂载点
        • 性能优化对比
        • 遇到的问题
        • 项目迁移公告
        • 领导力
      • 行家
        • 实战篇
        • 职业发展、领导力、个人成长
        • 高质量沟通
  • 慕课网
    • react-native原理
    • react-native学习
  • 杂文
    • Dom 节点变动检测并录制的简单实现
    • 错误监控&错误捕获
    • NextJS与NuxtJS
    • 负载均衡的几种常用方式
    • PM2
    • service worker 控制网络请求?
    • SSL 和 TLS 的区别
    • Babel 你太美
    • echart踩坑经验
    • keyup、keydown你都知道有什么区别吗
    • 常见概念
    • 首屏加载优化与性能指标分析
    • preload 和 prefetch 的详解
    • 在项目中配置这几个关系
    • roullp 解析
    • tinymce原理浅析
    • wasm 在前端的应用
    • websocket
    • webworker
    • 项目
    • 从 ajax 到 axios
    • 从postcss 到自己开发一款插件
    • 从输入浏览器到页面展示涉及的缓存机制
    • 代码整洁之道
    • 你知道什么是aop吗
    • 函数式编程
    • 函数式编程指南
    • 前端input框文字最大值
    • 攻坚战
    • 前端书写 sdk
    • 前端文字转语音播放
    • 前端领域的 Docker 和 Kubernetes
    • 前端安全
    • 前端进阶之内存空间
    • 前端音频浅析
    • 十分钟搞定多人协作开发
    • 字符串的比较
    • 尾递归
    • 前文
    • 常见的算法可以分为以下三类
    • 手机调试--mac篇
    • 数组的原生系列
    • COOP 和 COEP - 新的跨域策略
    • 浅谈react组件书写
    • 浏览器与 Node.js 事件循环的区别
    • 由三道题引伸出来的思考
    • 移动端300ms点击延迟
    • 移动端和pc端事件
    • Git 常见疑惑
    • 我们离发 npm 包还有多远
    • 重绘和重排
    • AI 时代下的前端编程范式
    • 音频可视化实战
  • 极客时间
    • Serverless入门课
    • 二分查找
    • 二叉树
    • 全栈工程师
    • 动态规划面试宝典
    • 前端与rust
    • 散列表
    • 前端方面的Docker和Kubernetes
    • 栈
    • 深入浅出区块链
    • 玩转 vue 全家桶
    • 玩转 webpack
    • 程序员的个人财富课
    • 算法
    • 说透元宇宙
    • 跳表
    • 链表
    • 10x 程序员工作法
      • index
    • Node开发实战
      • HTTP服务的性能测试
      • JavaScript语言精髓与编程实战
      • 什么是node。js
      • svg精髓
    • ReactHooks核心原理与实战
      • ReactHooks核心原理与实战
    • Rust
      • Rust编程第一课
      • 前置篇
      • 深度思维
      • 重构
      • 类型体操
      • 基础知识
    • WebAssembly入门课.md
      • 基础篇
      • SSR的注水和脱水
      • jsBriage通信原理
      • 基础知识篇
    • 互联网的英语私教课
      • 互联网人的英语私教课
    • 代码之丑
      • 代码之丑
    • 前端全链路优化实战课
      • 网页指标
    • 图解 Google V8
      • 图解 Google V8
    • 浏览器工作原理与实践
      • 浏览器工作原理与实践
    • 算法面试通关 40 讲
      • 算法面试通关40讲
    • 跟月影学可视化
      • index
    • 软件设计之美
      • 软件设计之美
    • 重学前端
      • js
  • 后续的文件增加都会增加到上面并以编号对应
    • 1029. 两地调度
    • 151.翻转字符串里的单词
    • 2022.3.15
    • 前端数据结构
    • 前端常见算法
    • 前端常见排序
    • 恢复一棵树
  • 设计模式
    • 前端常见设计模式之MVC与MVVM
    • 前端之代理模式
    • 前端常见设计模式之单例模式
    • 前端常见设计模式之发布订阅模式
    • 前端之工厂模式
    • 观察者模式
    • 前端常见设计模式之适配器模式
  • 译文
    • [译] 如何使用CircleCI for GitHub Pages持续部署
    • 您是否优化了 API 的性能
    • [译][官方] Google 正式发布 Flutter 1.2 版本
    • 什么是 Deno ,它将取代 NodeJS ?
  • 读后感
    • JavaScript二十年
    • 1368个单词就够了
    • js编程精解
    • labuladong 的算法小抄
    • lodash常用方法
    • vue的设计与实现
    • 所有的静态资源都是get请求
    • 人生
    • 人生护城河
    • 你不知道的JavaScript
    • 前端核心知识进阶
    • 华为工作法
    • 反脆弱
    • 好好学习
    • 左耳听风
    • 摩托车维修之道
    • 数学之美
    • 深入理解svg
    • 浏览器的ESM到底是啥
    • 经济学原理
    • 编程珠玑
    • 防御式 css 精讲
    • 韭菜的自我修养
  • 雪狼
    • 2022-07-17
    • 基础知识
    • 阶一课程
      • 实战辅导一
      • 实战辅导二
  • 嵌入式
    • 树莓派
      • 排序
  • 源码
    • React
      • 核心知识点
      • errorBoundaries
      • immutable.js 的实现原理
      • React.Suspense
      • react源码分析之Fiber
      • batchedUpdate
      • Component
      • Context
      • react 源码分析之 diff 算法
      • React 中的 key 属性:原理、使用场景与注意事项
      • 使用方式
      • react源码分析之memo
      • react 源码分析之mixin
      • 实战篇
      • react源码分析之react-dom
      • 使用方式
      • scheduleWork
      • useImperativeHandle的使用与原理
      • React 书写小技巧
      • 入口和优化
      • 合成事件和原生事件的区别
      • react 性能优化
      • 构建一个 hooks
      • 浅析 styled-components
      • 生命周期
      • 组合 vs 继承
      • 通信机制
      • 高阶组件
      • 慕课网
        • 应用篇
        • 课程导学
    • ReactHook
      • useCallback
      • useContext
      • useEffect 与 useLayoutEffect
      • useHook
      • useMemo
      • useReducer
      • 原理
      • useState
      • 总结
    • Redux
      • mobx 原理解析
      • redux-saga
      • redux-thunk
      • Mobx 和 Redux 对比
      • 使用方法
      • redux 原理
    • Vite
      • Vite原理
      • Vite配置
      • 热更新原理
      • vite 为什么生产环境用 Rollup
    • Webpack
      • PostCSS
      • Webpack5 核心原理与应用实践-loader
      • Webpack5 核心原理与应用实践-plugin
      • Webpack5 核心原理与应用实践
      • 区分
      • 升级详情
      • treeShaking(树摇Tree Shaking)
      • 编写一个自己的webpack插件plugin
      • 代码分离(code-splitting)
      • webpack 打包优化
      • 基础配置
      • webpack 打包优化
      • webpack 工作原理
      • webpack 按需加载原理
      • webpack 热更新 HMR(Hot Module Replacement)
      • 缓存
      • webpack 自定义 plugin
    • next
      • tailwind
      • 什么是水合
    • sveltejs
      • index
    • tinymce
      • 并发篇
    • 源码手写系列
      • create
      • call
      • bind
      • call
      • es6 单例
      • forEach vs Map
      • instanceOf
      • new
      • reduce
      • 取两个重复数组的交集
      • 函数柯理化
      • 动态规划
      • 基于Generator函数实现async
      • 新建 js 文件
      • 手写一个 slice 方法
      • 手写一个 webpack loader
      • Plugin
      • 手写一个寄生组合式继承
      • 二叉树
      • 链表相关的操作
      • 手动实现发布订阅
      • 数组去重
      • 数组扁平化
      • 数组
      • 构造大顶堆和小顶堆
      • 深浅拷贝 深拷贝
      • 两者对比
    • vue
      • vue2
        • vm.attrs与$listeners
        • vue 和 react 的 diff 算法比较
        • vue 源码分析
        • vue 优化的 diff 策略
        • extends
        • 核心原理篇
        • keep-alive
        • vue 源码分析之 mixins
        • vue 源码分析之 nextTick
        • vue之slot
        • vnode
        • vue 源码分析之 watch
        • 原理
        • vue 源码分析之transition
        • vue 源码分析之异步组件
        • 调用的是 watch
        • 安装
        • react源码分析之portals
        • event 的实现原理(事件的实现原理)
        • 什么是h
        • 分析provide 和 inject
        • vue 源码分析之 use
        • v-model
        • vue源码分析之vuex
        • 响应式原理
        • 初始化的流程
        • 组件更新
        • 编译
        • 父子组件生命周期
        • 原理
        • 多实例
        • Vue 面试
        • 源码研读一
        • 响应式原理
        • 常见问题
        • 数组的劫持
        • vue之自定义指令
        • 运行机制全局概览
      • vue3相比vue2的提升点
        • vue composition api
        • vue3的虚拟dom优化
        • vue3层面的双向数据绑定
        • 预处理优化
  • 重构
    • notification
      • 讲解
  • 面试
    • AfterShip经历
      • JS对URL进行编码和解码
      • ShippingLabelTemplate
      • 接入keycloak详解
      • reCAPTCHA接入
      • yalc与动态解决升级的依赖包
      • RBAC 简介
      • 多语言计划
      • 接入Google登录及其主动弹出快捷登录方式
      • 读书计划
        • 传染
        • 这就是OKR
    • 编程猫经历
      • 2024.1.16
      • 2025.2.20
      • 2025.2.21
      • 2025.2.26
      • 2025.3.28
      • 2025.3.3
      • 2025.3.7
      • 行动轨迹
      • 面试主观题
    • 腾讯经历
      • 2022.02.21
      • 2022.03.30
      • 2022.04.24
      • 2022.04.25
      • 2022.04.27
      • 2022.04.28
      • 2022.04.29
      • 2022.05.05
      • 不同公司的面试关注点不同
      • 2022.05.07
      • 2022.05.09
      • 2022.05.10
      • 2022.05.11
      • 2022.05.12
      • 2022.05.13
      • 2022.05.16
      • 2022.05.17
      • 2022.05.19
      • 2022.05.27
      • 面试
      • 行动轨迹
      • 面试主观题
    • 针对字节
      • 2022.05.14
      • 2022.05.17
      • HR面试准备
      • Promise的相关题目
      • React 进阶实践指南(二)
      • React 面试准备
      • vue 与 react 有什么不同 (react 和 vue 有什么区别)
      • TypeScript 全面进阶指南
      • cookie和session区别
      • express 面试准备 koa 中间件原理
      • next面试准备
      • requestCallBack
      • interface 与 type 异同点
      • 取消 promise
      • 如何设计一个前端项目
      • 进阶篇
      • 早早聊面试准备
      • 自动化部署
      • 挖掘项目的深度
      • 面试
      • 出题指数
    • 魅族经历
      • 2020.09.11
      • 一灯
      • 一灯
      • 一灯
      • 2020.09.20
      • 2020.09.21
      • 网易二面
      • 2020.09.23
      • 头条
      • 360 金融面试题
      • 富途一面
      • 算法
      • 字节
      • 2020.11.04
      • baidu 一面
      • meta 标签的作用
      • 字节
      • 2020.11.22
      • 2020.11.25
      • 微前端接入笔记
      • 面试的基本原则
由 GitBook 提供支持
在本页
  • 1. 权衡的艺术
  • 声明式和命令式
  • 响应系统
  • 解决无限循环
  • 调度执行
  • 计算属性computed与lazy
  • watch的实现原理
  • 非原始值的响应式方案
  • proxy只能代理对象,无法代理非对象值,例如字符串、布尔值
  • 原始值的响应式方案
  • 简单的 diff 算法
  • 双端diff算法
  • 快速diff算法

这有帮助吗?

  1. 读后感

vue的设计与实现

1. 权衡的艺术

声明式和命令式

  • 命令式框架的一大特点就是关注过程---jquery

  • 声明式框架更关注结果--vue vue内部实现一定是命令式的, 而暴露给用户的缺更加声明式

总结: 声明式代码的性能不优于命令式代码代码的性能

原因如下:

直接修改的性能消耗定义为A  找出差异的消耗定义为B

则

命令式代码的更新性能消耗≈A
声明式代码的更新性能消耗≈B+A
  • 比较innerHTML和虚拟DOM的性能

对于innerHTML来说, 为了创建页面, 我们需要先把字符串解析成dom树, 这是一个DOM层面的计算, 涉及到DOM的运算要远比Javascript层面的计算性能差.

innerHTML创建页面的性能: HTML字符串拼接的计算量 + innerHTML的DOM计算量

虚拟DOM创建页面的过程分为两步: 第一步是创建JavaScript对象, 这个对象可以理解为真实DOM层面的描述; 第二步是递归地遍历虚拟DOM树并创建真实DOM

虚拟DOM创建页面的性能: 创建JavaScript对象的计算量 + 创建真实DOM的计算量

innerHTML和document.createElement等DOM操作方法有何差异

当设计框架时候我们有三种选择: 纯运行时 运行时+ 编译时或纯编译时

  • 纯运行时: render函数 处理数据对象 渲染到页面

  • 编译时: 编译得到树形结构的数据对象

compiler的程序, 它的作用就是把HTML字符串编译成树形结构的数据对象

  • 运行时 + 编译时 相结合 就是把这两个组合起来 即支持编译时, 用户可以提供HTML字符串, 我们将其编译成数据对象再交给运行时处理

准确来说 其实是运行时编译, 意思是代码运行的时候才开始编译, 而这会产生一定的开销, 因此我们也可以在构建的时候就执行Compiler程序将用户提供的内容编译好, 等到运行时就无需编译了, 这对性能是非常友好的.

  • 纯编译时 直接将HTML字符串编译成命令式代码

< div>
    < span> hello world < /span>
< /div>

编译

const div = document.createElement('div');
const span = document.createElement('span');
span.innerText = 'hello world';
div.appendChild(span);
document.body.appendChild(div)

纯运行时: 由于没有编译过程, 我们没办法分析用户提供内容, 但如果加入编译步骤, 我们可以分析用户提供的内容, 进行优化, 并在render函数得到这些信息之后, 进行优化. 纯编译时: 也能做到内容的分析, 并优化.

Tree-Shaking 是一种排除DEAD CODE 的机制 工具/#PURE/ 注释 Tree-Shaking 依赖于ESM的静态结构以及是否产生副作用, 如果一个函数的副作用 调用函数的时候会对外部产生影响

rollup 配置里面format有三种形式: 'iife', 'esm', 'cjs'

  1. 在浏览器环境中,除了能够用scripts标签引用iife格式的资源外,还可以直接引用ESM格式的资源

  2. 在node.js环境中 资源的模块格式应该是commonJs

如果在package.json 中存在module 字段,那么会优先使用module 字段指向的资源来替代main字段指向的资源

  • vue.runtime.esm-bundler.js

  • vue.runtime.esm-browser.js

带有 -bundler 字样的 ESM 资源是在rollup.js 或webpack等打包工具使用,而带有 -browser 字样的ESM资源是直接给 <\script type='module'> 使用的 它们的区别在与__DEV__常量替换为字面量true或者false, 后者将_DEV_常量替换为process.env.NODE_ENV !== 'production' 语句

使用模板和 JavaScript 对象描述UI有何不同: 使用 JavaScript 对象描述UI更加灵活.

而使用 JavaScript 对象描述UI的方式, 其实就是所谓的虚拟DOM

所以vue.js 除了支持使用模板描述UI外,还支持使用虚拟DOM描述UI.

import { h} form 'vue'
export default {
    render() {
        return h('h1', { onClick: handler }) //  虚拟DOM  
    }
}

这里用到的h函数调用,返回值,就是一个对象, 其作用就是让我们编写虚拟DOM更加轻松

虚拟DOM: 用 JavaScript 对象来描述真实的DOM结构

渲染器的作用就是把虚拟DOM渲染为真实DOM

渲染器render的实现思路:

1.创建元素 2.为元素添加属性和事件 3. 处理children

组件就是一组DOM元素的封装,它可以返回虚拟DOM的函数,也可以是一个对象,但这个对象下必须要有一个函数用来产生组件要渲染的虚拟DOM.

render 函数 要处理 组件

function mountElement(vnode, container){
    const el = document.element(vnode.tag);

    for(const key in vnode.props){
        if(/^on/.test(key)){
            el.addEventListener(
               key.substr(2).toLowerCase(),  // 事件名称 onClick  ---->click
                vnode.props[key]  // 事件处理函数
            )    
        
        };
    }
}

编译器的作用就是将模板编译为渲染函数.

对于编译器来说,模板就是一个普通的字符串.

编译器会把模板内容编译成渲染函数并添加到<\script>标签块的组件对象上.

对于一个组件来说,它要渲染的内容最终都是通过渲染函数产生的,然后渲染器再把渲染函数返回的虚拟DOM渲染为真实DOM

渲染器在渲染组件时,会先获取组件要渲染的内容,即执行组件的渲染函数并得到返回值,我们称之为subtree,最后在递归地调用渲染器将subtree渲染出来即可.

响应系统

响应函数和副作用

指的是会产生副作用的函数

// 用一个全局变量存储被注册的副作用函数
let activeEffect

function effect(fn){
    activeEffect = fn
    // 执行副作用
    fn();
};

解决无限循环

 function trigger(target, key) {
    const depsMap = bucket.get(target);
    if(!depsMap) return 
    const effects = depsMap.get(key);
    
    const effectsToRun = new set()
    effects && effects.forEach(effectFn => {
    // 如果trigger 触发执行的副作用函数与当前正在执行的副作用函数相同,则不触发函数
        if(effectFn !== activeEffect){
            effectsToRun.add(effectFn)
        }
    });
    effectsToRun.forEach(effectFn => effectFn()) 
 }

调度执行

可调度性是响应式系统非常重要的特性,首先我们需要明确什么是可调度性。所谓可调度性,指的是当trigger动作触发副作用函数重新执行时,有能力决定副作用函数执行时的时机、次数以及方式

//JavaScript
    effect(
        () = {
            console.log(obj.foo)
        },
        // options
        {
            // 调度器 scheduler 是一个函数
            scheduler(fn){
                // 
            }
        }
    )
function effect(fn, options){
    const effectFn = ()=> {
        cleanup(effectFn);
        // 当调用effect注册副作用函数时,将副作用函数复制给activeEffect
        activeEffect = effectFn
        // 在调用副作用函数之前将当前副作用函数压栈
        effectStack.push(effectFn)
        fn();
        // 在调用副作用函数执行完毕,将当前副作用函数弹出,并把activeEffect还原为之前的值
        effectStack.pop()
        activeEffect = effectStack[effectStack.length - 1]
    }
    // 将options 挂载到effectFn 上
    effectFn.options = options //
    // activeEffect.deps 用来存储所有与该副作用相关的依赖集合
    effectFn.deps = []; 
    // 执行副作用函数
    effectFn()
};    

effect函数来注册副作用函数, 用来追踪和收集依赖的track函数 用来触发副作用函数重新执行的trigger函数

 function trigger(target, key) {
    const depsMap = bucket.get(target);
    if(!depsMap) return 
    const effects = depsMap.get(key);

    const effectsToRun = new set()
    effects && effects.forEach(effectFn => {
    // 如果trigger 触发执行的副作用函数与当前正在执行的副作用函数相同,则不触发函数
        if(effectFn !== activeEffect){
            effectsToRun.add(effectFn)
        }
    });
    effectsToRun.forEach(effectFn => {
        // 如果一个副作用函数存在调度器,则调用调度器,并将副作用函数作为参数传递
        if(effectFn.options.scheduler){
            effectFn.options.scheduler(effectFn) // 新增
        } else {
            // 否则直接执行副作用
            effectFn()  // 新增
        }
    })
 }

计算属性computed与lazy

懒执行

//JavaScript
function effect(fn, options = {}){
    const effectFn = () => {
        cleanup(effectFn);
        activeEffect = effectFn
        effectStack.push(effectFn)
        fn()
        effectStack.pop()
        activeEffect = effectStack[effectStack.length - 1]
    }
    effectFn.options = options
    effectFn.deps = [];
    if(!options.lazy) {  //只有非lazy的时候,才执行
        effectFn();
    } 

    return effectFn //  新增
}

如果我们能够实现自定义调度

//JavaScript
function effect(fn, options = {}){
    const effectFn = () => {
        cleanup(effectFn);
        activeEffect = effectFn
        effectStack.push(effectFn)
        // 将 fn 的执行结果存储到 res 中 
        const res = fn();
        effectStack.pop()
        activeEffect = effectStack[effectStack.length - 1]
        // 将res 做为effectFn的返回值
        return res
    }
    effectFn.options = options
    effectFn.deps = [];
    if(!options.lazy) {  //只有非lazy的时候,才执行
        effectFn();
    } 

    return effectFn //  新增
}
//  调度器
function computed(){
    let value
    let dirty = true
    const effectFn = effect(getter, {
        lazy: true,
        // 添加调度器,在调度器中将dirty重置为 true
        scheduler() {
            dirty = true
        }
    })
    const obj = {
        get value(){
            // 只有“脏”时才计算值,并将得到的值缓存到value中
            if(dirty){
                value = effectFn();
                // 将dirty设置为false,下一次访问直接使用缓存到value中的值
                dirty = false
            }
            return value
        }
    }
    return obj
}

// 解决属性变化之后, effectFn 没有发生改变

//  调度器
function computed(){
    let value
    let dirty = true
    const effectFn = effect(getter, {
        lazy: true,
        // 添加调度器,在调度器中将dirty重置为 true
        scheduler() {
            if(!dirty){
                 dirty = true
                // 当计算属性依赖的响应式数据变化时,手动调用 trigger 函数触发响应
                trigger(obj, 'value') 
            }
        }
    })
    const obj = {
        get value(){
            // 只有“脏”时才计算值,并将得到的值缓存到value中
            if(dirty){
                value = effectFn();
                // 将dirty设置为false,下一次访问直接使用缓存到value中的值
                dirty = false
            }
            // 当读取 value 中,手动调用track函数进行追踪
            track(obj, 'value')
            return value
        }
    }
    return obj
}

watch的实现原理

本质上就是观测一个响应式数据,当数据发生变化时,通知并执行相应的回调函数

watch本质上就是利用了effect以及options.scheduler选项

//JavaScript
effect(() => {
    console.log(obj.foo)
},{
    scheduler(){
        //当obj.foo的值发生变化时,会执行scheduler调度函数
    }
})

如果副作用函数存在scheduler选项,当响应式数据发生变化时,会触发scheduler调度函数执行,而非直接触发副作用函数执行。从这个角度来看,其实scheduler调度函数就相当于一个回调函数 调度执行

非原始值的响应式方案

proxy只能代理对象,无法代理非对象值,例如字符串、布尔值

const p  = new Proxy(obj, {
    // 拦截读取属性操作
    get(){

    }
    // 拦截设置属性操作
    set(){
        
    }
})

第一个参数是被代理的对象,第二个参数也是一个对象,这个对象是一组夹子(trap)

const fn = (name) => {
    console.log('我是:', name)
}
// 调用函数是对对象的基本操作
fn()
const p2 = new Proxy(fn, {
    // 使用 apply 拦截函数调用
    apply(target, thisArg, argArray){
        target.call(thisArg, ...argArray)
    }
})

原始值的响应式方案

proxy 只能代理对象,无法代理非对象的值,例如字符串、布尔值。 所谓的代理,指的是对一个对象基本语义的代理,它允许我们拦截并重新定义一个对象的基本操作。

obj.foo // 读取属性foo的值
obj.foo++ //读取和设置属性foo的值

类似这种读取、设置属性值的操作,就属于基本语义的操作。 在js世界里,万物皆是对象。例如一个函数也是一个对象,所以调用函数也是对一个对象的基本操作。

const fn = (name) => {
    console.log('我是', name)
}
// 调用函数是对对象的基本操作
fn()
const p2 = new Proxy(fn, {
    // 使用apply 拦截函数调用
    apply(target, thisArg, argArray){
        target.call(thisArg, ...argArray)
    }
})
p2('sjh') // 

proxy只能够拦截对一个对象的基本操作,调用对象下的方法就是典型的非基本操作,我们叫它复合操作:obj.fn 实际上调用一个对象下的方法,是由两个基本语义组成, 第一个基本语义是get,既先通过get操作得到obj.fn属性,第二个基本语义是函数调用,即通过get得到obj.fn的值后再调用它,也就是我们上面说到的apply。

Reflect

Reflect.get函数还能够接收第三个参数,即指定接收者receiver,可以理解为调用过程中的this

    const obj = {foo :1}
    console.log(Reflect.get(obj, foo, {foo: 2}))

原始状态:

### proxy只能代理对象,无法代理非对象值,例如字符串、布尔值

const p  = new Proxy(obj, {
    // 拦截读取操作, 接收第三个参数receiver
    get(target, key, receiver){
        track(target, key)
        // 使用Reflect.get返回读取到的属性值
        return Reflect.get(target, key, receiver)
    }
})

this由原始对象obj变成了代理对象p。 这会在副作用函数雨响应式数据之间监理响应式联系

JavaScript对象及Proxy的工作原理

根据ECMAScript规范,在JavaScript中有两种对象,其中一种叫做常规对象, 另一种叫做异质对象

在javascript 中,对象的实际语义是由对象的内部方法(internal method)指定的, 所谓内部方法,指的是当我们对一个对象进行操作时在引擎内部调用的方法, 这些方法对于JavaScript使用者来说是不可见的。

在ECMAScript 规范中使用【【xxx】】来代表内部方法或内部槽

如果一个对象需要作为函数调用,那么这个对象就必须部署内部方法【【Call】】。 如何区分一个对象是普通对象还是函数呢? 通过内部方法和内部槽来区分对象,例如函数对象会部署内部方法【【Call】】,而普通对象不会

函数的多态--- 普通对象和proxy对象都部署了【【call】】这个内部方法,但它们的逻辑不同。

如果在创建代理对象时没有指定对应的拦截函数,例如没有指定get()拦截函数,那么当我们通过代理对象访问属性值时,代理对象的内部方法【【Get】】会调用原始对象的内部方法【【Get】】来获取属性值,这其实就是代理透明性质。

创建代理对象时指定的拦截函数,实际上是用来自定义代理对象本身的方法和行为的,而不是用来指定被代理对象的内部方法和行为的

// 删除

// 新增

function reactive(obj){
    return new Proxy(obj, {
        set(target, key, newVal, receiver){
            const oldValue = target[value];
            const type  = Object.prototype.hasOwnProperty.call(target, key) ? 'SET' : 'ADD';
            const res = Reflect.set(target, key, newVal, receiver);

            // target === receiver.raw 说明receiver 就是target的代理对象
            if (target === receiver.raw) {
                if(oldValue !== newVal && (oldValue === oldValue) || (newVal === newVal)){
                        trigger(target, key, value)
                }
            }
            return res  
        }
    })
}

深响应和浅响应

function createReactive(obj, isShallow == false){
    return new Proxy(obj, {
        get(target, key, receiver){
            if(key === 'raw'){
                return target
            } 

            const res = Reflect.get(target, key, receiver)
               track(target, key)

            // 如果是浅响应,则直接返回原始值
            if(isShallow) {
                return res
            }
            
            if(typeof res === 'obj'  && res !== null){
                return reactive(res)
            }

            return res
        }
    })
}
function reactive(obj){
    return createReactive(obj)
}

function shallowReactive(obj){
    return createReactive(obj, true)
}

简单的 diff 算法

渲染器更改完之后 会去改变真实的DOM,然后再去移动节点 完成真实的DOM更新 调度执行

移动元素

移动节点指的是, 移动一个虚拟节点所对应的真实dom节点,并不是移动虚拟节点本身。

function patchElement(n1, n2){
    // 新的 vnode 也引用了真实dom 元素
    const el = n2.el = n1.el;
}

添加元素

  1. 想办法找到新增节点

  2. 将新增节点挂载到正确位置

总结: 简单diff算法的核心逻辑是,拿新的一组子节点去旧的一组子节点中寻找可复用的节点。如果这道则记录该节点的位置索引。我们把这个位置索引称为最大索引。在整个更新过程中,如果一个节点的索引小雨最大索引,则说明该节点对应的真实dom元素需要移动。

双端diff算法

同时对新旧两组子节点的两个端点进行比较的算法,因此我们需要四个索引,分别指向新旧两组子节点的端点

快速diff算法

这在vue3中运用到了
上一页lodash常用方法下一页所有的静态资源都是get请求

最后更新于2个月前

这有帮助吗?