深入函数

96 阅读5分钟

1. 声明、参数、返回值

1.1 声明

1.1.1 深入对象语法

  • 对象语法
type Name = {
  name: string
}

// 等价于省略一万行
// 以下这个属于上面的子集,限制越少,集合越大
type NameChild = { name: string; age: number } | { name: string; age: string }

接口实现
interface Name {
  name: string
}
  • 索引签名
// 索引签名 Index Signature
type Hash = {
  [k: string]: unknown
  length: number
}
type List = {
  [k: number]: unknown
  length: number
}
  • 映射类型
// 映射类型 Mapped Type
type Hash = {
  [k in string]: unknown
  // 映射类型与索引签名的区别在于
  // 1. 映射类型使用in关键字后不能在其他声明方式
  // 2. 映射类型多用于泛型

  // 下面这行代码报错
  // length: number
}
type List = {
  [k in number]: unknown
}
  • ?表示可选、readonly表示只读
// 问号表示不选
interface InputProps {
  defaultValue?: string
  value?: string
  // value 虽然在类型声明上等价于 value2
  // 但是value2是必须要传的,而?可传可不传
  // value2: string | undefined
  onChange?: () => void
}
// 当什么都不写的时候也不会报错
const props: InputProps = {}

interface User {
  readonly id: number
  readonly name: string
  readonly scores: number[]
  age?: number
}
const u: User = {
  id: 1,
  name: 'lwz',
  scores: [99, 98],
}
// 妄图直接修改值,会直接报错
// u.id = 2
// u.scores = [100, 100]
// 但是修改属性内部的索引值对应的值不会报错
u.scores[0] = 100
u.scores[1] = 100
console.log(u)
  • 总结
/**
 * 描述对象
 * 1. type or interface
 * 2. 索引签名 和 映射类型
 * 3. 问号表示可选
 * 4. readonly表示只读
 */

1.1.2 深入函数

// F 和 F1 的区别在于 前者可以另外添加属性
/**
 * 对象的语法全部适用于函数
 * 1. type or interface
 * 2. 问号表示可选
 * 3. readonly表示只读
 * 4. 索引签名和映射类型
 */

type F = {
  (a: number, b: number): number
  readonly count?: number
}
type F1 = (a: number, b: number) => number

const f: F = (x, y) => {
  return x + y
}
// f.count = 1

1.1.3 声明函数的4种方法

function f1(a: number): number {
  return a + 1
}

type F2 = (x: number) => number
// ts 不支持匿名函数
const f2: F2 = function (a) {
  return a + 1
}

// ts 也不支持箭头函数,所以需要将其赋值
type F3 = (x: number) => number
const f3: F3 = (a) => {
  return a + 1
}

// 以上几种都属于先写类型在赋值
// 还有几种是先实现函数,再获取类型(普通函数/箭头函数/匿名函数)
const f4 = (a: number, b: number): number => {
  return a + b
}
type F4 = typeof f4

1.1.4 类型谓词

type Person = {
  name: string
}
type Animal = {}
function f1(a: Person | Animal) {
  if (isPerson(a)) {
    a
  }
}

function isPerson(x: Person | Animal): x is Person {
  return 'name' in x
}
// 当类型声明写在右边的时候不会报错
const isPerson2 = (x: Person | Animal): x is Person => 'name' in x
// 当类型声明写在左边的时候就会报错
// 所以一般情况下推荐使用普通函数方式声明
// type X = (x: Person | Animal) => x is Person
// const isPerson3: X = (a) => 'name' in a
// () => x is Person      () => boolean

1.1.5 参数的相关语法

// 可选参数的实现方式一: 默认参数
// function addEventListener(eventType: string, fn: (this: HTMLElement, e: Event) => void, useCapture = false) {
//   const element = {} as HTMLElement
//   const event = {} as Event
//   fn.call(element, event)
//   // 浏览器实现
//   console.log(eventType, fn, useCapture)
// }

// 遇到this将其消掉变成不需要使用this的方法
function addEventListener(eventType: string, fn: (e: Event, el: Element) => void, useCapture = false) {
  const element = {} as HTMLElement
  const event = {} as Event
  fn(event, element)
  // 浏览器实现
  console.log(eventType, fn, useCapture)
}

// 可选参数的实现方式二: 问号
// function addEventListener(eventType: string, fn: unknown, useCapture?: boolean) {
//   if (useCapture === undefined) {
//     useCapture = false
//   }
//   // 浏览器实现
//   console.log(eventType, fn, useCapture)
// }

// addEventListener 的 fn 参数传了一个箭头函数,此时是可以这样写的,但是
// 不能使用this,箭头函数没有this
// 写成箭头函数不会报错的原因是虽然我声明了this,但是我可以选择不用
addEventListener('click', () => {
  // console.log(this)
  return 1
})

1.1.6 函数的柯里化

const add = (a: number, b: number) => a + b

// const createAdd = (a: number) => {
//   return (b: number) => a + b
// }
// 等价于下面
const createAdd = (a: number) => (b: number) => a + b

// const f = createAdd(6)
// console.log(f(14))
// 一下这两种形式是等价的,只不过是书写习惯不同
add(6, 14)
createAdd(6)(14)

type CreateAdd = (x: number) => (y: number) => number
const createAdd1: CreateAdd = (a) => (b) => a + b

2. 函数重载、this和as count

2.1 函数重载

// 函数重载 over load
/**
 * 本质: 同名函数
 * 1. 参数类型不同 (一般使用类型联合,不使用重载)
 * 2. 参数个数不同
 * 3. 都不同
 * 对于重载能不用就不用
 */

function print(x: string | number | boolean) {}

function createDate(n: number): Date
function createDate(year: number, month: number, date: number): Date

function createDate(a: number, b?: number, c?: number): Date {
  // if (arguments.length === 3) {
  // 由于length是一个动态属性,在使用这个属性的时候并不能保证b,c一定有值
  //   return new Date(a, b, c)
  // } else if (arguments.length === 1) {
  //   return new Date(a)
  // }
  if (a !== undefined && b !== undefined && c !== undefined) {
    return new Date(a, b, c)
  } else if (a !== undefined && b === undefined && c === undefined) {
    return new Date(a)
  } else {
    // 防止用户使用打包后的js代码来调用createDate
    throw new Error('只接受一个或三个参数!')
  }
}

createDate(1000)
createDate(2011, 0, 1)
// createDate(2011, 0)

function createDateFromNumber(n: number): Date {
  return new Date(n)
}
function createDateFromYMD(year: number, month: number, date: number): Date {
  return new Date(year, month, date)
}
createDateFromNumber(1000)
createDateFromYMD(2011, 0, 1)

2.2 指定函数的this

type Person = {
  name: string
}

function f(this: Person, word: string) {
  console.log(this.name + ' ' + word)
}

// 拼凑 person.f()
// 让我想到了高中数学里面的使用中间变量
const p: Person & { f: typeof f } = { name: 'lwz', f: f }
p.f('hi')

// f.call(this, p1)
const p1: Person = { name: 'lwz' }
f.call(p1, 'hi')

// f.apply(this, [p1])
f.apply(p, ['hi'])

// f2 = f.bind(this, p1, p2, p3); f2()
const f2 = f.bind(p)
f2('hi')

// bind 相当于函数的柯里化
f.bind(p)('hi')

// const f3 = f.bind(p) // this => p
// const f4 = f3.bind(null, 'hi') // word => 'hi'
// f4()

// type Person = {
//   name: string
//   sayHi: (this: Person, word: string) => void
// }

// function f(this: Person, word: string) {
//   console.log(this.name + ' ' + word)
// }

// // 拼凑 person.f()
// const p: Person = { name: 'lwz', sayHi: f }
// p.sayHi('hi')

2.3 剩余参数

function sum(name: string, ...array: number[]) {
  // let result = 0
  // for (let i = 0; i < array.length; i++) {
  //   result += array[i]
  // }
  // return result
  return array.reduce((pre, current) => pre + current, 0)
}

console.log(sum('one', 1))
console.log(sum('two', 1, 2))
console.log(sum('ten', 1, 2, 3, 4, 5, 6, 7, 8, 9, 10))

2.4 展开参数

// 展开参数 ...
function sum(name: number, ...array: number[]) {
  // f(array)
  f.apply(null, array)
  f(...array)
}

function f(...array: number[]) {
  console.log(array)
}

2.5 as count

let a1 = 'b' as const
// const array = [1, 'hi']
const array = [1, 'hi'] as const

// 虽然array被声明为const,但是array.push()还可以用
// 所以此时array并不是严格的const, 需要使用到 as const
// array.push(2)
// [number|string][]
// 二元组 [number, string]

function sum(name: number, ...array: number[]) {
  // const a = [1, 2] // number[]
  const a = [1, 2] as const
  f(...a)
}

function f(a: number, b: number) {
  return a + b
}

2.6 函数的更多细节

type Config = {
  url: string
  method: 'GET' | 'POST' | 'PATCH' | 'DELETE'
  data?: unknown
  headers?: unknown
}

// 使用默认值
// function ajax({ url, method, ...rest }: Config = { url: '', method: 'GET' }) {
//   console.log(url, method)
// }
// 类型写到后面需要用到断言as
function ajax({ url, method, ...rest } = { url: '', method: 'GET' } as Config) {
  console.log(url, method)
}

function f1(): void {
  return
}
function f2(): void {
  return undefined
}
function f3(): void {}
function f4(): void {
  // return null
}

type Fn = () => void
const f: Fn = () => {
  return undefined // null
}