Skip to content

202303

金三银四,三月是人员变动比较频繁的一个月!💔

0301

DANGER

算法能不能进一步点?? 灵魂拷问??

  • vitepress 头部 head 设置,阔以设置 icon 和 meta
js
  head: [
    ['link', { rel: 'icon', type: 'image/svg+xml', href: '/logo.svg' }],
    ['link', { rel: 'icon', type: 'image/png', href: '/logo.png' }],
    [
      'meta',
      { name: 'wwads-cn-verify', content: '5878a7ab84fb43402106c575658472fa' },
    ],
    ],
  ],
  • Web Components 概念

Web Components 是一组标准化的浏览器 API,它们可以帮助我们创建可重用的组件。使用 Web Components,我们可以将一个组件封装在一个自定义元素中,并且可以在多个项目中重复使用这个组件。

Web Components 主要包括四个 API:Custom Elements、Shadow DOM、HTML Templates 和 HTML Imports。其中

  • Custom Elements 允许我们创建自定义元素,
  • Shadow DOM 允许我们创建封装的 DOM 节点,
  • HTML Templates 允许我们创建可重复使用的模板,
  • HTML Imports 允许我们导入 HTML 文件并在页面中使用它们。

0302

前端设计模式之工厂模式

工厂模式通俗点说就是:更方便地去创建实例

js
class Axios {}
class Factory {
  create() {
    return new Axios();
  }
}
const axios = new Factory();
export default axios;
// 创建实例
const httpRequest1 = axios.create();
const httpRequest2 = axios.create();
const httpRequest3 = axios.create();

单例模式

定义一个类,生成一个实例,并且整个项目仅此这一个实例,封装一个请求的 Axios 实例然后暴露出去

js
// utils/request.js
// 定义一个类
class HttpRequest {
  instance: AxiosInstance;
  constructor(options: CreateAxiosOptions) {
    this.instance = axios.create(options)
  }
  setHeader() {...}
  get() {...}
  post() {...}
  put() {...}
  delete() {...}
}
// 生成一个实例
const request = new HttpRequest({})

// 全局仅用这么一个请求实例
export default request

然后在项目中各处去使用这一个请求实例

js
import request '@/utils/request'

const fetchData = (url) => {
  return request.get(url)
}

适配器模式

核心: 将一种格式适配成你所需要的格式

比如有一个场景:后端给你返回了三种数据格式,但是你需要把这三种格式转成你前端所需要的格式

js
// 格式1
const data1 = [{ age1: 20, name1: "林三心" }];
// 格式2
const data2 = [{ age2: 20, name2: "林三心" }];

这个时候你需要定义几个适配器类

js
class Adapter1 {
  data: { age1: number, name1: string }[];
  constructor(data) {
    this.data = data;
  }
  transform() {
    return this.data.map(({ age1, name1 }) => ({
      age: age1,
      name: name1,
    }));
  }
}

class Adapter2 {
  // 同理
}

当你需要转换成你需要的数据时,调用这些类就行

js
const adapter1 = new Adapter1(data1);
// 适配成功
const data = adapter1.transform();

观察者模式

核心:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知

我们平时使用的框架 Vue,它的响应式就是基于观察者模式去做的,下面是简单展示一下它的原理

ts
export class Subject {
  count: number;
  observers: any[];
  constructor() {
    this.count = 0;
    this.observers = [];
  }
  getCount() {
    return this.count;
  }
  setCount(count: number) {
    // 设置值之后通知更新
    this.count = count;
    this.notify();
  }
  notify() {
    this.observers.forEach((o) => {
      o.update();
    });
  }
  push(o) {
    this.observers.push(o);
  }
}

class Observer {
  private name;
  private subject;
  constructor(name: string, sub: Subject) {
    this.name = name;
    this.subject = sub;
    this.subject.push(this);
  }
  update() {
    console.log(`${this.name} 变了 ${this.subject.getCount()}`);
  }
}
const sub = new Subject();
// 观察一号
const observer1 = new Observer("一号", sub);
// 观察二号
const observer2 = new Observer("二号", sub);

sub.setCount(1);
// 一号 变了 1
// 二号 变了 1

发布订阅模式

发布订阅模式跟观察者模式很像,他们其实都有发布者和订阅者,但是他们是有区别的 观察者模式的发布和订阅是互相依赖的 发布订阅模式的发布和订阅是不互相依赖的,因为有一个统一调度中心

Vue EventBus 就是用了发布订阅模式

js
class EventEmitter {
  constructor() {
    this.cache = {};
  }
  on(name, fn) {
    const tasks = this.cache[name];
    if (tasks) {
      this.cache[name].push(fn);
    } else {
      this.cache[name] = [fn];
    }
  }
  off(name, fn) {
    const tasks = this.cache[name];
    if (task) {
      const index = tasks.findIndex((item) => item === fn);
      if (index >= 0) {
        this.cache[name].splice(index, 1);
      }
    }
  }
  emit(name, ...args) {
    // 复制一份。防止回调里继续on,导致死循环
    const tasks = this.cache[name].slice();
    if (tasks) {
      for (let fn of tasks) {
        fn(...args);
      }
    }
  }
  once(name, cb) {
    function fn(...args) {
      cb(args);
      this.off(name, fn);
    }
    this.on(name, fn);
  }
}

const eventBus = new EventEmitter();
// 组件一
eventBus.on("event", (val) => {
  console.log(val);
});
// 组件二
eventBus.emit("event", "params");

0303

0304

0305

html
<!-- Dark mode enabled -->
<html class="dark">
  <body>
    <!-- Will be black -->
    <div class="bg-white dark:bg-black">
      <!-- ... -->
    </div>
    <script>
      // On page load or when changing themes, best to add inline in `head` to avoid FOUC
      if (
        localStorage.theme === "dark" ||
        (!("theme" in localStorage) &&
          window.matchMedia("(prefers-color-scheme: dark)").matches)
      ) {
        document.documentElement.classList.add("dark");
      } else {
        document.documentElement.classList.remove("dark");
      }
      // Whenever the user explicitly chooses light mode
      localStorage.theme = "light";
      // Whenever the user explicitly chooses dark mode
      localStorage.theme = "dark";

      // Whenever the user explicitly chooses to respect the OS preference
      localStorage.removeItem("theme");
    </script>
  </body>
</html>

0306

  • 今天老铁问我要不要去他们那边,那边留有一个高级的 HC,我还是没有准备好,那到底啥时候准备好?明明知道自己的劣势和缺点,为啥就不能在努力准备准备呢?

问问自己,是否真的已经足够努力了?是否对得起自己对前端的这份热爱?

函数柯里化

一个多参数的函数转换成多个嵌套的单参数函数

js
function add(a, b, c, d) {
  return a + b + c + d;
}
// expected
curry(add)(1)(2)(3)(4);

function curry(target) {
  return function fn(...rest) {
    if (target.length === rest.length) {
      return target.apply(null, rest);
    } else {
      // return fn.bind(null, ...rest)
      return (...arg) => fn(...arg, ...rest);
    }
  };
}
// 参数不固定
function curry2(target) {
  var arr = [];
  return function fn(...arg) {
    if (arg.length) {
      arr.push(...arg);
      return fn;
    } else {
      var value = target.apply(null, arr);
      arr = [];
      return value;
    }
  };
}
const add2 = (...rest) => {
  return rest.reduce((acc, cur) => cur + acc, 0);
};
curry2(add2)(1)(2)(3)();

0307

  • 多重循环中嵌套异步函数最佳实践?

猜猜打印顺序,async/await 原文地址

js
async function jackson() {
  console.log(await Promise.resolve("jackson"));
}
async function bear() {
  console.log(await "bear");
}
async function daxiong() {
  console.log("daxiong");
}
jackson();
bear();
daxiong();
// daxiong  jackson bear

看看下列打印顺序

js
async function async1() {
  await new Promise((resolve, reject) => {
    resolve();
  });
  console.log("A");
}
async1();
new Promise((resolve) => {
  console.log("B");
  resolve();
})
  .then(() => {
    console.log("C");
  })
  .then(() => {
    console.log("D");
  });
// B A C D
js
async function async1() {
  await async2();
  console.log("A");
}
async function async2() {
  return new Promise((resolve, reject) => {
    resolve();
  });
}
async1();
new Promise((resolve) => {
  console.log("B");
  resolve();
})
  .then(() => {
    console.log("C");
  })
  .then(() => {
    console.log("D");
  })
  .then(() => {
    console.log("E");
  });
// B C D A E

async 根据返回值的类型,引起 js 引擎 对返回值处理方式的不同

async 函数在抛出返回值时,会根据返回值类型开启不同数目的微任务

  • return 结果值:非 thenable、非 promise(不等待)

  • return 结果值:thenable(等待 1 个 then 的时间)

  • return 结果值:promise(等待 2 个 then 的时间)

  • 非 thenable、非 promise(不等待)

js
async function testA() {
  return 111;
}

// async function testA1() {
//   return Promise.resolve("2222");
// }

testA1().then((a) => console.log(1, a));
Promise.resolve()
  .then(() => console.log(2))
  .then(() => console.log(3));
// 1 111
// 2
// 3
  • thenable(等待 1 个 then 的时间)
js
async function testB() {
  return {
    then(cb) {
      cb();
    },
  };
}

testB().then(() => console.log(1));
Promise.resolve()
  .then(() => console.log(2))
  .then(() => console.log(3));

// (等待一个then)最终结果👉: 2 1 3
  • promise 等待 2 个 then
js
async function testC() {
  return new Promise((resolve, reject) => {
    resolve();
  });
}

testC().then(() => console.log(1));
Promise.resolve()
  .then(() => console.log(2))
  .then(() => console.log(3));

// (等待两个then)最终结果👉: 2 3 1

async function testC() {
  return new Promise((resolve, reject) => {
    resolve();
  });
}

testC().then(() => console.log(1));
Promise.resolve()
  .then(() => console.log(2))
  .then(() => console.log(3))
  .then(() => console.log(4));

// (等待两个then)最终结果👉: 2 3 1 4

经典面试题

js
async function async1() {
  console.log("1");
  await async2();
  console.log("AAA");
}

async function async2() {
  console.log("3");
  return new Promise((resolve, reject) => {
    resolve();
    console.log("4");
  });
}

console.log("5");

setTimeout(() => {
  console.log("6");
}, 0);

async1();

new Promise((resolve) => {
  console.log("7");
  resolve();
})
  .then(() => {
    console.log("8");
  })
  .then(() => {
    console.log("9");
  })
  .then(() => {
    console.log("10");
  });
console.log("11");

// 5 1 3 7 11 8 9 4 aaa 10 6
// 5 1 3 4 7 11 8 9 aaa 10 6

步骤拆解:

  • 先执行同步代码,输出 5
  • 执行 setTimeout,是放入宏任务异步队列中
  • 接着执行 async1 函数,输出 1
  • 执行 async2 函数,输出 3
  • Promise 构造器中代码属于同步代码,输出 4

    async2 函数的返回值是 Promise,等待 2 个 then 后放行,所以 AAA 暂时无法输出

  • async1 函数暂时结束,继续往下走,输出 7
  • 同步代码,输出 11
  • 执行第一个 then,输出 8
  • 执行第二个 then,输出 9
  • 终于等到了两个 then 执行完毕,执行 async1 函数里面剩下的,输出 AAA
  • 再执行最后一个微任务 then,输出 10
  • 执行最后的宏任务 setTimeout,输出 6

await 右值类型区别

非 thenable

js
async function test() {
  console.log(1);
  await 1;
  console.log(2);
}
test();
console.log(3);
// 最终结果👉: 1 3 2
js
function func() {
  console.log(2);
}
async function test() {
  console.log(1);
  await func();
  console.log(3);
}
test();
console.log(4);
// 1 2 4 3

async function test() {
  console.log(1);
  await 123;
  console.log(2);
}

test();
console.log(3);

Promise.resolve()
  .then(() => console.log(4))
  .then(() => console.log(5))
  .then(() => console.log(6))
  .then(() => console.log(7));
// 1 3 2   4567

总结: await 后面接非 thenable 类型,会立即向微任务队列添加一个微任务 then,但不需等待

thenable 类型

js
async function test() {
  console.log(1);
  await {
    then(cb) {
      cb();
    },
  };
  console.log(2);
}
test();
console.log(3);

Promise.resolve()
  .then(() => console.log(4))
  .then(() => console.log(5))
  .then(() => console.log(6))
  .then(() => console.log(7));
// 1 3 4 2 5 6 7

总结: await 后面接 thenable 类型,需要等待一个 then 的时间之后执行

Promise 类型

js
async function test() {
  console.log(1);
  await new Promise((resolve, reject) => {
    resolve();
  });
  console.log(2);
}

test();
console.log(3);
Promise.resolve()
  .then(() => console.log(4))
  .then(() => console.log(5))
  .then(() => console.log(6))
  .then(() => console.log(7));
// 1 32  4 5 6 7

❓ 为什么表现的和非 thenable 值一样呢?为什么不等待两个 then 的时间呢?

js
async function func() {
  console.log(1);
  await 1;
  console.log(2);
  await 2;
  console.log(3);
  await 3;
  console.log(4);
}

async function test() {
  console.log(5);
  await func();
  console.log(6);
}

test();
console.log(7);

Promise.resolve()
  .then(() => console.log(8))
  .then(() => console.log(9))
  .then(() => console.log(10))
  .then(() => console.log(11));

// 5 1 7 2 8 3 9 4 10 6 11

async function test() {
  console.log(5);
  console.log(1);
  Promise.resolve()
    .then(() => console.log(2))
    .then(() => console.log(3))
    .then(() => console.log(4))
    .then(() => console.log(6));
}

test();
console.log(7);

Promise.resolve()
  .then(() => console.log(8))
  .then(() => console.log(9))
  .then(() => console.log(10))
  .then(() => console.log(11));

// 最终结果👉: 5 1 7 2 8 3 9 4 10 6 11

走一个案例

js
async function func() {
  console.log(2);
  return {
    then(cb) {
      cb();
    },
  };
}
async function test() {
  console.log(1);
  await func();
  console.log(3);
}
test();
console.log(4);
new Promise((resolve) => {
  console.log("B");
  resolve();
})
  .then(() => {
    console.log("C");
  })
  .then(() => {
    console.log("D");
  });
// 1 2 4 b c 3 d

async/await 总结

  • async 函数返回值 结论:async 函数在抛出返回值时,会根据返回值类型开启不同数目的微任务 1.return 结果值:非 thenable、非 promise(不等待) 2.return 结果值:thenable(等待 1 个 then 的时间) 3.return 结果值:promise(等待 2 个 then 的时间)

  • await 右值类型区别:

    1.接非 thenable 类型,会立即向微任务队列添加一个微任务 then,但不需等待

    2.接 thenable 类型,需要等待一个 then 的时间之后执行

    3.接 Promise 类型(有确定的返回值),会立即向微任务队列添加一个微任务 then,但不需等待

图片裁剪?ctx.drawImage()

原图像从坐标 (33,71) 处截取一个宽度为 104 高度为 124 的图像。并将其绘制到画布的 (21, 20) 坐标处,并将其缩放为宽 87、高 104 的图像。

js
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
const image = document.getElementById("source");

image.addEventListener("load", (e) => {
  ctx.drawImage(image, 33, 71, 104, 124, 21, 20, 87, 104);
});
  • 更新了下自己的简历,真想加这两句话?hhhh,我真的太自恋了,希望假以时日吧
md
- 精通 react
- 精通 ssr

实力配不上我的野心???how to do?

什么时候我才能像三元大佬,岀一版自己的小册子基于 Vite 的 SSG 框架开发实战

0308

字节出品 感觉就不一样,还是太厉害了,膜拜膜拜

什么是构建?简单来说:构建就是将开发环境的代码转化成生产环境可用来部署的代码。为了生产出生产环境可用的 JS、CSS,构建工具实现了诸如:代码转换、代码压缩、tree shaking、code spliting 等。

0309

0310

  • 个人主题首页还没有整完,这速度也太慢了!!!自己开发效率太慢了

0311

  • 去了一趟常州,累个半死,😷,来回车票花销 300
  • 结婚前的婚纱照拍摄需要提上日程,最迟这周五确定,还要确定的是婚庆相关的事宜
  • 确定提亲的日子,提什么亲?

0312

  • 结婚事宜女方这边难以推进,艰难
  • 手写各种排序
js
// bubble sort

0313

  • 突发奇想:如果从银行里借 10 万用于提前还房贷,这个每个月能少 500 的利息,银行十万一个月 400 的利息,这样每个月还你少还 100 左右的利息

react 每日小记

jsx 会被编译成 render function,也就是类似 React.createElement 这种。所以之前写 React 组件都必须有一行 import * as React from 'react',因为编译后会用到 React 的 api。但后来改为了这种 render function:

js
const Test = <div>THis is Test</div>;

由 babel、tsc 等编译工具自动引入一个 react/jsx-runtime 的包. vdom 转 fiber 的流程叫做 reconcile,我们常说的 diff 算法就是在 reconcile 这个过程中.经过 reconcile 之后,就有了新的 fiber 树了.

react 渲染流程整体分为两个大阶段: render 阶段和 commit 阶段。

  • render 阶段也就是 reconcile 的 vdom 转 fiber 的过程

  • commit 阶段就是具体操作 dom,以及执行副作用函数的过程。commit 阶段还分为了 3 个小阶段:before mutation、mutation、layout。

0314

  • 今天开始接触 nextjs 中文,nextjs en从下周开始忙开发,会一直忙到下个月,希望能有新的收获!!!

面试官问什么是 event loop?

答:eventLoop 是 js 运行时的一个重要机制,解决 js 单线程运行时不会阻塞的一种机制,负责协调和管理 js 程序中各种任务的执行。eventloop 原理是通过将任务分配到不同的队列中,并按照一定的规则来管理任务的执行顺序。在 js 中,有一个主线程负责执行任务,同时还有一个或者多个微任务和宏任务队列,微任务队列中的优先级比较高,优于宏任务执行,而同一队列中的任务是按照添加的先后顺序执行。

1.Javascript 有一个 main thread 主线程和 call-stack 调用栈(执行栈),所有的任务都会被放到调用栈等待主线程执行。 2.JS 调用栈采用的是后进先出的规则,当函数执行的时候,会被添加到栈的顶部,当执行栈执行完成后,就会从栈顶移出,直到栈内被清空。 3.Javascript 单线程任务被分为同步任务和异步任务,同步任务会在调用栈中按照顺序等待主线程依次执行,异步任务会在异步任务有了结果后,将注册的回调函数放入任务队列中等待主线程空闲的时候(调用栈被清空),被读取到栈内等待主线程的执行。

大白话大致是这样的:

  • 所有同步任务都在主线程上执行,形成一个执行栈 (Execution Context Stack)。
  • 而异步任务会被放置到 Task Table,也就是上图中的异步处理模块,当异步任务有了运行结果,就将该函数移入任务队列。
  • 一旦执行栈中的所有同步任务执行完毕,引擎就会读取任务队列,然后将任务队列中的第一个任务压入执行栈中运行。

主线程不断重复第三步,也就是 只要主线程空了,就会去读取任务队列,该过程不断重复,这就是所谓的 事件循环。

Nodejs 中的 eventLoop?

Node 的 Event loop 一共分为 6 个阶段,每个细节具体如下:

  • timers: 执行 setTimeout 和 setInterval 中到期的 callback。
  • pending callback: 上一轮循环中少数的 callback 会放在这一阶段执行,nodejs 内部在使用
  • idle/prepare: 仅在内部使用。
  • poll: 最重要的阶段,执行 pending callback,在适当的情况下会阻塞在这个阶段。
  • check: 执行 setImmediate(setImmediate()是将事件插入到事件队列尾部,主线程和事件队列的函数执行完成之后立即执行 setImmediate 指定的回调函数)的 callback。
  • close callbacks: 执行 close 事件的 callback,例如 socket.on('close'[,fn]) or http.server.on('close, fn)

大循环和小循环

大循环指的就是 event loop,小循环就是指由 next tick callback queue 和 microtask callback queue 所组成的小循环。我们可以下这么一个结论:一旦进入大循环之后,每执行完一个大循环 callback 之后,就必须检查小循环。如果小循环有 callback 要执行,则需要执行完所有的小循环 calback 之后才会回归到大循环里面。 注意,这里强调的是,nodejs 不会把 event loop 中当前阶段的队列都清空之后才进入小循环,而是执行了一个 callback 之后,就进入了小循环了

nodejs 与 browser 中 event loop 的区别

  • 相同点 从运行机制的实质上来看,两者大体上是没有什么区别的。具体展开来说就是:如果把 nodejs event loop 中的 mainline code 和各个阶段中的 callback 都归纳为 macrotask callback,把 next tick callback 和其他诸如 Promise/then()的 microtask callback 都归纳为 microtask callback 的话,这两个 event loop 机制大体是一致的:都是先执行一个 macrotask callback,再执行一个完整的 microtask callback 队列。microtask callback 都具备递归入队的特性,无限递归入队都会产生“event loop starvation”后果。只有执行完 microtask callback queue 中的所有 callback,才会执行下一个 macrotask callback。

  • 异同点

    1.在 nodejs event loop 的实现中,没有 macrotask 的说法。 nodejs event loop 是按照阶段来划分的,具有六个阶段,对应六种类型的队列(其中两种是只供内部使用);而 browser event loop 不按照阶段划分,只有两种类型的队列,即 macrotask queue 和 microtask queue。从另外一个角度我们可以这么理解:nodejs event loop 有 2 个 microtask 队列,有 4 个 macrotask 队列;而浏览器 event loop 只有 1 个 microtask 队列,有 1 个 macrotask 队列。

    2.最大的不同,在于 nodejs evnet loop 有个轮询阶段。当 evnet loop 中所有队列都为空的时候,browser event loop 会退出 event loop(或者说处于休眠状态)。但是 nodejs event loop 不一样,它会持续命中轮询阶段,并且在那里等待处于pending 状态的 I/O callback。只有等待时间超出了 nodejs 计算出来的限定时间或者再也没有未完成的 I/O 任务的时候,nodejs 才会退出 event loop。这就是 nodejs event loop 跟 browser event loop 最大不同的地方。

0315

  • 如何修改 create-react-app 打包输出的文件路径/名称(appBuild) 在 React 项目跟目录下(跟 src 同级)添加 .env.production 文件
js
// 例如我需要将默认的 build 修改为 dist
BUILD_PATH = dist;
  • pm2 维护 nextjs 项目,在公司平台上发布,健康检查没有通过,还是不太懂这块发布流程,需要学习下!!!

0316

  • 跨域,今天测试同学说以为只要主域是 bytheway.com 的都不涉及跨域,其实不是的 在请求时,如果出现了以下情况中的任意一种,那么它就是跨域请求:
  • 协议不同,如 http 和 https;
  • 域名不同,即使主域相同,子域不同也是跨域的
  • 端口不同。

cors 策略时,设置“Access-Control-Allow-Origin”参数即可解决跨域问题,此参数就是用来表示允许跨域访问的原始域名的,当设置为“*”时,表示允许所有站点跨域访问

  • vite 项目打完包之后,执行vite preview是怎样做到预览的?

0317

Modern.js v2 基于 React v18 的全新 API 支持了流式渲染(Streaming SSR )。 如果大家关注去年 React v18 的版本更新,可能会注意到它提供了新的 Server Side APIs,并对 Suspense 做了完整支持。在 Modern.js v2 中,我们通过 React Router v6 暴露的能力,在框架层面支持了流式渲染。 流式渲染利用 HTTP 的能力,允许将页面的 HTML 分割成较小的块,并逐步将这些块从服务器发送到客户端。页面无需等待所有数据加载完成,即可呈现完整用户界面,因此与数据无关的局部内容能够更早地显示出来。 在 Modern.js v2 中,只需要配置 server.ssr.mode,即可开启流式渲染:

js
// modern.config.js
export default defineConfig({
  server: {
    mode: "stream",
  },
});
  • 简历中的每一份工作经历,重点是带来了什么成果?,举个例子比如从 0 到 1 部署 nextjs 项目,带了 100 万级的 seo 流量等等

0318

  • 结婚:在考虑不需要婚庆
  • 婚摄摄影:查找合适的,实惠一点的
  • 身体修养
  • 对象家里人。平常心对待

0319

  • 个人博客主题:首页继续完善,tailwindcss 写的有点慢,一边写一边看文档

一文了解 NextJS 并对性能优化做出最佳实践

  • 针对非首屏组建基于 dynamic 动态加载
  • next/script 优化 script 加载时
  • next/image 优化图片资源
  • next/link 预加载
  • 静态内容预加载 基于 getStaticProps 对不需要权限的内容进行预加载,它将在 NextJS 构建时被编译到页面中,减少了 http 请求数量
js
function Blog({ posts }) {
  return (
    <ul>
      {posts.map((post) => (
        <li>{post.title}</li>
      ))}
    </ul>
  );
}
export async function getStaticProps() {
  const res = await fetch("https://.../posts");
  const posts = await res.json();
  return {
    props: {
      posts,
    },
  };
}
export default Blog;

0320

  • 接下来的工作重点是开发和部署 nextjs 项目,也就是奇迹文学网站

  • nextjs 接入健康检查

    解决 引入 koa 和 koa-router

js
const server = new Koa();
const router = new Router();
router.get("/xx-qiji/healthcheck", (ctx) => {
  ctx.body = "healthCheck success cpp";
});
  • NextJS 内置 getStaticProps、getServerSideProps 的区别是什么?

getStaticProps (Static Generation): Fetch data at build time. 静态生成,构建时获取数据

getStaticPaths (Static Generation): Specify dynamic routes to pre-render pages based on data. 静态生成,指定基于数据预渲染页面的动态路由

getServerSideProps (Server-side Rendering): Fetch data on each request. 服务端渲染,在每个请求上获取数据

  • getStaticProps 用于静态网站生成,当你知道你在构建时要获取什么数据时就使用它,例如一篇博客文章,性能非常快,而且有利于 SEO。

    基于 getStaticProps 对不需要权限的内容进行预加载,它将在 NextJS 构建时被编译到页面中,减少了 http 请求数量 如果你能在打包的时候控制数据就用它,因为 getStaticProps 的内容会被预加载成 html,也就是固定的,不会变

  • getServerSideProps 用于服务器端渲染,当你有可能经常变化的数据时使用,例如天气 API,但性能可能比 SSG 略差

在 Next.js 中,在 Page 页面中可以导出一个 getServerSideProps 方法,用于服务端获取数据

  • 如何判断上线 nextjs 项目是用的 ssr 还是 ssg 模式?

  • nextjs 深度好文

  • IncreIncremental Site Rendering (ISR): 增量静态生成,基于页面内容的缓存机制,仅对未缓存过的静态页面进行生成,依赖服务端

    这个原理是啥,如何做到的

ts
async function getStaticProps() {
  const res = await fetch("https://.../posts");
  const posts = await res.json();
  return {
    props: {
      posts,
    },
    // Next.js will attempt to re-generate the page:
    // - When a request comes in
    // - At most once every 10 seconds
    revalidate: 10, // In seconds
  };
}

0321

  • Nextjs 项目继续打磨,如何进行很好的布局,这样多个页面就能同时用?
tsx
(Home as NextPageWithLayout).getLayout = function getLayout(
  page: React.ReactElement
) {
  return <Layout>{page}</Layout>;
};
export default Home;
  • tailwindcss 继续上手,如何激发 tailwindcss 最大威力? 配合 clsx 的动态能力,搭载 tailwindcss 很爽!
tsx
import clsx from "clsx";

export const SkeletonCard = ({ isLoading }: { isLoading?: boolean }) => (
  <div
    className={clsx("rounded-2xl bg-gray-900/80 p-4", {
      "relative overflow-hidden": isLoading,
      "bg-vercel-blue text-white": !isLoading,
    })}
  >
    <div className="space-y-3">
      <div className="h-14 rounded-lg bg-gray-700" />
      <div className="h-3 w-11/12 rounded-lg bg-gray-700" />
      <div className="h-3 w-8/12 rounded-lg bg-gray-700" />
    </div>
  </div>
);
  • 项目里 prettier 有问题,对 tsx 后缀文件没有格式化代码,修复如下
json
// vscode 中的settings,json
"[vue]": {
  "editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[javascript]": {
  "editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescript]": {
  "editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescriptreact]": {
  "editor.defaultFormatter": "esbenp.prettier-vscode"
},

0322

React Server Components ( RFC ) 与传统的 SSR 不同,优点是拥有流式 HTML 和选择性注水?

完蛋,这个 RFC 又是啥玩意,看不懂 react 文档介绍

Details

React Server Components 是 React 团队提出的一种新的概念,旨在提高 Web 应用程序的性能和可维护性。它们是一种新的 React 组件类型,可以在服务器端渲染和处理,以提高应用程序的性能和可维护性。

  • React Server Components 的主要思想是将组件的渲染和处理移动到服务器端,以减轻客户端的负担。这样可以减少客户端的 JavaScript 代码量,提高应用程序的性能和可维护性。
  • React Server Components 的另一个重要特点是它们可以在运行时动态加载和卸载。这意味着它们可以根据需要动态地加载和卸载组件,从而减少应用程序的内存占用和加载时间。

场景:

1.大型单页应用程序:在大型单页应用程序中,可能会有许多组件,其中一些可能只在特定的页面或状态下使用。使用 React Server Components,可以在需要时动态加载这些组件,从而减少初始加载时间和页面大小。

2.动态内容:在某些情况下,可能需要根据用户的行为或其他条件动态加载和卸载组件。使用 React Server Components,可以根据需要动态加载和卸载组件,从而提高页面的性能和响应速度。

在 app 目录下的组件默认都是 React Server Components,那么 React Server Components 有什么优势呢? 这里有几个概念

  • CSR:所有前端打包到前端,通常会引起浏览器加载 JavaScript 过大,从而导致首屏白屏时间过长

  • SSR:数据在服务端请求,通过 renderToString 方法将字符串 DOM 结构输出给浏览器,此时浏览器还不能交互,React 不能管理已经存在的 DOM,需要重新执行一遍,这个过程叫“注水/水合”(Hydrate)。Next12 getServerSideProps 的渲染方式也就是 SSR。

SSR 解决了白屏时间过长的问题和 SEO 的问题,但也并不是完美的,过多的请求会导致服务端响应时间变长,“注水/水合”(Hydrate)的过程也会导致客户端代码量的增加。

React Suspense API 解锁了 React 18 中的两个主要 SSR 功能:

  • 在服务器上流式传输 HTML。 要实现这个功能,需要从原来的方法切换 renderToString 切换到新renderToPipeableStream方法。

    服务端一段一段传给客户端

  • 客户端选择性注水。 使用 hydrateRoot 代替 createRoot 方法,使用Ract.lazy包裹和 Suspense 组件

    即使 HTML 被流式传输,页面也不会可交互的,除非页面的整个 JavaScript 被下载完。这就是选择性注水的用武之地。

    现在 React.lazy 在服务端开箱即用。 当你将 lazy 组件包裹在 Suspense 中时,不仅告诉 React 你希望它被流式传输,而且即使包裹在 Suspense 中的组件仍在被流式传输,也允许其余部分注水

    React 18 中的流式 SSR

    客户端选择性注水

比如上面的博客实例,评论接口查询速度较慢,就可以使用 Suspense 实现流渲染。 所谓的流就是通过 script 动态返回最小 html,并且插入到正确的位置,页面中如果有多个 Suspense,是没有先后顺序的,React Server Components 是并行的。

js
import { lazy } from "react";
const Comments = lazy(() => import("./Comments.js"));
//...
return (
  <Layout>
    <NavBar />
    <Sidebar />
    <RightPane>
      <Post />
      <Suspense fallback={<Spinner />}>
        <Comments />
      </Suspense>
    </RightPane>
  </Layout>
);

看了几天的 Nextjs 文档,怀疑自己看了假的文档地址

fetch

tsx
// test.tsx
export default async function Page() {
  // This request should be cached until manually invalidated.
  // Similar to `getStaticProps`.
  // `force-cache` is the default and can be omitted.
  const staticData = await fetch(`https://...`, { cache: "force-cache" });

  // This request should be refetched on every request.
  // Similar to `getServerSideProps`.
  const dynamicData = await fetch(`https://...`, { cache: "no-store" });

  // This request should be cached with a lifetime of 10 seconds.
  // Similar to `getStaticProps` with the `revalidate` option.
  const revalidatedData = await fetch(`https://...`, {
    next: { revalidate: 10 },
  });

  return <div>...</div>;
}

tailwindCss 自定义设置属性

html
<div class="bg-[url('/img/hero-pattern.svg')]">
  <!-- ... -->
</div>
<div class="top-[117px] lg:top-[344px]">
  <!-- ... -->
</div>

或者完全手写自己的 css,需要方括号包起来

html
<div class="[--scroll-offset:56px] lg:[--scroll-offset:44px]">
  <!-- ... -->
</div>

0323

  • 金三银四 不能放弃机会,时刻准备着,用 ES5 实现 es6 class 中的 extends(高频面试题)
js
function extend(Child, Parent, proto) {
  var prototype = Object.create(Parent.prototype); // 1
  prototype.constructor = Child; // 2
  Child.prototype = prototype; // 3
  for (var k in proto) {
    Child.prototype[k] = proto[k];
  }
}
function Par(name) {
  this.name = name;
}
Par.prototype.logName = function () {
  console.log(`My name is ${this.name}`);
};

function Child(name, age) {
  Par.call(this, name);
  this.age = age;
}

extend(Child, Par, {
  logAge() {
    console.log(`my age is ${this.age}`);
  },
});
var b = new Child("cpp", 31);
b.logName();
b.logAge();
  • 突发奇想,看到了 OPENAI 在招前端工程师

  • OpenAi 官网上一个华裔女员工的个人博客,主题简约,阔以试试.从头开始搭建博客,个人主题,不依赖于 vitepress 或者 modern.js

  • object-fit 一般用于 img 和 video 标签,一般可以对这些元素进行保留原始比例的剪切、缩放或者直接进行拉伸等。

css
object-fit: fill|contain|cover|scale-down|none|initial|inherit;

0324

  • 前几天晚上码代码时间太久了,白天没休息过来,有点累了,最近休息下
  • 个人主题如何与 tailwindcss 结合实现 dark/light 模式切换
  • 世纪难题,react 流式渲染到底会不会影响 seo?影不影响爬虫爬取页面?
  • React Server Component: 混合式渲染
  • React Streaming SSR 原理解析
  • 混合渲染思路有点像 Islands 架构,但又有点不太一样,不一样在哪里
  • vitepress/vite 工具接下来的研究重点,如何打造出自己的 SSG 生态,然后发布自定义主题

    这个拖了好久 拖着拖着就不做了

0325-26

  • 经历了痛苦的周末,对象连续两天发烧,高烧 39 度,深夜进行物理降温,这次最大的教训一个是医保没有落实到位,一个是挂水之前没有吃饭导致血管闭塞,插不了针,护士说是平时缺乏锻炼导致
  • 上周五给 Modern.js 提了一个pr,主要是官网介绍里 mdx 格式错误导致文档凌乱,算是一种开始

0327

  • 最近几天需要深度休息,个人学习暂时往后挪一下

  • 如何为开源做贡献 ChatGPT 为您找到如下答案: 找到合适的开源仓库:首先,你需要找到一个你感兴趣的开源项目,可以通过 GitHub、GitLab、Bitbucket 等代码托管平台搜索。

    阅读贡献指南:每个开源项目都会有贡献指南,这些指南会告诉你如何为该项目做出贡献,包括如何提交代码、如何报告问题等。

    1.Fork 项目:在项目页面上点击“Fork”按钮,将该项目的代码复制到你的 GitHub 账户中。

    2.创建分支:在本地仓库中创建一个新的分支,用于修改代码。

    3.修改代码:在新的分支上修改代码,可以使用任何你喜欢的编辑器或 IDE。

    4.提交代码:将修改后的代码提交到你的 Fork 仓库中。

    5.创建 Pull Request:在你的 Fork 仓库页面上点击“New Pull Request”按钮,创建一个 Pull Request,将你的修改提交给原始仓库。

    6.等待审核:等待原始仓库的维护者审核你的 Pull Request,如果有需要,他们会提出修改意见。

    7.合并代码:如果你的 Pull Request 被接受,你的代码将被合并到原始仓库中,你的贡献就完成了。

  • 手写题,深拷贝,ts 版

ts
// function deepClone<T>(obj:T): T {
//   if(typeof obj !== 'object' || obj === null) {
//     return obj
//   }
//   let newObj = Array.isArray(obj) ? [] : {}
//   for (let i in obj) {
//     if (obj.hasOwnProperty(i)) {
//       newObj[i] = typeof obj[i] === 'obejct' ? deepClone(obj[i]):obj[i]
//     }
//   }
//   return newObj
// }

Object.hasOwnProperty() 自身属性与继承属性

js
o = new Object();
o.hasOwnProperty("prop"); // false
o.prop = "exists";
o.hasOwnProperty("prop"); // 返回 true
o.hasOwnProperty("toString"); // 返回 false
o.hasOwnProperty("hasOwnProperty"); // 返回 false

0328

  • 对象生病还没好,今天早点做完早点回去照顾
  • 我似乎有一种执念,对于想看的电视剧非要看完,明知时间已经很晚了,还要做这种伤害身体的事,年纪大了,是不是身体健康永远是第一位的
  • Modern.js 看起来有些内容需要优化,1.modern.js 搜索点击关闭,内容关不掉 bug 2.moder.js 代码切换优化,阔以借鉴 vitepress
  • 今天继续手写代码,几种排序需要回忆下
js
// 交换之冒泡排序
function bubbleSort(arr) {
  let len = arr.length;
  for (let i = 0; i < len; i++) {
    for (let j = 0; j < len - i - 1; j++) {
      if (arr[j] > arr[j + 1]) {
        let temp = arr[j];
        arr[j] = arr[j + 1];
        arr[j + 1] = temp;
      }
    }
  }
  return arr;
}
var test = [22, 11, 1, 222, 99];
bubbleSort(test);
// 交换之快速排序
function quickSort(arr) {
  if (arr.length <= 1) return arr;
  var mid = arr[0];
  var len = arr.length;
  let left = [],
    right = [];
  // 注意遍历是从1开始
  for (let i = 1; i < len; i++) {
    if (arr[i] > mid) {
      right.push(arr[i]);
    } else if (arr[i] <= mid) {
      left.push(arr[i]);
    }
  }
  console.log(left, right);
  return [...quickSort(left), mid, ...quickSort(right)];
}
quickSort(test);
  • webpack 中 tree shaking 原理 树摇晃是指在代码在打包过程中,删掉未使用代码的一种优化技术。底层原理是基于 ES6 模块化的静态分析,通过检测代码中的依赖关系,找到未使用的代码,将其从打包结果中删除,webpack 中的 tree shaking 的实现主要依靠 2 个功能:静态分析和副作用标记

    1.静态分析是指在构建时分析模块和代码之间的依赖关系,找到未被使用的代码。webpack 会从入口文件开始递归分析代码中的依赖关系,将所有的依赖关系构建成一个依赖树。依赖树中包含了每个模块的依赖关系和导出信息,通过分析这些信息,Webpack 可以识别哪些代码被使用和哪些代码没有被使用。

    2.副作用标记是指用来标记代码中是否存在副作用的标记。在 Javascript 中,除了返回值,还可能会存在一些副作用(Side Effects) 的操作,例如修改全局变量、读取外部数据等。这些操作会影响代码的运行结果,如果没有标记,Webpack 将无法识别哪些代码具有副作用。通过在代码中添加特定的注释或者使用特定的编译工具,可以将副作用标记添加到代码中,从而帮助 Webpack 识别代码中的副作用,进而实现 Tree Shaking。

    总的来说,Webpack 的 Tree Shaking 实现依靠静态分析和副作用标记两个功能,通过分析代码中的依赖关系,找到未被使用的代码,并将其从打包结果中删除,从而实现代码的优化和精简。

0329

  • 连续两天没带电脑回去,有点疲软,主要是身体还未恢复,今天比昨天要多休息半小时,早起半小时,23:30-8:00,每天下午有五个小时时间,2 小时工作,三小时学习,学习内容,一个是手写题,一个是知识点零散记忆,同时抽半小时看下最感兴趣的开源仓库,试试有没有优化空间的可能性
  • 复习下昨日的手写题,同时新增选择排序和插入排序
js
function bubbleSort(arr) {
  for (let i = 0; i < arr.length; i++) {
    for (let j = 0; j < arr.length - i - 1; j++) {
      if (arr[j] > arr[j + 1]) {
        var temp = arr[j];
        arr[j] = arr[j + 1];
        arr[j + 1] = temp;
      }
    }
  }
  return arr;
}
function quickSort(arr) {}
// 选择排序
// 思路是将最小的放在数组的起始位置,然后从剩下的找最小的元素然后放到已排序的后面
function selectSort(arr) {
  let len = arr.length;
  let min;
  for (let i = 0; i < len; i++) {
    min = i;
    for (let j = i + 1; j < len; j++) {
      if (arr[j] <= arr[min]) {
        min = j;
      }
    }
    let temp = arr[i];
    arr[i] = arr[min];
    arr[min] = temp;
  }
  return arr;
}
selectSort([1, 33, 2, 111, 99, 22]);
// 插入排序
// 思路 大循环是遍历每一个 小循环是从后往前遍历 当前元素和前面元素进行交交换 最终当前元素放到合适的位置
function insertSort(arr) {
  let len = arr.length;
  for (let i = 1; i < len; i++) {
    let cur = arr[i];
    let pre = i - 1;
    while (pre >= 0 && arr[pre - 1] > cur) {
      arr[pre + 1] = arr[pre];
      pre--;
    }
    arr[pre + 1] = cur;
  }
  return arr;
}

function insertSort(arr) {
  var len = arr.length;
  for (let i = 1; i < len; i++) {
    let cur = arr[i];
    let j = i;
    while (j >= 0 && arr[j - 1] > cur) {
      arr[j] = arr[j - 1];
      j--;
    }
    arr[j] = cur;
  }
  return arr;
}
var test = [3, 22, 11, 2, 33, 0, 8];
insertSort(test);

0330

  • 看源码 一方面要看懂,另一方面要去学习里面用到的前端工程化知识以及设计思路和理念。很多人只是去下载了源码,然后阅读就没事了,我希望自己能更进一步
  • 删掉 npm 包里某一个版本
bash
npm unpublish 包名 --force # 强制删除
npm unpublish guitest@1.0.1 # 指定版本号
npm deprecate # 某些情况
  • git clone 拉取仓库代码,老是报 Failed to connect to github.com port 443: Operation timed out,知乎到一个解决措施: 用 ssh 的地址来拉代!!!前提是你的公钥以及在 github 个人设置里面注册过,目前看还行!!

git push 老是推不上去,切换远程地址

  • 使用命令,先查看自己的代码是否关联了 git.命令:git remote -v
  • 直接修改远程仓库地址。使用命令 git remote set-url origin URL
  • 先删除远程仓库地址,然后在添加
bash
git remote rm origin 删除现有远程仓库
git remote add origin url 添加新远程仓库

0331

tailwindcss 相关知识进阶一点,base/components/utilities 三者的区别是啥,如何进行有效扩展

css
@tailwind base;
@tailwind components;
@tailwind utilities;

从日常开发中,看看有没有什么插件阔以写的,现在官方确认的几个插件有

js
// tailwind.config.js
module.exports = {
  // ...
  plugins: [
    require("@tailwindcss/typography"),
    require("@tailwindcss/forms"),
    require("@tailwindcss/line-clamp"),
    require("@tailwindcss/aspect-ratio"),
  ],
};
bash
degit user/repo

# these commands are equivalent
degit github:user/repo
degit git@github.com:user/repo
degit https://github.com/user/repo
// 实战
npx degit niaogege/vitepress-wmh/packages/template my-test

degit 会对 git 仓库进行复制。当你运行 degit something-user/some-repo 时,它会在 https://github.com/some-user/some-repo 上找到最新的提交,并下载相关的 tar 文件到~/.degit/some-user/some-repo/commithash.tar.gz。(这比使用 git clone 要快得多,因为你不会下载整个 git 历史记录)