各类手写问题汇总

Youky ... 2021-10-11 前端
  • JS
About 4 min

# 更改函数 this 指向的三种方法

# call

  1. 函数首先接收上下文参数 context,若没有则默认为 window

  2. 在 context 上绑定要调用的函数 func(也就是 this 的指向)

  3. 截取 arguments 得到调用函数时要传入的参数

  4. 调用后,删除 context 的 func 属性

    Function.prototype.myCall = function (context, ...args) {
      // 调用者不是函数,报错
      if (typeof this !== "function") {
        throw new TypeError("调用者不是函数")
      }
    
      // 若没有传入context,默认值为widow
      context = context || window
    
      // 在context上下文中调用函数
      context.func = this
      let res = context.func(...args)
    
      // 调用后删除func属性
      delete context.func
    
      // 返回调用结果
      return res
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19

# apply

Function.prototype.myApply = function (context, args) {
  // 调用者必须是函数
  if (typeof this !== "function") {
    throw new TypeError("调用者不是函数")
  }
  // apply方法的第二个参数如果存在,则必须是数组
  if (args && !Array.isArray(args)) {
    throw new TypeError("第二个参数必须是数组")
  }
  context = context || window
  context.fn = this
  const res = args ? context.fn(...args) : context.fn()
  delete context.fn
  return res
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# bind

Function.prototype.myBind = function (context, ...args) {
  if (typeof this !== "function") {
    throw new TypeError("调用者不是函数")
  }
  const self = this
  return function f() {
    // 说明f是在当做构造函数调用
    // 参考new一个对象的过程,用new调用时这里的this的__proto__指向f
    if (this instanceof f) {
      return new self(...args, ...arguments)
    }
    // 当做普通函数调用
    else {
      return self.apply(context, [...args, ...arguments])
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 深拷贝函数

# 最简单的实现

JSON.parse(JSON.stringify())
1

问题:

  • 不能拷贝函数
  • 不能解决循环引用问题
  • 无法拷贝一些特殊对象,如 Map、Set 等

# 完整实现

/**
 * 深拷贝函数
 * @param {any} data 要克隆的变量
 * @param {WeakMap} map 记录已克隆的变量
 * @returns
 */
function clone(data, map = new WeakMap()) {
  // 非引用类型变量(或函数),直接返回
  if (typeof data !== "object" || data == null) return data

  // map中已存在的变量,直接返回
  if (map.has(data)) {
    return map.get(data)
  }

  // 判断数据的真实类型
  const type = Object.prototype.toString.call(data).slice(8, -1)

  // 针对不同类型递归处理
  if (type == "Date") return new Date(data)
  else if (type == "RegExp") return new RegExp(data)
  else if (type == "Object" || type == "Array") {
    // 如果是对象或数组
    const res = type == "Array" ? [] : {}
    map.set(data, res)
    for (let key in data) {
      res[key] = clone(data[key], map)
    }
    return res
  } else if (type == "Map") {
    // 如果是Map
    const res = new Map()
    map.set(data, res)
    for (let key of data.keys()) {
      res.set(key, clone(data.get(key), map))
    }
    return res
  } else if (type == "Set") {
    // 如果是Set
    const res = new Set()
    map.set(data, res)
    for (let value of data) {
      res.add(clone(value, map))
    }
    return res
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

# 实现一个 new

function myNew(con, ...args) {
  // 如果new后面不是函数类型,则报错
  if (typeof con !== "function") {
    throw new TypeError(`${con} is not a constructor`)
  }
  // 创建对象,并调用构造函数
  const obj = Object.create(con.prototype)
  const res = con.apply(obj, args)
  // 若构造函数返回了引用类型变量,则返回它;否则返回obj
  return res instanceof Object ? res : obj
}
1
2
3
4
5
6
7
8
9
10
11

# Promise

# 实现一个 Promise

# 简易实现(不能链式调用)

class MyPromise {
  constructor(fn) {
    this.status = "pending"
    this.value = null
    this.reason = null
    this.onFulfiledList = []
    this.onRejectedList = []
    const resolve = (data) => {
      if (this.status == "pending") {
        this.status = "fulfilled"
        this.value = data
        setTimeout(() => {
          this.onFulfiledList.forEach((f) => f(data))
        })
      }
    }
    const rejected = (data) => {
      if (this.status == "pending") {
        this.status = "rejected"
        this.reason = data
        setTimeout(() => {
          this.onRejectedList.forEach((f) => f(data))
        })
      }
    }
    try {
      fn(resolve, rejected)
    } catch (e) {
      rejected(e)
    }
  }
  then(resolveFn, rejectedFn) {
    const realResolveFn =
      typeof resolveFn == "function"
        ? resolveFn
        : (data) => Promise.resolve(data)
    const realRejectedFn =
      typeof rejectedFn == "function"
        ? rejectedFn
        : (err) => Promise.reject(err)
    if (this.status == "pending") {
      this.onFulfiledList.push(realResolveFn)
      this.onRejectedList.push(realRejectedFn)
    } else if (this.status == "fulfilled") {
      realResolveFn(this.value)
    } else {
      realRejectedFn(this.reason)
    }
    return this
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51

# 实现链式调用

主要是修改了 then 方法

class MyPromise {
  constructor(fn) {
    this.status = "pending"
    this.value = null
    this.reason = null
    this.onFulfiledList = []
    this.onRejectedList = []
    const resolve = (data) => {
      if (this.status == "pending") {
        this.status = "fulfilled"
        this.value = data
        setTimeout(() => {
          this.onFulfiledList.forEach((f) => f(data))
        })
      }
    }
    const rejected = (data) => {
      if (this.status == "pending") {
        this.status = "rejected"
        this.reason = data
        setTimeout(() => {
          this.onRejectedList.forEach((f) => f(data))
        })
      }
    }
    try {
      fn(resolve, rejected)
    } catch (e) {
      rejected(e)
    }
  }

  then(resolveFn, rejectedFn) {
    const realResolveFn =
      typeof resolveFn == "function"
        ? resolveFn
        : (data) => Promise.resolve(data)
    const realRejectedFn =
      typeof rejectedFn == "function"
        ? rejectedFn
        : (err) => Promise.reject(err)
    // 为了实现链式调用,then方法应返回一个Promise
    // 无论当前Promise的状态,都直接向回调队列添加回调函数。
    // 因为回调的执行是异步事件,而回调的添加是同步执行的
    return new MyPromise((resolve, rejected) => {
      // 这里的this指向的是当前执行的Promise而不是新返回的Promise(所以这里是箭头函数)
      this.onFulfiledList.push(() => {
        try {
          // 前一个回调函数的返回结果
          const x = realResolveFn(this.value)

          // 如果在前一个回调函数中返回了Promise,
          // 则传给下一个回调的是这个Promise的value,而不是Promise本身
          x instanceof MyPromise ? x.then(resolve, rejected) : resolve(x)
        } catch (err) {
          rejected(err)
        }
      })
      this.onRejectedList.push(() => {
        try {
          const x = realRejectedFn(this.value)
          x instanceof MyPromise ? x.then(resolve, rejected) : resolve(x)
        } catch (e) {
          rejected(e)
        }
      })
    })
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69

# 实现 Promise.all

Promise.all = (iterable) => {
  return new Promise((resolve, reject) => {
    const arr = Array.from(iterable)
    if (arr.length == 0) {
      return reject("arguments must be an array")
    }
    let resolvedNum = 0
    const resolvedValues = []
    for (let i = 0; i < arr.length; i++) {
      Promise.resolve(arr[i])
        .then((value) => {
          resolvedNum++
          resolvedValues[i] = value
          if (resolvedNum == arr.length) {
            return resolve(resolvedValues)
          }
        })
        .catch((e) => {
          return reject(e)
        })
    }
  })
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 实现 async/await 函数

实现需求:

  • 按 generator 的写法书写异步代码,实现和 async/await 相同的效果
  • 接收 generator 函数为参数
function genToAsync(gen) {
  return function () {
    return new Promise((resolve, rejected) => {
      const ito = gen()
      function walk(arg) {
        try {
          const { value, done } = ito.next(arg)
          if (done) {
            // done为true时,value是函数的返回值,此时是否是Promise不影响
            resolve(value)
          } else {
            // 兼容yield后不是Promise的情况
            Promise.resolve(value)
              .then((res) => walk(res))
              .catch((err) => rejected(err))
          }
        } catch (e) {
          rejected(e)
        }
      }
      walk()
    })
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

使用测试:

// 模拟异步请求函数
function getData() {
  return new Promise((r) => {
    setTimeout(() => {
      r(`data`)
    }, 500)
  })
}

// 定义generator函数
function* testG() {
  // await被编译成了yield
  const data = yield getData()
  console.log("data: ", data)
  const data2 = yield getData()
  console.log("data2: ", data2)
  const data3 = yield 3
  console.log(`data3: ${data3}`)
  return "success"
}

// 测试
const asyncFunction = genToAsync(testG)
asyncFunction().then((res) => console.log(`finish, return: ${res}`))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Last update: May 17, 2023 13:04
Contributors: Youky , youky7