Skip to content

console.trace

打印堆栈

性能监控

陷入死循环,cpu飙升

切换source

手动暂停,查看call stack

js

import { highlightCode, consoleInfo } from "../utils/printCode";
export const infoData = {
  tableexpose: async () => {
    const list = [
      "getSelectionRows 用于多选表格,返回当前选中的行数据,也可以监听select-change进行赋值",
      "toggleRowSelection 用于多选表格,切换某一行的选中状态,第一个参数是行数据(row),如果使用了第二个参数,则可直接设置这一行选中与否",
      "clearSelection 用于多选表格,清空用户的选择",
    ];
    consoleInfo(list, "el-table expose");
  },
  validTable: async () => {
    const list = [
      "table的源数据作为form :model绑定的一个属性",
      "在需要校验的组件外包裹el-form-item组件",
      `<el-form-item :prop="'tableData.' + index + '.name'" :rules="rules.name">`,
      `这里校验的是:model绑定数据下.tableData[index].name,所以组件也绑定这个值即可`,
      `<el-input v-model="row.name">`,
    ];
    consoleInfo(list, "el-form校验el-table");
  },
  debounce: () => {
    const codeSnippet = `
function debounce(func, duration) {
  let timeout;

  return function (...args) {
    clearTimeout(timeout);
    timeout = setTimeout(()=>{func.apply(this, args)}, duration);
  };
}
`;
    highlightCode(codeSnippet);
  },
  throttle: () => {
    const codeSnippet = `
function throttle(func, duration) {
  let wait = false;

  return function (...args) {
    if(!wait){
      wait = true
      func.apply(this,args)
      setTimeout(()=>{wait = false},duration)
    }
  };
}
`;
    highlightCode(codeSnippet);
  },
  难点: () => {
    const list = [
      "ui有一个要求,希望我们的搜索结果里把搜索词高亮显示.",
      "开始我们找到一个支持vue3的包来进行高亮显示,但是对于组件库里的组件实现高亮这个包就无能为力了,为此不得不使用原生dom设置innerhtml",
      "这样一来场景不同,实现高亮的方法也不同,而且原生操作很麻烦,第三方库使用起来也有点麻烦.",
      "所以为了统一高亮的实现方案,我花了一些时间研究,发现web api有相关的高亮方法.",
      "在项目中实验可行,因为我们有三个项目都需要统一规范,所以使用 Webpack 打包成一个 npm 包,方便团队共享,并且未来在其他项目中也能复用",
    ];
    const list1 = [
      "前后端联调时间短:之前我们的联调时间比较充裕,这个问题没有暴露出来.",
      "但是这个项目开始是跨科室协作的,而且前期工作量大.导致排期的时候联调时间是被大大压缩的",
      "我们前端主要就是拿到后端给的数据渲染页面,先让我们的页面展示出来,也方便我们完善ui.",
      "所以我就想到了mock数据,接口定好协议我们前端的工作就可以很顺利的展开了",
      "选型上可以使用第三方平台或者我们的文档就是用yapi,它也支持mock数据的功能",
    ];
    const list2 = [
      "大文件上传",
      "通过上传组件我们可以拿到一个file文件,它继承blob,blob提供了一个slice方法,使用这个方法将大文件切片",
      "接下来为文件生成hash,为了防止阻塞,放在了web worker里执行,使用spark-md5以增量方式生成hash",
      "将hash和文件",
    ];
    const list3 = [
      "发现有一些可以公用的方法每个人都单独实现了,造成了项目体积无意义的膨胀。分析了几个原因",
      "其他人压根不知道已经有实现这个功能的方法",
      "有学习成本,需要看别人的代码了解怎么实现",
      "因此需要通用集中管理hook和utils,最好直接展示在vscode侧边栏里,所以想到开发一个插件",
      "首先需要拿到一个文件导出的内容,开始想直接使用import,webpack打包忽略这个import语句,并且使用tsc编译ts文件,但是实际上是不支持",
      "也考虑使用正则,但是太过繁琐,因为导出语法有几种。而且不是很踏实",
      "最后发现使用babel解析出ast,然后在分析这个树,可以获得导出的内容",
      "然后根据获取到的数据在插件中递归渲染一个树视图,得益于ast额外提供的注释节点,位置信息",
      "鼠标悬浮即可显示注释,点击可以跳转文件并自动滚动到导出内容并高亮,点击操作按钮在当前活跃文件上自动导入",
    ];
    const list4 = [
      "解决路由表路由子信息meta数据冗余问题,有的时候新增一个路由,只是从别的位置简单的复制粘贴",
      "导致meta出现一些无意义的数据,而且了解这些meta属性是需要一定时间的",
      "从更长远的角度来看,如果一段时间转向react,在转回vue,vuerouter里这些路由配置是否还记得是什么意思呢",
      "在此基础上,创建完路由还需要创建页面",
      "所以使用脚手架来解决这个问题,基本思路是解析路由表,选择节点位置插入新路由,配置路由表,里面会有相关属性的描述。",
      "自动更新路由表,选择模板文件并生成,生成文件是抽离注册成另一个命令",
    ];
    const list5 = [
      //  "还有一个问题当使用组件库时,需要查找对应组件文档,这就造成了工作流上下文频繁切换的问题,而有时我们只是需要简单的模版",
      //  "我的思路是使用脚手架生成组件模版,里面配置一些常见的属性,当然不仅仅是模版,还有响应式数据,函数的,",
      //  "配合vscode插件将这些部分分别插入template,script.这也解决了vue文件需要滚动在固定区块写代码",
      //  "比较复杂的是el-form,因为elformitem里需要包含其他组件,所以注册组件应该是可复用的."
      "开发过程中发现经常要用组件库的组件,然后去对应的组件文档里复制粘贴过来,而且可能分别需要添加在template和script中",
      "而且还有一些自定义的情况,例如有一个指令禁止输入全角字符,但是我又不记得是怎么写的,我想要用的时候不得不去其他地方找",
      "如果我能通过一些交互生成模版,响应式数据声明,函数.再利用vscode插件提供的接口在当前活跃文件内全部注入",
      "并且需要考虑复用性,可扩展性,使用策略模式避免大量的ifelse,例如怎么生成el-form,因为el-form-item下可以复用生成其他组件的逻辑",
      "对于组件属性配置,我给出了常用的配置,以及自定义的属性例如指令,并添加描述,对于值是非string需要在属性前加上v-bind的简写",
      "除此之外,我还使用了chalk让整个交互具备层次感.",
    ];
  },
  虚拟列表: () => {
    const list = [
      "首先确定一下html结构,最外面 div 就是设定高度的窗口,内部一层的 div 需要计算出总高度,再内部一层的 div 通过 translateY XX 移动到合适的位置",
      "初始时计算总高度和页面展示的子项个数,需要加上缓冲区",
      "滚动过程中计算开始节点(需要减去上缓冲区)和需要展示的子项",
      "对于不定高的,开始需要计算出高度数组和需要展示的子项个数,滚动过程中二分查找计算开始节点,并计算应该展示多少个子节点需要加上缓冲区",
    ];
  },
  "vue2/vue3": () => {
    const list = [
      "重构了响应式系统,性能优化,和Api的改进",
      "vue3使用了基于proxy的响应式系统,支持深度响应式和动态属性的监听",
      "引入了Composition api,使逻辑复用更加方便",
      "新增了fragment,teleport,suspense这样的内置组件",
      "使用ts进行重写,在idea中的代码提示效果更好",
      "在性能方面,优化了模板编译和diff算法,并且支持树摇,打包体积进一步缩小",
    ];
    consoleInfo(list, "vue2与vue3的不同");
    const list1 = [
      "Vue2的响应式原理是基于Object.defineProperty,通过把data返回的对象作为target",
      "这样不管是简单数据类型还是复杂数据类型,都可以通过这个api监听getter和setter",
      "但是它只能监听指定对象的指定属性,所以对于复杂数据类型需要递归监听",
      "当给响应式数据动态新增数据,会出现响应式丢失的问题,因此Vue2提供了一个$set",
      "并且当时proxy的兼容性不好",
    ];
    consoleInfo(list1, "响应式原理的不同");
  },
  vue的响应式原理: () => {
    const list = [
      "主要通过两个api reactive和ref",
      "对于ref注册的响应式数据,他有一个访问器属性value,getter时收集依赖,setter时派发更新",
      "对于setter中的value,使用toReactive进行处理,首先判断value是不是Object,如果不是直接返回,否则会调用reactive使value变成响应式数据",
      "而reactive基于proxy,它接受两个参数,第一个参数是原始对象,第二个参数是一组traps,对于reative里面包含get set deleteProperty has ownKeys这些traps",
      "在get中进行track依赖收集,如果value是个Object,会递归调用reactive实现深度监听,如果是个ref类型,会进行自动解包",
      "在set中处理新增和修改属性,进行trigger派发更新",
      "在deleteProperty处理删除属性,触发trigger",
      "在has拦截in操作符,触发track",
      "在ownKeys拦截forin,触发track",
      "通过配置这些traps可以进一步实现shallowReactive和readonly",
    ];
    consoleInfo(list, "vue的响应式原理");
  },
  "watch/watcheffect": () => {
    const list = [
      "都是用来监听响应式数据的变化,但使用场景不同",
      "watch需要显式指定依赖对象,监听多个数据需要用数组,并能提供新值和旧值",
      "watcheffect能自动收集所有依赖",
      "watch可以配置flush,deep,immediated,watcheffect只有flush,没有deep,immediated相当于是true",
      "如果只需要监听一个对象中的几个属性,使用watcheffect更好,因为它将只跟踪回调中使用到的属性,而不是递归的跟踪所有属性",
    ];
    consoleInfo(list, "watch watcheffect的区别");
  },
  "ref/reactive": () => {
    const list = [
      "都是用来创建响应式数据,但是使用场景有些不同",
      "reactive只能用于复杂数据类型,不能替换整个对象,对解构操作不友好。而ref更为通用,但是使用需要多写一个.value。",
      "并且需要注意ref有自动解包策略",
      "watch对ref reactive数据监听不同,如果是reactive,修改它的任何属性都会触发",
      "对应ref,默认情况下只会对.value的重新分配做出反应,但是可以使用deep让他监听所有的嵌套属性",
      "https://github.com/orgs/vuejs/discussions/9428(playground中有bug未解决)",
    ];
    consoleInfo(list, "ref reactive");
  },
  "watch/computed": () => {
    const list = [
      "watch是监听动作,computed是计算属性",
      "watch没缓存,只要监听的数据变化就执行。computed有缓存,只有响应式数据改变才会重新计算",
      "watch可以执行异步操作,computed不行",
      "watch常用于一个数据影响多个数据,而computer常用于多个数据影响一个数据",
    ];
    consoleInfo(list, "watch/computed 的区别");
  },
  "defineProperty/proxy": () => {
    const list = [
      "使用defineProperty是因为当时proxy兼容性不好",
      "defineProperty只能劫持对象属性的getter和setter,并且无法监听会修改原数组的数组方法,所以对这些方法就行重写",
      "proxy能直接劫持整个对象",
      "可以直接监听对象,数组的变化,并且拦截类型多达13种",
    ];
    consoleInfo(list, "defineProperty/proxy 的区别");
  },
  data: () => {
    const list = [
      "data是一个组件的状态,如果他是一个对象",
      "这个组件被多次引用,那么data将指向同一个地址,但是我们不需要状态共享",
      "所以通过函数返回一个新对象,把状态隔离",
    ];
    consoleInfo(list, "为什么data是一个函数");
  },
  cli: () => {
    const list = [
      "为了降低在项目寻找最佳实践的成本,用于代码生成和自动化,遵循了模块化,便于扩展的原则",
      "例如为input写了一个指令,于是我不得不前往其他文件里查找复制粘贴进来",
    ];
    consoleInfo(list, "背景");
    const list1 = [
      "新增模块需要在路由表进行注册然后新建文件,通过脚手架命令把这个步骤自动化",
      "首先使用babel/parse路由表进行分析,解析出它的层级关系,在通过inquery的交互功能选择在哪个节点上进行新增",
      "确定完位置后对路由表信息进行配置,根据bable/types生成ast节点,将新的ast解析成js代码。这样路由表也就自动更新了",
      "配置之后会询问是否生成模板代码,查找模板自动生成文件",
    ];
    consoleInfo(list1, "难点");
    const list2 = [
      "生成element组件模板代码,但是form表单比较特殊。因为el-form-item会包含其他组件",
      "所以我需要开启另一个进程,并与主进程共享输入输出,但是这样父子进程之间无法通信",
      "想了一些办法来解决,我导出一个对象,在子进程修改对象的键值,等待子进程执行完。再在主进程读取这个对象。",
      "但是发现拿到的对象还是初始值。所以换了一种方式,把对象换成了直接输出文件,在进行读取。这样就可以拿到了",
    ];
    consoleInfo(list2, "难点");
  },
  scoped: () => {
    const list = [
      "scoped会为组件生成唯一标识,并在dom上添加这个属性,选择器也会在末尾加上这个属性选择器",
      "使用scoped无法修改第三方组件库的样式,因为最后选择器会加上这个属性,但是使用样式穿透可以实现修改样式",
      "本质是用了样式穿透后,在deep之后的选择器最后就不会加上这个属性",
      "或者新增一个不带scoped的style,但要注意不要产生全局污染",
    ];
    consoleInfo(list, "scoped");
  },
  js基本数据类型有哪些及它们的区别: () => {
    const list = [
      "js有八种数据类型,分别是null,undefined,number,string,boolean,object,symbol,bigint",
      "symbol和bigint是es6新增的,symbol是为了创建一个独一无二的数据,解决可能出现的全局变量冲突的问题",
      "js的number类型是基于IEEE754标准,最大可以表示的数是2^53-1,超过这个范围精度会丢失,bigint能表示任意大小的数,不会出现精度丢失",
      "这些数据可以分为原始数据类型和引用数据类型",
      "两种类型的区别在于存储位置的不同,原始数据类型放在栈中,引用数据类型放在堆中.但是在栈中会存放指向堆的指针",
    ];
  },
  数据类型检测的方式有哪些: () => {
    const list = [
      "typeof",
      "typeof null 的结果为 object,这是官方承认的 typeof 的错误,这个问题来自于 JavaScript 语言的早期阶段,并为了兼容性而保留了下来。null 绝对不是一个 object。null 有自己的类型,它是一个特殊值。typeof 的行为在这里是错误的。",
      "typeof alert 按理应该是返回 object。但是 typeof 会对函数区分对待,并返回 function。这也是来自于 JavaScript 语言早期的问题。从技术上讲,这种行为是不正确的,但在实际编程中却非常方便。",
      "instanceof,其内部运行机制是判断在其原型链中能否找到该类型的原型,只能正确判断引用数据类型,而不能判断基本数据类型",
    ];
  },
  this: () => {
    const list = [
      "指向当前执行上下文中的 执行环境 或 函数调用的上下文",
      "箭头函数的写法更简洁,箭头函数没有this,继承与外部词法环境,不能被修改,没有arguments,不能成为构造函数",
    ];
  },
  原型: () => {
    const list = [
      "对象有一个特殊的隐藏属性prototype,它要么为null,要么就是另一个对象的引用,该对象被称为原型",
      "属性 [[Prototype]] 是内部的而且是隐藏的,但是使用特殊的名字 __proto__ 可以设置它",
      "当访问一个对象的属性,如果没找到就会到原型里找,原型里又有它的原型,这样一直寻找,就是一条原型链,原型链的终点是null",
    ];
  },
  闭包: () => {
    const list = [
      "闭包 是指一个函数可以记住其外部变量并可以访问这些变量",
      "例如防抖节流函数",
      "注意内存泄漏",
    ];
  },
  内存泄漏: () => {
    const list = ["意外的全局变量", "闭包", "定时器", "没有清理的dom引用"];
  },
  "var,let,const": () => {
    const list = [
      "let const 有块级作用域,var没有",
      "var允许重复声明",
      "使用var声明的全局函数和变量会成为全局对象的属性",
      "var声明会被提升,,但是赋值不会,let const有暂时性死区",
      "const必须设置初始值,const声明之后不能重新赋值",
    ];
  },
  new: () => {
    const list = [
      "创建一个空对象分配给this",
      "执行函数体,通常会修改this",
      "返回this",
    ];
  },
  es6: () => {
    const list = [
      "let const",
      "箭头函数",
      "解构赋值",
      "模版字符串",
      "promise",
      "扩展运算符",
    ];
  },
  promise: () => {
    const codeSnippet = `//ajax改造成promise
      function ajax(url) {
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest();
    xhr.open('GET', url, true);

    // 设置请求成功的回调
    xhr.onload = function() {
      if (xhr.status >= 200 && xhr.status < 300) {
        resolve(xhr.responseText); // 请求成功,返回响应内容
      } else {
        reject(new Error()); // 请求失败
      }
    };

    // 设置请求失败的回调
    xhr.onerror = function() {
      reject(new Error('Network error')); // 网络错误
    };

    // 发送请求
    xhr.send();
  });
}

// 使用 Promise 的方式进行调用
ajax('https://api.example.com/data')
  .then(response => {
    console.log('Success:', response);
  })
  .catch(error => {
    console.log('Error:', error);
  });
`;
    highlightCode(codeSnippet);
  },
  "null/undefined": () => {
    const list = [
      "基本是同义的,只有一些细微的差别,null表示此处不应该有值,undefined表示此处应该有一个值,只是没有定义",
      "所以访问一个不存在的对象属性返回是undefined",
      "在双等检查中返回true,除此之外,它们在双等检查中不会进行隐式转换",
    ];
    consoleInfo(list, "null undefined的区别");
  },
  key: () => {
    const list = [
      "key作为vue的diff算法提示,在比较新旧节点列表时用于识别vnode",
      "",
      "",
    ];
  },
  "ref/reactive实现原理区别": () => {
    const list = ["", "", ""];
  },
  "get/post": () => {
    const list = [
      "get请求的请求参数会放在url之后,参数之间使用&符号连接。post则是放在请求体里",
      "并且浏览器对url的长度是有限制的",
      "post因为请求参数放在请求体里相对安全一点",
      "get会被缓存,post不会,除非响应头包含适当的cache-control或expires",
    ];
    consoleInfo(list, "get/post的区别");
  },
  vite为什么比webpack快: () => {
    const list = [
      "浏览器开始原生支持 ES 模块,且越来越多 JavaScript 工具使用编译型语言编写。",
      "更快的开发服务器启动",
      "当冷启动开发服务器时,基于打包器的方式启动必须抓取并构建整个应用。然后才能提供服务",
      "vite把模块分为依赖和源码两部分",
      "首次启动vite,使用esbuild进行预构建依赖,默认情况下,它是自动且透明地完成的,esbuild使用go写的,速度比基于js的工具更快",
      "vite以原生esm提供源码,这实际上是让浏览器接管了部分打包工作,vite只需要在浏览器请求源码时进行转换并按需提供源码",
      "更快的热更新",
      "在 Vite 中,HMR 是在原生 ESM 上执行的。当编辑一个文件时,Vite 只需要精确地使已编辑的模块与其最近的 HMR 边界之间的链失活,使得无论应用大小如何,HMR 始终能保持快速更新。",
      "而对于整个页面的刷新,利用http请求头加速整个页面重新加载,源码请求会进行协商缓存,依赖请求会进行强缓存",
    ];
    consoleInfo(list, "vite为什么比webpack快");
  },
  热更新: () => {
    const list = [
      "通过websocket实现浏览器与服务器的通信,当文件修改之后,通知浏览器修改相应代码",
    ];
    consoleInfo(list, "热更新");
  },
  依赖预构建: () => {
    const list = [
      "利用esbuild将依赖全转成esm",
      "为了提高后续页面的加载性能,Vite 将那些具有许多内部模块的 ESM 依赖项转换为单个模块",
      "对于有多个内置模块的依赖,大量请求会导致浏览器端的网络拥塞,使页面加载变得明显缓慢",
      "将这样的依赖预构建成单个模块,就只需要一个http请求",
    ];
    consoleInfo(list, "依赖预构建");
  },
  "为何不用 ESBuild 打包": () => {
    const list = [
      "vite目前的插件api不兼容esbuild,rollup提供了更好的权衡在性能与拓展性方面",
      "并且rollup也在着手改进性能",
    ];
    consoleInfo(list, "为何不用 ESBuild 打包");
  },
  promise执行顺序console: () => {
    // 第一部分:Promise的执行顺序和说明
    const list1 = [
      `new Promise(resolve => {
      console.log('test');
      resolve();
    });`,
      "new Promise在then之前都是同步的,会立即打印test",
      `new Promise(resolve => {
      resolve();
      console.log('test');
      reject();
    });`,
      `执行了resolve、打印"test"、reject,这3句代码都会执行,但是reject不会生效`,
      "方法:从上往下执行,先执行同步代码,微任务放入一个队列,宏任务放入一个队列,promise.then之前都是同步的",
    ];

    consoleInfo(list1, "promise执行顺序console");

    // 第二部分:第一个Promise示例(复杂的Promise嵌套和微任务)
    const test1 = `
    const first = () => (new Promise((resolve, reject) => {
      console.log(3);
      let p = new Promise((resolve, reject) => {
        console.log(7);
        setTimeout(() => {
          console.log(5);
          resolve();
        }, 0);
        resolve(1);
      });
      resolve(2);
      p.then((arg) => {
        console.log(arg);
      });
    }));
    first().then((arg) => {
      console.log(arg);
    });
    console.log(4);
  `;
    // 预期输出顺序:3, 7, 4, 1, 2, 5
    highlightCode(test1);

    // 第三部分:关于async/await的注意点
    const list2 = [
      "async await,await之前的包括await这行都是同步执行,下面的进入微任务",
      "async修饰的函数,如果不是返回一个promise,则会包装成一个已经fulfilled(resolve)的promise",
      "函数return的是promise这种特殊情况,return意味着外层内容是等到return结果之后才执行的,return未完成,这个then就未完成",
      "所以这个promsie会被加入微任务,但是对于async修饰的函数,并且不是返回promise,默认会被promise包装,这个promise不会进入微任务",
    ];

    consoleInfo(list2, "async await的注意点");

    // 第四部分:async/await 示例代码
    const test2 = `
    async function async1() {
      console.log('async1 start');
      await async2();
      console.log('async1 end');
    }

    async function async2() {
      console.log('async2');
    }

    console.log('script start');
    async1();
    new Promise(function (resolve) {
      console.log('promise1');
      resolve();
    }).then(function () {
      console.log('promise2');
    });
    console.log('script end');
  `;
    // 预期输出顺序:script start、async1 start、async2、promise1、script end、async1 end、promise2
    highlightCode(test2);

    const test3 = `
    async function async1() {
      console.log('async1 start');
      await async2();
      console.log('async1 end');
    }
    async function async2() {
      console.log('async2');
      return new Promise((resolve, reject) => {
        resolve()
      });
    }
    console.log('script start');
    async1();
    new Promise(function (resolve) {
      console.log('promise1');
      resolve();
    }).then(function () {
      console.log('promise2');
    });
    console.log('script end');
  `;
    // 预期输出顺序:script start、async1 start、async2、promise1、script end、promise2、async1 end
    highlightCode(test3);

    const test4 = `
    Promise.resolve().then(() => {
      console.log(0);
      return Promise.resolve(4);
    }).then((res) => {
      console.log(res)
    });

    Promise.resolve().then(() => {
      console.log(1);
    }).then(() => {
      console.log(2);
    }).then(() => {
      console.log(3);
    }).then(() => {
      console.log(5);
    }).then(() => {
      console.log(6);
    });
  `;
    highlightCode(test4);
    // 预期输出顺序:0, 1, 2, 3, 4, 5, 6
    // 备注说明:
    // return意味着外层内容是等到return结果之后才执行的,这个promise默认会执行.then从而加入微任务
    // return Promise.resolve(4)会占据两个微任务
    // 参考文章链接:
    // https://juejin.cn/post/7092396674693201956
    // https://blog.csdn.net/qq_53109172/article/details/138552985
    // https://juejin.cn/post/6945319439772434469#heading-31
  },
  this打印结果: () => {
    const test1 = `
    var a = 1;
    var b = {
        a: 2,
        echo: () => {
            console.log(this.a);
        }
    };
    b.echo();
    `;
    highlightCode(test1);

    const test2 = `
    function a() {
        let b = {
            fn: () => {
                console.log(this.data);
            }
        };
        return b;
    }

    let c = {
        data: 1,
        a
    };

    let d = {
        data: 2,
        a
    };

    c.a().fn();
    d.a().fn();
    `;
    highlightCode(test2);
  },
  "怎么使用 for...of 遍历一个对象": () => {
    const list = [
      "for...of 只能遍历可迭代对象,如果遍历对象",
      "需要实现一个 Symbol.iterator 方法",
      "当 for...of 启动时会调用这个方法,这个方法必须返回一个迭代器,一个有 next 方法的对象",
      "当循环希望获得下一个值,会调用这个 next 方法",
      "next 返回的结果格式必须是 {done: boolean, value: any},当 done 为 true,代表循环结束",
    ];

    // 使用 consoleInfo 显示相关信息
    consoleInfo(list, "怎么使用 for...of 遍历一个对象");

    const codeSnippet = `
    let a = {
        start: 1, // 起始值
        end: 4,   // 结束值
        // 实现 Symbol.iterator 方法使得对象可迭代
        [Symbol.iterator]() {
            let current = this.start; // 当前值从 start 开始
            return {
                next: () => {
                    if (current <= this.end) {
                        // 如果当前值在范围内,返回 {done: false, value: current++}
                        return { done: false, value: current++ };
                    } else {
                        // 否则返回 {done: true},表示结束
                        return { done: true };
                    }
                }
            };
        }
    };

    // 使用 for...of 遍历对象
    for (let value of a) {
        console.log(value); // 输出: 1, 2, 3, 4
    }
    `;

    // 高亮代码显示
    highlightCode(codeSnippet);
  },
  /*
  vue源码系列
  */
  响应式原理: () => {
    const list = [
      "Vue的响应式系统在运行时跟踪属性的访问,它通过结合proxy包装器和getter/setter函数来实现",
      "proxy第一个参数是被代理对象,第二个参数是一组traps,通过这些traps可以控制被代理对象的基本操作",
      "对于reactive来说,有get set deleteProperty has ownKeys这些traps,在get中触发track依赖收集",
      "在track内部我们会检查当前是否有正在运行的副作用。如果有,我们会查找到一个存储了所有追踪了该属性的订阅者的 Set,然后将当前这个副作用作为新订阅者添加到该 Set 中",
      "副作用订阅将被存储在一个全局的 WeakMap<target, Map<key, Set<effect>>> 数据结构中。如果在第一次追踪时没有找到对相应属性订阅的副作用集合,它将会在这里新建",
      "在set中处理新增和修改属性,会触发trigger派发更新",
      "在trigger内部,我们会再查找到该属性的所有订阅副作用。但这一次我们需要执行它们",
      "最常见的响应式副作用就是更新dom.每个组件实例创建一个响应式副作用来渲染和更新dom,",
      "而对于ref,返回一个对象,里面有一个响应式属性value,执行getter时,进行track,执行setter时触发trigger,对于setter的参数value则会使用reactive处理",
    ];
    consoleInfo(list, "响应式原理");
  },
  computed原理: () => {
    const list = [
      "使用computed会返回一个对象,它也有一个访问器属性value,初始化时会执行一遍里面的回调函数,搜集依赖",
      "并且还有两个属性dirty和value,dirty初始化设置为true,代表需要重新执行回调,value则是回调return的值",
      "当computed依赖的响应式数据发生变化,dirty被设置为true,当触发getter时会重新执行回调,并更新value,将新值返回",
      "如果依赖没有改变,触发getter不会重新执行回调,而是返回缓存的value",
    ];
    consoleInfo(list, "computed原理");
  },
  nextTick原理: () => {
    const list = [
      "这跟vue的异步更新队列有关,vue会同步将任务放进任务队列,在微任务中执行任务队列",
      "因此要获取最新的dom,需要在微任务执行后再执行",
      "nextTick就是通过promise的链式调用,将nextTick里的回调放在上面的微任务.then里执行",
    ];
    consoleInfo(list, "nextTick原理");
  },
  watch原理: () => {
    const list = [
      "基于vue响应式系统,首先会将watch的第一个参数标准化,也就是getter函数的形式,将其创建为一个响应式副作用,配合调度器控制watch回调的执行",
      "当响应式数据发生改变,触发trigger派发更新,由于调度器的存在,会再次执行watch里的回调,也会执行上面的getter函数拿到新值,旧值通过闭包拿到,最后将新旧值做为回调的参数",
      "通过源码可以发现,watch监听ref reactive数据类型是不同的,当是reactive类型,默认会调用traverse进行深度监听,对于ref类型,不会进行深度监听,通过设置deep:true能实现深度监听",
      "对于flush,这跟组件的更新时机有关系,默认是pre,也就是在父组件更新之后,子组件更新之前执行,post是在组件更新之后,sync则是同步执行",
      "watchEffect则是vue提供的创建响应式副作用的api",
    ];
    consoleInfo(list, "watch原理");
  },
  keepalive原理: () => {
    const list = [
      "卸载时并不会真的卸载,而是移动到一个隐藏容器里,挂载时也不是真的挂载,而是从隐藏容器中取出放在页面上",
      "keepalive有一个特殊标识表明他是缓存组件,keepalive通过ctx实现与渲染器的通信,keepalive会在ctx上实现activate/deactivate,",
    ];
    consoleInfo(list, "keepalive原理");
  },
  /* 
  算法
  */
  递归: () => {
    const list = [
      "三要素:终止条件,参数,return的值",
      "如果依赖子问题的结果,return dfs(node.left)||dfs(node.right)",
      "如果是查找某个子节点符合XX要求,需要根据子问题return的结果做判断,更新全局变量",
    ];
    consoleInfo(list, "递归");
  },
  动态规划: () => {
    const list = [
      "dp[i]代表什么:前i(下标)个的结果或者是以第i个结束的结果,背包问题最好是前i个,而不是下标",
      "需要注意如果有n个数据,要加上0的情况所以需要拿到dp[n],给的参数注意下标,不要越界取错了值",
      "对于最简单的动态规划,尽可能补全初始值,直到判断不了,复杂的可能需要两个for循环",
    ];
    consoleInfo(list, "动态规划");
  },
  "0/1背包": () => {
    const list = [
      "都需要两层for循环,一层循环物品,一层循环背包(从0开始)",
      "二维:dp[i][j]代表从从第0~i个物品中选满足重量j的最大价值",
      "dp[i][j]=Math.max(dp[i-1][j],d[[i-1][j-weight[i]]+value[i]])",
      "一维:dp[j]代表从容量为j的最大价值,为什么能用一维,可以看上当前层只依赖上一层,dp[i]=Math.max(dp[i],dp[i-weight[i]]+value[i]",
      "循环背包时逆序,因为只跟上方和左上方的数据有关,这个时候不能提前更新它",
    ];
    consoleInfo(list, "0/1背包");
    console.log("先初始化数组,长度target+1,添加默认值,先遍历物品,后遍历背包");
    const codeSnippet = ` let dp=new Array(target+1).fill(false)
    dp[0]=true
    for(let i=0;i<nums.length;i++){
      for(let j=target;j>=nums[i];j--){
        dp[j]=dp[j]||dp[j-nums[i]]
      }
    }
    return dp[target]`;
    highlightCode(codeSnippet);
  },
  完全背包: () => {
    const list = [
      "初始化bp数组,长度是背包value+1,设置初始值",
      "let dp = new Array(amount + 1).fill(Infinity)",
      "dp[0] = 0;",
      "两层for循环,外层循环背包,i从1开始,取值要注意减一,内层循环物品,满足条件,进行判断dp[i]=dp[i]||dp[i-cur.length]",
      "注意i,j不要用混了,length不要拼写错误",
    ];
    consoleInfo(list, "完全背包");
    const codeSnippet = `var wordBreak = function(s, wordDict) {
    let dp=new Array(s.length+1).fill(false)
    dp[0]=true
    for(let i=1;i<=s.length;i++){
       for(let j=0;j<wordDict.length;j++){
        let cur = wordDict[j]
        if(i-cur.length>=0&&s.slice(i-cur.length,i)===cur){
            dp[i]=dp[i]||dp[i-cur.length]
        }
       }
    }
    return dp[s.length]
};`;
    highlightCode(codeSnippet);
  },
};