常见手写题
一、JavaScript 基础
1. 手写 Object.create
思路:将传入的对象作为原型
function create(obj) {
function F() {}
F.prototype = obj
return new F()
}
2. 手写 instanceof 方法
instanceof 运算符用于判断构造函数的 prototype 属性是否出现在对象的原型链中的任何位置。
实现步骤:
- 首先获取类型的原型
- 然后获得对象的原型
- 然后一直循环判断对象的原型是否等于类型的原型,直到对象原型为 null,因为原型链最终为 null
具体实现:
function myInstanceof(left, right) {
let proto = Object.getPrototypeOf(left), // 获取对象的原型
prototype = right.prototype; // 获取构造函数的 prototype 对象
// 判断构造函数的 prototype 对象是否在对象的原型链上
while (true) {
if (!proto) return false;
if (proto === prototype) return true;
proto = Object.getPrototypeOf(proto);
}
}
3. 手写 new 操作符
在调用 new 的过程中会发生以上四件事情:
(1)首先创建了一个新的空对象
(2)设置原型,将对象的原型设置为函数的 prototype 对象。
(3)让函数的 this 指向这个对象,执行构造函数的代码(为这个新对象添加属性)
(4)判断函数的返回值类型,如果是值类型,返回创建的对象。如果是引用类型,就返回这个引用类型的对象。
function myNew(constructor, ...args) {
if (typeof constructor !== "function") return
let obj = {}
obj.prototype = Object.create(constructor.prototype)
const res = constructor.apply(obj, args)
if (res && (typeof res !== "object" || typeof res === "function")) return res
return obj
}
function Fn(obj) {
this.obj =obj
}
let obj =myNew(Fn,'222')
console.log(obj);
4. 手写 Promise
class MyPromise {
constructor(callback) {
this.status = "pending"
this.value = undefined
this.reason = undefined
this.onResolvedCallback = []
this.onRejectedCallback = []
const resolve = value => {
if (value instanceof MyPromise) {
return value.then(resolve, reject)
}
if (this.status === "pending") {
this.status = "resolved"
this.value = value
this.onResolvedCallback.forEach(fn => fn())
}
}
const reject = reason => {
if (this.status === "pending") {
this.status = "rejected"
this.reason = reason
this.onRejectedCallback.forEach(fn => fn())
}
}
try {
callback(resolve, reject)
} catch (error) {
reject(error)
}
}
then(onResolved, onRejected) {
onResolved = typeof onResolved === "function" ? onResolved : value => value
onRejected =
typeof onRejected === "function"
? onRejected
: reason => {
throw reason
}
const promise2 = new MyPromise((resolve, reject) => {
if (this.status === "resolved") {
try {
const x = onResolved(this.value)
resolve(x)
} catch (error) {
reject(error)
}
} else if (this.status === "rejected") {
try {
const x = onRejected(this.reason)
resolve(x)
} catch (error) {
reject(error)
}
}
if (this.status === "pending") {
this.onResolvedCallback.push(() => {
try {
const x = onResolved(this.value)
resolve(x)
} catch (error) {
reject(error)
}
})
this.onRejectedCallback.push(() => {
try {
const x = onRejected(this.reason)
resolve(x)
} catch (error) {
reject(error)
}
})
} else {
this.onResolvedCallback = []
this.onRejectedCallback = []
}
})
return promise2
}
catch(callback) {
return this.then(null, callback)
}
resolve(value) {
return new MyPromise((resolve, reject) => {
resolve(value)
})
}
reject(reason) {
return new MyPromise((resolve, reject) => {
reject(reason)
})
}
race(promises) {
return new MyPromise((resolve, reject) => {
promises.forEach(promise => {
MyPromise.resolve(promise)
.then(res => {
resolve(res)
})
.catch(rej => {
reject(rej)
})
})
})
}
allsettled(promises) {
if (!Array.isArray(promises)) {
throw new TypeError(`argument must be a array`)
}
return new MyPromise((resolve, reject) => {
const result = []
promises.forEach((promise, index) => {
Promise.resolve(promise)
.then(
res => {
result[index] = {
status: "resolved",
value: res,
}
},
rej => {
result[index] = {
status: "rejected",
reason: rej,
}
}
)
.finally(() => {
if (result.length === promises.length) {
resolve(result)
}
})
})
})
}
all(promises) {
if (!Array.isArray(promises)) {
throw new TypeError(`argument must be a array`)
}
return new MyPromise((resolve, reject) => {
const result = []
promises.forEach((promise, index) => {
Promise.resolve(promise)
.then(res => {
result[index] = res
})
.catch(rej => {
reject(rej)
})
.finally(() => {
if (result.length === promises.length) {
resolve(result)
}
})
})
})
}
finally(callback) {
return this.then(
value => {
return MyPromise.resolve(callback()).then(() => value)
},
reason => {
return MyPromise.resolve(callback()).then(() => {
throw reason
})
}
)
}
}
5. 手写防抖函数
函数防抖是指在事件被触发 n 秒后再执行回调,如果在这 n 秒内事件又被触发,则重新计时。这可以使用在一些点击请求的事件上,避免因为用户的多次点击向后端发送多次请求。
// 函数防抖的实现
function debounce(fn, wait) {
let timer = null
return function (...args) {
let ctx = this
if (timer) {
clearTimeout(timer)
timer = null
}
timer = setTimeout(() => {
fn.apply(ctx, args)
}, wait)
}
}
clearTimeout()清除了定时器之后timer会变成一个类型为Number的数字,所以timer为true,下次就不生效了。所以要设置为null
6. 手写节流函数
函数节流是指规定一个单位时间,在这个单位时间内,只能有一次触发事件的回调函数执行,如果在同一个单位时间内某事件被触发多次,只有一次能生效。节流可以使用在 scroll 函数的事件监听上,通过事件节流来降低事件调用的频率。
function throttle(fn, wait) {
let timer = null
return function (...args) {
const ctx = this
if (!timer) {
timer = setTimeout(() => {
fn.apply(ctx, args)
timer = null
}, wait)
}
}
}
function throttle(fn, wait) {
let curTime = Date.now()
return function (...args) {
let ctx = this
let nowTime = Date.now()
// 如果两次时间间隔超过了指定时间,则执行函数。
if (nowTime - curTime >= wait) {
curTime = Date.now()
return fn.apply(ctx, args)
}
}
}
7. 手写类型判断函数
function getType(value) {
// 判断数据是 null 的情况
if (value === null) {
return value + "";
}
// 判断数据是引用类型的情况
if (typeof value === "object") {
let valueClass = Object.prototype.toString.call(value),
type = valueClass.split(" ")[1].split("");
type.pop();
return type.join("").toLowerCase();
} else {
// 判断数据是基本数据类型的情况和函数的情况
return typeof value;
}
}
8. 手写 call 函数
call 函数的实现步骤:
- 判断调用对象是否为函数,即使我们是定义在函数的原型上的,但是可能出现使用 call 等方式调用的情况。
- 判断传入上下文对象是否存在,如果不存在,则设置为 window 。
- 处理传入的参数,截取第一个参数后的所有参数。
- 将函数作为上下文对象的一个属性。
- 使用上下文对象来调用这个方法,并保存返回结果。
- 删除刚才新增的属性。
- 返回结果。
Function.prototype.myCall = function (ctx, ...args) {
if (typeof this !== "function") return
ctx = ctx || window
const fn = Symbol()
ctx[fn] = this
const result = ctx[fn](...args)
delete ctx[fn]
return result
}