防抖
- 浏览器的 resize、scroll、keypress、mousemove 等事件在触发时,会不断地调用绑定在事件上的回调函数,极大地浪费资源,降低前端性能。为了优化体验,需要对这类事件进行调用次数的限制
- 作用是在短时间内多次触发同一个函数,只执行最后一次,或者只在开始时执行
- 以用户拖拽改变窗口大小,触发 resize 事件为例,在这过程中窗口的大小一直在改变,所以如果我们在 resize 事件中绑定函数,这个函数将会一直触发,而这种情况大多数情况下是无意义的,还会造成资源的大量浪费。
ts
/**
* @description 函数防抖
* @param {Function} method 延时调用函数
* @param {Number} wait 延迟时长
* @param {Boolean} immediate 立即执行选项
*/
export const Ddebounce = <T extends (...args: any[]) => any>(
method: T,
wait: number,
immediate: boolean
) => {
if (typeof method !== 'function') {
throw new TypeError('Expected a function')
}
let timeout: ReturnType<typeof setTimeout> | null = null
// Ddebounce函数为返回值
// 使用Async/Await处理异步,如果函数异步执行,等待setTimeout执行完,拿到原函数返回值后将其返回
// args为返回函数调用时传入的参数,传给method
let Ddebounce = async (...args: Parameters<T>): Promise<ReturnType<T>> => {
return new Promise((resolve) => {
// 用于记录原函数执行结果
let result: ReturnType<T>
// 将method执行时this的指向设为 debounce 返回的函数被调用时的this指向
let context = this
// 如果存在定时器则将其清除
if (timeout) {
clearTimeout(timeout)
}
// 立即执行需要两个条件,一是immediate为true,二是timeout未被赋值或被置为null
if (immediate) {
// 如果定时器不存在,则立即执行,并设置一个定时器,wait毫秒后将定时器置为null
// 这样确保立即执行后wait毫秒内不会被再次触发
let callNow = !timeout
timeout = setTimeout(() => {
timeout = null
}, wait)
// 如果满足上述两个条件,则立即执行并记录其执行结果
if (callNow) {
result = method.apply(context, args)
resolve(result)
}
} else {
// 如果immediate为false,则等待函数执行并记录其执行结果
// 并将Promise状态置为fullfilled,以使函数继续执行
timeout = setTimeout(() => {
// args是一个数组,所以使用fn.apply
// 也可写作method.call(context, ...args)
result = method.apply(context, args)
resolve(result)
}, wait)
}
})
}
// 在返回的 Ddebounce 函数上添加取消方法
// Ddebounce.cancel = function () {
// clearTimeout(timeout)
// timeout = null
// }
return Ddebounce
}
javascript
const run = function () {
console.log("i'm run")
}
window.addEventListener('resize', Ddebounce(run, 2000, false))
节流
- 节流是指在一段时间内只允许函数执行一次
- 应用场景如: 输入框的联想、可以限定用户在输入时,只在每两秒钟响应一次联想(或是两秒钟发送一次搜索请求)
ts
/**
* 节流
* @param func 回调函数
* @param delay 延迟时间
*/
export const Dthrottle = <T extends (...args: any[]) => void>(func: T, delay: number) => {
let timer: ReturnType<typeof setTimeout> | null = null
return function (this: ThisParameterType<T>, ...args: Parameters<T>) {
const context = this
if (!timer) {
timer = setTimeout(function () {
func.apply(context, args)
timer = null
}, delay)
}
}
}
javascript
// 假设有一个需要被节流的函数
function expensiveOperation() {
console.log('Running expensive operation...')
// 这里可以是一些耗时较长的操作
}
// 使用 throttle 函数对 expensiveOperation 进行节流
const throttledFunc = throttle(expensiveOperation, 1000)
// 模拟频繁调用 expensiveOperation 函数
setInterval(function () {
throttledFunc()
}, 500)
柯里化
- 函数柯里化的主要目的就是为了减少函数传参,同时将一些固定参数私有化
javascript
const selfCurryFn = (fn) => {
const fnLen = fn.length
function curry(...args) {
const argLen = args.length
if (argLen < fnLen) {
function otherCurry(...args2) {
return curry.apply(this, args.concat(args2))
}
return otherCurry
} else {
return fn.apply(this, args)
}
}
return curry
}
javascript
const f = (a, b, c) => {
return console.log([a, b, c])
}
const curried = selfCurryFn(f)
curried(1)(2)(3)
拷贝
typescript
function deepClone<T>(obj: T, clonedMap = new WeakMap()): T {
// 检查是否已经拷贝过该对象,避免循环引用导致的无限递归
if (clonedMap.has(obj)) {
return clonedMap.get(obj) as T
}
let newObj: T
// 特殊类型的处理
if (obj instanceof Date) {
newObj = new Date(obj.getTime()) as T
} else if (obj instanceof RegExp) {
newObj = new RegExp(obj) as T
} else if (typeof obj === 'function') {
// 自定义需要特殊处理的函数类型
// newObj = obj.bind(null) as T;
newObj = obj as T
} else if (Array.isArray(obj)) {
newObj = [] as unknown as T
} else if (obj && typeof obj === 'object') {
newObj = {} as T
} else {
return obj
}
// 将当前对象添加到已拷贝的对象 Map 中
clonedMap.set(obj, newObj)
// 遍历对象的属性进行深拷贝
for (let key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
newObj[key] = deepClone(obj[key], clonedMap)
}
}
return newObj
}
javascript
// 基本类型
const num = 123
const str = 'hello'
const bool = true
console.log(deepClone(num)) // 123
console.log(deepClone(str)) // 'hello'
console.log(deepClone(bool)) // true
// 数组
const arr = [1, 2, [3, 4], { a: 5 }]
const clonedArr = deepClone(arr)
console.log(clonedArr) // [1, 2, [3, 4], { a: 5 }]
console.log(arr === clonedArr) // false
clonedArr[0] = 100
console.log(clonedArr) // [100, 2, [3, 4], { a: 5 }]
console.log(arr) // [1, 2, [3, 4], { a: 5 }]
clonedArr[2][0] = 300
console.log(clonedArr) // [100, 2, [300, 4], { a: 5 }]
console.log(arr) // [1, 2, [300, 4], { a: 5 }]
// 对象
const obj = { x: 1, y: { z: 2 } }
const clonedObj = deepClone(obj)
console.log(clonedObj) // { x: 1, y: { z: 2 } }
console.log(obj === clonedObj) // false
clonedObj.x = 100
console.log(clonedObj) // { x: 100, y: { z: 2 } }
console.log(obj) // { x: 1, y: { z: 2 } }
clonedObj.y.z = 200
console.log(clonedObj) // { x: 100, y: { z: 200 } }
console.log(obj) // { x: 1, y: { z: 200 } }
// 循环引用
const circularObj = { prop: null }
circularObj.prop = circularObj
const clonedCircularObj = deepClone(circularObj)
console.log(clonedCircularObj) // { prop: [Circular] }
console.log(clonedCircularObj.prop === clonedCircularObj) // true
手写 call、apply 及 bind 函数
javascript
Function.prototype.Dcall = function (context) {
if (typeof this !== 'function') {
throw new TypeError('Error')
}
var context = context || window
context.say = this
var args = []
for (let i = 1; i < arguments.length; i++) {
args.push('arguments[' + i + ']')
}
var result = eval('context.say(' + args + ')')
delete context.say
return result
}
javascript
Function.prototype.Dapply = function (context, arr) {
if (typeof this !== 'function') {
throw new TypeError('Error')
}
var context = context || window
var result
context.fn = this
if (!arr) {
result = context.fn()
} else {
var args = []
for (let i = 0; i < arr.length; i++) {
args.push('arr[' + i + ']')
}
result = eval('context.fn(' + args + ')')
}
return result
}
javascript
Function.prototype.Dbind = function (context) {
if (typeof this !== 'function') {
throw new TypeError('Error')
}
var self = this
var args = Array.prototype.slice.call(arguments, 1)
return function () {
var bindArgs = Array.prototype.slice.call(arguments)
return self.apply(context, args.concat(bindArgs))
}
}
javascript
// 示例函数
function greet() {
console.log(`Hello, ${this.name}!`)
}
// 1. 使用 Dcall 方法调用函数,并指定上下文对象和参数
const person1 = { name: 'Alice' }
greet.Dcall(person1) // 输出:Hello, Alice!
// 2. 使用 Dapply 方法调用函数,并指定上下文对象和参数数组
const person2 = { name: 'Bob' }
const args = ['Nice to meet you!']
greet.Dapply(person2, args) // 输出:Hello, Bob!
// 3. 使用 Dbind 方法创建新函数,预先绑定上下文和参数
const person3 = { name: 'Charlie' }
const greetPerson3 = greet.Dbind(person3)
greetPerson3() // 输出:Hello, Charlie!
new
在调用 new 的过程中会发生以上四件事情:
- 新生成了一个对象
- 链接到原型
- 绑定
this
- 返回新对象
实现分析:
- 创建一个空对象
- 获取构造函数
- 设置空对象的原型
- 绑定
this
并执行构造函数 - 确保返回值为对象
javascript
Function.prototype.Dnew = function (fn, ...args) {
var obj = Object.create(fn.prototype)
var ret = fn.apply(obj, args)
return ret instanceof Object ? ret : obj
}
// 或
Function.prototype.DDnew = function () {
var obj = {}
var Constructor = Array.prototype.shift.call(arguments)
obj.__proto__ = Constructor.prototype
var result = Constructor.call(obj, arguments)
return result instanceof Object ? result : obj
}
instanceof
实现原理
就是只要右边变量的 prototype 在左边变量的原型链上即可。因此,instanceof 在查找的过程中会遍历左边变量的原型链,直到找到右边变量的 prototype,如果查找失败,则会返回 false,告诉我们左边变量并非是右边变量的实例。
javascript
{
function new_instance_of(leftVaule, rightVaule) {
let rightProto = rightVaule.prototype
leftVaule = leftVaule.__proto__
while (true) {
if (leftVaule === null) return false
if (leftVaule === rightProto) return true
leftVaule = leftVaule.__proto__
}
}
}
{
function new_instance_of(leftVaule, rightVaule) {
let proto = Object.getPrototypeOf(leftVaule)
while (true) {
if (proto == null) return false
if (proto === rightVaule.prototype) return true
proto = Object.getPrototypeOf(proto)
}
}
}
总结:
使用 typeof 来判断基本数据类型是 ok 的,不过需要注意当用 typeof 来判断 null 类型时的问题,如果想要判断一个对象的具体类型可以考虑用 instanceof,但是 instanceof 也可能判断不准确,比如一个数组,他可以被 instanceof 判断为 Object。所以我们要想比较准确的判断对象实例的类型时,可以采取 Object.prototype.toString.call() 方法