5个常见的前端手写功能:New、call apply bind、防抖和节流、instanceof、ajax

news2025/1/9 16:18:13

实现New

  • 首先创建一个新的空对象
  • 设置原型,将对象的原型设置为函数的prototype对象
  • 让函数的this指向这个对象,执行构造函数的代码
  • 判断函数的返回值类型,如果是值类型,返回创建的对象。如果是引用类型,就返回这个引用类型的对象
function myNew(constructor, ...args) {
  // 如果不是一个函数,就报错
  if (typeof constructor !== "function") {
    throw "myNew function the first param must be a function";
  }
  // 基于原型链 创建一个新对象,继承构造函数constructor的原型对象上的属性
  let newObj = Object.create(constructor.prototype);
  // 将newObj作为this,执行 constructor ,传入参数
  let res = constructor.apply(newObj, args);
  // 判断函数的执行结果是否是对象,typeof null 也是'object'所以要排除null
  let isObject = typeof res === "newObject" && res !== null;
  // 判断函数的执行结果是否是函数
  let isFunction = typeof res === "function";
  // 如果函数的执行结果是一个对象或函数, 则返回执行的结果, 否则, 返回新创建的对象
  return isObject || isFunction ? res : newObj;
}
 
// 用法
function Person(name, age) {
  this.name = name;
  this.age = age;
 // 如果构造函数内部,return 一个引用类型的对象,则整个构造函数失效,而是返回这个引用类型的对象
}
Person.prototype.say = function() {
  console.log(this.age);
};
let p1 = myNew(Person, "poety", 18);
console.log(p1.name);	//poety
console.log(p1);	//Person {name: 'poety', age: 18}
p1.say();	//18

测试结果:

image.png

call、apply、bind

实现call

思路:接受传入的context上下文,如果不传默认为window,将被调用的方法设置为上下文的属性,使用上下文对象来调用这个方法,删除新增属性,返回结果。

//写在函数的原型上
Function.prototype.myCall = function (context) {
  // 如果要调用的方法不是一个函数,则报错
  if (typeof this !== "function") {
    throw new Error("Type error");
  }
  // 判断 context 是否传入,如果没有传就设置为 window
  context = context || window;
  // 获取参数,[...arguments]把类数组转为数组
  let args = [...arguments].slice(1);
  let result = null;
  // 将被调用的方法设置为context的属性,this即为要调用的方法
  context.fn = this;
  // 执行要被调用的方法
  result = context.fn(...args);
  // 删除手动增加的属性方法
  delete context.fn;
  // 将执行结果返回
  return result;
};

//测试
function f(a, b) {
  console.log(a + b);
  console.log(this.name);
}
let obj = {
  name: 1,
};
f.myCall(obj, 1, 2); // 3,1

测试结果:

image.png

实现apply

思路:除了传参方式是数组,其它与call没区别

Function.prototype.myApply = function (context) {
  if (typeof this !== "function") {
    throw new Error("Type error");
  }
  let result = null;
  context = context || window;
  // 与上面代码相比,我们使用 Symbol 来保证属性唯一,也就是保证不会重写用户自己原来定义在 context 中的同名属性
  const fnSymbol = Symbol();
  context[fnSymbol] = this;
  // 执行要被调用的方法,处理参数和 call 有区别,判断是否传了参数数组
  if (arguments[1]) {
    //传了参数数组
    result = context[fnSymbol](...arguments[1]);
  } else {
    //没传参数数组
    result = context[fnSymbol]();
  }
  delete context[fnSymbol];
  return result;
};

//测试
function f(a, b) {
  console.log(a, b);
  console.log(this.name);
}
let obj = {
  name: "张三",
};
f.myApply(obj, [1, 2]); //1 2,张三

测试结果:

image.png

实现bind

思路:bind返回的是一个函数,需要判断函数作为构造函数的情况,当作为构造函数时,this指向实例,不会被任何方式改变this,所以要忽略传入的context上下文。

bind可以分开传递参数,所以需要将参数拼接。如果绑定的是构造函数,还需要继承构造函数原型上的属性和方法,保证不丢失。

Function.prototype.myBind = function (context) {
  // 判断调用对象是否为函数
  if (typeof this !== "function") {
    throw new Error("Type error");
  }
  // 获取参数
  const args = [...arguments].slice(1);
  const fn = this; // 保存this的值,代表调用bind的函数
  //返回一个函数,此函数可以被作为构造函数调用,也可以作为普通函数调用
  const Fn = function () {
    // 根据调用方式,传入不同绑定值
    // 当作为构造函数时,this 指向实例,不会被任何方式改变 this,要忽略传入的context上下文
    return fn.apply(
      this instanceof Fn ? this : context,
      // bind可以分开传递参数(如f.bind(obj, 1)(2)),所以需要将参数拼接,这里使用apply,参数拼接成一个数组
      args.concat(...arguments)//当前的这个 arguments 是指 Fn 的参数,也可以用剩余参数的方式
    );
  };
  //对于构造函数,要保证原函数的原型对象上的属性不能丢失
  Fn.prototype = Object.create(fn.prototype);
  return Fn;
};
 
// 1.先测试作为构造函数调用
function Person(name, age) {
  console.log(name);
  console.log(age);
  console.log(this); //构造函数this指向实例对象
}
// 构造函数原型的方法
Person.prototype.say = function () {
  console.log("say");
};
var obj = {
  name: "cc",
  age: 18,
};
var bindFun = Person.myBind(obj, "cxx");
var a = new bindFun(10);
// cxx
// 10
// Person {}
a.say(); // say
 
// 2.再测试作为普通函数调用
function normalFun(name, age) {
  console.log(name);
  console.log(age);
  console.log(this); // 普通函数this指向绑定bind的第一个参数 也就是例子中的obj
}
var obj = {
  name: "aa",
  age: 18,
};
var bindNormalFun = normalFun.myBind(obj, "cc");
bindNormalFun(12);
// cc
// 12
// { name: 'aa', age: 18 }

测试结果:

image.png

防抖和节流

防抖

防抖是指在事件被触发n秒后在执行回调,如果在这n秒内时间又被触发,则重新计时。

可以使用在一些点击请求的事件上,避免因为用户的多次点击向后端发送多次请求。

//fn是需要防抖的函数,delay是等待时间
function debounce(fn, delay = 500) {
    let timer = null;
    // 这里返回的函数是每次用户实际调用的防抖函数
    return function(...args) {	//...args是es6的剩余参数语法,将多余的参数放入数组,用来代替arguments对象
    	// 如果已经设定过定时器了就清空上一次的定时器
    	if(timer) {
        	clearTimeout(timer);	
        }
        // 开始一个新的定时器,延迟执行用户传入的方法;注:定时器的返回值是一个数值,作为定时器的编号,可以传入clearTimeout来取消定时器
        timer = setTimeout(() => {  //这里必须是箭头函数,不然this指向window,要让this就指向fn的调用者
        	fn.apply(this, args);   
        }, delay)	
    }
}

节流

节流就是一定时间内执行一次事件,即使重复触发,也只有一次生效。

可以使用在监听滚动scroll事件上,通过事件节流来降低事件调用的频率。

定时器版本
throttle(fn, delay = 500) {
    let timer = null;
    return function(...args) {
    	// 当前有任务了,直接返回
        if(timer) {
        	return;
        }
        timer = setTimeout(() => {
            fn.apply(this, args);
            //执行完后,需重置定时器,不然timer一直有值,无法开启下一个定时器
            timer = null;	
        }, delay)
    }
}
时间戳版本
// 节流
function throttle(fn, delay = 500) {
  let prev = Date.now();// 上一次执行该函数的时间
  return function(...args) {
    let now = Date.now();//返回从UTC到当前时间的毫秒数
    // 如果差值大于等于设置的等待时间就执行函数
    if (now - prev >= delay) {
      fn.apply(this, args);
      prev = Date.now();
    }
  };
}

实现instanceof

instanceof用于检测构造函数的prototype属性是否出现在某个实例对象的原型链上。

myInstanceof(instance, constructor) {
    //如果不是对象,或者是null,直接返回false
    if (typeof instance !== "object" || instance === null) {
    	return false;
    }
    // 获取对象的原型
    let proto = Object.getPrototypeOf(instance);
    // 获取构造函数的 prototype 对象
    let prototype = constructor.prototype;
    // 判断构造函数的 prototype对象是否在对象的原型链上
    while (true) {
        // 到达原型链终点null,说明没找到
        if (!proto) {
        	return false;
        }
        if (proto === prototype) {
        	return true;
        }
        // 如果没有找到,就继续从其原型上找
        proto = Object.getPrototypeOf(proto);
    }
}
 
//测试
let Fn = function () { }
let p1 = new Fn()
console.log(myInstanceof(p1, Fn));//true
console.log(myInstanceof([], Fn));//false
console.log(myInstanceof([], Array)) // true
console.log(myInstanceof(function(){}, Function)) // true

测试结果:

image.png

实现Ajax

创建一个XMLHttpRequest对象
在这个对象上使用open方法创建一个HTTP请求(参数为请求方法、请求地址、是否异步和用户的认证信息)
通过send方法来向服务器发起请求(post请求可以入参作为发送的数据体)
监听请求成功后的状态变化:根据状态码进行相应的出来。onreadystatechange设置监听函数,当对象的readyState变为4的时候,代表服务器返回的数据接收完成,这个时候可以通过判断请求的状态,如果状态是200则为成功,404或500为失败。

function ajax(url) {
    //1.创建XMLHttpRequest对象
    const xhr = new XMLHttpRequest();
    //2.使用open方法创建一个GET请求
    xhr.open('GET',url);
    //xhr.open('GET',url,true);//true代表异步,已完成事务的通知可供事件监听器使用;如果为false,send() 方法直到收到答复前不会返回
    //3.发送请求
    xhr.send(); 
    //4.监听请求成功后的状态变化(readyState改变时触发):根据状态码(0~5)进行相应的处理
    xhr.onreadystatechange = function () {
        //readyState为4代表服务器返回的数据接收完成
        if (xhr.readyState == 4) { 
            //请求的状态为200或304代表成功
            if(xhr.status == 200 || xhr.status == 304) {
                 //this.response代表返回的数据
                handle(this.response);
            } else {
                //this.statusText代表返回的文本信息
                console.error(this.statusText);
            }
        }
    };
}

使用Promise封装Ajax:

function ajax(url) {
    return new Promise((resolve, reject) => {
        let xhr = new XMLHttpRequest()
        xhr.open('get', url)
        xhr.send()  
        xhr.onreadystatechange = () => {
          if (xhr.readyState == 4) {
            if (xhr.status == 200 || xhr.status == 304) {
              resolve(this.response)
            } else {
              reject(new Error(this.statusText));
            }
          }
        }
  	})
}
//使用
let url = '/data.json'
ajax(url).then(res => console.log(res))
  .catch(reason => console.log(reason))

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1601859.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

四维轻云|如何使用场景在线协作功能?

众所周知,四维轻云是一款轻量化的地理空间数据管理云平台,支持地理空间数据的在线管理、编辑以及分享。平台有项目管理、数据上传、场景搭建、发布分享、素材库等功能模块。现在,就为大家介绍一下如何使用场景协作编辑功能。 1、协作模式开启…

【Redis 神秘大陆】004 高可用集群

四、Redis 高可用和集群 当你发现这些内容对你有帮助时,为了支持我的工作,不妨给一个免费的⭐Star,这将是对我最大的鼓励!感谢你的陪伴与支持!一起在技术的路上共同成长吧!点击链接:GitHub | G…

最新!!又5本On Hold无情被踢!!还剩11本期刊调查ing

【SciencePub学术】众所周知,期刊如果被打上“On Hold”的标签,就说明该期刊正在被进行调查评估,后面如果调查出期刊存在问题的话,则会被WOS期刊目录剔除! 4.15号,科睿唯安官方更新了4月的SCI/SSCI期刊目录…

API数据应用场景电商运营选品API接口接入key获取演示

在电商运营中,API(应用程序接口)数据可以用于各种场景,特别是在选品过程中。以下是一些API数据应用场景,以帮助电商运营进行更有效的选品: 市场趋势分析:通过调用第三方数据提供商的API&#xf…

2023 中国 SDS 年度报告发布:XSKY 蝉联对象存储软件第一,整体 TOP5

近日,IDC 发布了《IDC China SDS Market Overview, 2023》市场报告,XSKY 星辰天合继续蝉联对象存储软件第一,文件存储保持领先地位,并且在中国市场整体 SDS 排名第五,也是 TOP5 里面唯一的专业 SDS 厂商。 …

力扣算法-回溯

递归 104.二叉树的最大深度 回溯 17.电话号码的字母组合 ①子集型回溯 78.子集 (1)选不选 (2)选哪个 131.分割回文串 (1593.拆分字符串使唯一子字符串的数目最大 也可以用这个思路解:从结果角度,分割字符串) ②组合型回溯…

Windows版MySQL5.7解压直用(免安装-绿色-项目打包直接使用)

windows下mysql分类 MySQL分为 安装版和解压版 安装版: 安装方便,下一步------下一步就OK了,但重装系统更换环境又要重新来一遍,会特别麻烦解压版(推荐): 这种方式(项目打包特别方便&#xf…

EDMI电表光通讯口数采案例

【上海数采物联网科技有限公司】 工商业光伏发电并网项目 EDMI协议电表数采案例 项目背景及需求 项目地点:重庆港西光伏电站(中广核重庆) 项目背景:光伏发电并网项目电能监控 项目目的及难点:实现对EDMI协议电表…

毕设(五)——画pico扩展板

文章目录 一、扩展板原理图二、PCB三、3d预览 一、扩展板原理图 用pico作为主控,调用三个传感器,加上一个NB模块 排针间距差不多都是2.54(只要能插在洞洞板或者面包板)使用网络标签,对端口进行命名,相同…

终于看到一个不在 Backbone上研究 ReNet的了!直接优化小目标检测性能,不卷ImageNet-1K数据集!

终于看到一个不在 Backbone上研究 ResNet的了!直接优化小目标检测性能,不卷ImageNet-1K数据集! 前言 传统的基于深度学习的目标检测网络在数据预处理阶段常通过调整图像大小以达到特征图中的统一尺寸和尺度。调整大小的目的是为了便于模型传播…

1W 3KVDC 隔离 单输出 DC/DC 电源模块——TPB-1W 系列

TPB-1W系列产品是专门针对PCB上分布式电源系统中需要与输入电源隔离且输出精度要求较高的电源应用场合而设计。该产品适用于;1)输入电源的电压变化≤5%;2)输入输出之前要求隔离电压≥3000VDC;3)对输出电压稳…

CESS 受邀出席香港Web3.0标准化协会第一次理事会议,共商行业未来

2024 年 4 月 5 日,CESS(Cumulus Encrypted Storage System)作为香港 Web3.0 标准化协会的副理事会成员,于香港出席了 2024 年度第一次理事会会议。此次会议汇聚了来自不同领域的知名企业和专家(参会代表名单见文末&am…

Vue.extend()和我的两米大砍刀

Vue.extends是什么&#xff1f; 一个全局API,用于注册并挂载组件。 传统的引用组件的方式是使用import直接引入&#xff0c;但是使用Vue.extends()也可以实现。 使用规则 <div id"mount-point"></div>// 创建构造器 var Profile Vue.extend({templat…

4.16

1.总结keil5下载代码和编译代码需要注意的事项 1.&#xff09;仿真器设置&#xff1a; 点击魔术棒&#xff0c;选择debug选项&#xff0c;找到使用的仿真器&#xff0c;选择ST-LINK仿真器&#xff0c;点击setting&#xff0c;选择flash download ,勾选reset and run,选择pack…

SpringBoot整合Nacos

文章目录 nacosnacos下载nacos启动nacos相关配置demo-dev.yamldemo-test.yamluser.yaml 代码pom.xmlUserConfigBeanAutoRefreshConfigExampleValueAnnotationExampleDemoApplicationbootstrap.yml测试结果补充.刷新静态配置 nacos nacos下载 下载地址 一键傻瓜试安装即可,官…

其它IO合集

其它IO合集 1. 缓冲流1.1 概述1.2 字节缓冲流构造方法效率测试 1.3 字符缓冲流构造方法特有方法 2. 转换流2.1 字符编码和字符集字符编码字符集 2.2 编码引出的问题2.3 InputStreamReader类构造方法指定编码读取 2.4 OutputStreamWriter类构造方法指定编码写出转换流理解图解 3…

Java开发从入门到精通(十):Java的面向对象编程OOP:抽象类

Java大数据开发和安全开发 &#xff08;一)Java的抽象类1.1 什么是抽象类?1.2 抽象类的注意事项、特点1.3 认识一下抽象类1.4 抽象类的场景和好处1.4.1 抽象类案例 1.5 抽象类的常见应用场景:模板方法设计模式1.5.1 模板方法设计模式作用1.5.1 模板方法设计模式的写法 &#x…

AI开发者急需看的大语言模型基本原理

在学习大语言模型的基本原理的时候&#xff0c;发现遇到了很多问题&#xff0c;比如一个个单词是怎么转换为向量的&#xff0c;是提前得到的还是训练过程中得到&#xff1f;Transformer的输出向量又是怎么转化为自然语言的&#xff1f;Finetune的时候不是只需要输入和输出吗&am…

百度驾驶证C++离线SDK V1.1 C#接入

百度驾驶证C离线SDK V1.1 C#接入 目录 说明 效果 项目 代码 下载 说明 自己根据SDK封装了动态库&#xff0c;然后C#调用。 SDK包结构 效果 项目 代码 using Newtonsoft.Json; using OpenCvSharp; using System; using System.Collections.Generic; using System.…

不用Linux也可以的强大文本处理方法

不用Linux也可以的强大文本处理方法 标题党了&#xff0c;其实是论VIM的使用。 做生物信息分析最合适的还是Linux操作系统&#xff0c;所以生信宝典在最开始就推出了Linux学习系列&#xff0c;由浅入深的讲述了Linux学习中的关键点。 主要文章列举如下&#xff1a; Linux学…