📒 Golang 如何根据指针访问对应的值
原始类型需要手动使用 * 操作符,复杂对象会自动解除指针引用:
num := &42
fmt.Println(num) // 打印的是内存地址
fmt.Println(*num) // 42
ms := &myStruct{foo: 42}
(*ms).foo = 17
fmt.Println((*ms).foo) // 17
// 对于复杂对象,直接操作就行
ms.foo = 17
fmt.Println(ms.foo) // 17
📒 Golang 创建对象指针的三种方式
Golang 中所有的赋值操作都是 copy,例如原始类型、array、struct,有两种例外:map 和 slice,它们具有内部指针,在赋值的时候传递指针类型。
// 第一种:对已有的值类型使用 `&` 操作符
ms := myStruct{foo: 42}
p := &ms
// 第二种:在初始化的时候使用 `&` 操作符
p := &myStruct{foo: 42}
// 第三种:使用 `new` 关键字,这种方法不能在初始化的时候进行赋值
var ms *myStruct = new(myStruct)
📒 如何渲染虚拟 DOM
所谓虚拟 DOM 其实就是一棵多叉树,可以使用下面的结构表示:
class VDOM {
  type: ElementTagName;
  props: ElementProps;
  children: VDOM[];
}
渲染虚拟 DOM,很明显要用递归,对不同的类型做不同的处理:
- 如果是文本类型,就要用 document.createTextNode创建文本节点;
- 如果是元素类型,就要用 document.createElement创建元素节点,元素节点还有属性要处理,并且要递归渲染子节点;
实现 render 函数如下:
const render = (vdom, parent = null) => {
  const mount = (el) => {
    if (!parent) return el;
    // 如有父节点则挂载到父节点,组装为 DOM 树
    return parent.appendChild(el);
  }
  if (isTextVdom(vdom)) {
    // 创建文本节点
    return mount(document.createTextNode(vdom));
  } else if (isElementVdom(vdom)) {
    // 创建元素节点
    const dom = mount(document.createElement(vdom.type));
    // 递归渲染子节点,这里使用深度优先遍历
    for (const child of vdom.children) {
      render(child, dom);
    }
    // 给元素添加属性
    for (const prop in vdom.props) {
      setAttribute(dom, prop, vdom.props[prop]);
    }
    return dom;
  }
};
如何判断文本节点:
function isTextVdom(vdom) {
  return typeof vdom == 'string' || typeof vdom == 'number';
}
如何判断元素节点:
function isElementVdom(vdom) {
  return typeof vdom == 'object' && typeof vdom.type == 'string';
}
如何处理样式、事件、属性:
const setAttribute = (dom, key, value) => {
  if (typeof value == 'function' && key.startsWith('on')) {
    // 事件处理,使用 `addEventListener` 设置
    const eventType = key.slice(2).toLowerCase();
    dom.addEventListener(eventType, value);
  } else if (key == 'style' && typeof value == 'object') {
    // 样式处理,合并样式
    Object.assign(dom.style, value);
  } else if (typeof value != 'object' && typeof value != 'function') {
    // 属性处理,使用 `setAttribute` 设置
    dom.setAttribute(key, value);
  }
}
📒 heapify:最快的 JavaScript 优先级队列库
📒 easyjson:Golang 中的序列化库,比 encoding/json 快 4-5 倍
📒 fast-json-stringify:比 JSON.stringify 快两倍
📒 Nodejs 如何将图片转为 base64
使用 Buffer 对象:
import fs from "node:fs";
import path from "node:path";
const raw = fs.readFileSync(path.join(__dirname, './2333.png'), 'binary');
const buf = Buffer.from(raw, 'binary');
const string = buf.toString('base64');
同理可以将 base64 转回图片:
const raw =  Buffer.from(string, 'base64').toString('binary');
📒 Nodejs 如何实现图片处理
推荐使用 sharp 这个库,可以实现图片压缩,转 JPEG、PNG、WebP 等格式:
📒 如何打印 26 个字母的字符串
一行代码搞定:
String.fromCharCode(...Array.from({ length: 26 }, (_, index) => 97 + index));
// 'abcdefghijklmnopqrstuvwxyz'
📒 如何用 Map 实现 Set
关于 Map 和 Set 是两个抽象数据结构,Map 存储一个键值对集合,其中键不重复,Set 存储一个不重复的元素集合。本质上 Set 可以视为一种特殊的 Map,Set 其实就是 Map 中的键:
class NewSet<T extends unknown> {
  private collection: Map<T, undefined>;
  
  constructor(iterable: T[] = []) {
    this.collection = new Map(
      iterable.map(it => [it, undefined])
    );
  }
}
📒 方法重载与参数默认值
为了支持可变参数,在 Java 中通过 方法重载 实现,通过定义多个方法签名,根据实际调用传递的参数去匹配签名。在 TypeScript 中也提供了方法重载特性,但在开发中很少用到,一般都通过 参数默认值 实现可变参数:
type NewSet<T> = (iterable: T[] = []) => void
注意使用参数默认值之后,TS 会自动将这个参数推导为可变参数,例如上面这个会推导为
NewSet<T>(iterable?: T[]): void
📒 项目常用工具库
- dayjs:与- moment的 API 设计保持一样,但体积仅有 2KB;
- qs:解析 URL query 参数的库;
- js-cookie:简单、轻量的处理 cookie 的库;
- flv.js:bilibili 开源的 HTML5 flash 播放器,使浏览器在不借助 flash 插件的情况下可以播放 flv;
- vConsole:一个轻量、可拓展、针对手机网页的前端开发者调试面板;
- animate.css:一个跨浏览器的 css3 动画库,内置了很多典型的 css3 动画,兼容性好,使用方便;
- lodash:一个一致性、模块化、高性能的 JavaScript 实用工具库;
📒 如何防止 CSS 样式污染
- 使用命名约定
- CSS Modules
- CSS in JS
其中命名约定最流行的方式是 BEM 101。它代表了 Block、Element、Modifier 方法。
[block]__[element]--[modifier]
/* Example */
.menu__link--blue {
  ...
}