【JS】一些手写函数:深拷贝、bind、debounce等

深拷贝:考虑数组 对象 正则 Date

前提知识:typeof ,==,值类型和引用类型,instanceof,for...in遍历,hasOwnProperty

typeof 1
'number'
typeof 1.2
'number'
typeof 'ss'
'string'
typeof undefined
'undefined'
typeof true
'boolean'
typeof Symbol
'function'
typeof class student{}
'function'

typeof null
'object'
typeof []
'object'
typeof {}
'object'
typeof /^\w+$/
'object'
typeof new Date()
'object'

null == undefined
true

[1,2,3] instanceof Array
true

[1,2,3].__proto__ === Array.prototype
true

值类型:在栈空间中:key是变量名,value是变量值。

引用类型:在栈空间中:key是变量名,value是一个地址。在堆空间中,key是地址,value是引用类型的内容本身。即引用类型变量本身是一个对数据内容的引用。

所以要使用深拷贝。

for...in会遍历出原型对象上新增的属性或方法,所以需要使用hasOwnproperty判断某属性是否是数组本身具有的属性。

// 手写deepClone
// 深拷贝:考虑数组 对象 正则  Date
function deepClone(obj){
    if(typeof obj !=='object' || obj==null)
        return obj ;
    if(obj instanceof Date){
        const res=new Date();
        res.setTime(obj.getTime());
        return res;
    }
    if(obj instanceof RegExp){
        const Constructor =obj.constructor;
        return new Constructor(obj);
    }
    if(obj instanceof Array || obj instanceof Object){
        const res=Array.isArray(obj)?[]:{};
        for (const key in obj) {
            if (obj.hasOwnProperty(key)) {
                res[key]=deepClone1(obj[key]); 
            }
        }
        return res;
    }
}

结合Object.assign()实现的深拷贝(Object.assign()并不是深拷贝,只能复制属性和基本类型的属性值,当属性值是引用类型时,复制的是地址。所以要手动写深拷贝)

deepClone = (obj) => {
  if (obj == null) return;
  if (typeof obj !== "object") return obj;
  let resultObj = {};
  for (let item in obj) {
    let temp = {};
    if (typeof obj[item] === "object" && obj[item] !== null) {
      if (!Array.isArray(obj[item])) {
        temp[item] = this.deepClone(obj[item]);
      } else {
        temp[item] = [];
        for (let i in obj[item]) {
          temp[item][i] = this.deepClone(obj[item][i]);
        }
      }
    } else {
      temp[item] = obj[item];
    }
    Object.assign(resultObj, temp);
  }
  return resultObj;
};

JSON.parse(JSON.stringify(obj))也可以实现深拷贝,但是前提要求是,被序列化和反序列化的数据必须是符合JSON格式需求的,否则会直接被忽略掉。

浅拷贝可以用:

obj2={...obj1}或者obj2=Object.assign({},obj1)实现。

Object.assign(target,...source)用于将一个或多个源对象复制到目标对象,并返回目标对象。

instanceof函数:(大致思路)

// a instanceof b
function instanceof1(){
    while(a!==null){
        if(a.__proto__ === b.prototype)
            return true;
        a=a.__proto__;
    }
    return false;
}

bind函数

fn.bind(obj,a,b),返回一个新的函数,当新函数被调用时,指定fn的调用者为obj,并执行fn函数。

前提知识:原型和原型链,this指向函数的调用者

Function.prototype.bind1=function(){
    // 这样直接指定slice的调用者为arguments是不可以的
    // 必须通过call或apply指定slice的调用者为arguments
    // console.log(arguments.slice());
    const args=Array.prototype.slice.call(arguments);
    // 指定fn的调用者对象
    const obj=args.shift() || window;
    
    // 拿到fn
    const self=this;
    return function(){
        // 指定fn的调用者对象,并执行fn
        self.apply(obj,args);
    }
}

call函数

fn.call(obj,a,b),指定fn函数的调用者为obj,参数为非数组形式,并执行函数

前提知识:数组解构赋值

Function.prototype.call1=function(){
    const args=Array.prototype.slice.call(arguments);
    const obj=args.shift() || window;
    obj.fn=this;
    if(args[0])
        obj.fn(...args);
    else
        obj.fn();
}

apply函数

fn.apply(obj,[a,b]) ,指定fn函数的调用者为obj,参数为数组形式,并执行函数

Function.prototype.apply1=function(){
    const args=Array.prototype.slice.call(arguments);
    const obj=args.shift() || window;
    obj.fn=this;
    if(args[0])
        obj.fn(...args[0]);
    else
        obj.fn();
}

防抖debounce函数

前提知识:定时器

// 封装debounce
function debounce(fn, delay = 500) {
    // timer是在闭包中的,不能被外界修改
    let timer = null;
    // 函数作为返回值,使用闭包
    return function () {
        if (timer)
            clearTimeout(timer);
        timer = setTimeout(() => {
            fn.apply(this,arguments)
            // 清空定时器,到时清空
            timer = null;
        }, delay)
    }
}

 节流throttle函数

// 节流封装
function throttle(fn,delay=500){
    let timer=null;
    return function(){
        if(timer)
            return;
        timer=setTimeout(()=>{
            // arguments是[返回的函数]的参数,并不是给fn的,所以fn.()直接调用并没有事件对象e
            fn.apply(this,arguments);
            timer=null;
        },delay)
    }
}

手写Promise加载一张图片:

function loadImg(src){
    // pending
    return new Promise((resolve,reject)=>{
        let img=document.createElement('img');
        // onload是加载完之后执行回调函数,也是异步
        img.onload=function(){
            resolve(img);//resolved
        }
        img.onerror=function(){
            reject(new Error(`图片加载失败${src}`));//rejected
        }
        img.src=src;
        document.body.appendChild(img);
    })
}

通用的事件监听函数:

前提知识:事件代理

// 事件监听函数
function bindEvent(elem, type, fn, selector) {
    elem.addEventListener(type, event => {
        const target = event.target;
        // 有选择器,说明存在事件代理
        if (selector) {
            // 再判断target和选择器selector是否匹配
            if (target.matches(selector)) {
                fn.call(target, event);
            }
        } else
            fn.call(target, event);
    })
}

手写ajax,结合promise

function ajax(url){
    const p=new Promise((resolve,reject)=>{
        const xhr=new XMLHttpRequest();
        xhr.open('get',url,true);
        xhr.onreadystatechange=function(){
            if(xhr.readyState===4){
                if(xhr.status===200){
                    resolve(JSON.parse(xhr.responseText));
                }else if(xhr.status===404){
                    reject(new Error('404 not found'));
                }
            }
        }
        xhr.send(null);

        // post请求
        // xhr.open('post',url,true);
        // xhr.send(JSON.stringify({name:'tom'}));
    })
}

深度比较:判断两个对象的具体内容是否相等。(因为地址不同,===肯定是false)

前提知识:

typeof:

值类型:number string boolean undefined symbol

引用类型object:[] {} (typeof null也是object)

function:type of class也是function

值类型判断相等用===

// 手写深度比较
const obj1 = {
    a: 100, b: {
        x: 200,
        y: 300,
    },
    d:[1,2,3]
}
const obj2 = {
    a: 100, b: {
        x: 200,
        y: 300,
    },
    d:[1,2,3]
}
console.log(obj1===obj2);//false


// 判断是否为引用类型:[]或{}
function isObject(obj) {
    return typeof obj === 'object' && obj !== null;
}
// typeof:值类型[number string boolean undefined symbol],引用类型[object],function
// 值类型判断相等直接用===,引用类型才用isEqual,函数一般不用isEqual
function isEqual(obj1, obj2) {
    // 只要有一个不是[]或{},就用===判断
    if (!isObject(obj1) || !isObject(obj2))
        // 值类型,注意:参与equal的一般不会是函数
        return obj1 === obj2;
    // 传的参数是同一个
    if(obj1===obj2)
        return true;

    // 下面保证了都是引用类型:[]或{}
    // 先获取到两个对象的keys构成的数组,比较长度
    const obj1keys=Object.keys(obj1);
    const obj2keys=Object.keys(obj2);
    if(obj1keys.length!==obj2keys.length)
        return false;

    // 保证key的个数一致,再比较具体内容
    for (let item in obj1) {
        if (obj2.hasOwnProperty(item)) {
            const res= isEqual(obj1[item], obj2[item]);
            if(!res)
                return false;
        }
        else
            return false;
    }
    return true;
}

console.log(isEqual(obj1,obj2));//true

手写字符串的trim()方法:

前提知识:原型,this,正则表达式,replace()

原型:trim()方法不是字符串的方法,而是其原型对象上的方法。

this:trim()方法中的this,就是其调用者对象字符串。

正则表达式:/^/匹配开头,/$/匹配结尾,\s匹配空格,+表示一个或多个

replace():匹配字符串的子串,并进行替换,返回新串

即用正则表达式匹配空格,用空字符串进行替换。

String.prototype.trim1=function(){
    return this.replace(/^\s+/,'').replace(/\s+$/,'');
}

求几个数的最大值:

// 求几个数的最大值
// 用api
const arr=[10,50,60,42,30,74,54,65];
console.log(Math.max(...arr));

// 手写一个函数
function max(){
    const args=Array.prototype.slice.call(arguments);
    // foreach遍历
    // let max=args[0];
    // args.forEach(element => {
    //     if(element>max)
    //         max=element;
    // });
    // return max;

    // 或者用reduce
    return args.reduce((prev,current)=>{
        return current>prev?current:prev;
    })
    
}
console.log(max(...arr));

获取url中的参数值:

// 获取url中的参数值,给定key,求value
function query(name){
    // location.search获取到所有参数,'?a=10&b=20&c=30',substring(1),截取?后面的,类似slice
    const search=location.search.substring(1);
    console.log(search);
    // 方法一:split
    // const arr=search.split('&');
    //  console.log(arr);
    // for (const item of arr) {
    //     const sub=item.split('=');
    //     if(sub[0]===name)
    //         return sub[1];
    // }
    // return null;
    // 方法二:正则表达式
    // 'a=10&b=20&c=30'
    // (^|&)匹配key=value对的前一个字符,要么是开头,要么是&
    // ([^&]*)匹配value,[^&]表示匹配不是&的字符,即遇到&就停止匹配,*表示0个或多个字符
    // (&|$)匹配key=value对的后一个字符,要么是&,要么是结尾
    const reg=new RegExp(`(^|&)${name}=([^&]*)(&|$)`,'i');
    const res=search.match(reg);
    // res为数组,第一个元素是整个表达式匹配到的内容,后面三个元素是三个括号分别匹配到的内容
    if(res==null)
        return null;
    console.log(res);
    return res[2];
}
// res:['&b=20&', '&', '20', '&', index: 4, input: 'a=10&b=20&c=50', groups: undefined]
console.log(query('b'))

function query1(name){
    const search=location.search
    const p=new URLSearchParams(search);
    return p.get(name);
}

实现异步函数的重试机制RunWithRetry:调用fn函数直至成功,或达到重试次数上限

// fn返回成功或失败的Promise对象,调用runWithRetry直至fn成功(返回成功的Promise对象),或者达到重试次数上限(返回失败的Promise对象)
function runWithRetry(fn,retryTimes){
    let i=1;
    return new Promise(async (resolve,reject)=>{
        while(i<=retryTimes){
            try{
                const res=await fn();
                resolve(res);
                break;
            }catch(ex){
                if(i===retryTimes)
                    reject(ex);
            }
            i++;
        }
    })
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值