libraries

抖音前端UI框架: https://github.com/DouyinFE/semi-design/
jsx的另一种选择: https://markojs.com/

aplayer
image-lazy-load

layout

src
- index.ts
- global.d.ts   # 自己定义的各种声明应该放到独立的.d.ts(不会被编译为代码,仅存在于编译期)
- shims-vue.ts  # 让ts正确理解vue文件; 删除的话,import-vue文件会报错
types
- foo
	- index.d.ts
tsconfig.json

event loop

同步任务由js主线程依次执行
异步任务会被委托给宿主环境(node/explorer)去执行

  • setTimeout: 宏任务,会被放到任务队列等待执行(会等待微任务执行完毕)
  • new Promise(): 同步任务,与其他标准语句一样
  • then: 这里面属于微任务

basic

  • undefined/null
    所有类型的子类型
  • lib.d.ts
    安装TypeScript时会自动附带一个lib.d.ts;
    可通过如下禁用: tsconfig.json.noLib: true
    JS Runtime + DOM Environments: window/Window, document/Document, math/Math
  • demos ➡️
export {b, s as ss};  // 一般会在文件最后一次性导出(并做重命名处理)
export * from './foo';  // 从其他模块导入后整体导出(也可像上面那样,部分导出)
export const foo = 1;  // foo.ts
import {foo, xx as xx} from './foo';  // 导入其他模块export的变量; 若foo/则查找路径:./node_modules/foo; ../node_modules/foo; ...直到根目录
import * as $ from 'jquery';  // 实际上可以from一个css文件
import Ax from './axios';

const DAYS_IN_WEEK = 7;
let y;  // default is any type; let声明的变量与其他语言一样:块级作用域(var则是违反尝试,外层也可访问)
let y: any = 'x';  // 顶级类型,可以假定它是任何类型(包括函数). 不安全,故不推荐使用,多数场合使用undefined替代都是合适的!
(y as string).length;  // 类型断言
// 解决any的不安全问题,可以被赋值为任意类型! 但unknown变量仅可被赋值给'any/unknown'类型变量!
// unknown类型对象不许有各种奇怪的操作, 必须先做类型收窄: typeof/instanceof/as
// unknown类型仅能使用4个运算符: == === != !==
let x: unknown = y;
let h: number = 0xf00d;  // 0b1010 0o744 NaN Infinity; 其实类型修饰可以省略,编译器可以类型推论!
let b: boolean = !!h;  // 两次逻辑非可以在第2次时将null/undefined转换为boolean!
let s: string = `${n + 1}`;  // let s = 'xx';
let a: number[] = [1, 2];  // 元素类型必须为number(除非使用any[]); <==> Array<number>
let t: [string, number] = [b.toString(), 1];  // tuple(注意:顺序相关的); t[0]=''; t=['',2];
let [a,b] = t;  // 数组(或叫tuple)的解构! 也可以用来swap俩数! 对象object也可结构(且可只指定部分成员)
let u: string | number;  // union,变量被赋值时会推断出其类型,否则仅能访问共有方法!
let u: "on" | "off" = "on";
const menuConfig = {  // 内联类型(匿名接口)
  title: 'defaultValue',  // newconfig中有的字段是可选的,没有指定.此处希望有一个默认值
  ...newConfig,  // 通常是传递进来的参数:要设置的最新配置(追加并替换上面的)
}

// a?表可选参数. b自动被识别为可选参数(但不用放在最后位置)! 剩余参数类型: any[]
async function say<U extends Leng, T=string, readonly d: Direction>(t: [T, U], a?: number, b: number = 2, ...items): [T, U] {
  if (a && (typeof (name as Fish).swim == 'function')) {
    items.forEach(function(item){
    });
  }
  const user = await getUser<User>();
  return '1' + name;

  // 注意: 声明的变量只能定义它的类型,不要去定义实现,包括 declare class/enum
  // 使用jQuery: 建议都置于jQuery.d.ts声明文件中,编译后自动被删除.
  // 也可以不用这么麻烦:直接安装对应的声明模块即可: npm install @types/jquery --save-dev
  declare const jQuery: (selector: string) => any;
  for (let i=0; i<8; i++) {
    setTimeout(function(){
      console.log(i);  // 若是js的var声明的i,则此处循环打印8!
    }, 100*i);
  }
 
  if ('attr' in obj && +s===0) { // 是否存在属性; +:把变量转变为number类型
    let x: typeof b;  // 'boolean'
  }
  Object.defineProperty(obj, 'prop1', { // add a property to a object
    value: 2
    enumerable: true,  // default can not be enumerated
    writable: true,  // default can not be wrote
    configurable: true,  // default can not be deleted
    get: (){ // called when read obj.prop1
      return hex  // offen as a variable so prop1 can be changed along with the variable!
    },
    set: (){ // called when setting the property: obj.prop1
      hex = 2
    },
    sum: sum,  // 注意:value是一个变量(而不是字符串)才可以简写: 'sum,'
  })
}

namespace Utility {
  export type EventNames = 'click' | 'scroll' | 'mousemove';  // literal type
  type OneToFile = 1|2|3|4|5;
  type Direction = 'North' | 'East' | 'South' | 'West';
  type Foo = { // 定义Foo为一个内敛类型(匿名接口)的别名
    kind: "foo";  // 声明的时候直接初始化,以便在复合类型时可以判别其类型: arg.kind=="foo"
    [key: string]: number;  // 自定义索引签名:"动态属性". 注意: 其他确定与可选属性的返回类型都必须是索引签名的子类!
    [key: number]: MyNumber;  // 会自动把number换成string,故此时他们同时存在时,其返回类型必须时上面的子类!
    readonly bar: number;  // 也可于class中指定为只读; Readonly<Foo>也可将所有属性都标记为只读
  }
  type FooKeyName = keyof typeof foo;  // 字符化每一个key,并将所有的key作为一个联合类型: 'kind' | 'bar'
  let name: FooKeyName;
  name = 'bar';
  name = 'xx';  // error
}

// 枚举与数字类型互相兼容
enum Days {
  Sun = 7, Mon = 1, Tue, Wed, Thu, Fri, Sat,  // Days['Tue'] = 2; 若Mon=1.2,则Tue=2.2,Wed=3.2...
  haha = 'ha'  // 可为字符串! 'ha' as Days
}
namespace Days { // 给enum添加静态方法
  function isBusinessDay(day: Days) {
    switch(day) {
      case Days.Saturday:
      case Days.Sunday:
        return false;
      default:
        return true;
    }
  }
}

Promise.resolve(123)
  .then((res) => {
    console.log(res);  // 123
    // 后续then会收到456(且会保持接收到的类型同返回类型:boolean),如果此处再throw异常,则后续then也不会被调用.直接进入.catch()
    return true;
  })
  .then((res) => {
    console.log(res);  // true
    return Promise.resolve(123);
  })
  .then((res) => {
    console.log(res);  // 123
    return 123;
  })
Promise.reject(new Error('xx'))
  .then((res) => {
    console.log(res);  // not called
    return 456;
  })
  .catch((err) => {
    console.log(err.message);  // xx
    return 123;  // 再向后传播链,注意catch捕获到上头的异常后就吞掉了,后续的catch不会再次捕获!
  })
  .then((res) => {
    console.log(res);  // 123
  })

Interface

  • 一种强制约束的类(也可创建实例): 所有成员必须强制初始化.
  • 优点: 可以再被声明一遍以便添加扩展字段. 当作函数参数时可以避免参数名写错的漏洞!
  • 注意: 包括class,只要里面的结构一样,名称无关紧要—视为同一种类型
interface Rsp<T = any> extends X,Y { // foo.ts
  readonly code: number;  // 只能在对象第1次创建时赋值! 属性使用时一般使用readonly,变量一般用const!
  readonly message: string;
  result: T;
  age?: number;  // 可选属性; 否则:tom初始化时属性还必须全,否则报错;多了也报错!
  [propName: string]: any;  // 任意属性. 此时,确定属性与可选属性的类型必须是它的类型的子集. 任意属性只能有一个!也可是联合类型!
  (p1: T): boolean;
}
function getUser<T>() {
  return Ax.get<Rsp<T>>('/path')
    .then().catch();  // 若catch放在then前头则then肯定会被执行!
}
declare let person: Rsp; // 其他文件可以扩展person的属性

interface String { // 一般放到一个global.d.ts里面
  endsWith(suffix: string): boolean;
}
String.prototype.endsWith = function(suffix: string): boolean {
  const str: string = this;
  return str && str.indexOf(suffix, str.length - suffix.length) != -1;
}

// 注意: 定义多个同名的类或接口时会合并里面的成员(适用于接口)
// 注意: 只要里面的成员一样(构造函数与静态成员除外),名称就无关紧要,对象之间可以相互赋值!
// 注意: T未泛化前相当于any,可以互相赋值.
// 实际上也同时声明了一个同名的接口(以下函数自动排除:构造函数,static成员)
class Cat<T> extends Animal implement XX,FF {
  private readonly name = 'lei';  // 实例属性. 子类中也无法访问; readonly也可在构造函数种赋值
  get name() { // automatic readonly
  }
  set name(value) {
  }
  constructor(name) { // 若未private则不许被继承或实例化
    super(name);
  }
  abstract swim();
  static isOk() {} // 仅可使用类名调用
  static num = 2;
}

Utilities

common

// trueTypeOf(() => {});  // function
const trueTypeOf = (obj) => Object.prototype.toString.call(obj).slice(8, -1).toLowerCase();

// 检测对象是否为空
const isEmpty = obj => Reflect.ownKeys(obj).length === 0 && obj.constructor === Object;

// capitalize("hello world")  // Hello world
const capitalize = str => str.charAt(0).toUpperCase() + str.slice(1)

// reverse('hello world');   // 'dlrow olleh'
const reverse = str => str.split('').reverse().join('');

// randomString();
const randomString = () => Math.random().toString(36).slice(2);

const stripHtml = html => (new DOMParser().parseFromString(html, 'text/html')).body.textContent || '';

// isNotEmpty([1, 2, 3]);  // true
const isNotEmpty = arr => Array.isArray(arr) && arr.length > 0;
// const merge = (a, b) => [...a, ...b];
const merge = (a, b) => a.concat(b);

// average(1, 2, 3, 4, 5);   // 3
const average = (...args) => args.reduce((a, b) => a + b) / args.length;

// random(1, 50);
const random = (min, max) => Math.floor(Math.random() * (max - min + 1) + min);
const randomBoolean = () => Math.random() >= 0.5;

// round(1.555, 2) //1.56
const round = (n, d) => Number(Math.round(n + "e" + d) + "e-" + d)

// rgbToHex(255, 255, 255);  // '#ffffff'
const rgbToHex = (r, g, b) => "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);

// 获取一个随机的十六进制颜色
const randomHex = () => `#${Math.floor(Math.random() * 0xffffff).toString(16).padEnd(6, "0")}`;

// 将文本复制到剪贴板
const copyToClipboard = (text) => navigator.clipboard.writeText(text);

// 清除所有cookie
const clearCookies = document.cookie.split(';').forEach(cookie => document.cookie = cookie.replace(/^ +/, '').replace(/=.*/, `=;expires=${new Date(0).toUTCString()};path=/`));

// 获取选中的文本
const getSelectedText = () => window.getSelection().toString();

// 检测是否是黑暗模式
const isDarkMode = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches

// 滚动到页面顶部
const goToTop = () => window.scrollTo(0, 0);

// 判断页面是否已经底部
const scrolledToBottom = () => document.documentElement.clientHeight + window.scrollY >= document.documentElement.scrollHeight;

// 判断当前标签页是否激活
const isTabInView = () => !document.hidden;

const isAppleDevice = () => /Mac|iPod|iPhone|iPad/.test(navigator.platform);

datetime

// isDateValid("December 17, 1995 03:24:00");  // true
const isDateValid = (...val) => !Number.isNaN(new Date(...val).valueOf());

// dayDif(new Date("2021-11-3"), new Date("2022-2-1"))  // 90
const dayDif = (date1, date2) => Math.ceil(Math.abs(date1.getTime() - date2.getTime()) / 86400000)

// dayOfYear(new Date());   // 307
const dayOfYear = (date) => Math.floor((date - new Date(date.getFullYear(), 0, 0)) / 1000 / 60 / 60 / 24);

// timeFromDate(new Date(2021, 11, 2, 12, 30, 0));  // 12:30:00
const timeFromDate = date => date.toTimeString().slice(0, 8);

misc

// 华氏度和摄氏度之间的转换
const celsiusToFahrenheit = (celsius) => celsius * 9/5 + 32;
const fahrenheitToCelsius = (fahrenheit) => (fahrenheit - 32) * 5/9;