关于 JS 中一些重要的 api 实现, 巩固你的原生 JS 功底
1. 手写 call 方法
Function.prototype.myCall = function (context, ...args) {
if (typeof context === 'object' || typeof context === 'function') {
context = context || window
} else {
context = Object.create(null)
}
// 为了避免函数名与上下文 (context) 的属性发生冲突
// 利用 Symbol 的唯一性生成一个不重复的键
let fn = Symbol('fn')
context[fn] = this
const result = context[fn](...args)
// 调用函数后将对象上的函数删掉
delete context[fn]
return result
}
let person = { name: '一哥' }
function test (age, sex) {
console.log(this.name, age, sex)
}
test.myCall(person, 27, '男')
2. 手写 apply 方法
Function.prototype.myApply = function (context, args) {
if (typeof context === 'object' || typeof context === 'function') {
context = context || window
} else {
context = Object.create(null)
}
let fn = Symbol('fn')
context[fn] = this
const result = context[fn](...args)
delete context[fn]
return result
}
let person = { name: '慧宝' }
function test (age, sex) {
console.log(this.name, age, sex)
}
test.myApply(person, [25, '女'])
call 和 apply 两者实现的区别在于它们的第二个参数的不同
3. 手写 bind 方法
Function.prototype.myBind = function (context, ...args) {
let self = this // 谨记 this 表示调用 bind 函数
let bindFn = function (...newArgs) {
// 使用 call 指定 this
// 当返回的绑定函数作为构造函数呗 new 调动,绑定的上下文指向实例对象
return self.call(this instanceof bindFn ? this : context || window,
...args, ...newArgs)
}
// 设置绑定函数的 prototype 为原函数的 prototype
bindFn.prototype = Object.create(self.prototype)
return bindFn
}
let person = { name: '一哥' }
function testBind (age, sex) {
console.log(this.name, age, sex)
}
const test = testBind.myBind(person, 27)
test('男')
4. 手写 new 操作符
/*
* 1. 创建一个新的空对象
* 2. 将新的空对象的 __proto__ 属性指向原函数的 prototype
* 3. 将新的空对象绑定到此函数的 this 上
* 4. 返回新的空对象
*/
const myNew = function (Con, ...args) {
let obj = {}
// obj.__proto__ = Con.protitype // ES5
Object.setPrototypeOf(obj, Con.prototype) // ES6
const result = Con.apply(obj, args)
return result instanceof Object ? result : obj
}
function Test (name, age) {
this.name = name
this.age = age
}
Test.prototype.sayHi = function () {
console.log('Test-----实现 new 关键字')
}
const test = myNew(Test, '测试', 18)
console.log(test.name) // 测试
console.log(test.age) // 18
test.sayHi() // Test-----实现 new 关键字
5. 手写 instanceof 方法
/*
* 1. 获取类型的原型
* 2. 获取对象的原型
* 3. 一直循环判断对象的原型是否等于类型的原型,直到对象原型为 null,因为原型链最终为 null
*/
const myInstanceof = function (left, right) {
let prototype = right.prototype // 获取类型的原型
let left = left.__proto__ // 获取对象的原型
while (true) {
if (left === null || left === undefined) return false
if (prototype === left) return true
left = left.__proto__
}
// let proto = Object.getPrototypeOf(left)
// while (true) {
// if (proto == null) return false
// if (proto == right.prototype) return true
// proto = Object.getPrototypeOf(proto)
// }
}
MDN 关于 Object.getPrototypeOf() 方法
6. 手写 Object.create() 方法
const myCreate = function (proto) {
function Fn () {}
Fn.prototype = proto
Fn.prototype.constructor = Fn
return new Fn()
}
7. 实现节流函数功能
如果一个事件被频繁触发多次,节流函数可以按照固定的频率去执行对应的事件处理方法。
函数节流保证一个事件一定时间内只执行一次。
应用场景:
- DOM 元素的拖拽功能实现(
mousemove
) - 搜索联想(
keyup
) - 滚动事件
scroll
(只要页面滚动就会间隔一段时间判断一次) - 计算鼠标移动的距离(
mousemove
) - 射击游戏的
mousedown
/keydown
事件(单位时间只能发射一颗子弹)
const throttle = (fn, delay = 500) => { // 每隔一段时间触发一次,像水滴一样
let flag = true
return (...args) => {
if (!flag) return;
flag = false
setTimeout(() => {
fn.apply(this, args)
flag = true
}, delay)
}
}
8. 实现防抖函数功能
如果一个事件被频繁触发多次,并且触发的事件间隔过短,则防抖函数可以使得对应的事件。
处理函数只执行最后触发的一次。
应用场景
- 手机号、邮箱输入验证
- 搜索框搜索输入(只需要最后一次输入完后再发 Ajax 请求)
- 窗口大小
resize
事件(只需要窗口调整完成后,计算窗口大小,防止重复渲染) - 滚动事件
scroll
(只需要执行触发的最后一次滚动事件的处理程序)
const debounce = (fn, delay = 500) => {
let timer = null
return (...args) => {
// 如果事件被触发,清除timer并重新开始计时
clearTimeout(timer)
timer = setTimeout(() => {
fn.apply(this, args)
}, delay)
}
}