前言

最近裁员找工作,在此记录一下面试中常见的手写函数实现,每天闲时不定时更新。

防抖函数

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
/**
* 实现函数的防抖(目的是频繁触发中只执行最后一次)
* @param {*} callback 需要执行的函数
* @param {*} delay 检测防抖的间隔频率,默认500ms
* @param {*} immediate 是否是立即执行 true:第一次,false:最后一次,默认false
* @return {可被调用执行的函数}
*/
const debounce = (callback, delay = 500, immediate = false) => {
// 声明一个定时器容器
let timer = null
// isImmediateInvoke变量用来记录是否立即执行, 默认为false
let isImmediateInvoke = false
// 判断callback是否为一个函数
if (typeof callback !== 'function') {
throw new TypeError('callback is not a function')
}
const _debounce = (...args) => {
// 保存上下文的this指向
let context = this
// 判断定时器是否存在,存在则清空定时器重新开启
timer && clearTimeout(timer)
// 判断是否第一次触发
if (!isImmediateInvoke && immediate) {
// 改变callback函数的this指向
callback.apply(context, args)
// 将isImmediateInvoke设置为true,这样不会影响到后面函数的调用
isImmediateInvoke = true
} else {
timer = setTimeout(() => {
// 改变callback函数的this指向
callback.apply(context, args)
// 将isImmediateInvoke设置为false,这样下一次的第一次触发事件才能执行
isImmediateInvoke = false
}, delay)
}
}
// 取消防抖函数功能
_debounce.cache = () => {
clearTimeout(timer)
timer = null
}
return _debounce
}

测试:

1
2
3
4
5
6
<button id="btn">点击</button>

function fn() {
console.log('点击按钮')
}
document.querySelector('#btn').addEventListener('click', debounce(fn, 1000, true))

节流函数

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
/**
* 实现函数的节流(目的是频繁触发中只执行一次)
* @param {*} callback 需要执行的函数
* @param {*} delay 检测防抖的间隔频率,默认500ms
* @param {*} immediate 是否是立即执行 true:第一次,false:最后一次,默认false
* @return {可被调用执行的函数}
*/
const throttle = (callback, delay = 500, immediate = false) => {
// 记录上一次执行时间
let preTime = 0
// 判断callback是否为一个函数
if (typeof callback !== 'function') {
throw new TypeError('callback is not a function')
}
return (...args) => {
// 保存上下文的this指向
let context = this,
nowTime = +new Date(), // 记录当前的时间
flag = nowTime - preTime >= delay // 执行命令
// 如果flag为true,则不中断函数
if (!flag) return
preTime = nowTime // 更新执行时间
// 判断是否第一次触发
immediate ? callback.apply(context, args) : setTimeout(() => {
// 改变callback函数的this指向
callback.apply(context, args)
}, delay)
}
}

测试:

1
2
3
4
5
6
<button id="btn">点击</button>

function fn() {
console.log('点击按钮')
}
document.querySelector('#btn').addEventListener('click', throttle(fn, 1000, true))

函数柯里化

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
/**
* 实现函数的柯里化(把一个多参数的函数,转化为单参数函数)
* @param {*} callback 需要执行的函数
* @return {可被调用执行的函数}
*/

// ES6箭头函数写法,简洁但可读性差

/*
调用curry函数,获取函数fn的参数。
定义一个新的函数judge,接收参数为...args。
判断新函数接受的参数长度是否大于等于fn剩余参数需要接收的长度
满足要求,则执行fn函数,并传入新函数的参数
否则,返回一个新的匿名函数,这个函数把所有传入参数...args保存在arg数组中,而这个匿名函数被执行后,就把以前收到的参数数组和当前的参数数组合并后,放到前面说的逻辑中,在judge函数里判断,重复第3步。
*/
const curry = callback => judge = (...args) => args.length >= callback.length ? callback(...args) : (...args2) => judge(...args, ...args2)

// 常规写法,可读性好
const curry = callback => {
// 判断callback是否为一个函数
if (typeof callback !== 'function') {
throw new TypeError('callback is not a function')
}
return judge = (...args) => {
if (args.length >= callback.length) {
return callback(...args)
} else {
return (...args2) => {
return judge(...args, ...args2)
}
}
}
}

测试:

1
2
3
4
5
6
7
8
function add(a, b, c) {
return a + b + c
}
let addCurry = curry(add)
console.log(addCurry(1, 2, 3)) // 6
console.log(addCurry(1, 2)(3)) // 6
console.log(addCurry(1)(2)(3)) // 6
console.log(addCurry(1)(2, 3)) // 6

深拷贝

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* 实现深克隆(针对JS的引用类型)
* @param {*} target 需要深克隆的数据
* @return 返回克隆的对象
*/

// 递归使用Reflect.ownKeys实现
const deepClone = target => {
// 判断target是否是引用类型
const isObject = target => typeof target === 'object' && target !== null
if (!isObject) {
throw new TypeError('Target is not a Object')
}
// 判断target是数组还是对象
const result = Array.isArray(target) ? [...target] : { ...target }
// 使用Reflect.ownKeys递归遍历
Reflect.ownKeys(result).forEach(key => {
// 判断key是否是引用类型,deepClone递归
result[key] = isObject(target[key]) ? deepClone(target[key]) : target[key]
})
return result
}

测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const data = {
name: 'Cyan',
age: 27,
desc: {
jobs: '前端开发'
}
}

const cloneData = deepClone(data)

data.name = '白雾茫茫丶'
data.desc.jobs = '后端开发'

console.log(data)
console.log(cloneData)

Instanceof 实现

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
/**
* 实现instanceof(判断左侧对象是否是右侧构造函数的实例化对象)
* @param {*} _leftObj 左侧实例对象,_rightClass 右侧构造函数
* @return Boolean
*/

const _instanceof = (_leftObj, _rightClass) => {
const isObject = target => (typeof target === 'object' || typeof target === 'function') && target !== null
// 判断参数类型,左侧必须为对象类型,右侧必须为函数类型
if (!isObject(_leftObj) || !isObject(_rightClass)) {
throw new TypeError('Incorrect parameter type')
}
// Object.getPrototypeOf获取实例对象的原型和右侧构造函数的prototype对象
const leftProto = Object.getPrototypeOf(_leftObj)
const rightProto = _rightClass.prototype
// 循环查找原型链
while (true) {
// 找到Object原型链的顶端 Object.prototype.__proto__
if (leftProto === null) {
return false
}
// 在当前实例对象的原型链上找到了实例类
if (leftProto === rightProto) {
return true
}
// 沿着原型链__ptoto__一层一层向上查找
leftProto = Object.getPrototypeOf(leftProto)
}
}

测试:

1
2
3
4
const dataA = [1, 2]
const dataO = { name: 'Cyan' }
console.log(_instanceof(dataA, Array))
console.log(_instanceof(dataO, Object))

Object.create 实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* 实现Object.create(Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__)
* @param {*} prototype 新创建对象的原型对象
* @return Object
*/

Object.myCreate = (prototype) => {
const isObject = target => typeof target === 'object' && target !== null
// 判断prototype是否是对象
if (!isObject(prototype)) {
throw new TypeError('prototype is not a Object')
}
// 内部声明一个构造函数
function Fn() { }
// 将构造函数的原型设置为prototype
Fn.prototype = prototype
// 给Fn.prototype添加构造属性,使其不可枚举
Object.defineProperty(Fn.prototype, 'constructor', {
value: Fn,
enumerable: false
})
return new Fn()
}

测试:

1
2
3
4
5
6
7
8
const Person = {
name: 'Cyan',
sayName: function () {
console.log(`我的名字是${this.name}`)
}
}
const child = Object.myCreate(Person)
child.sayName()

New 实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* 实现new
* @param {*} callback 要操作的类(最后要创建这个类的实例)
* @param {*} args 存储未来传递给callback类的实参
* @return Object
*/

const _new = (callback, ...args) => {
// // 创建一个空对象
// const obj = new Object()
// // 将obj的__proto__指向callback.prototype
// obj.__proto__ = callback.prototype
// IE环境不支持__proto__
let obj = Object.create(callback.prototype)
// 将callback执行并改变this指向
const result = callback.call(obj, ...args)
// 判断函数的返回值
return typeof result === 'object' && result !== null ? result : obj
}

测试:

1
2
3
4
5
6
function Person(name, age) {
this.name = name
this.age = age
}
const child = _new(Person, 'Cyan', 27)
console.log(child)

手写 Bind

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 实现bind
* @param {*} ctx 传递的对象
* @return Function
*/

Function.prototype.myBind = function (ctx) {
// 获取bind传递的剩余参数
let args = Array.prototype.slice.call(arguments, 1)
// fn.bind中的fn
const fn = this
return function () {
return fn.apply(ctx, args)
}
}

测试:

1
2
3
4
5
function fn(name, age) {
console.log(this, name, age)
}
const newFn = fn.myBind({}, 'Cyan', 27)
newFn()

手写 Apply

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* 实现apply
* @param {*} ctx 传递的对象
* @param {*} args[Array] 传递的参数
* @return Function
*/

Function.prototype.myApply = function (ctx, args) {
// 必须保证ctx是一个对象,如果是null或undefined,则指向全局,否则转化成对象
// 此处全局使用globalThis,是因为JS的运行环境有浏览器和node
ctx = (ctx === null || ctx === undefined) ? globalThis : Object(ctx)
// 使用Symbol,避免和ctx重名
let key = Symbol('fn')
// Object.defineProperty设置不可枚举
Object.defineProperty(ctx, key, {
enumerable: false,
value: this
})
// 执行函数,并将执行的结果返回
return ctx[key](...args)
}

测试:

1
2
3
4
function fn(name, age) {
console.log(this, name, age)
}
fn.myApply({}, ['Cyan', 27])

手写 Call

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* 实现call
* @param {*} ctx 传递的对象
* @param {*} args 传递的参数
* @return Function
*/

Function.prototype.myCall = function (ctx, ...args) {
// 必须保证ctx是一个对象,如果是null或undefined,则指向全局,否则转化成对象
// 此处全局使用globalThis,是因为JS的运行环境有浏览器和node
ctx = (ctx === null || ctx === undefined) ? globalThis : Object(ctx)
// 使用Symbol,避免和ctx重名
let key = Symbol('fn')
// Object.defineProperty设置不可枚举
Object.defineProperty(ctx, key, {
enumerable: false,
value: this
})
// 执行函数,并将执行的结果返回
return ctx[key](...args)
}

测试:

1
2
3
4
function fn(name, age) {
console.log(this, name, age)
}
fn.myCall({}, 'Cyan', 27)

基于 Promise 封装 Ajax

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
/**
* 基于promise封装ajax
* @param {*} url 请求地址
* @param {*} methods 请求方法
* @return promise
*/

function ajax(url, methods = 'get') {
// 返回promise实例对象
return new Promise((resovle, reject) => {
// 创建XMLHttpRequest异步对象
const xhr = new XMLHttpRequest()
// 调用open方法,打开url,与服务器建连接
xhr.open(url, methods, true)
// 监听状态
xhr.onreadystatechange = function () {
// 表示服务器响应完成,可以获取服务器响应
if (xhr.readyState === 4) {
// 获取成功
if (xhr.status === 200) {
resovle(xhr.responseText)
// 404
} else if (xhr.status === 404) {
reject(new Error('404'))
}
} else {
reject('请求数据失败')
}
}
})
}

手写 Map

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* 实现map方法
* @param {*} callback[Function] 执行函数
* @param {*} thisValue 执行 callback 函数时被用作this的值
* @return Array
*/
Array.prototype.myMap = function (callback, thisValue) {
// 判断参数类型
if (typeof callback !== 'function') {
throw new TypeError('callback is not a function')
}
// 定义一个空数组返回数据
const result = []
// 获取该方法的数组本身
const context = this
for (let i = 0; i < context.length; i++) {
// 循环遍历,执行函数,将结果保存到result中
result[i] = callback.call(thisValue, context[i], i, context)
}
return result
}

测试:

1
2
3
const arr = [1, 2, 3]
const res = arr.myMap(item => item * 2)
console.log(res)

手写 forEach

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* 实现forEach方法
* @param {*} callback[Function] 执行函数
* @param {*} thisValue 执行 callback 函数时被用作this的值
* @return Array
*/
Array.prototype.myForEach = function (callback, thisValue) {
// 判断参数类型
if (typeof callback !== 'function') {
throw new TypeError('callback is not a function')
}
// 获取该方法的数组本身
const context = this
for (let i = 0; i < context.length; i++) {
// 循环遍历,执行函数
callback.call(thisValue, context[i], i, context)
}
}

测试:

1
2
3
4
5
const arr = [1, 2, 3]
arr.myForEach((item, index) => {
arr[index] = item * 2
})
console.log(arr)

手写 Filter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* 实现filter方法
* @param {*} callback[Function] 执行函数
* @param {*} thisValue 执行 callback 函数时被用作this的值
* @return Array
*/
Array.prototype.myFilter = function (callback, thisValue) {
// 判断参数类型
if (typeof callback !== 'function') {
throw new TypeError('callback is not a function')
}
// 获取该方法的数组本身
const context = this
// 定义一个空数组
const result = []
for (let i = 0; i < context.length; i++) {
// 循环遍历,执行函数
callback.call(thisValue, context[i], i, context) && result.push(context[i])
}
return result
}

测试:

1
2
3
4
5
const arr = [1, 2, 3]
const arr2 = arr.myFilter(item => {
return item >= 2
})
console.log(arr2)

手写 Some

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* 实现some方法
* @param {*} callback[Function] 执行函数
* @param {*} thisValue 执行 callback 函数时被用作this的值
* @return Array
*/
Array.prototype.mySome = function (callback, thisValue) {
// 判断参数类型
if (typeof callback !== 'function') {
throw new TypeError('callback is not a function')
}
// 获取该方法的数组本身
const context = this
// 定义一个false值
let result = false
for (let i = 0; i < context.length; i++) {
// 循环遍历,执行函数,如果条件符合,则赋值true
callback.call(thisValue, context[i], i, context) && (result = true)
}
return result
}

测试:

1
2
3
4
5
const arr = [1, 2, 3]
const arr2 = arr.mySome(item => {
return item > 2
})
console.log(arr2)

手写 Every

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* 实现every方法
* @param {*} callback[Function] 执行函数
* @param {*} thisValue 执行 callback 函数时被用作this的值
* @return Array
*/
Array.prototype.myEvery = function (callback, thisValue) {
// 判断参数类型
if (typeof callback !== 'function') {
throw new TypeError('callback is not a function')
}
// 获取该方法的数组本身
const context = this
// 定义一个true值
let result = true
for (let i = 0; i < context.length; i++) {
// 循环遍历,执行函数,如果有一项不符合,则赋值为false
!callback.call(thisValue, context[i], i, context) && (result = false)
}
return result
}

测试:

1
2
3
4
5
const arr = [1, 2, 3]
const arr2 = arr.myEvery(item => {
return item > 2
})
console.log(arr2)

手写 Reduce

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* 实现reduce方法
* @param {*} callback[Function] 执行函数
* @param {*} initValue 执行 callback 函数时被用作第一个参数的值
* @return
*/
Array.prototype.myReduce = function (callback, initValue) {
// 判断参数类型
if (typeof callback !== 'function') {
throw new TypeError('callback is not a function')
}
// 获取该方法的数组本身
const context = this
// 判断initValue是否有传值,true代表没传值
const flag = initValue === undefined
// 定义一个变量,如果initValue有值就取,没有就取context第一项
let result = flag ? context[0] : initValue
// 如果initValue有值就从0开始,否则从1开始
for (let i = (flag ? 1 : 0); i < context.length; i++) {
// 循环遍历,执行函数,把结果赋值给上一个值
result = callback(result, context[i], i, context)
}
return result
}

测试:

1
2
3
4
5
const arr = [1, 2, 3, 4]
const arr2 = arr.myReduce((perv, current) => {
return perv + current
}, 5)
console.log(arr2)

手写 Promise

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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
/**
* 实现promise
* @param {*} handler[Function] 执行函数,接收两个参数resolve和reject,分别为成功和失败时的回调
* @return promise
*/
const PENDING = 'pending' // 初始化状态
const FULFILLED = 'fulfilled' // 成功状态
const REJECTED = 'rejected' //失败状态
class MyPromise {
constructor(handler) {
// 初始化状态
this.initValue()
// 接收的参数必须是一个函数
if (typeof handler !== 'function') {
throw new TypeError('handler is not a function')
}
// 这一步是当promise抛出异常的时候
try {
// 给handler传入两个回调函数并改变this指向
handler(this.resolve.bind(this), this.reject.bind(this))
} catch (e) {
this.reject(e)
}
}
// 初始化 Promise 基本状态
initValue() {
// 刚创建的promise状态是pending
this.PromiseState = PENDING
// promise的结果
this.PromiseResult = null
// 保存成功回调
this.onFulfilledCallbacks = []
// 保存失败时回调
this.onRejectedCallbacks = []
}
// 成功时的回调
resolve(value) {
// 状态不可改变
if (this.PromiseState !== PENDING) return
// 成功时的状态
this.PromiseState = FULFILLED
// 成功时的结果
this.PromiseResult = value
// 执行保存成功回调
while (this.onFulfilledCallbacks.length) {
this.onFulfilledCallbacks.shift()(this.PromiseResult)
}
}
// 失败时的回调
reject(reason) {
// 状态不可改变
if (this.PromiseState !== PENDING) return
// 失败时的状态
this.PromiseState = REJECTED
// 失败时的结果
this.PromiseResult = reason
// 执行保存失败回调
while (this.onRejectedCallbacks.length) {
this.onRejectedCallbacks.shift()(this.PromiseResult)
}
}
/*
1、then接收两个参数,一个是成功回调,一个是失败回调
2、当Promise状态为fulfilled为成功回调,状态为rejected为失败回调
3、then支持链式调用,下一次then的值受上一次then的返回值影响
4、当有定时器时,需等定时器结束再执行then
*/
then(onFulfilled, onRejected) {
// 参数校验,两个回调必须为function
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : promiseResult => promiseResult
onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw (reason) }

// then支持链式调用,模拟返回一个Promise
const thenPromise = new MyPromise((resolve, reject) => {
// 使用queueMicrotask执行微任务,让then方法变成微任务
queueMicrotask(() => {
// 定义一个promise函数,用来处理不同状态下的返回值
const resolvePromise = cb => {
try {
// 拿到上一个promise回调执行的结果
const result = cb(this.PromiseResult)
// 判断返回结果是否是一个Promise对象
if (result instanceof MyPromise) {
// 如果返回值是Promise,只有then知道是成功还是失败
result.then(resolve, reject)
} else {
// 如果不是Promise就直接resolve
resolve(result)
}
} catch (error) {
// 处理报错
reject(err)
throw new Error(error)
}
}
// 判断 Promise 状态,执行不同的操作
switch (this.PromiseState) {
// 当状态为fulfilled时为成功时的回调
case FULFILLED:
// 执行成功函数
resolvePromise(onFulfilled)
break;
// 当状态为rejected时为失败时的回调
case REJECTED:
// 执行失败函数
resolvePromise(onRejected)
break;
// 当状态还未改变时,也就是pending时
case PENDING:
// 如果状态待定,暂时保存两个回调
this.onFulfilledCallbacks.push(resolvePromise.bind(this, onFulfilled))
this.onRejectedCallbacks.push(resolvePromise.bind(this, onRejected))
break;
}
})
})
return thenPromise
}
// catch方法其实就是then方法失败回调函数的语法糖
catch(onRejected) {
return this.then(undefined, onRejected)
}
// finally方法,不管Promise状态是失败还是成功都会执行
finally(callback) {
queueMicrotask(() => {
callback.call(this)
})
}
}

测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const p = new MyPromise((resolve, reject) => {
console.log('MyPromise立即执行')
resolve('100')
})
p.then(res => res, reason => {
console.log('then是微任务')
}).then(res => {
console.log(res * 2)
return res * 2
}).then(res => {
console.log(res * 2)
})
p.catch(err => {
console.log(err)
})
p.finally((a, b) => {
console.log('不管什么状态都会执行')
})
// 同步任务
console.log(3)