手撸bind,call,apply,new,节流,防抖,深拷贝

230 阅读4分钟

前言

面试中经常会遇到一些手写XXX函数的题目,今天自己复习总结下这一类题目。

bind,call,apply比较

  • 相同点:xxx.bind(obj,,y1,y2,...),xxx.call(obj,y1,y2,...),xxx.apply(obj,[y1,y2,y3]),修改xxx函数的this指向为obj,obj不传默认为window
  • 不同点: 1.调用时机:apply和call,会直接调用执行,bind不会直接调用,2.传参方式:bind,call接受的是参数列表,apply接受的是参数数组。
// 手写call
/**
 * call 做了哪些事情
 * 普通函数this指向当前调用者这个对象,xxx.call(yyy,...zzz),即this此时指向xxx
 * call传入参数,是运行时该函数的执行上下文,即this
 * 1.判断传入对象是否为空,为空默认为window
 * 2.运行时需要执行的函数赋值给该对象
 * 3.执行该对象的函数,那么该函数就可以使用该对象上任意属性,此时函数执行上下文就变成了yyy
 * 4.返回函数执行结果
 * 修改调用者this到,指定对象上
 * @param {*} context 可选,在 function 函数运行时使用的 this
 * @param  {...any} args 参数列表
 */
Function.prototype.myCall = function(context,...args){
    context = context || window;
    context.fn = this;
    const result = context.fn(...args);
    delete context.fn;
    return result;
}

// 手写apply
/**
 * call 和apply ,作用一样可以修改当前函数执行的上下文,只不过传参不一样,apply,接受数组形式,call接受参数列表
 * 1.判断传入对象是否存在,不存在默认取window
 * 2.运行执行的函数赋值给该对象fn属性
 * 3.判断入参第二个参数是否为空,即数组长度是否存在
 * 4.执行fn函数,并返回结果
 * @param {*} context 可选,在 function 函数运行时使用的 this
 * @param {*Array} args 参数列表数组
 */
Function.prototype.myApply = function(context,args){
    context = context || window;
    context.fn = this;
    let result;
    if(args && args.length){
        result = context.fn(...args)
    }else{
        result = context.fn();
    }
    delete context.fn;
    return result;
}

// 手写bind
/**
 * bind和call,apply的作用是一样的,修改当前函数的执行上下文为context,且是一个新的函数,不会立马执行,传参为参数列表
 * 1.判断传入对象是否存在,不存在默认取window
 * 2.运行执行的函数赋值给该对象fn属性
 * 3.返回新函数,函数内执行该对象的fn函数
 * @param {*} context 
 * @param  {...any} args 
 * @returns 修改this指向后的新函数
 */
Function.prototype.myBind = function(context,...args){
    context = context || window;
    context.fn = this;
    return (arguments) => {
        return context.fn(...args,...arguments)
    }
}


let obj = {
    name : 'test',
    sayGoodBye:function(){
        console.log(this.name)
    }
}
let obj2 = {
    name: 'obj2-test',
    sayHello: function(age,sex){
        console.log(this.name)
        age && console.log(age);
        sex && console.log(sex)
    }
}
obj.sayGoodBye.myCall(obj2)
obj2.sayHello.myApply(obj,[23])
obj2.sayHello.myBind(obj,23)('女')

实现一个new关键字

// 手写new
/**
 * 实例化一共做了哪些事情
 * 1.实例化后生成一个新对象,且实例的__proto__指向构造函数的原型
 * 2.创建一个空对象
 * 3.将构造函数的原型赋值给空对象的__proto__
 * 4.修改构造函数的this指向
 * 5.返回修改this指向后的对象
 * @param {*} context 需要实例化的对象
 * @param  {...any} args 实例化传入的参数
 */
function myNew(context,...args){
    let obj = Object.create(null);
    obj.__proto__ = context.prototype;
    const result = context.myApply(obj, args);
    return typeof result === 'object' ? result : obj;

}
let a = myNew(Person,'test-person-name')
console.log(a.name)

节流防抖

  1. 场景介绍:
  • 防抖场景:一些高频触发的事件,例如需要记录最后一次的操作结果,例如连续点赞,取消点赞,input快速快速输入等
  • 节流场景:一些高频触发的事件,只想单位时间内执行一次,例如 onrize,onscroll等
  1. 节流实现
// 手写节流函数
/**
 * 1.单位时间内只执行一次
 * @param {*节流函数} fn
 * @param {*延时ms数} delay
 * 方式1:使用setTimeout实现
 */
function throttle(fn, delay) {
    // 方式1:setTimeout实现
    // let timer = null;
    // return function(){
    //     if(timer) return;
    //     setTimeout(() => {
    //         fn.apply(this,arguments)
    //         timer = null;
    //     },delay)
    // }
    // 方式2: 判断时间间隔实现
    let endTime = null;
    return function(){
        let startTime = Date.parse(new Date())
        //判断当前时间和上一次执行时间间隔是否超过delay时间,没有不执行函数
        if(startTime - endTime <= delay) return;
        fn.apply(this,arguments)
        endTime = Date.parse(new Date());
    }
}
  

3.防抖实现

// 手写防抖函数
/**
 * 2.每n秒触发一次,如果n秒内再次触发重新开始计时
 * @param {*防抖函数} fn 
 * @param {*Number} delay 延时ms数
 * @param {*Boolean} immediate 是否立即执行
 */
 function debounce(fn,delay,immediate){
    let timer = null;
    return function(){
        // n 秒内再次触发重新计时
        if(timer) clearTimeout(timer)
        if(immediate){
           if(!timer) fn.apply(this,arguments)
        }else{
            timer = setTimeout(() => {
                fn.apply(this,arguments)
            },delay)
        }
    }
}

4.深拷拷贝简单实现

// 手写深拷贝
function deepClone(obj) {
	let result = {};
	for (const key in obj) {
		// 此处只做了对象类型判断,还需要判断function,array,还有一些异常情况判断
		if (Object.prototype.toString.call(obj[key]) === "[object Object]") {
			result[key] = deepClone(obj[key]);
		} else {
			result[key] = obj[key];
		}
	}
	return result;
}

5.instanceof原理(使用场景一般用来判断某个实例对象是不是某构造函数的实例)

/**
 * 考虑子实例对象,是不是构造函数parentFn的实例
 * 即 childObj.__proto__ === parentFn.prototype,即证明
 * 基于原型链属性查找规则验证,如果在子对象的原型链对象上找到,就说明字实例对象是构造函数parentFn的实例
 */
function myInstanceOf(childObj, parentFn) {
	let __proto__ = parentFn.prototype;
	let child = childObj.__proto__;
	while (true) {
		if (child === null) return false;
		if (child === __proto__) {
			return true;
		}
		child = child.__proto__;
	}
}

function Person(name) {
	this.name = name;
}
let p1 = new Person("ljz");
console.log(myInstanceOf(p1, Person));