1. 防抖函数(debounce)
目的:防止用户多次触发事件,多次请求造成浪费
原理:在事件触发n秒后,再执行回调;如果在n秒内再次被触发,则重新计时;
const debounce = function(func,delay = 50){
/*
func: 需要防抖的函数
awit: 需要等待的时间,默认为50ms
*/
let timer = 0; //建立一个定时器的id
/* 这里面相当于一个工厂函数:
把用户传进的func函数,加上防抖的功能;
然后再返回
*/
return function(...args){ // 对调用func时传入的参数进行展开
if(timer){
// 如果定时器存在,说明已经一次或多次触发该函数,但还未执行
clearTimeout(timer); // 清除定时器任务
}
// 无论是否存在,都再次设置定时器
timer = setTimeout( ()=>{
func.apply(this,args); // 使用apply函数,自调用函数,并将定时器id记录在timer中
},delay); // 设置延时
}
}
2. 节流函数(throttle)
目的:防止用户多次触发,造成多次请求,浪费资源
原理:使用户单位时间内的多次触发,只执行一次;
const throttle = function(func, unitTime=1000) {
let lastTime = 0; // 用于记录上一次触发的时间
return function(...args){
let now = +new Date();
// 如果超出了设定时间范围
if(now - lastTime > unitTime) {
lastTime = now; // 记录当前有效触发的触发时间
func.apply(args);
}
}
}
3. 手写call()函数
若要手写call()函数,必先知道它完成了什么工作
- 函数进行自己调用自己的操作
- 为函数拓展作用域;
- 如果不传参,默认函数的this指向window
// 在原型上实现
Function.prototype.myCall = function(context=window , ...args){ // (实现了第3个功能)
let key = Symbol('key'); // 用符号创建一个唯一标识
context[key] = this; // 在上下文中添加新属性,这个新属性指向调用函数本身;
let result = context[key](...args); // 存储调用函数的结果 (实现了第1个功能)
delete context[key]; // 删除属性
return result; // 返回调用的结果
}
function func(a,b){
console.log(`${this.nickname}is${a+b}`);
}
let newContext = {
nickname:'zyzc',
color:'red'
}
func.myCall(newContext,1,2); // zyzcis3
// 其实可以这样理解:
// 可以这样理解:
let callFunc = Symbol('key');
let newContextAfterMyCall = {
nickname:'zyzc',
color:'red',
[callFunc]:(function(a,b){
console.log(`${this.nickname}is${a+b}`);
})
}
newContextAfterMyCall[callFunc](1,2);
delete newContextAfterMyCall[callFunc];
4. 手写apply()函数
其实,apply()和call(),只是接收参数上的不同而已
- 对于apply(),第二个参数是一个参数数组
- 对于call(),第二个参数开始,是逐个参数
// 在原型上实现
Function.prototype.myCall = function(context=window , ...args){
let key = Symbol('key');
context[key] = this;
// 只需要在这里稍作改变就好
let result = context[key](args);
// 如果担心第一个参数是context会有影响,可以这样做
// let args = [...argument].slice(1);
delete context[key];
return result;
}
5. 手写bind()函数
bind()
方法创建一个新的函数,在bind()
被调用时,这个新函数的this
被指定为bind()
的第一个参数,而其余参数将作为新函数的参数,供调用时使用。
bind
返回了一个函数,对于函数来说有两种方式调用:
- 直接调用,
- 用
new
来构建- 对于直接调用而言,可以使用
apply()
,但是需要注意以下情况:
bind
可以实现类似这样的代码f.bind(obj, 1)(2)
,所以我们需要将内外部参数拼接。- 对于 new 的方式而言,
this
的指向不会被改变,所以不要考虑传入this
,直接将内外部参数拼接传入即可;
Function.prototype.myBind = function(context,...outerArgs){
let self = this; // 存储函数调用者
return F(...innerArgs){
if(self instance of F){ // 如果使用new类型来创建
return new self(...outerArgs,...innerArgs);
}
// 如果是直接调用
return self.apply(context,[...outerArgs , ...innerArgs]);
}
}
6. 手写instanceof
instanceof
运算符用于检测构造函数的 prototype
属性是否出现在某个实例对象的原型链上。
判断某个实例对象,是否在某个原型链上。
function myInstanceof(exmaple,classFunc){
let proto = Object.getPrototypeOf(exmaple);
while(true) {
if( proto == null ) return false; // 直到找到Object的原型null上
// 判断当前
if( proto == classFunc.prototype ) return true; //
proto = Object.getProtypeOf(proto); // 获取proto的原型,一直顺着原型链找;
// 也可以换成这个:proto = proto.__proto__
}
}
7. 手写new
new
运算符创建一个用户定义的对象类型的实例或具有构造函数的内置对象的实例。
new
完成了什么事情:
- 创建一个全新的对象
- 这个对象的
__proto__
要指向构造函数的原型prototype- 执行构造函数,使用
call/apply
改变 this 的指向- 返回值为
object
类型则作为new
方法的返回值返回,否则返回上述全新对象
function myNew(fn,...args){
// 传入一个构造函数
// 传入构造函数的参数
// 创建一个新对象,这个新对象的原型 指向 fn的原型
let instance = Object.create(fn.prototype)
let res = fn.apply(instance,args); // 改变this的指向
// 确保返回的值是object
return typeof res === 'object' ? res : instance;
}