[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"$f1NEmJejrjVpUORA1BQ-HAHVfvoFZLUU1PaYsuexQR2Q":3,"donations-sidebar":9,"posts-{\"page\":1,\"pageSize\":10,\"tagId\":\"994cc226-578b-4a72-a57e-a47a63d2793e\"}":10,"sidebar-data":163},{"id":4,"name":5,"slug":6,"createdAt":7,"updatedAt":8},"994cc226-578b-4a72-a57e-a47a63d2793e","JavaScript生态","javascript生态","2022-05-21T09:59:55.000Z","2023-02-08T02:49:14.000Z",[],{"list":11,"total":160,"page":161,"pageSize":162},[12,34,54,68,80,96,109,121,134,147],{"id":13,"title":14,"slug":15,"content":16,"excerpt":17,"coverImage":18,"status":19,"isPinned":20,"pinnedAt":21,"viewCount":22,"publishedAt":23,"createdAt":23,"updatedAt":24,"category":25,"author":29,"tags":32},"354600df-6e7c-4735-b928-9f5692f2c4f6","JavaScript 中的class写法","javascript-中的class写法","\n## 1. 基本写法\n\n```js\nclass Person {\n    constructor(name, age) {\n        this.name = name;\n        this.age = age;\n    }\n\n    sayHello() {\n        console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);\n    }\n}\n\n```\n\n## 创建实例\n\n```js\nconst person = new Person('John', 30);\nperson.sayHello(); \u002F\u002F 输出: Hello, my name is John and I am 30 years old.\n```\n\n> constructor 方法：是类的构造函数，用于初始化类的实例。它会在创建对象时自动调用。\n实例方法：在类中定义的普通方法，可以通过实例调用。\n## 2. 类的继承\n> JavaScript 的类支持继承，使用 extends 关键字可以实现子类继承父类。\n\n```js\nclass Animal {\n    constructor(name) {\n        this.name = name;\n    }\n\n    speak() {\n        console.log(`${this.name} makes a noise.`);\n    }\n}\n\nclass Dog extends Animal {\n    constructor(name, breed) {\n        super(name); \u002F\u002F 调用父类的构造函数\n        this.breed = breed;\n    }\n\n    speak() {\n        console.log(`${this.name} barks.`);\n    }\n}\n\nconst dog = new Dog('Buddy', 'Golden Retriever');\ndog.speak(); \u002F\u002F 输出: Buddy barks.\n```\n\n> extends 关键字：用于声明子类继承父类。\nsuper() 方法：在子类的构造函数中调用父类的构造函数，用于初始化继承的属性。\n## 3. 静态方法\n>静态方法是属于类本身的方法，而不是类的实例。静态方法可以通过类名直接调用，而不需要创建实例。\n\n\n```js\nclass MathUtils {\n    static add(a, b) {\n        return a + b;\n    }\n\n    static multiply(a, b) {\n        return a * b;\n    }\n}\n\nconsole.log(MathUtils.add(5, 3)); \u002F\u002F 输出: 8\nconsole.log(MathUtils.multiply(5, 3)); \u002F\u002F 输出: 15\n```\n\n## 4. Getter 和 Setter\n> 类可以使用 get 和 set 关键字来定义属性的访问器方法，用于封装属性的读取和设置逻辑。\n\n```js\nclass User {\n    constructor(name) {\n        this._name = name;\n    }\n\n    get name() {\n        return this._name.toUpperCase();\n    }\n\n    set name(value) {\n        this._name = value.trim();\n    }\n}\n\nconst user = new User(' John Doe ');\nconsole.log(user.name); \u002F\u002F 输出: JOHN DOE\nuser.name = 'Jane Smith ';\nconsole.log(user.name); \u002F\u002F 输出: JANE SMITH\n```\n\n> get 方法：定义了如何读取属性的值。\nset 方法：定义了如何设置属性的值。\n## 5. 静态属性和实例属性\n> 实例属性：通过 this 定义在构造函数中，每个实例都有自己的副本。\n静态属性：通过类名直接访问，属于类本身，而不是实例。\n\n\n```language\nclass MyClass {\n    constructor(value) {\n        this.instanceProperty = value; \u002F\u002F 实例属性\n    }\n\n    static staticProperty = 'Static Value'; \u002F\u002F 静态属性\n}\n\nconst instance = new MyClass('Instance Value');\nconsole.log(instance.instanceProperty); \u002F\u002F 输出: Instance Value\nconsole.log(MyClass.staticProperty); \u002F\u002F 输出: Static Value\n```\n\n## 6. 私有属性和方法（ES2020+）\n> 私有属性和方法只能在类的内部访问，不能通过类的实例或子类直接访问。\n\n\n```js\nclass Counter {\n    #count = 0; \u002F\u002F 私有属性\n\n    increment() {\n        this.#count++;\n        console.log(`Count is now ${this.#count}`);\n    }\n\n    #reset() { \u002F\u002F 私有方法\n        this.#count = 0;\n        console.log('Count has been reset.');\n    }\n}\n\nconst counter = new Counter();\ncounter.increment(); \u002F\u002F 输出: Count is now 1\ncounter.increment(); \u002F\u002F 输出: Count is now 2\n\u002F\u002F counter.#reset(); \u002F\u002F 错误：私有方法无法从外部访问\n```\n\n## 总结\n> class 是一种语法糖，底层仍然是基于原型链实现的。\n类的写法更加直观，适合面向对象编程。\n使用 class 可以实现继承、封装、多态等面向对象的特性。","在 JavaScript 中，class 是一种基于原型的面向对象编程语法，用于创建对象的构造函数和定义对象的属性与方法。ES6 引入了 class 语法，使得类的定义更加简洁和直观。以下是关于 class 的基本写法和一些常见用法的介绍。","https:\u002F\u002Fcdn.xiaolong0418.com\u002Fmyblog\u002Fimages\u002F42606f265746d42a5b80c653a2868095_1740048554225.png","PUBLISHED",false,null,238,"2025-02-20T11:07:35.000Z","2026-06-26T08:50:21.170Z",{"id":26,"name":27,"slug":28},"da130ba9-d4f4-49f3-aa0f-149078097ef0","JavaScript","javascript",{"id":30,"name":31,"avatar":21},"f9d0f2de-c700-4f90-b535-afd3dbe78128","Admin",[33],{"id":4,"name":5,"slug":6},{"id":35,"title":36,"slug":36,"content":37,"excerpt":38,"coverImage":39,"status":19,"isPinned":20,"pinnedAt":21,"viewCount":40,"publishedAt":41,"createdAt":41,"updatedAt":42,"category":43,"author":47,"tags":48},"085eb2b0-52d6-409a-b90d-1201ba45def9","使用html-to-image结合jspdf实现下载pdf","## 安装\n```language\npnpm add html-to-image jspdf\n\n```\n\n## 使用\n```language\nimport * as htmlToImage from 'html-to-image';\nimport jsPDF from 'jspdf';\n\n\nhtmlToImage\n        .toCanvas(document.getElementById('five1'), {\n        .then(function (canvas) {\n          \u002F\u002F document.body.appendChild(canvas);\n          var contentWidth = canvas.width;\n          var contentHeight = canvas.height;\n\n          \u002F\u002F一页pdf显示html页面生成的canvas高度;\n          var pageHeight = contentWidth \u002F 592.28 * 841.89;\n          \u002F\u002F未生成pdf的html页面高度\n          var leftHeight = contentHeight;\n          \u002F\u002F页面偏移\n          var position = 0;\n          \u002F\u002Fa4纸的尺寸[595.28,841.89]，html页面生成的canvas在pdf中图片的宽高\n          var imgWidth = 595.28;\n          var imgHeight = 592.28 \u002F contentWidth * contentHeight;\n\n          var pageData = canvas.toDataURL('image\u002Fjpeg', 1.0);\n\n          var pdf = new jsPDF('', 'pt', 'a4');\n\n          \u002F\u002F有两个高度需要区分，一个是html页面的实际高度，和生成pdf的页面高度(841.89)\n          \u002F\u002F当内容未超过pdf一页显示的范围，无需分页\n          if (leftHeight \u003C pageHeight) {\n            console.log(imgWidth, imgHeight, 'imgWidth, imgHeight')\n            pdf.addImage(pageData, 'JPEG', 0, 0, imgWidth, imgHeight);\n          } else {\n            while (leftHeight > 0) {\n              pdf.addImage(pageData, 'JPEG', 0, position, imgWidth, imgHeight)\n              leftHeight -= pageHeight;\n              position -= 841.89;\n              \u002F\u002F避免添加空白页\n              if (leftHeight > 0) {\n                pdf.addPage();\n              }\n            }\n          }\n          pdf.save(`xxxxxx.pdf`);\n        }).catch((err) => {\n          console.log(err)\n          message.warning(\\\"导出PDF失败\\\")\n        });\n\n```\n\n","截图+PDF","https:\u002F\u002Fcdn.xiaolong0418.com\u002Fmyblog\u002Fimages\u002F2c01dcf58e9e921548b5c8832b3a0185.png",430,"2023-12-06T00:42:50.000Z","2026-06-26T09:54:46.414Z",{"id":44,"name":45,"slug":46},"6b9179c3-17b2-43ff-a431-a03d6eb32d89","Vue2 生态","vue2-生态",{"id":30,"name":31,"avatar":21},[49,53],{"id":50,"name":51,"slug":52},"2fef3b91-1c1c-4ae8-b2c1-0e04b4f9b3a2","Vue2生态","vue2生态",{"id":4,"name":5,"slug":6},{"id":55,"title":56,"slug":57,"content":58,"excerpt":59,"coverImage":60,"status":19,"isPinned":20,"pinnedAt":21,"viewCount":61,"publishedAt":62,"createdAt":62,"updatedAt":63,"category":64,"author":65,"tags":66},"7e72af98-ae65-4c2b-9d4a-25f998dee4bf","vue项目中使用websocket和EventSource\u002Fes","vue项目中使用websocket和eventsource-es","## websocket其他特点如下：\n```language\n1. 建立在 TCP 协议之上，服务器端的实现比较容易。\n2. 与 HTTP 协议有着良好的兼容性。默认端口也是80和443，并且握手阶段采用 HTTP 协议，因此握手时不容易屏蔽，能通过各种 HTTP 代理服务器。\n\n3. 数据格式比较轻量，性能开销小，通信高效。\n\n4. 可以发送文本，也可以发送二进制数据。\n\n5. 没有同源限制，客户端可以与任意服务器通信。\n\n6. 协议标识符是ws（如果加密，则为wss），服务器网址就是 URL。\n\n```\n\n### 封装WebSocket的hook\n```js\nimport { ref, onMounted, onUnmounted } from 'vue';\n\u002F**\n * @description websocket hook\n * @param url websocket地址：ws:\u002F\u002Fxxxxxxx\u002F\n * @param onMessage 收到消息的回调\n * @param heartbeatInterval 心跳间隔时间\n *\u002F\nexport default function useWebsocket(url:string, onMessage:(res:any)=>void,heartbeatInterval = 3000,) {\n  \u002F\u002F 用来存放websocket实例\n  const socketTask = ref(null as any);\n  \u002F\u002F 连接是否处于断开状态的标识\n  const isDisconnect = ref(true);\n  \u002F\u002F 心跳定时器\n  let heartbeatTimer = null as any;\n  \u002F\u002F 连接\n  const connect = () => {\n    if (isDisconnect.value) {\n      isDisconnect.value = false;\n      console.log('WebSocket连接中...',url)\n      console.log('socketTask',socketTask)\n      socketTask.value = new WebSocket(url);\n      socketTask.value.onopen = () => {\n        console.log('WebSocket连接已打开');\n        startHeartbeat();\n      }\n\n      socketTask.value.onclose = () => {\n        console.log('WebSocket连接已关闭');\n        if (isDisconnect.value) {\n          setTimeout(() => {\n            console.log('WebSocket尝试重新连接');\n            connect();\n          }, 1000);\n        }\n      } ;\n\n      socketTask.value.onerror =(error:any) => {\n        console.error('WebSocket连接发生错误:', error);\n        isDisconnect.value = true;\n      }\n\n      socketTask.value.onmessage = (res:{data:string}) => {\n        let data = JSON.parse(res.data);\n        console.log('收到服务器消息:', data)\n        if (onMessage) {\n          onMessage(data);\n        }\n      };\n    }\n  };\n  \u002F\u002F 断开连接\n  const disconnect = () => {\n    console.log('主动断开')\n    if (socketTask.value) {\n      isDisconnect.value = false;\n      socketTask.value.close();\n      stopHeartbeat();\n      socketTask.value = null;\n    }\n  };\n  \u002F\u002F 发送心跳\n  const startHeartbeat = () => {\n    heartbeatTimer = setInterval(() => {\n      if (socketTask.value) {\n        console.log('发送心跳')\n        socketTask.value.send({});\n      }\n    }, heartbeatInterval);\n  };\n  \u002F\u002F 停止心跳\n  const stopHeartbeat = () => {\n    clearInterval(heartbeatTimer);\n  };\n  \u002F\u002F 发送消息\n  const sendMessage = (message:any) => {\n    console.log('发送消息:socketTask.value', socketTask.value)\n    if (socketTask.value) {\n      socketTask.value.send({\n        data: JSON.stringify(message)\n      });\n    }\n  };\n  \u002F\u002F 组件挂载时连接\n  onMounted(() => {\n    connect();\n  });\n  \u002F\u002F 组件卸载时断开连接\n  onUnmounted(() => {\n    disconnect();\n  });\n\n  return {\n    socketTask,\n    connect,\n    disconnect,\n    sendMessage,\n  };\n}\n\n\n```\n\n\n### EventSource\n\n```language\n\u002F\u002F Vue项目中，EventSource触发的事件中this指向变了\n\u002F\u002F 使用const that = this，然后在EventSource触发的事件中使用that\n \nif (typeof (EventSource) !== 'undefined') {\n    const evtSource = new EventSource('\u002Flog\u002Fprint', { withCredentials: true }) \u002F\u002F 后端接口，要配置允许跨域属性\n    \u002F\u002F 与事件源的连接刚打开时触发\n    evtSource.onopen = function(e){\n        console.log(e);\n    }\n    \u002F\u002F 当从事件源接收到数据时触发\n    evtSource.onmessage = function(e){\n        console.log(e);\n    }\n    \u002F\u002F 与事件源的连接无法打开时触发\n    evtSource.onerror = function(e){\n        console.log(e);\n        evtSource.close(); \u002F\u002F 关闭连接\n    }\n    \u002F\u002F 也可以侦听命名事件，即自定义的事件\n    evtSource.addEventListener('notice', function(e) {\n        console.log(e.data)\n    })\n} else {\n    console.log('当前浏览器不支持使用EventSource接收服务器推送事件!');\n}\n\n```\n\n## es封装与使用\n```language\n\u002F\u002F utils\u002Fsse.js\n\u002F\u002F如果加自定义参数可以使用三方插件event-source-polyfill(添加请求头token)\nimport { EventSourcePolyfill } from 'event-source-polyfill'\n\nfunction createSSE(deviceName, handle) {\n  const clientId = JSON.parse(localStorage.getItem('params')).id\n  const token = JSON.parse(localStorage.getItem('params')).token\n  const sse = new EventSourcePolyfill(`\u002Fmonitor\u002Fsse\u002FcreateConnect?sign=${deviceName}&clientId=${clientId}`, {\n    headers: { token: token }\n  })\n  sse.addEventListener('message', env => {\n    const obj = JSON.parse(env.data)\n    handle(obj)\n  })\n  return sse\n}\n\nexport default createSSE\n\n\u002F\u002F vue\n\u003Ctemplate>\n  \u003Cdiv>\n    \u003Cdiv class=\\\"dialog\\\">\n      \u003Cdiv class=\\\"dialogTltle\\\">\n        \u003C img :src=\\\"\n            require('@\u002Fassets\u002Fimages\u002FdigitalTwin\u002F' +\n              (title == '采煤机'\n                ? 'coal_cutter'\n                : title == '破碎机'\n                ? 'crushing_machine'\n                : title == '运输机-机头'\n                ? 'transport_machine'\n                : title == '运输机-机尾'\n                ? 'transport_machine'\n                : title == '乳化液泵'\n                ? 'emulsion_pump'\n                : title == '清水泵'\n                ? 'pump'\n                : title == '液压支架'\n                ? 'hydraulic_support'\n                : title == '转载机'\n                ? 'transport_machine'\n                : title == '皮带机'\n                ? 'transport_machine'\n                : 'transport_machine') +\n              '.png')\n          \\\"\n          alt=\\\"\\\" \u002F>\n        \u003Cdiv>{{ title }}\u003C\u002Fdiv>\n      \u003C\u002Fdiv>\n      \u003C img @click=\\\"handleEyeClick\\\"\n        v-show=\\\"state.eyeFlag\\\"\n        src=\\\"@\u002Fassets\u002Fimages\u002FdigitalTwin\u002Fclose_eye.png\\\"\n        alt=\\\"\\\" \u002F>\n      \u003C img @click=\\\"handleEyeClick\\\"\n        v-show=\\\"!state.eyeFlag\\\"\n        src=\\\"@\u002Fassets\u002Fimages\u002FdigitalTwin\u002Feye.png\\\"\n        alt=\\\"\\\" \u002F>\n      \u003Cdiv class=\\\"dialogContent\\\">\n        \u003Cdiv v-for=\\\"item in Object.keys(newLabelObj)\\\"\n          :key=\\\"item\\\">\n          \u003Cdiv v-if=\\\"\n              item.indexOf('VoltageSide') === -1 &&\n              item != 'crusher' &&\n              item != 'pumpRunningQ' &&\n              item != 'pumpRunningR' &&\n              item != 'bracketInfo'\n            \\\">\n            \u003Cdiv class=\\\"title\\\">\n              {{\n                state.moduleType.find(val => val.typeDes === item)\n                  ? state.moduleType.find(val => val.typeDes === item).title\n                  : '未知'\n              }}\n            \u003C\u002Fdiv>\n            \u003Cel-row :gutter=\\\"20\\\">\n              \u003Cel-col :span=\\\"12\\\"\n                v-for=\\\"item1 in Object.keys(newLabelObj[item])\\\"\n                :key=\\\"item1\\\">\n                \u003Cdiv class=\\\"dialogName\\\">{{ item1 ? newLabelObj[item][item1] : '' }}\u003C\u002Fdiv>\n                \u003Cdiv class=\\\"dialogNameR\\\"\n                  :class=\\\"\n                    item1.indexOf('ArmMiningHeight') != -1 ||\n                    item1 === 'communicationState' ||\n                    item1 === 'runningDirection' ||\n                    item1 === 'warningCode' ||\n                    item1 === 'faultCode' ||\n                    item1 === 'deviceState' ||\n                    item1 === 'speedDisplay'\n                      ? 'dialogNameBackgroundArm'\n                      : item1 === 'totalFailure'\n                      ? 'dialogVoltageQ'\n                      : 'dialogNameBackground'\n                  \\\">\n                  \u003Cspan>\n                    \u003Cspan>\n                      {{\n                        state.moduleType.find(val => val.typeDes === item).data[item1]\n                          ? state.moduleType.find(val => val.typeDes === item).data[item1]\n                          : '--'\n                      }}\n                    \u003C\u002Fspan>\n                  \u003C\u002Fspan>\n                \u003C\u002Fdiv>\n              \u003C\u002Fel-col>\n            \u003C\u002Fel-row>\n          \u003C\u002Fdiv>\n          \u003Cdiv\n            v-if=\\\"item === 'crusher' || item === 'pumpRunningQ' || item === 'pumpRunningR' || item === 'bracketInfo'\\\">\n            \u003Cel-row :gutter=\\\"20\\\"\n              v-for=\\\"item1 in Object.keys(newLabelObj[item])\\\"\n              :key=\\\"item1\\\">\n              \u003Cel-col :span=\\\"14\\\">\n                \u003Cdiv class=\\\"dialogName\\\">{{ item1 ? newLabelObj[item][item1] : '' }}\u003C\u002Fdiv>\n                \u003Cdiv class=\\\"dialogNameR\\\"\n                  :class=\\\"\n                    item1.indexOf('ArmMiningHeight') != -1 ||\n                    item1 === 'communicationState' ||\n                    item1 === 'runningDirection' ||\n                    item1 === 'runState' ||\n                    item1 === 'runningState' ||\n                    item1 === 'bracketNumber' ||\n                    item1 === 'miningHigh' ||\n                    item1 === 'frontPillarPressure' ||\n                    item1 === 'advanceDistance' ||\n                    item1 === 'action' ||\n                    item1 === 'frontBeamInclination'\n                      ? 'dialogNameBackgroundArm'\n                      : 'dialogNameBackground'\n                  \\\">\n                  \u003Cspan>\n                    \u003Cspan>\n                      {{\n                        state.moduleType.find(val => val.typeDes === item).data[item1]\n                          ? state.moduleType.find(val => val.typeDes === item).data[item1]\n                          : '--'\n                      }}\n                    \u003C\u002Fspan>\n                  \u003C\u002Fspan>\n                \u003C\u002Fdiv>\n              \u003C\u002Fel-col>\n            \u003C\u002Fel-row>\n          \u003C\u002Fdiv>\n        \u003C\u002Fdiv>\n        \u003Cdiv>\n          \u003Ctemplate v-if=\\\"Object.keys(newLabelObj).find(val => val.indexOf('VoltageSide') != -1)\\\">\n            \u003Cdiv class=\\\"title\\\">移变参数\u003C\u002Fdiv>\n            \u003Cel-row :gutter=\\\"20\\\">\n              \u003Cel-col :span=\\\"12\\\">\n                \u003Cdiv class=\\\"dialogVoltageNameH\\\">高压侧\u003C\u002Fdiv>\n              \u003C\u002Fel-col>\n              \u003Cel-col :span=\\\"12\\\">\n                \u003Cdiv class=\\\"dialogVoltageNameH\\\">低压侧\u003C\u002Fdiv>\n              \u003C\u002Fel-col>\n              \u003Cel-col :span=\\\"12\\\">\n                \u003Cdiv v-for=\\\"item in Object.keys(newLabelObj)\\\"\n                  :key=\\\"item\\\">\n                  \u003Cdiv v-if=\\\"item.indexOf('ShiftHighVoltageSide') != -1\\\">\n                    \u003Cdiv v-for=\\\"item1 in Object.keys(newLabelObj[item])\\\"\n                      :key=\\\"item1\\\">\n                      \u003Cdiv :class=\\\"item1 === 'communicationStatus' ? 'dialogVoltageState' : 'dialogVoltage'\\\">\n                        {{ item1 ? newLabelObj[item][item1] : '--' }}\n                      \u003C\u002Fdiv>\n                      \u003Cdiv class=\\\"dialogVoltageT\\\"\n                        :class=\\\"\n                          item1 == 'faultCode'\n                            ? 'dialogVoltageQ'\n                            : item1 == 'communicationStatus'\n                            ? 'dialogVoltageStateValue'\n                            : 'dialogVoltage'\n                        \\\">\n                        \u003Cspan>\n                          \u003Cspan>\n                            {{\n                              state.moduleType.find(val => val.typeDes === item).data[item1]\n                                ? state.moduleType.find(val => val.typeDes === item).data[item1]\n                                : '--'\n                            }}\n                          \u003C\u002Fspan>\n                        \u003C\u002Fspan>\n                      \u003C\u002Fdiv>\n                    \u003C\u002Fdiv>\n                  \u003C\u002Fdiv>\n                \u003C\u002Fdiv>\n              \u003C\u002Fel-col>\n              \u003Cel-col :span=\\\"12\\\">\n                \u003Cdiv v-for=\\\"item in Object.keys(newLabelObj)\\\"\n                  :key=\\\"item\\\">\n                  \u003Cdiv v-if=\\\"item.indexOf('ShiftLowVoltageSide') != -1\\\">\n                    \u003Cdiv v-for=\\\"item1 in Object.keys(newLabelObj[item])\\\"\n                      :key=\\\"item1\\\">\n                      \u003Cdiv :class=\\\"item1 === 'communicationStatus' ? 'dialogVoltageState' : 'dialogVoltage'\\\">\n                        {{ item1 ? newLabelObj[item][item1] : '--' }}\n                      \u003C\u002Fdiv>\n                      \u003Cdiv class=\\\"dialogVoltageT\\\"\n                        :class=\\\"\n                          item1 == 'faultCode'\n                            ? 'dialogVoltageQ'\n                            : item1 == 'communicationStatus'\n                            ? 'dialogVoltageStateValue'\n                            : 'dialogVoltage'\n                        \\\">\n                        \u003Cspan>\n                          \u003Cspan>\n                            {{\n                              state.moduleType.find(val => val.typeDes === item).data[item1]\n                                ? state.moduleType.find(val => val.typeDes === item).data[item1]\n                                : '--'\n                            }}\n                          \u003C\u002Fspan>\n                        \u003C\u002Fspan>\n                      \u003C\u002Fdiv>\n                    \u003C\u002Fdiv>\n                  \u003C\u002Fdiv>\n                \u003C\u002Fdiv>\n              \u003C\u002Fel-col>\n            \u003C\u002Fel-row>\n          \u003C\u002Ftemplate>\n        \u003C\u002Fdiv>\n      \u003C\u002Fdiv>\n    \u003C\u002Fdiv>\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n\n\u003Cscript >\nimport { ref, reactive, onMounted, onUnmounted, watch } from 'vue'\nimport { labelObj, symbolObj } from '@\u002Fassets\u002Fcontant\u002FdigitalTwinLabel'\nimport createSSE from '@\u002Futils\u002Fsse'\nlet sse = null\nconst moduleType = [\n  \u002F\u002F 采煤机\n  {\n    typeDes: 'shearer',\n    showType: 'flexList',\n    title: '实时数据',\n    voltage: false,\n    data: {}\n  },\n  {\n    typeDes: 'shearerShiftHighVoltageSide',\n    showType: 'flexList',\n    title: '移变参数高',\n    voltage: true,\n    data: {}\n  }\n]\nvar transObj = {}\nexport default {\n  components: {},\n  props: {\n    armData: {\n      type: Object,\n      default: () => { }\n    },\n    title: {\n      type: String,\n      default: ''\n    },\n    bracketInfoIndex: {\n      type: String,\n      default: '2'\n    }\n  },\n  setup(props, { emit }) {\n    const newLabelObj = ref({ ...labelObj })\n    const state = reactive({\n      title: '数字孪生',\n      titlekey: '',\n      eyeFlag: false,\n      moduleType\n    })\n    watch(\n      () => props.title,\n      newProps => {\n        state.titlekey =\n          newProps === '皮带机'\n            ? 'belt'\n            : newProps === '清水泵'\n              ? 'pumpRunningQ'\n              : newProps === '乳化液泵'\n                ? 'pumpRunningR'\n                : newProps === '破碎机'\n                  ? 'crusher'\n                  : newProps === '转载机'\n                    ? 'transfer'\n                    : newProps === '运输机-机头'\n                      ? 'transport'\n                      : newProps === '运输机-机尾'\n                        ? 'transporter'\n                        : newProps === '液压支架'\n                          ? 'bracketInfo'\n                          : newProps === '采煤机'\n                            ? 'shearer'\n                            : ''\n        transObj = {}\n        Object.keys(labelObj).forEach(item => {\n          \u002F\u002F 除了采煤机的实时数据需要两个地方获取\n          if (state.titlekey === 'transport') {\n            item.indexOf(state.titlekey) === 0 && item !== 'transporterTailInfo' && (transObj[item] = labelObj[item])\n          } else if (state.titlekey === 'transporter') {\n            item.indexOf(state.titlekey) === 0 && item !== 'transporterHeaderInfo' && (transObj[item] = labelObj[item])\n          } else if (state.titlekey === 'pumpRunningQ') {\n            item.indexOf('pumpRunningQ') === 0 && (transObj[item] = labelObj[item])\n          } else if (state.titlekey === 'pumpRunningR') {\n            item.indexOf('pumpRunningR') === 0 && (transObj[item] = labelObj[item])\n          } else {\n            item.indexOf(state.titlekey) === 0 && (transObj[item] = labelObj[item])\n          }\n        })\n        console.log(transObj)\n        newLabelObj.value = []\n        newLabelObj.value = transObj\n      }\n    )\n\n    sse = createSSE('digitalTwin', data => {\n      state.moduleType.forEach(item => {\n        const type = item.typeDes\n        if (data.type === type) {\n          if (type === 'bracketInfo') {\n            item.data = Object.assign(item.data, { ...data.data[props.bracketInfoIndex] })\n          } else {\n            item.data = Object.assign(item.data, { ...data.data })\n          }\n        }\n        if (type === 'shearer') {\n          if (data.type === 'shearerLeftArm' || data.type === 'shearerRightArm') {\n            item.data = Object.assign(item.data, { ...data.data })\n          }\n        }\n        if (data.type === 'pumpRunning') {\n          if (type === 'pumpRunningR') {\n            item.data = Object.assign(item.data, { ...data.data[4] })\n          }\n          if (type === 'pumpRunningQ') {\n            item.data = Object.assign(item.data, { ...data.data[0] })\n          }\n        }\n      })\n      emit('realDta', data)\n    })\n    const handleEyeClick = () => {\n      emit('closeEye')\n    }\n\n    const initData = JSON.parse(JSON.stringify(labelObj))\n    for (const p in initData) {\n      for (const k in initData[p]) {\n        initData[p][k] = '--'\n      }\n    }\n    state.moduleType.forEach(item => {\n      item.data = initData[item.typeDes]\n    })\n    state.moduleType = JSON.parse(JSON.stringify(state.moduleType))\n\n    onMounted(() => { })\n    onUnmounted(() => {\n      sse && sse.close()\n    })\n    return {\n      state,\n      symbolObj,\n      labelObj,\n      newLabelObj,\n      handleEyeClick\n    }\n  }\n}\n\u003C\u002Fscript>\n\n\n```\n\n\n","前端和后端的交互模式最常见的就是前端发数据请求，从后端拿到数据后展示到页面中。如果前端不做操作，后端不能主动向前端推送数据，这也是http协议的缺陷。\n　　因此，一种新的通信协议应运而生—websocket，他最大的特点就是服务端可以主动向客户端推送消息，客户端也可以主动向服务端发送消息，实现了真正的平等。","https:\u002F\u002Fcdn.xiaolong0418.com\u002Fmyblog\u002Fimages\u002F80c4f8a30cc865c61ab680ad7741e923.png",706,"2023-11-30T19:59:39.000Z","2026-06-26T09:54:46.524Z",{"id":26,"name":27,"slug":28},{"id":30,"name":31,"avatar":21},[67],{"id":4,"name":5,"slug":6},{"id":69,"title":70,"slug":70,"content":71,"excerpt":70,"coverImage":72,"status":19,"isPinned":20,"pinnedAt":21,"viewCount":73,"publishedAt":74,"createdAt":74,"updatedAt":75,"category":76,"author":77,"tags":78},"fa105e69-976c-4de5-a868-20042006eae8","iframe相关知识","\n## 通讯\n> postMessage() 方法用于安全地实现跨源通信。\n\n## 语法\n```js\notherWindow.postMessage(message, targetOrigin, [transfer]);\n```\n| 参数 | 说明 | \n| - | - |\n| otherWindow | 其他窗口的一个引用，比如 iframe 的 contentWindow 属性、执行 window.open 返回的窗口对象、或者是命名过或数值索引的 window.frames。 | \n| message | 将要发送到其他 window的数据。 | \n| targetOrigin | 指定哪些窗口能接收到消息事件，其值可以是 *（表示无限制）或者一个 URI。 | \n| transfer | 可选，是一串和 message 同时传递的 Transferable 对象。这些对象的所有权将被转移给消息的接收方，而发送一方将不再保有所有权。 | \n\n\n\n```js\n\u003Cdiv>\n    \u003Cinput id=\\\"text\\\" type=\\\"text\\\" value=\\\"Runoob\\\" \u002F>\n    \u003Cbutton id=\\\"sendMessage\\\" >发送消息\u003C\u002Fbutton>\n\u003C\u002Fdiv>\n\u003Ciframe id=\\\"receiver\\\" src=\\\"https:\u002F\u002Fc.runoob.com\u002Frunoobtest\u002FpostMessage_receiver.html\\\" width=\\\"300\\\" height=\\\"360\\\">\n    \u003Cp>你的浏览器不支持 iframe。\u003C\u002Fp>\n\u003C\u002Fiframe>\n\u003Cscript>\nwindow.onload = function() {\n    var receiver = document.getElementById('receiver').contentWindow;\n    var btn = document.getElementById('sendMessage');\n    btn.addEventListener('click', function (e) {\n        e.preventDefault();\n        var val = document.getElementById('text').value;\n        receiver.postMessage(\\\"Hello \\\"+val+\\\"！\\\", \\\"https:\u002F\u002Fc.runoob.com\\\");\n    });\n}\n\u003C\u002Fscript>\n```\n\n\n```js\n\u003Cdiv id=\\\"recMessage\\\">\nHello World!\n\u003C\u002Fdiv>\n\u003Cscript>\nwindow.onload = function() {\n    var messageEle = document.getElementById('recMessage');\n    window.addEventListener('message', function (e) {\n        alert(e.origin);\n        if (e.origin !== \\\"https:\u002F\u002Fwww.runoob.com\\\") {\n            return;\n        }\n        messageEle.innerHTML = \\\"从\\\"+ e.origin +\\\"收到消息： \\\" + e.data;\n    });\n}\n\u003C\u002Fscript>\n```\n\n","https:\u002F\u002Fcdn.xiaolong0418.com\u002Fmyblog\u002Fimages\u002F72734264934e5e1689a7df5a080768e5.png",518,"2023-07-10T18:36:45.000Z","2026-06-26T09:54:48.062Z",{"id":26,"name":27,"slug":28},{"id":30,"name":31,"avatar":21},[79],{"id":4,"name":5,"slug":6},{"id":81,"title":82,"slug":82,"content":83,"excerpt":84,"coverImage":85,"status":19,"isPinned":20,"pinnedAt":21,"viewCount":86,"publishedAt":87,"createdAt":87,"updatedAt":88,"category":89,"author":93,"tags":94},"832f843c-1039-4688-9c02-9ca87a25665a","localforage超大存储","## 安装\n```shell\npnpm add -S localforage\n```\n\n\n## 方法封装\n```js\nlocalforage.config({\n    name: \\\"mj-opreation-vue\\\", \u002F\u002F 项目名，即数据表名字\n});\n\u002F\u002F 查\nlocalforage.getItem(key);\n\n\u002F\u002F 增 | 改\nlocalforage.setItem(key, value);\n\n\u002F\u002F 删\nlocalforage.removeItem(key);\n\n```\n","localForage 是一个快速简单的 JavaScript 存储库。localForage 通过使用简单的localStorage类似 API 的异步存储（IndexedDB 或 WebSQL）来改善 Web 应用程序的离线体验。","https:\u002F\u002Fcdn.xiaolong0418.com\u002Fmyblog\u002Fimages\u002F92c81574d19a39b31c2092b25290f993.png",512,"2023-06-02T03:48:21.000Z","2026-06-26T09:54:47.420Z",{"id":90,"name":91,"slug":92},"e0f3b8d8-cfe7-41fb-802b-a79699d95968","JavaScript插件","javascript插件",{"id":30,"name":31,"avatar":21},[95],{"id":4,"name":5,"slug":6},{"id":97,"title":98,"slug":99,"content":100,"excerpt":98,"coverImage":101,"status":19,"isPinned":20,"pinnedAt":21,"viewCount":102,"publishedAt":103,"createdAt":103,"updatedAt":104,"category":105,"author":106,"tags":107},"1dcdd755-0e46-45a3-8998-9a213fd3fcd5","Vue3 导入导出Excel","vue3-导入导出excel","## 安装\n```shell\npnpm add -S XLSX\n```\n\n\n## 方法封装\n```js\nimport * as XLSX from 'xlsx';\n\n\u002F\u002F参数说明\n\u002F\u002Fconfiguration: {\n\u002F\u002F  data: [], \u002F\u002F 导出的数据\n\u002F\u002F  head: {}, \u002F\u002F 导出的数据对应的表头\n\u002F\u002F  name: '', \u002F\u002F 导出的文件名\n\u002F\u002F  label: '', \u002F\u002F 导出的表单名\n\u002F\u002F  widthArr: [], \u002F\u002F 导出的表单列宽\n\u002F\u002F}\n\n\u002F\u002F 导出excel\nexport const ExportXlsx = (configuration) => {\n  const { data, head, name, label, widthArr } = configuration;\n\n  const list = data.map((item) => {\n    const obj = {};\n    for (const k in item) {\n      if (head[k]) {\n        obj[head[k]] = item[k];\n      }\n    }\n    return obj;\n  });\n\n  \u002F\u002F 创建工作表\n  const xLSXData = XLSX.utils.json_to_sheet(list);\n  \u002F\u002F 创建工作簿\n  const wb = XLSX.utils.book_new();\n  \u002F\u002F 将工作表放入工作簿中\n  XLSX.utils.book_append_sheet(wb, xLSXData, label);\n  xLSXData['!cols'] = [];\n  \u002F\u002F 设置列宽\n  widthArr.forEach((item) => {\n    xLSXData['!cols'].push({ wpx: item });\n  });\n\n  \u002F\u002F 生成文件并下载\n  XLSX.writeFile(wb, `${name}.xlsx`);\n};\n\n\u002F\u002F 导入excel\nexport const ImportXlsx = (e) => {\n  const file = e.target.files[0];\n  const reader = new FileReader();\n  reader.readAsArrayBuffer(file);\n  reader.onload = (e) => {\n    const data = e.target.result;\n    const workbook = XLSX.read(data, { type: 'binary', cellDates: true });\n    const wsname = workbook.SheetNames[0];\n    const outdata = XLSX.utils.sheet_to_json(workbook.Sheets[wsname]);\n    console.log(outdata);\n  };\n};\n\n```\n","https:\u002F\u002Fcdn.xiaolong0418.com\u002Fmyblog\u002Fimages\u002F80231a326d3846b895a7d46c99e738ce.png",1334,"2023-03-14T06:18:59.000Z","2026-06-26T09:54:47.798Z",{"id":26,"name":27,"slug":28},{"id":30,"name":31,"avatar":21},[108],{"id":4,"name":5,"slug":6},{"id":110,"title":111,"slug":111,"content":112,"excerpt":111,"coverImage":113,"status":19,"isPinned":20,"pinnedAt":21,"viewCount":114,"publishedAt":115,"createdAt":115,"updatedAt":116,"category":117,"author":118,"tags":119},"c536eeaa-4ba3-439c-a68f-e2de06eaf994","小程序web-view教程","## 官方教程\n```language\nhttps:\u002F\u002Fdevelopers.weixin.qq.com\u002Fminiprogram\u002Fdev\u002Fcomponent\u002Fweb-view.html\n```\n\n\n## web-view页面通过wx.miniProgram.navigateTo跳转到小程序页面\n> CDN导入\n```language\n\u003Cscript src=\\\"https:\u002F\u002Fres.wx.qq.com\u002Fopen\u002Fjs\u002Fjweixin-1.3.2.js#wechat_redirect\\\">\u003C\u002Fscript>\n```\n\n> npm安装\n```language\nnpm i -S weixin-js-sdk\n```\n\n```js\nimport wx from 'weixin-js-sdk';\n```\n\n\n\n## 在需要做跳转的事件中：\n```js\n\u002F\u002F关闭浏览器（微信内置浏览器私有接口）\nWeixinJSBridge.call('closeWindow');\n\u002F\u002F返回小程序页面\nwx.miniProgram.navigateTo({\n      url: '\u002Fpages\u002Fad\u002Findex'\n})\n```\n\n\n\n","https:\u002F\u002Fcdn.xiaolong0418.com\u002Fmyblog\u002Fimages\u002F32d9090e7ffb54c7e29011f04ababd8c.png",575,"2023-02-23T10:04:52.000Z","2026-05-24T20:08:40.000Z",{"id":90,"name":91,"slug":92},{"id":30,"name":31,"avatar":21},[120],{"id":4,"name":5,"slug":6},{"id":122,"title":123,"slug":124,"content":125,"excerpt":123,"coverImage":126,"status":19,"isPinned":20,"pinnedAt":21,"viewCount":127,"publishedAt":128,"createdAt":128,"updatedAt":129,"category":130,"author":131,"tags":132},"93de6c59-fc6d-49d6-877d-4405ab187652","一文掌握JavaScript事件循环机制（event loop）","一文掌握javascript事件循环机制-event-loop","## 简介\n> javascript是一个单线程语言\n\n## 任务\n|任务| 优先级 | 执行位置 | \n| - | - | - | \n|同步任务| 只要被扫描到，就可以被主线程马上执行的任务。（优先于所有异步任务） |主线程 | \n|异步任务| 即使被扫描到，也不会马上执行，异步任务会被压入异步任务队列中，等待主线程中的任务全部清空了，再被召唤执行。 | macrotask或者microtask| \n\n## 常见的任务有如下几种\n| 任务 | 举例 |\n| - | - |\n| 同步任务 | new promise()、console.log() |\n| 微任务(micro task) | promise、async、await、process.nextTick(node)、mutationObserver(html5新特性) |\n| 宏任务(macro task) | script(整体代码)、setTimeout()、setInterval()、setImmediate 、I\u002FO、UI render |\n\n\n```js\nsetTimeout(() => {\nconsole.log(\\\"1\\\");\n},0)\n\nconsole.log(2)\n\u002F\u002F输出结果是 2，1  虽然setTimeout的延迟是0，但setTimeout是一个异步任务，他一定会在所有同步任务执行完毕之后再去执行。\n```\n\n\n> 当有异步任务被压入异步任务队列时候，javascript会将这些异步任务分为宏任务和微任务两个新的队列。然后，在所有同步任务执行完毕之后，异步任务会优先执行所有已经存在任务队列中的微任务。在所有的微任务执行完毕之后，再去宏任务队列中执行一个（注意是一个,注意是一个,注意是一个）宏任务，执行完一个宏任务之后会再去微任务队列中检查是否有新的微任务，有则全部执行，再回到宏任务队列执行一个宏任务，以此循环。这一套流程，就是事件循环（event loop）\n\n![](https:\u002F\u002Fcdn.xiaolong0418.com\u002Fmyblog\u002Fimages\u002F1a5f1dc1a249b951eb6c5ebf879e140e.webp)\n\n```js\n  setTimeout(() => { console.log(\\\"1\\\") }, 0);  \u002F\u002F异步任务 - 宏任务\n\n  console.log(2);   \u002F\u002F同步任务\n\n  Promise.resolve().then(() => { console.log(3) }) \u002F\u002F异步任务 - 微任务\n\n  console.log(6);   \u002F\u002F同步任务\n\n```\n> 输出结果：2 6 3 1\n> 首先，同步任务必定优先于所有所有异步任务并按顺序执行。所以输出 2 6。同步任务执行完毕后，还剩下一个宏任务和一个微任务。微任务优先于宏任务执行，所以先输出 3 再输出 1\n\n\n```js\n        \u002F\u002F第一个宏任务\n         setTimeout(() => {\n              console.log(1); \u002F\u002F宏任务中的同步任务\n              Promise.resolve().then(() => { console.log(7) }) \u002F\u002F宏任务中的微任务\n         }, 0);  \u002F\u002F异步任务 - 宏任务\n\n        console.log(2);   \u002F\u002F同步任务\n\n        Promise.resolve().then(() => { console.log(3) }) \u002F\u002F异步任务 - 微任务\n\n        \u002F\u002F第二个宏任务\n        setTimeout(() => { \n          console.log(8); \u002F\u002F宏任务中的同步任务\n          setTimeout(() => { console.log(5) }, 0)      \u002F\u002F宏任务中的宏任务 第四个宏任务\n        }, 0);\n\n        \u002F\u002F第三个宏任务\n        setTimeout(() => { \n          Promise.resolve().then(() => { console.log(4) })  \u002F\u002F宏任务中的微任务\n        }, 0);\n        \n         console.log(6);   \u002F\u002F同步任务\n```\n> 输出结果：2 6 3 1 7 8 4 5\n\n\n> 首先，同步任务必定优先于所有所有异步任务并按顺序执行。所以输出 2 6。\n然后同一批次中剩下一个微任务和一个三个宏任务。\n因为宏任务必定会在同一批次环境中的微任务全部执行完毕后再执行，所以场上当前批次中唯一一个微任务先执行。输出3\n还剩下三个宏任务。执行第一个宏任务，宏任务中有一个同步任务和一个异步任务。这里要注意两点。\n统一批次宏任务中按顺序执行一次只执行一个宏任务，然后同步任务当场执行。微任务压入队列。然后就要去检查有没有微任务，有则执行\n所以，第一个宏任务执行的时候，产生了一个同步任务和一个微任务。需要注意,宏任务一次只执行一个。执行完之后发现同步任务当场执行（输出1），然后查看微任务队列中有没有微任务可以执行。发现有，则执行微任务（输出7）\n然后,才开始执行第二个宏任务。执行第二个宏任务产生了一个同步任务，同步任务当场执行（输出8），产生一个宏任务（宏任务压入红任务执行队列，也就是所有宏任务之后），按事件循环，再次检查是否存在未执行的微任务，发现没有，不执行。\n然后执行第三个宏任务，第三个宏任务中产生一个微任务，按事件循环，再去寻找是否存在未执行的微任务，发现有，则执行（输出4）\n最后执行第四个宏任务（第二个宏任务产生的）。走一遍事件循环的流程，输出5\n\n\n\n```js\nconsole.log('script start'); \u002F\u002F同步任务，顺序 1\n\nasync function async1() {\n    await async2(); \u002F\u002F异步任务 - 微任务 (async\u002Fawait 底层依然是 Promise，所以是微任务，只是 await 比较特殊)\n    console.log('async1 end'); \u002F\u002F顺序 5\n}\nasync function async2() {\n    console.log('async2 end');\n}\nasync1(); \u002F\u002F异步任务，顺序 2\n\nsetTimeout(function () {\n    console.log('setTimeout'); \u002F\u002F异步任务 - 宏任务，顺序 7\n}, 0);\n\nnew Promise(resolve => {\n    console.log('Promise'); \u002F\u002F同步任务，顺序 3\n    resolve();\n})\n    .then(function () {\n        console.log('promise1'); \u002F\u002F异步任务 - 微任务，顺序 5\n    })\n    .then(function () {\n        console.log('promise2'); \u002F\u002F异步任务 - 微任务，顺序 6\n    });\n\nconsole.log('script end'); \u002F\u002F同步任务，顺序 4\n\n\n```\n> 新版输出(新版的chrome浏览器优化了,await变得更快了,输出为)\n> 输出答案：script start、async2 end、Promise、script end、async1 end、promise1、promise2、setTimeout\n\n\n\n## async\u002Fawait （重点）\n### async\n> 当我们在函数前使用async的时候，使得该函数返回的是一个Promise对象\n```js\nasync function test() {\n    return 1   \u002F\u002F async的函数会在这里帮我们隐士使用Promise.resolve(1)\n}\n\u002F\u002F 等价于下面的代码\nfunction test() {\n   return new Promise(function(resolve, reject) {\n       resolve(1)\n   })\n}\n\u002F\u002F 可见async只是一个语法糖，只是帮助我们返回一个Promise而已\n```\n\n### await\n> await表示等待，是右侧「表达式」的结果，这个表达式的计算结果可以是 Promise 对象的值或者一个函数的值（换句话说，就是没有特殊限定）。并且只能在带有async的内部使用\n使用await时，会从右往左执行，当遇到await时， ★★★★★会阻塞函数内部处于它后面的代码，去执行该函数外部的同步代码，当外部同步代码执行完毕，再回到该函数内部执行剩余的代码★★★★★, 并且当await执行完毕之后，会先处理微任务队列的代码\n\n```js\n\u002F\u002F1\nconsole.log('1');    \n\u002F\u002F2\nsetTimeout(function() {\n    console.log('2');\n    process.nextTick(function() {\n        console.log('3');\n    })\n    new Promise(function(resolve) {\n        console.log('4');\n        resolve();\n    }).then(function() {\n        console.log('5')\n    })\n})\n\u002F\u002F3\nprocess.nextTick(function() {\n    console.log('6');\n})\n\u002F\u002F4\nnew Promise(function(resolve) {\n    console.log('7');\n    resolve();\n}).then(function() {\n    console.log('8')\n})\n\u002F\u002F5\nsetTimeout(function() {\n    console.log('9');\n    process.nextTick(function() {\n        console.log('10');\n    })\n    new Promise(function(resolve) {\n        console.log('11');\n        resolve();\n    }).then(function() {\n        console.log('12')\n    })\n})\n\n\u002F\u002F 先执行1 输出1\n\u002F\u002F 执行到2，把setTimeout放入异步的任务队列中（宏任务）\n\u002F\u002F 执行到3，把process.nextTick放入异步任务队列中（微任务）\n\u002F\u002F 执行到4，上面提到promise里面是同步任务，所以输出7，再将then放入异步任务队列中（微任务）\n\u002F\u002F 执行到5，同2\n\u002F\u002F 上面的同步任务全部完成，开始进行异步任务\n\u002F\u002F 先执行微任务，发现里面有两个微任务，分别是3，4压入的，所以输出6 8\n\u002F\u002F 再执行一个宏任务，也就是第一个setTimeout\n\u002F\u002F 先输出2，把process.nextTick放入微任务中，再如上promise先输出4，再将then放入微任务中\n\u002F\u002F 再执行所以微任务输出输出3 5\n\u002F\u002F 同样的，再执行一个宏任务setTImeout2，输出9 11 在执行微任务输出10 12\n\u002F\u002F 所以最好的顺序为：1 7 6 8 2 4 3 5 9 11 10 12\n```\n\n```js\nasync function async1() {\n    console.log('async1 start');\n    await async2();\n    console.log('async1 end');\n}\n\nasync function async2() {\n    console.log('async2');\n}\nconsole.log('script start');\nsetTimeout(function () {\n    console.log('setTimeout');\n}, 0);\nasync1();\nnew Promise(function (resolve) {\n    console.log('promise1');\n    resolve();\n}).then(function () {\n    console.log('promise2');\n});\nconsole.log('script end');\n\n\u002F\u002F 首先执行同步代码，console.log( 'script start' )\n\u002F\u002F 遇到setTimeout,会被推入宏任务队列\n\u002F\u002F 执行async1(), 它也是同步的，只是返回值是Promise，在内部首先执行console.log( 'async1 start' )\n\u002F\u002F 然后执行async2(), 然后会打印console.log( 'async2' )\n\u002F\u002F 从右到左会执行, 当遇到await的时候，阻塞后面的代码，去外部执行同步代码\n\u002F\u002F 进入new Promise,打印console.log( 'promise1' )\n\u002F\u002F 将.then放入事件循环的微任务队列\n\u002F\u002F 继续执行，打印console.log( 'script end' )\n\u002F\u002F 外部同步代码执行完毕，接着回到async1()内部, 继续执行 await async2() 后面的代码，执行 console.log( 'async1 end' ) ，所以打印出 async1 end 。（个人理解：async\u002Fawait本质上也是Promise，也是属于微任务的，所以当遇到await的时候，await后面的代码被阻塞了，应该也是被放到微任务队列了，当同步代码执行完毕之后，然后去执行微任务队列的代码，执行微任务队列的代码的时候，也是按照被压入微任务队列的顺序执行的）\n\u002F\u002F 执行微任务队列的代码, 打印 console.log( 'promise2' )\n\u002F\u002F 进入第二次事件循环，执行宏任务队列, 打印console.log( 'setTimeout' )\n\u002F**\n * 执行结果为：\n * script start\n * async1 start\n * async2\n * promise1\n * script end\n * async1 end\n * promise2\n * setTimeout\n *\u002F\n\n```\n\n```js\nconsole.log(1);\nasync function fn(){\n    console.log(2)\n    await console.log(3)\n    await console.log(4)\n    await console.log(\\\"await之后的：\\\",11)\n    await console.log(\\\"await之后的：\\\",22)\n    await console.log(\\\"await之后的：\\\",33)\n    await console.log(\\\"await之后的：\\\",44)\n}\nsetTimeout(()=>{\n    console.log(5)\n},0)\nfn();\nnew Promise((resolve)=>{\n    console.log(6)\n    resolve();\n}).then(()=>{\n    console.log(7)\n})\nconsole.log(8)\n\n\u002F**\n * 执行结果为：\n * 1\n * 2\n * 3\n * 6\n * 8\n * 4\n * 7\n * await之后的： 11\n * await之后的： 22\n * await之后的： 33\n * await之后的： 44\n * 5\n *\u002F\n\u002F*\n由此可见，代码执行的时候，只要碰见 await ，都会执行完当前的 await 之后，\n把 await 后面的代码放到微任务队列里面。但是定时器里面的 5 是最后打印出来的，\n可见当不断碰见 await ，把 await 之后的代码不断的放到微任务队列里面的时候，\n代码执行顺序是会把微任务队列执行完毕，才会去执行宏任务队列里面的代码。\n*\u002F\n```\n\n\n\n\n","https:\u002F\u002Fcdn.xiaolong0418.com\u002Fmyblog\u002Fimages\u002F52c39c689c44243cc9e821da5c7fa0b1.png",487,"2023-02-08T09:04:22.000Z","2026-05-25T08:55:27.000Z",{"id":26,"name":27,"slug":28},{"id":30,"name":31,"avatar":21},[133],{"id":4,"name":5,"slug":6},{"id":135,"title":136,"slug":137,"content":138,"excerpt":136,"coverImage":139,"status":19,"isPinned":20,"pinnedAt":21,"viewCount":140,"publishedAt":141,"createdAt":141,"updatedAt":142,"category":143,"author":144,"tags":145},"3531c155-3fe4-41e4-9887-404c4729d775","详解ES6中的class","详解es6中的class","## 简介\n> class是一个语法糖，其底层还是通过 构造函数 去创建的。所以它的绝大部分功能，ES5 都可以做到。新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已\n\n\n### ES5写法\n```js\nfunction Person(name, age) {\n    this.name = name;\n    this.age = age;\n}\nPerson.prototype.sayName = function() {\n    return this.name;\n}\n\nconst xiaoming = new Person('小明', 18);\nconsole.log(xiaoming);\n\n```\n\n### ES6写法\n```language\nclass Person {\n    constructor(name, age) {\n      this.name = name;\n      this.age = age;\n    }\n  \n    sayName() {\n      return this.name;\n    }\n}\nconst xiaoming = new Person('小明', 18)\nconsole.log(xiaoming);\n\u002F\u002F { name: '小明', age: 18 }\n\nconsole.log((typeof Person));\n\u002F\u002F function\nconsole.log(Person === Person.prototype.constructor);\n\u002F\u002F true\n\n```\n> constructor方法，这就是构造方法，this关键字代表实例对象。 类的数据类型就是函数，类本身就指向构造函数。定义类的时候，前面不需要加 function, 而且方法之间不需要逗号分隔，加了会报错。\n\n\n### 类的所有方法都定义在类的prototype属性上面,\n```js\nclass A {\n    constructor() {}\n    toString() {}\n    toValue() {}\n}\n\u002F\u002F 等同于\nfunction A () {\n    \u002F\u002F constructor\n};\nA.prototype.toString = function() {};\nA.prototype.toValue = function() {};\n\nlet a = new A();\n\u002F\u002F在类的实例上面调用方法，其实就是调用原型上的方法。\na.constructor === A.prototype.constructor \u002F\u002F true\n\n```\n\n## constructor 方法\n> constructor方法是类的默认方法，通过new命令生成对象实例时，自动调用该方法。一个类必须有constructor方法，如果没有显式定义，一个空的constructor方法会被默认添加。\n```js\nclass A {\n}\n\n\u002F\u002F 等同于\nclass A {\n  constructor() {}\n}\n\nclass A {\n  constructor() {\n      return Object.create(null);\n  }\n}\n\n\u002F\u002Fconstructor方法默认返回实例对象（即this），完全可以指定返回另外一个对象。\nconsole.log((new A()) instanceof A);\n\u002F\u002F false\n```\n\n## 类的实例\n>实例的属性除非显式定义在其本身（即定义在this对象上），否则都是定义在原型上（即定义在class上）。\n\n### 注意：\n\n#### class不存在变量提升,因为 ES6 不会把类的声明提升到代码头部。这种规定的原因与继承有关，必须保证子类在父类之后定义。\n```js\nnew A(); \u002F\u002F ReferenceError\nclass A {}\n\n{\n  let A = class {};\n  class B extends A {}\n}\n\u002F\u002F上面的代码不会报错，因为 B继承 A的时候，A已经有了定义。但是，如果存在 class提升，上面代码就会报错，因为 class 会被提升到代码头部，而let命令是不提升的，所以导致 B 继承 A 的时候，Foo还没有定义。\n```\n\n#### this的指向 类的方法内部如果含有this，它默认指向类的实例。但是，必须非常小心，一旦单独使用该方法，很可能报错。\n\n\n## 静态方法\n> 类相当于实例的原型，所有在类中定义的方法，都会被实例继承。 如果在一个方法前，加上 static 关键字，就表示该方法不会被实例继承，而是直接通过类来调用，这就称为\\\"静态方法\\\"。\n\n```js\nclass A {\n    static classMethod() {\n        return 'hello';\n    }\n}\nA.classMethod();\nconsole.log(A.classMethod());\n\u002F\u002F 'hello'\n\nconst a = new A();\na.classMethod();\n\u002F\u002F TypeError: a.classMethod is not a function\n\n\n```\n\n>A 类的classMethod 方法前有 static关键字，表明这是一个静态方法，可以在 A 类上直接调用，而不是在实例上调用\n在实例a上调用静态方法，会抛出一个错误，表示不存在改方法。\n如果静态方法包含this关键字，这个this指的是类，而不是实例。\n\n```js\nclass A {\n    static classMethod() {\n      this.baz();\n    }\n    static baz() {\n      console.log('hello');\n    }\n    baz() {\n      console.log('world');\n    }\n}\nA.classMethod();\n\u002F\u002F hello\n```\n\n> 静态方法classMethod调用了this.baz，这里的this指的是A类，而不是A的实例，等同于调用A.baz。另外，从这个例子还可以看出，静态方法可以与非静态方法重名。父类的静态方法，可以被子类继承。\n\n```js\nclass A {\n    static classMethod() {\n        console.log('hello');\n    }\n}\n\nclass B extends A {}\n\nB.classMethod() \u002F\u002F 'hello'\n```\n\n## 静态属性\n> 静态属性指的是 Class 本身的属性，即Class.propName，而不是定义在实例对象（this）上的属性。 写法是在实例属性的前面，加上static关键字。\n```js\nclass MyClass {\n  static myStaticProp = 42;\n\n  constructor() {\n    console.log(MyClass.myStaticProp); \u002F\u002F 42\n  }\n}\n```\n## 继承\n> Class 可以通过extends关键字实现继承\n\n```js\nclass Animal {}\nclass Cat extends Animal { };\n```\n\n> 上面代码中 定义了一个 Cat 类，该类通过 extends关键字，继承了 Animal 类中所有的属性和方法。 但是由于没有部署任何代码，所以这两个类完全一样，等于复制了一个Animal类。 下面，我们在Cat内部加上代码。\n\n```js\nclass Cat extends Animal {\n    constructor(name, age, color) {\n        \u002F\u002F 调用父类的constructor(name, age)\n        super(name, age);\n        this.color = color;\n    }\n    toString() {\n        return this.color + ' ' + super.toString(); \u002F\u002F 调用父类的toString()\n    }\n}\n```\n\n> constructor方法和toString方法之中，都出现了super关键字，它在这里表示父类的构造函数，用来新建父类的this对象。\n子类必须在 constructor 方法中调用 super 方法，否则新建实例就会报错。\n这是因为子类自己的this对象，必须先通过 父类的构造函数完成塑造，得到与父类同样的实例属性和方法，然后再对其进行加工，加上子类自己的实例属性和方法。如果不调用super方法，子类就得不到this对象。\n\n```js\nclass Animal { \u002F* ... *\u002F }\n\nclass Cat extends Animal {\n  constructor() {\n  }\n}\n\nlet cp = new Cat();\n\u002F\u002F ReferenceError\n```\n> Cat 继承了父类 Animal，但是它的构造函数没有调用super方法，导致新建实例报错。\n如果子类没有定义constructor方法，这个方法会被默认添加，代码如下。也就是说，不管有没有显式定义，任何一个子类都有constructor方法。\n\n```js\nclass Cat extends Animal {\n\n}\n\u002F\u002F 等同于\n\nclass Cat extends Animal {\n    constructor(...args) {\n        super(...args);\n    }\n}\n```\n>另一个需要注意的地方是，es5 的构造函数在调用父构造函数前可以访问 this, 但 es6 的构造函数在调用父构造函数(即 super)前不能访问 this。\n\n```js\nclass A {\n  constructor(x, y) {\n    this.x = x;\n    this.y = y;\n  }\n}\n\nclass B extends A {\n  constructor(x, y, name) {\n    this.name = name; \u002F\u002F ReferenceError\n    super(x, y);\n    this.name = name; \u002F\u002F 正确\n  }\n}\n\n```\n\n>上面代码中，子类的constructor方法没有调用super之前，就使用this关键字，结果报错，而放在super方法之后就是正确的。\n\n>父类的静态方法，也会被子类继承。\n```js\nclass A {\n  static hello() {\n    console.log('hello world');\n  }\n}\n\nclass B extends A {\n}\n\nB.hello()  \u002F\u002F hello world\n```\n\n## super\n> super这个关键字，既可以当作函数使用，也可以当作对象使用\n### super作为函数调用\n> super作为函数调用时，代表父类的构造函数。ES6 要求，子类的构造函数必须执行一次super函数。\n\n```language\nclass A {}\n\nclass B extends A {\n  constructor() {\n    super();\n  }\n}\n```\n\n","https:\u002F\u002Fcdn.xiaolong0418.com\u002Fmyblog\u002Fimages\u002F39ded663bbccfdd00e61b7c365bc8285.png",536,"2023-02-08T07:11:56.000Z","2026-05-24T20:10:09.000Z",{"id":26,"name":27,"slug":28},{"id":30,"name":31,"avatar":21},[146],{"id":4,"name":5,"slug":6},{"id":148,"title":149,"slug":150,"content":151,"excerpt":149,"coverImage":152,"status":19,"isPinned":20,"pinnedAt":21,"viewCount":153,"publishedAt":154,"createdAt":154,"updatedAt":155,"category":156,"author":157,"tags":158},"325a2a6b-3b8b-494b-af9e-95b6fbcd4ca8","Promise入门到精通","promise入门到精通","## 简介\n> Promise 是一个 ECMAScript 6 提供的类，目的是更加优雅地书写复杂的异步任务。\n\n\n## 属性\n| 方法 | 作用 | 应用场景 |\n| - | - | - | \n| .then() | 将参数中的函数添加到当前 Promise 的正常执行序列，.then() 传入的函数会按顺序依次执行，有任何异常都会直接跳到 catch 序列： |- | \n| .catch()  | 设定 Promise 的异常处理序列 | - | \n| .finally() | 在 Promise 执行的最后一定会执行的序列 |- | \n| .all() | 等待所有都完成（或第一个失败） | 聚合请求 |\n| .allSettled  |返回一个promise，该promise在所有给定的promise已被解析或被拒绝后解析，并且每个对象都描述每个promise的结果。 | |\n\n\n```js\nnew Promise(function (resolve, reject) {\n    setTimeout(function () {\n        console.log(\\\"First\\\");\n        resolve();\n    }, 1000);\n}).then(function () {\n    return new Promise(function (resolve, reject) {\n        setTimeout(function () {\n            console.log(\\\"Second\\\");\n            resolve();\n        }, 4000);\n    });\n}).catch(function (err) {\n    console.log(err);\n}).finally(function () {\n    console.log(\\\"End\\\");\n});\n```\n\n\n```js\nconst promise1 = Promise.resolve(3);\nconst promise2 = 42;\nconst promise3 = new Promise((resolve, reject) => {\n  setTimeout(resolve, 100, 'foo');\n});\n\nPromise.all([promise1, promise2, promise3]).then((values) => {\n  console.log(values);\n});\n```\n\n","https:\u002F\u002Fcdn.xiaolong0418.com\u002Fmyblog\u002Fimages\u002Fe582ec27a0007f63896b331ce1e5ef15.png",578,"2023-02-08T06:24:11.000Z","2026-05-24T20:10:40.000Z",{"id":26,"name":27,"slug":28},{"id":30,"name":31,"avatar":21},[159],{"id":4,"name":5,"slug":6},24,1,10,{"categories":164,"tags":241,"postCount":418,"tagCount":419,"hotPosts":420},[165,172,178,185,191,198,202,206,210,214,218,220,226,231,235],{"id":166,"name":167,"slug":168,"description":169,"sortOrder":170,"createdAt":171,"updatedAt":171,"postCount":161},"e8d0bd45-d10c-46d3-8afb-0c072df7f8a7","技术","tech","技术文章",0,"2026-06-27T04:18:37.371Z",{"id":173,"name":174,"slug":175,"description":174,"sortOrder":161,"createdAt":176,"updatedAt":8,"postCount":177},"15ac46ad-edf7-4435-9a64-ff78117d58c5","Vue3 生态","vue3-生态","2022-05-21T08:05:39.000Z",6,{"id":179,"name":180,"slug":181,"description":180,"sortOrder":182,"createdAt":183,"updatedAt":8,"postCount":184},"11d4d397-685c-4180-a7b3-9b0e3a1e411e","Css","css",2,"2022-05-23T07:19:37.000Z",9,{"id":186,"name":187,"slug":187,"description":187,"sortOrder":188,"createdAt":189,"updatedAt":8,"postCount":190},"d10456a5-e649-4741-a38f-f07f266ce5f2","开发环境",3,"2022-05-24T01:52:41.000Z",13,{"id":192,"name":193,"slug":194,"description":193,"sortOrder":195,"createdAt":196,"updatedAt":8,"postCount":197},"5ed5cc62-43ea-49a2-b0b2-38bc7aae52a0","Vue3","vue3",4,"2022-05-24T01:55:05.000Z",8,{"id":26,"name":27,"slug":28,"description":27,"sortOrder":199,"createdAt":200,"updatedAt":8,"postCount":201},5,"2022-05-24T02:22:57.000Z",18,{"id":203,"name":204,"slug":204,"description":204,"sortOrder":177,"createdAt":205,"updatedAt":8,"postCount":161},"d8cbe380-54b3-4a61-a12d-5438c2918574","限时优惠","2022-05-25T07:18:03.000Z",{"id":90,"name":91,"slug":92,"description":91,"sortOrder":207,"createdAt":208,"updatedAt":8,"postCount":209},7,"2022-06-01T14:08:31.000Z",16,{"id":211,"name":212,"slug":212,"description":212,"sortOrder":197,"createdAt":213,"updatedAt":8,"postCount":177},"4ea3d8af-9cc3-49bb-a9cd-34dbcdc3bd85","构建工具","2022-06-02T07:28:13.000Z",{"id":215,"name":216,"slug":216,"description":216,"sortOrder":184,"createdAt":217,"updatedAt":8,"postCount":197},"9ed9827c-9cbb-42da-80e4-d04c7fdba886","开发工具","2022-06-21T03:35:05.000Z",{"id":44,"name":45,"slug":46,"description":45,"sortOrder":162,"createdAt":219,"updatedAt":8,"postCount":199},"2022-07-16T13:14:29.000Z",{"id":221,"name":222,"slug":223,"description":222,"sortOrder":224,"createdAt":225,"updatedAt":8,"postCount":199},"73a5f62c-3c47-45b9-9ae2-f29953ae8dc0","Node","node",11,"2022-07-16T13:15:39.000Z",{"id":227,"name":228,"slug":228,"description":228,"sortOrder":229,"createdAt":230,"updatedAt":8,"postCount":182},"2b696c16-48ef-403b-a88b-6e57cfc79596","开发问题",12,"2022-07-16T14:06:54.000Z",{"id":232,"name":233,"slug":233,"description":233,"sortOrder":190,"createdAt":234,"updatedAt":8,"postCount":161},"c0f0561e-a47a-4ecd-8caa-cc1df2315d57","算法","2022-07-16T14:22:34.000Z",{"id":236,"name":237,"slug":238,"description":237,"sortOrder":239,"createdAt":240,"updatedAt":8,"postCount":188},"a629c1f7-29f1-439e-be3c-29670b17ba20","Vue2","vue2",15,"2022-07-16T14:41:51.000Z",[242,248,253,255,260,265,266,270,275,280,285,290,293,298,302,306,311,316,321,326,330,334,335,337,341,346,351,354,357,360,363,366,370,373,375,377,380,384,387,391,395,398,401,403,406,408,412,415],{"id":243,"name":244,"slug":245,"createdAt":246,"updatedAt":247},"076bd8b9-293e-45cb-9dc3-e162007ca474","Axios","axios","2022-06-05T07:41:56.000Z","2025-12-30T07:26:21.000Z",{"id":249,"name":250,"slug":251,"createdAt":252,"updatedAt":8},"2aa7f6d0-1fac-4ed1-b9bb-f3afc813f42c","Axure","axure","2022-06-21T03:35:15.000Z",{"id":254,"name":180,"slug":181,"createdAt":7,"updatedAt":8},"b084ddd8-09be-4e57-98f0-cf4e376aecd7",{"id":256,"name":257,"slug":258,"createdAt":259,"updatedAt":8},"78a62bff-ff77-4878-8c25-3e6aae18c668","Docker","docker","2022-07-16T14:34:37.000Z",{"id":261,"name":262,"slug":263,"createdAt":264,"updatedAt":8},"2de16806-ef3f-4e54-a259-d1e1e182468c","Git","git","2022-07-16T14:25:15.000Z",{"id":4,"name":5,"slug":6,"createdAt":7,"updatedAt":8},{"id":267,"name":268,"slug":269,"createdAt":7,"updatedAt":8},"5086e93c-23b9-43d3-9643-cc87f0e9ee94","JenKins","jenkins",{"id":271,"name":272,"slug":273,"createdAt":274,"updatedAt":8},"b73007a8-bb5c-42a8-9fd9-163033a5b45d","Linux","linux","2022-07-16T14:40:17.000Z",{"id":276,"name":277,"slug":278,"createdAt":279,"updatedAt":8},"0b658b92-dd6b-4db3-a398-9f6d69950a02","Markdown","markdown","2022-07-16T14:39:25.000Z",{"id":281,"name":282,"slug":283,"createdAt":284,"updatedAt":8},"ab034d3a-6e5b-4db5-a2dc-faf4ccbb63f5","Nest","nest","2022-07-16T13:15:49.000Z",{"id":286,"name":287,"slug":288,"createdAt":289,"updatedAt":8},"52c41978-da06-4962-9636-45bbaeedda80","Nginx","nginx","2022-05-21T09:59:56.000Z",{"id":291,"name":292,"slug":292,"createdAt":289,"updatedAt":8},"0f1cc678-40e4-44b1-b2cf-a6fd8a1c867a","npm",{"id":294,"name":295,"slug":296,"createdAt":297,"updatedAt":8},"a4370d78-70e1-4073-a8f6-3dc5d81fd8fd","Nuxt","nuxt","2022-06-01T13:07:07.000Z",{"id":299,"name":300,"slug":301,"createdAt":7,"updatedAt":8},"d232e01f-048e-4151-8a0a-fff9561f946f","Pinia","pinia",{"id":303,"name":304,"slug":305,"createdAt":289,"updatedAt":8},"14e9ab02-b0bb-4c85-8604-fe6f1f0f33cd","Pnpm","pnpm",{"id":307,"name":308,"slug":309,"createdAt":310,"updatedAt":310},"399d1d38-cc0d-43ce-8baf-c769447a2ebd","React生态","react生态","2023-02-21T02:03:09.000Z",{"id":312,"name":313,"slug":314,"createdAt":315,"updatedAt":8},"c95bbe84-bdd0-410a-86a9-e87958c55f4f","Redis","redis","2022-10-05T05:14:14.000Z",{"id":317,"name":318,"slug":319,"createdAt":320,"updatedAt":8},"6d05f9df-e116-450f-af57-85ed710c4870","Swiper","swiper","2022-06-01T14:08:46.000Z",{"id":322,"name":323,"slug":324,"createdAt":325,"updatedAt":8},"66f3aeb0-84ef-45f6-a43a-944eefc9895a","Vite","vite","2022-06-02T07:28:24.000Z",{"id":327,"name":328,"slug":329,"createdAt":7,"updatedAt":8},"bf5b94d3-090b-4098-a03c-4bc69781fb2d","Vue","vue",{"id":331,"name":332,"slug":333,"createdAt":289,"updatedAt":8},"2f7fb1be-b9c5-4606-b54f-e9f66f2653b2","Vue-Router","vue-router",{"id":50,"name":51,"slug":52,"createdAt":7,"updatedAt":8},{"id":336,"name":193,"slug":194,"createdAt":7,"updatedAt":8},"62b94c93-724f-488d-8fc5-0449971d9204",{"id":338,"name":339,"slug":340,"createdAt":7,"updatedAt":8},"20bff9cd-7848-4c16-8775-42cf12b44b30","Vue3生态","vue3生态",{"id":342,"name":343,"slug":344,"createdAt":345,"updatedAt":8},"c807b2c6-cb12-4409-a1f1-6bea9f330a6b","Vuex","vuex","2022-07-16T13:14:59.000Z",{"id":347,"name":348,"slug":349,"createdAt":350,"updatedAt":8},"5782dff5-2ea2-4427-9696-d4363a7fd5bc","Webpack","webpack","2022-07-16T14:33:41.000Z",{"id":352,"name":353,"slug":353,"createdAt":7,"updatedAt":8},"d0aa41f4-68f8-48d4-a4ed-3a503ea90451","下载",{"id":355,"name":356,"slug":356,"createdAt":7,"updatedAt":8},"a046060c-39ef-474a-8c85-2546aca0e2e5","代码片段",{"id":358,"name":359,"slug":359,"createdAt":7,"updatedAt":8},"fee73435-b2be-4b55-85b1-d133ea96aea4","伪元素",{"id":361,"name":362,"slug":362,"createdAt":7,"updatedAt":8},"436bd369-8c57-4869-8827-e88e50e5e0ab","伪类",{"id":364,"name":365,"slug":365,"createdAt":7,"updatedAt":8},"4c6be544-8a00-4445-92a3-e3dcbaf6142e","动画",{"id":367,"name":368,"slug":368,"createdAt":369,"updatedAt":8},"9321a12e-ea72-49a9-a32d-5566149f812f","图片压缩","2022-08-02T00:37:47.000Z",{"id":371,"name":372,"slug":372,"createdAt":289,"updatedAt":8},"512b16fb-576a-4397-a7c5-dd20e6a8f9ca","布局",{"id":374,"name":216,"slug":216,"createdAt":7,"updatedAt":8},"f32faa96-f2ec-45c6-9a17-2c76062edcb0",{"id":376,"name":187,"slug":187,"createdAt":7,"updatedAt":8},"3c46ed3f-6d6b-4f91-bcb3-af5112860bf5",{"id":378,"name":379,"slug":379,"createdAt":7,"updatedAt":8},"dbfc086a-73a6-4560-814d-593acb61cf98","性能优化",{"id":381,"name":382,"slug":382,"createdAt":383,"updatedAt":8},"1831cd06-0d6b-48f7-94fa-324782fe23cb","拖拽","2022-07-28T12:39:13.000Z",{"id":385,"name":386,"slug":386,"createdAt":289,"updatedAt":8},"9a74300d-06f7-46d0-80d9-8fe67ec0539b","数组",{"id":388,"name":389,"slug":389,"createdAt":390,"updatedAt":8},"19ac8998-7e0a-459b-9702-bb1adca70e8c","文本复制","2022-07-17T01:54:45.000Z",{"id":392,"name":393,"slug":393,"createdAt":394,"updatedAt":8},"5ff33473-71a4-4e02-8c82-f9ea369a768f","时间","2022-07-17T01:51:12.000Z",{"id":396,"name":397,"slug":397,"createdAt":7,"updatedAt":8},"aa47ca4d-d3f6-4cac-b495-2c67c9592c36","最新优惠",{"id":399,"name":400,"slug":400,"createdAt":289,"updatedAt":8},"f6766d54-54fc-405e-932d-b7d550559125","服务器",{"id":402,"name":212,"slug":212,"createdAt":7,"updatedAt":8},"d856559a-03ff-40b4-980d-3f272b998c3c",{"id":404,"name":405,"slug":405,"createdAt":7,"updatedAt":8},"692d5d68-b188-4e5c-aca8-65d0229399a1","渐变",{"id":407,"name":233,"slug":233,"createdAt":7,"updatedAt":8},"38e1fd6b-d7c6-4d62-bf70-7bacc175bea9",{"id":409,"name":410,"slug":410,"createdAt":411,"updatedAt":8},"be7b10bc-49eb-4a03-bea7-ceb915d500fe","规范","2022-07-16T14:41:06.000Z",{"id":413,"name":414,"slug":414,"createdAt":289,"updatedAt":8},"b42e2916-ad62-4b8a-a863-cd8c19a829de","面试",{"id":416,"name":417,"slug":417,"createdAt":289,"updatedAt":8},"7069add9-b636-44f1-9cd4-ea3a6d2b85d3","面试题",104,48,[421,433,445,458,463],{"id":422,"title":423,"slug":423,"content":424,"excerpt":423,"coverImage":425,"status":19,"isPinned":20,"pinnedAt":21,"viewCount":426,"publishedAt":427,"createdAt":427,"updatedAt":428,"category":429,"author":430,"tags":431},"a1bd1f49-6f6d-4fea-9789-b5636e19a6b3","uni-app瀑布流","## 实现思路\n> 获取父组件的列表数组，watch监听数组长度变化，截取后面新的数据，创建两个左右数组，比较左右dom的长度，哪个短，就push一条数据进去，源数组删除一条数据。利用img的load（加载成功）和error方法（加载失败），触发数组的push，实现瀑布流\n\n## 代码实现\n\n```language\n\u003Ctemplate>\n    \u003Cview class=\\\"waterfall\\\">\n        \u003Cview class=\\\"waterfall_left\\\">\n            \u003Cview class=\\\"waterfall_list\\\" v-for=\\\"(item, index) in leftList\\\" :key=\\\"index\\\">\n                \u003CSearch\n                    :name=\\\"item.name\\\"\n                    :image=\\\"item.src\\\"\n                    :label=\\\"item.label\\\"\n                    :item=\\\"item\\\"\n                    @considerPush=\\\"considerPush\\\"\n                >\n                \u003C\u002FSearch>\n            \u003C\u002Fview>\n        \u003C\u002Fview>\n        \u003Cview class=\\\"waterfall_right\\\">\n            \u003Cview class=\\\"waterfall_list\\\" v-for=\\\"(item, index) in rightList\\\" :key=\\\"index\\\">\n                \u003CSearch\n                    :name=\\\"item.name\\\"\n                    :image=\\\"item.src\\\"\n                    :label=\\\"item.label\\\"\n                    :item=\\\"item\\\"\n                    @considerPush=\\\"considerPush\\\"\n                >\n                \u003C\u002FSearch>\n            \u003C\u002Fview>\n        \u003C\u002Fview>\n    \u003C\u002Fview>\n\u003C\u002Ftemplate>\n\n\u003Cscript>\nimport Search from '@\u002Fcomponents\u002Fsearch\u002Fsearch.vue';\nexport default {\n    components: { Search },\n    props: {\n        list: {\n            type: Array,\n            default: () => []\n        }\n    },\n    data() {\n        return {\n            \u002F\u002F 左侧列表\n            leftList: [],\n            \u002F\u002F 右侧列表\n            rightList: [],\n            \u002F\u002F 组件数据备份\n            newList: [],\n            \u002F\u002F默认请求数,主要为了正常排序\n            interceptNumber: 10\n        };\n    },\n    created() {\n        this.touchOff(); \u002F\u002F 触发排列\n    },\n    mounted() {},\n    watch: {\n        list(newValue, oldValue) {\n            this.interceptNumber = newValue.length - oldValue.length;\n            this.touchOff();\n        }\n    },\n    computed: {},\n    methods: {\n        \u002F\u002F 触发重新排列\n        touchOff() {\n            this.newList = [...this.list.slice(-this.interceptNumber)];\n            if (this.newList.length !== 0) {\n                this.leftList.push(this.newList.shift()); \u002F\u002F触发排列\n            }\n        },\n        \u002F\u002F 计算排列\n        considerPush() {\n            this.$nextTick(() => {\n                if (this.newList.length == 0) return; \u002F\u002F没有数据了\n                let leftH = 0;\n                let rightH = 0; \u002F\u002F左右高度\n                let query = uni.createSelectorQuery().in(this);\n                query.selectAll('.waterfall_left').boundingClientRect();\n                query.selectAll('.waterfall_right').boundingClientRect();\n                query.exec(res => {\n                    leftH = res[0].length != 0 ? res[0][0].height : 0; \u002F\u002F防止查询不到做个处理\n                    rightH = res[1].length != 0 ? res[1][0].height : 0;\n                    if (leftH == rightH || leftH \u003C rightH) {\n                        \u002F\u002F 相等 || 左边小\n                        this.leftList.push(this.newList.shift());\n                    } else {\n                        \u002F\u002F 右边小\n                        this.rightList.push(this.newList.shift());\n                    }\n\n                    \u002F\u002F console.log('左右高度：', leftH, rightH, leftH == rightH || leftH \u003C rightH);\n                });\n            });\n        }\n    }\n};\n\u003C\u002Fscript>\n\n\u003Cstyle scoped lang=\\\"scss\\\">\n.waterfall {\n    display: flex;\n    align-items: flex-start;\n    justify-content: flex-start;\n    .waterfall_left {\n        flex: 1;\n    }\n\n    .waterfall_right {\n        flex: 1;\n    }\n}\n\u003C\u002Fstyle>\n\n```\n","https:\u002F\u002Fcdn.xiaolong0418.com\u002Fmyblog\u002Fimages\u002F7d4fa8d1d2775177b882a4656e3a5ed5.png",1817,"2022-11-22T07:54:41.000Z","2026-06-26T10:25:05.932Z",{"id":26,"name":27,"slug":28},{"id":30,"name":31,"avatar":21},[432],{"id":4,"name":5,"slug":6},{"id":434,"title":435,"slug":435,"content":436,"excerpt":435,"coverImage":437,"status":19,"isPinned":20,"pinnedAt":21,"viewCount":438,"publishedAt":439,"createdAt":439,"updatedAt":440,"category":441,"author":442,"tags":443},"b2c46bf6-d971-4cce-b21b-052dbea8e8a2","v-html使用img点击实现放大效果","## 代码实现\n```js\n\u002F**\n * JS获取html代码中所有的图片地址\n * @param htmlstr\n * @returns arr 数组\n *\u002F\n\nexport function getimgsrc(htmlstr) {\n    let reg = \u002F\u003Cimg.+?src=('|\\\")?([^'\\\"]+)('|\\\")?(?:\\s+|>)\u002Fg;\n    let arr = [];\n    let tem = 0;\n    \u002F\u002Feslint-disable-next-line\n    while ((tem = reg.exec(htmlstr))) {\n        arr.push(tem[2]); \u002F\u002F eslint-disable-line\n    }\n\n    return arr;\n}\n\n```\n\n\n\n```vue\n\u003Ctemplate>\n    \u003Cdiv class=\\\"image-expansion\\\" :class=\\\"classArr\\\">\n        \u003Cdiv @click.stop=\\\"hanldeImage($event)\\\" v-html=\\\"formatHtmlData\\\">\u003C\u002Fdiv>\n\n        \u003Cel-image-viewer\n            v-if=\\\"imgPreviewUrl\\\"\n            :initial-index=\\\"subscript\\\"\n            :src=\\\"imgPreviewUrl\\\"\n            :on-close=\\\"closeViewer\\\"\n            :url-list=\\\"imgList\\\"\n        >\u003C\u002Fel-image-viewer>\n    \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n\n\u003Cscript>\nimport { getimgsrc } from '..\u002F..\u002Futils\u002Fgetimgsrc';\nimport ElImageViewer from 'element-ui\u002Fpackages\u002Fimage\u002Fsrc\u002Fimage-viewer';\nexport default {\n    components: {\n        ElImageViewer\n    },\n    props: {\n        htmlData: {\n            type: String,\n            default: () => {\n                return '';\n            }\n        },\n        classArr: {\n            type: Array,\n            default: () => {\n                return ['min'];\n            }\n        },\n\n        isArticle: {\n            type: Boolean,\n            default: () => {\n                return false;\n            }\n        }\n    },\n    data() {\n        return {\n            imgList: [],\n            formatHtmlData: '',\n            imgPreviewUrl: '',\n            subscript: 0\n        };\n    },\n    computed: {},\n\n    watch: {\n        \u002F\u002F监听数据，防止数据不更新\n        htmlData: {\n            handler(newName, oldName) {\n                \u002F\u002F判断是否为文章\n                if (this.isArticle) {\n                    newName ? (this.formatHtmlData = newName.replace(\u002F(\u003C([^>]+)>)\u002Fgi, '').replace(\u002F[\\\r\\\n]\u002Fg, '')) : '';\n                } else {\n                    \u002F\u002F剔除strong和p标签\n                    newName ? (this.formatHtmlData = newName.replace(\u002F(\u003C\\\u002F?strong.*?>)|(\u003C\\\u002F?p.*?>)\u002Fg, '')) : '';\n\n                    \u002F\u002F获取html全部图片，push成图片数组\n                    this.imgList = Object.values(getimgsrc(this.formatHtmlData));\n                    \u002F\u002F获取图片下标\n                    let subscript = this.imgList.indexOf(this.imgPreviewUrl);\n                    this.subscript = subscript > -1 ? subscript : 0;\n                }\n            },\n\n            immediate: true\n        }\n    },\n\n    mounted() {},\n\n    methods: {\n        \u002F\u002F监听点击事件\n        hanldeImage(event) {\n            if (event.target.nodeName === 'IMG' || event.target.nodeName === 'img') {\n                \u002F\u002F获取点击的图片url,decodeURIComponent转码一下，防禁url转码\n                this.imgPreviewUrl = decodeURIComponent(event.target.currentSrc);\n\n                \u002F\u002F获取图片下标\n                let subscript = this.imgList.indexOf(this.imgPreviewUrl);\n                this.subscript = subscript > -1 ? subscript : 0;\n\n                \u002F\u002F禁止遮罩层后面的内容滚动\n                document.documentElement.style.overflowY = 'hidden';\n            } else {\n                this.$emit('goDetail');\n            }\n        },\n\n        \u002F\u002F关闭弹框\n        closeViewer() {\n            this.imgPreviewUrl = '';\n            \u002F\u002F恢复遮罩层后面的内容滚动\n            document.documentElement.style.overflowY = 'auto';\n        }\n    }\n};\n\u003C\u002Fscript>\n\n\u003Cstyle lang=\\\"scss\\\" scoped>\n.image-expansion {\n}\n\n.min {\n    \u002Fdeep\u002F img {\n        cursor: pointer;\n        height: 28px;\n        padding: 0 10px 3px;\n    }\n}\n\n.max {\n    \u002Fdeep\u002F img {\n        cursor: pointer;\n    }\n}\n\n.class1 {\n    \u002Fdeep\u002F div {\n        font-size: 15px;\n        font-family: Microsoft YaHei;\n        font-weight: 400;\n        color: #888888;\n        line-height: 30px;\n    }\n}\n\u003C\u002Fstyle>\n\n\n```\n","https:\u002F\u002Fcdn.xiaolong0418.com\u002Fmyblog\u002Fimages\u002Fc77c3fb113d1ab2f67e7afba1ca33b95.png",1571,"2023-01-10T07:22:29.000Z","2026-06-26T10:25:07.650Z",{"id":44,"name":45,"slug":46},{"id":30,"name":31,"avatar":21},[444],{"id":367,"name":368,"slug":368},{"id":446,"title":447,"slug":448,"content":449,"excerpt":447,"coverImage":450,"status":19,"isPinned":20,"pinnedAt":21,"viewCount":451,"publishedAt":452,"createdAt":452,"updatedAt":453,"category":454,"author":455,"tags":456},"9f33b49b-0785-4404-9a4e-d85937e7fc92","在 vue3 中优雅的使用 jsx\u002Ftsx","在-vue3-中优雅的使用-jsx-tsx","## 安装插件（@vitejs\u002Fplugin-vue-jsx）\n```shell\npnpm add @vitejs\u002Fplugin-vue-jsx -D\n```\n\n## 配置vite.config.ts\n```ts\nimport vueJsx from \\\"@vitejs\u002Fplugin-vue-jsx\\\";\n\nexport default defineConfig({\n  plugins: [\n    vueJsx(),\n  ]\n})\n```\n\n## 插值\n```language\n\u002F\u002F vue3模板语法\n\u003Cspan>{{ a + b }}\u003C\u002Fspan>\n\n\u002F\u002F jsx\u002Ftsx\n\u003Cspan>{ a + b }\u003C\u002Fspan>\n```\n\n\n## class与style 绑定\n\n```ts\n\u002F\u002F 模板字符串\n\u003Cdiv className={`header ${ isBg ? 'headerBg' : '' }`}>header\u003C\u002Fdiv>\n\u002F\u002F数组\n\u003Cdiv class={ [ 'header', isBg && 'headerBg' ] } >header\u003C\u002Fdiv>\n```\n\n```ts\nconst color = 'red'\nconst element = \u003Csapn style={{ color, fontSize: '16px' }}>style\u003C\u002Fsapn>\n```\n\n## 条件渲染\n\n```ts\n   setup() {\n       const isShow = false\n       const element = () => {\n           if (isShow) {\n               return \u003Cspan>我是if\u003C\u002Fspan>\n           } else {\n               return \u003Cspan>我是else\u003C\u002Fspan>\n           }\n       }\n       return () => (\n           \u003Cdiv>\n               \u003Cspan v-show={isShow}>我是v-show\u003C\u002Fspan>\n               {\n                   element()\n               }\n               {\n                   isShow ? \u003Cp>我是三目1\u003C\u002Fp> : \u003Cp>我是三目2\u003C\u002Fp>\n               }\n           \u003Cdiv>\n       )\n   }\n```\n\n## 列表渲染\n```language\nsetup() {\n   const listData = [\n       {name: 'Tom', age: 18},\n       {name: 'Jim', age: 20},\n       {name: 'Lucy', age: 16}\n   ]\n   return () => (\n       \u003Cdiv>\n           \u003Cdiv class={'box'}>\n               \u003Cspan>姓名\u003C\u002Fspan>\n               \u003Cspan>年龄\u003C\u002Fspan>\n           \u003C\u002Fdiv>\n           {\n               prop.listData.map(item => {\n                   return \u003Cdiv class={'box'}>\n                       \u003Cspan>{item.name}\u003C\u002Fspan>\n                       \u003Cspan>{item.age}\u003C\u002Fspan>\n                   \u003C\u002Fdiv>\n               })\n           }\n       \u003C\u002Fdiv>\n   )\n}\n\n```\n\n## 事件处理\n\n```language\nsetup() {\n    const clickBox = val => {\n        console.log(val)\n    }\n    return () => (\n        \u003Cdiv class={'box1'} onClick={() => clickBox('box1')}>\n            \u003Cspan>我是box1\u003C\u002Fspan>\n            \u003Cdiv class={'box2'} onClick={() => clickBox('box2')}>\n                \u003Cspan>我是box2\u003C\u002Fspan>\n                \u003Cdiv class={'box3'} onClick={withModifiers(() => clickBox('box3'), ['stop'])}>我是box3\u003C\u002Fdiv>\n            \u003C\u002Fdiv>\n        \u003C\u002Fdiv>\n    )\n}\n```\n\n## v-model\n\n```ts\n\u002F\u002F 正常写法\n\u003Cinput v-model=\\\"value\\\" \u002F> \u002F\u002F vue\n\u003Cinput v-model={value} \u002F> \u002F\u002F jsx\n\n\u002F\u002F 指定绑定值写法\n\u003Cinput v-model:modelValue=\\\"value\\\" \u002F> \u002F\u002F vue\n\u003Cinput v-model={[value,'modelValue']} \u002F> \u002F\u002F jsx\n\n\u002F\u002F 修饰符写法\n\u003Cinput v-model:modelValue.trim=\\\"value\\\" \u002F> \u002F\u002F vue\n\u003Cinput v-model={[value,'modelValue',['trim']]} \u002F> \u002F\u002F jsx\n```\n\n## slot插槽\n\n### 定义插槽\n```ts\nimport { renderSlot } from \\\"vue\\\"\nexport default defineComponent({\n    \u002F\u002F 从ctx中解构出来 slots\n    setup(props, { slots }) {\n        return () => (\n            \u003Cdiv>\n                { renderSlot(slots, 'default') }\n                { slots.title?.() }\n            \u003C\u002Fdiv>\n        )\n    }\n})\n\n```\n\n### 使用插槽\n\n```ts\nimport Vslot from '.\u002FslotTem'\nexport default defineComponent({\n    setup() {\n        return () => (\n            \u003Cdiv class={'box'}>\n                \u003CVslot v-slots={{\n                    title: () => {\n                        return \u003Cp>我是title插槽\u003C\u002Fp>\n                    },\n                    default: () => {\n                        return \u003Cp>我是default插槽\u003C\u002Fp>\n                    }\n                }} \u002F>\n            \u003C\u002Fdiv>\n        )\n    }\n})\n\n```\n\n","https:\u002F\u002Fcdn.xiaolong0418.com\u002Fmyblog\u002Fimages\u002F3e4cb0d3df611ee3f57b8ed503e1015e.png",1391,"2023-04-19T09:23:27.000Z","2026-06-26T09:54:47.369Z",{"id":192,"name":193,"slug":194},{"id":30,"name":31,"avatar":21},[457],{"id":336,"name":193,"slug":194},{"id":97,"title":98,"slug":99,"content":100,"excerpt":98,"coverImage":101,"status":19,"isPinned":20,"pinnedAt":21,"viewCount":102,"publishedAt":103,"createdAt":103,"updatedAt":104,"category":459,"author":460,"tags":461},{"id":26,"name":27,"slug":28},{"id":30,"name":31,"avatar":21},[462],{"id":4,"name":5,"slug":6},{"id":464,"title":465,"slug":466,"content":467,"excerpt":465,"coverImage":468,"status":19,"isPinned":20,"pinnedAt":21,"viewCount":469,"publishedAt":470,"createdAt":470,"updatedAt":471,"category":472,"author":476,"tags":477},"80920598-a452-4357-bf53-842b200560e8","React18入门到精通教程","react18入门到精通教程","\n## JSX实现列表渲染\n> 使用`map()`方法遍历数组，必须添加`key`属性提高性能\n```jsx\nconst songs = [\n  { id: 1, name: \\\"helo1\\\" },\n  { id: 2, name: \\\"helo2\\\" },\n  { id: 3, name: \\\"helo3\\\" },\n];\n\nfunction App() {\n  return (\n    \u003Cdiv>\n      \u003Cul>\n        {songs.map((song) => (\n          \u002F\u002F 关键：添加唯一key标识符（避免使用索引）\n          \u003Cli key={song.id}>  \n            {song.id}-{song.name}\n          \u003C\u002Fli>\n        ))}\n      \u003C\u002Ful>\n    \u003C\u002Fdiv>\n  );\n}\n```\n\n**最佳实践：**\n1. 使用`\u003Cul>`包裹列表项\n2. `key`应使用稳定唯一标识（如ID），避免数组索引\n3. 空列表处理：`{songs.length > 0 && ...}` 或 `{songs.map(...) || \u003CEmptyView\u002F>}`\n\n## JSX实现条件渲染\n### 简单逻辑\n```jsx\n\u002F\u002F 三元表达式\n{isLoggedIn ? \u003CDashboard \u002F> : \u003CLoginForm \u002F>}\n\n\u002F\u002F 逻辑短路\n{hasNotification && \u003CNotificationBadge count={5} \u002F>}\n\n\u002F\u002F 空值处理\n{userProfile?.avatar || \u003CDefaultAvatar \u002F>}\n```\n\n### 复杂逻辑\n```jsx\nconst renderContent = (type) => {\n  switch(type) {\n    case 'success': \n      return \u003CSuccessAlert \u002F>;\n    case 'error':\n      return \u003CErrorAlert \u002F>;\n    default:\n      return \u003CInfoAlert \u002F>;\n  }\n}\n\nfunction App() {\n  return (\n    \u003Cdiv className=\\\"container\\\">\n      {renderContent(status)}\n      \n      {\u002F* 另一种模式：立即执行函数 *\u002F}\n      {(() => {\n        if (isLoading) return \u003CSpinner \u002F>;\n        if (isEmpty) return \u003CEmptyState \u002F>;\n        return \u003CDataTable \u002F>;\n      })()}\n    \u003C\u002Fdiv>\n  )\n}\n```\n\n## JSX样式处理\n### 行内样式\n```jsx\n\u002F\u002F 直接对象\n\u003Cdiv style={{ \n  color: 'white', \n  backgroundColor: 'teal',\n  padding: '1rem'\n}}>\n\n\u002F\u002F 样式对象复用\nconst alertStyle = {\n  padding: '15px',\n  borderRadius: '4px',\n  margin: '10px 0'\n};\n\nfunction Alert({ type }) {\n  return (\n    \u003Cdiv style={{\n      ...alertStyle,  \u002F\u002F 扩展运算符合并样式\n      background: type === 'error' ? '#f8d7da' : '#d4edda'\n    }}>\n      {message}\n    \u003C\u002Fdiv>\n  )\n}\n```\n\n### 类名控制（推荐）\n```css\n\u002F* styles.module.css *\u002F\n.card {\n  border: 1px solid #ddd;\n  border-radius: 8px;\n  padding: 20px;\n  box-shadow: 0 2px 4px rgba(0,0,0,0.1);\n}\n\n.highlight {\n  background-color: #ffffe0;\n}\n```\n\n```jsx\nimport styles from '.\u002Fstyles.module.css';\n\nfunction ProductCard({ featured }) {\n  return (\n    \u003Cdiv className={`${styles.card} ${featured ? styles.highlight : ''}`}>\n      {\u002F* 内容 *\u002F}\n    \u003C\u002Fdiv>\n  );\n}\n```\n\n## React 18新特性\n### 并发模式（Concurrent Mode）\n```jsx\nimport { startTransition } from 'react';\n\n\u002F\u002F 非紧急状态更新\nfunction handleSearch(query) {\n  startTransition(() => {\n    setSearchQuery(query); \u002F\u002F 可中断的渲染\n  });\n}\n```\n\n### 自动批处理（Automatic Batching）\n```jsx\n\u002F\u002F React 17及之前：两次渲染\n\u002F\u002F React 18：自动批处理，一次渲染\nfunction handleClick() {\n  setCount(c => c + 1);\n  setFlag(f => !f);\n}\n```\n\n## Redux状态管理（现代写法）\n### 安装依赖\n```bash\nnpm install @reduxjs\u002Ftoolkit react-redux\n```\n\n### 创建Store\n```js\n\u002F\u002F store.js\nimport { configureStore, createSlice } from '@reduxjs\u002Ftoolkit';\n\nconst counterSlice = createSlice({\n  name: 'counter',\n  initialState: { value: 0 },\n  reducers: {\n    increment: state => { state.value += 1 },\n    decrement: state => { state.value -= 1 },\n    incrementByAmount: (state, action) => {\n      state.value += action.payload\n    }\n  }\n});\n\n\u002F\u002F 异步操作示例\nexport const fetchUserData = () => async (dispatch) => {\n  const response = await fetch('\u002Fapi\u002Fuser');\n  dispatch(setUser(await response.json()));\n};\n\nexport const store = configureStore({\n  reducer: {\n    counter: counterSlice.reducer,\n    \u002F\u002F 其他reducer...\n  }\n});\n\nexport const { increment, decrement } = counterSlice.actions;\n```\n\n### 组件集成\n```jsx\n\u002F\u002F index.js\nimport { Provider } from 'react-redux';\nimport { store } from '.\u002Fstore';\n\nroot.render(\n  \u003CProvider store={store}>\n    \u003CApp \u002F>\n  \u003C\u002FProvider>\n);\n\n\u002F\u002F Counter.js\nimport { useSelector, useDispatch } from 'react-redux';\nimport { increment, decrement } from '.\u002Fstore';\n\nfunction Counter() {\n  const count = useSelector(state => state.counter.value);\n  const dispatch = useDispatch();\n\n  return (\n    \u003Cdiv>\n      \u003Cbutton onClick={() => dispatch(decrement())}>-\u003C\u002Fbutton>\n      \u003Cspan>{count}\u003C\u002Fspan>\n      \u003Cbutton onClick={() => dispatch(increment())}>+\u003C\u002Fbutton>\n    \u003C\u002Fdiv>\n  );\n}\n```\n\n## 最佳实践总结\n1. **组件设计**：遵循单一职责原则，拆分智能组件（容器组件）和展示组件\n2. **状态管理**：\n   - 局部状态用`useState`\u002F`useReducer`\n   - 全局共享状态用Redux\n   - 避免过度使用状态提升\n3. **性能优化**：\n   - 使用`React.memo`记忆组件\n   - 使用`useCallback`\u002F`useMemo`避免不必要的重渲染\n   - 虚拟化长列表（react-window）\n4. **Hooks规范**：\n   - 避免在循环\u002F条件中使用Hook\n   - 自定义Hook以`use`前缀命名\n5. **TypeScript集成**：\n   ```tsx\n   interface UserCardProps {\n     name: string;\n     age: number;\n     onSelect: (id: string) => void;\n   }\n   \n   const UserCard: React.FC\u003CUserCardProps> = ({ name, age }) => (\n     \u003Cdiv>{name} ({age})\u003C\u002Fdiv>\n   )\n   ```\n\n下面为您完善教程，增加React路由和生命周期相关内容：\n\n## React路由管理（React Router v6）\n\n### 安装与基础配置\n```bash\nnpm install react-router-dom@6\n```\n\n### 路由基础结构\n```jsx\n\u002F\u002F index.js\nimport { BrowserRouter } from 'react-router-dom';\n\nconst root = ReactDOM.createRoot(document.getElementById('root'));\nroot.render(\n  \u003CBrowserRouter>\n    \u003CApp \u002F>\n  \u003C\u002FBrowserRouter>\n);\n\n\u002F\u002F App.js\nimport { Routes, Route, Link } from 'react-router-dom';\n\nfunction App() {\n  return (\n    \u003Cdiv>\n      \u003Cnav>\n        \u003CLink to=\\\"\u002F\\\">首页\u003C\u002FLink>\n        \u003CLink to=\\\"\u002Fabout\\\">关于\u003C\u002FLink>\n        \u003CLink to=\\\"\u002Fusers\\\">用户列表\u003C\u002FLink>\n      \u003C\u002Fnav>\n      \n      \u003CRoutes>\n        \u003CRoute path=\\\"\u002F\\\" element={\u003CHome \u002F>} \u002F>\n        \u003CRoute path=\\\"\u002Fabout\\\" element={\u003CAbout \u002F>} \u002F>\n        \u003CRoute path=\\\"\u002Fusers\\\" element={\u003CUserList \u002F>} \u002F>\n        \u003CRoute path=\\\"\u002Fusers\u002F:id\\\" element={\u003CUserDetail \u002F>} \u002F>\n        \u003CRoute path=\\\"*\\\" element={\u003CNotFound \u002F>} \u002F>\n      \u003C\u002FRoutes>\n    \u003C\u002Fdiv>\n  );\n}\n```\n\n### 嵌套路由\n```jsx\n\u002F\u002F Dashboard.js\nimport { Outlet } from 'react-router-dom';\n\nfunction Dashboard() {\n  return (\n    \u003Cdiv>\n      \u003Ch2>仪表盘\u003C\u002Fh2>\n      \u003COutlet \u002F> {\u002F* 子路由渲染位置 *\u002F}\n    \u003C\u002Fdiv>\n  );\n}\n\n\u002F\u002F 路由配置\n\u003CRoutes>\n  \u003CRoute path=\\\"\u002Fdashboard\\\" element={\u003CDashboard \u002F>}>\n    \u003CRoute index element={\u003CDashboardHome \u002F>} \u002F>\n    \u003CRoute path=\\\"settings\\\" element={\u003CDashboardSettings \u002F>} \u002F>\n    \u003CRoute path=\\\"analytics\\\" element={\u003CDashboardAnalytics \u002F>} \u002F>\n  \u003C\u002FRoute>\n\u003C\u002FRoutes>\n```\n\n### 编程式导航\n```jsx\nimport { useNavigate, useParams, useLocation } from 'react-router-dom';\n\nfunction UserCard({ user }) {\n  const navigate = useNavigate();\n  \n  return (\n    \u003Cdiv onClick={() => navigate(`\u002Fusers\u002F${user.id}`)}>\n      {user.name}\n    \u003C\u002Fdiv>\n  );\n}\n\nfunction UserDetail() {\n  const { id } = useParams(); \u002F\u002F 获取URL参数\n  const location = useLocation(); \u002F\u002F 获取位置对象\n  \n  return (\n    \u003Cdiv>\n      \u003Ch2>用户ID: {id}\u003C\u002Fh2>\n      \u003Cp>当前路径: {location.pathname}\u003C\u002Fp>\n    \u003C\u002Fdiv>\n  );\n}\n```\n\n### 路由守卫（认证保护）\n```jsx\nimport { Navigate } from 'react-router-dom';\n\nfunction ProtectedRoute({ children }) {\n  const { isAuthenticated } = useAuth();\n  \n  if (!isAuthenticated) {\n    return \u003CNavigate to=\\\"\u002Flogin\\\" replace \u002F>;\n  }\n  \n  return children;\n}\n\n\u002F\u002F 使用\n\u003CRoute \n  path=\\\"\u002Fdashboard\\\" \n  element={\n    \u003CProtectedRoute>\n      \u003CDashboard \u002F>\n    \u003C\u002FProtectedRoute>\n  } \n\u002F>\n```\n\n## React生命周期\n\n### 类组件生命周期方法\n\n```jsx\nclass LifecycleDemo extends React.Component {\n  \u002F\u002F 1. 初始化阶段\n  constructor(props) {\n    super(props);\n    this.state = { count: 0 };\n    console.log('Constructor');\n  }\n\n  \u002F\u002F 2. 挂载阶段\n  componentDidMount() {\n    console.log('Component did mount');\n    \u002F\u002F 适合进行API调用、事件订阅\n    this.timer = setInterval(() => {\n      this.setState(prev => ({ count: prev.count + 1 }));\n    }, 1000);\n  }\n\n  \u002F\u002F 3. 更新阶段\n  shouldComponentUpdate(nextProps, nextState) {\n    console.log('Should component update?');\n    return nextState.count !== this.state.count;\n  }\n\n  componentDidUpdate(prevProps, prevState) {\n    console.log('Component did update');\n  }\n\n  \u002F\u002F 4. 卸载阶段\n  componentWillUnmount() {\n    console.log('Component will unmount');\n    \u002F\u002F 清理操作\n    clearInterval(this.timer);\n  }\n\n  render() {\n    console.log('Render');\n    return \u003Cdiv>Count: {this.state.count}\u003C\u002Fdiv>;\n  }\n}\n```\n\n### 函数组件生命周期（Hooks实现）\n\n```jsx\nimport { useState, useEffect } from 'react';\n\nfunction FunctionLifecycle() {\n  const [count, setCount] = useState(0);\n  const [data, setData] = useState(null);\n\n  \u002F\u002F 相当于componentDidMount + componentDidUpdate\n  useEffect(() => {\n    console.log('每次渲染后执行');\n  });\n\n  \u002F\u002F 相当于componentDidMount\n  useEffect(() => {\n    console.log('组件挂载后执行');\n    \n    \u002F\u002F 数据获取\n    fetch('\u002Fapi\u002Fdata')\n      .then(res => res.json())\n      .then(setData);\n    \n    \u002F\u002F 相当于componentWillUnmount\n    return () => {\n      console.log('组件卸载前执行');\n    };\n  }, []); \u002F\u002F 空依赖数组\n\n  \u002F\u002F 依赖变化时执行\n  useEffect(() => {\n    console.log('count变化时执行:', count);\n    \n    document.title = `Count: ${count}`;\n    \n    return () => {\n      console.log('清理count效果');\n    };\n  }, [count]); \u002F\u002F count依赖\n\n  return (\n    \u003Cdiv>\n      \u003Cp>Count: {count}\u003C\u002Fp>\n      \u003Cbutton onClick={() => setCount(c => c + 1)}>增加\u003C\u002Fbutton>\n      {data && \u003Cpre>{JSON.stringify(data, null, 2)}\u003C\u002Fpre>}\n    \u003C\u002Fdiv>\n  );\n}\n```\n\n### 生命周期阶段对比\n\n| 阶段 | 类组件方法 | 函数组件Hook |\n|------|------------|--------------|\n| **挂载** | constructor | useState初始化 |\n|       | render      | 函数体执行    |\n|       | componentDidMount | useEffect(() => {}, []) |\n| **更新** | shouldComponentUpdate | React.memo, useMemo |\n|       | render      | 函数体执行    |\n|       | componentDidUpdate | useEffect(() => {}) |\n| **卸载** | componentWillUnmount | useEffect返回函数 |\n| **错误处理** | componentDidCatch | 暂无直接等效，需错误边界组件 |\n\n### 现代React开发建议\n\n1. **优先使用函数组件+Hooks**：\n   - 90%的场景可替代类组件\n   - 更简洁的代码结构\n   - 更好的逻辑复用\n\n2. **关键生命周期替代**：\n   - `componentDidMount` → `useEffect(() => {}, [])`\n   - `componentDidUpdate` → `useEffect(() => {})` 或带依赖的 `useEffect`\n   - `componentWillUnmount` → `useEffect(() => { return cleanup }, [])`\n   - `shouldComponentUpdate` → `React.memo` 或 `useMemo`\n\n3. **数据获取最佳实践**：\n```jsx\nuseEffect(() => {\n  let isMounted = true;\n  \n  const fetchData = async () => {\n    try {\n      const result = await api.getData();\n      if (isMounted) setData(result);\n    } catch (error) {\n      if (isMounted) setError(error);\n    }\n  };\n  \n  fetchData();\n  \n  return () => {\n    isMounted = false; \u002F\u002F 避免组件卸载后设置状态\n  };\n}, []);\n```\n\n## 路由与生命周期整合示例\n\n```jsx\nfunction UserProfile() {\n  const { id } = useParams();\n  const [user, setUser] = useState(null);\n  const [loading, setLoading] = useState(true);\n\n  useEffect(() => {\n    let isActive = true;\n    \n    const fetchUser = async () => {\n      try {\n        setLoading(true);\n        const data = await fetchUserById(id);\n        if (isActive) {\n          setUser(data);\n          setLoading(false);\n        }\n      } catch (error) {\n        if (isActive) {\n          setError(error.message);\n          setLoading(false);\n        }\n      }\n    };\n    \n    fetchUser();\n    \n    return () => {\n      isActive = false; \u002F\u002F 清理效果\n    };\n  }, [id]); \u002F\u002F id变化时重新获取\n\n  if (loading) return \u003CSpinner \u002F>;\n  \n  return (\n    \u003Cdiv>\n      \u003Ch2>{user.name}\u003C\u002Fh2>\n      \u003Cp>Email: {user.email}\u003C\u002Fp>\n    \u003C\u002Fdiv>\n  );\n}\n```\n\n## 完整项目结构建议\n\n```\nsrc\u002F\n├── components\u002F      # 通用UI组件\n├── pages\u002F           # 页面组件\n├── layouts\u002F         # 布局组件\n├── hooks\u002F           # 自定义Hooks\n├── store\u002F           # Redux状态\n│   ├── slices\u002F\n│   └── store.js\n├── services\u002F        # API服务\n├── routers\u002F         # 路由配置\n├── utils\u002F           # 工具函数\n├── assets\u002F          # 静态资源\n└── App.js           # 主应用组件\n```\n\n这些新增内容涵盖了React路由的现代用法（v6版本）以及React生命周期的详细解释，包括类组件和函数组件的实现方式对比。同时还提供了路由与生命周期整合的实际示例，帮助开发者理解如何在真实项目中应用这些概念。","https:\u002F\u002Fcdn.xiaolong0418.com\u002Fmyblog\u002Fimages\u002F46e205aa1bd33d1bb7201019fc2fdf43.png",1147,"2023-02-21T02:01:52.000Z","2026-06-26T10:25:15.418Z",{"id":473,"name":474,"slug":475},"f1701085-b8c1-413a-8750-58e7a0a33832","React","react",{"id":30,"name":31,"avatar":21},[478],{"id":307,"name":308,"slug":309}]