手写代码(1) call apply bind 数据类型判断 Object.create new 防抖节流 柯里化

手写代码(1) call apply bind 数据类型判断 Object.create new 防抖节流 柯里化手写代码 0 开始手写代码之前 需要知道什么 call apply 和 bind 介绍 call apply bind 都是为了改变函数中 this 的指向 可以让任意对象调用任意函数 等同于将函数作为了对象的属性 可以通过对象 属性的方式调用函数 第一个参数都是 this 要指向的对象

大家好,我是讯享网,很高兴认识大家。

手写代码(0) 开始手写代码之前,需要知道什么

call,apply和bind

介绍

call,apply,bind都是为了改变函数中this的指向,可以让任意对象调用任意函数,等同于将函数作为了对象的属性,可以通过对象.属性的方式调用函数,第一个参数都是this要指向的对象,如果没有这个参数或参数为undefined或null,默认this指向全局window

区别:

  • call接受第一个参数是this的指向,后面传入的是参数列表;apply接受两个参数,第一个是this的指向,第二个参数是函数接受的参数,以数组的形式传入;bind接受第一个参数是this的指向,后面传入的是参数列表(可以分多次传入)
  • call和apply在改变this指向的同时会立即执行函数返回结果,是一次性的;bind不会立即执行函数,而是返回一个永久改变this指向的函数,并且此后this的指向无法再通过call,apply,bind改变

call 函数

实现流程
  1. 判断调用对象是否为函数,即使是定义在函数的原型上,也可能出现用call等方式调用的情况
  2. 判断传入上下文对象是否存在,不存在则设置为window
  3. 处理传入的参数,截取第一个参数后的所有参数
  4. 将函数作为上下文对象的一个属性
  5. 使用上下文对象来调用这个方法并保存返回结果
  6. 删除刚才新增的属性
  7. 返回结果
代码
Function.prototype.myCall = function(context) { 
    if (typeof this !== "function") { 
    console.error("type error"); } let args = [...arguments].slice(1);//将arguments每个元素取出来再放到数组里,截取参数 let result = null; context = context || window; context.fn = this;//普通函数中this指向函数调用者 result = context.fn(...args);//将需要调用的函数作为对象上的一个属性,通过context.fn()的方式来调用 delete context.fn; return result; }; 

讯享网

在这里插入图片描述
讯享网

apply 函数

实现流程
  1. 判断调用对象是否为函数,即使我们是定义在函数的原型上的,但是可能出现使用 call 等方式调用的情况。
  2. 判断传入上下文对象是否存在,如果不存在,则设置为 window 。
  3. 将函数作为上下文对象的一个属性。
  4. 判断参数值是否传入
  5. 使用上下文对象来调用这个方法,并保存返回结果。
  6. 删除刚才新增的属性
  7. 返回结果
代码
讯享网Function.prototype.myApply = function(context) { 
    if (typeof this !== "function") { 
    throw new TypeError("Error"); } let result = null; context = context || window; context.fn = this; if (arguments[1]) { 
   //存在函数接受的参数数组 result = context.fn(...arguments[1]);//从类数组中取出,传参 } else { 
    result = context.fn(); } delete context.fn; return result; }; 

在这里插入图片描述

bind 函数

实现流程
  1. 判断调用对象是否为函数,即使我们是定义在函数的原型上的,但是可能出现使用 call 等方式调用的情况。
  2. 保存当前函数的引用,获取其余传入参数值。
  3. 创建一个函数返回
  4. 函数内部使用 apply 来绑定函数调用,需要判断函数作为构造函数的情况,这个时候需要传入当前函数的this给apply 调用,其余情况都传入指定的上下文对象。
代码
Function.prototype.myBind = function(context) { 
    if (typeof this !== "function") { 
    throw new TypeError("Error"); } var args = [...arguments].slice(1), fn = this; return function Fn() { 
    //因为bind返回的是函数,如果它被new Fn(),即Fn作为了构造函数,Fn的实例就是当前函数的this return fn.apply( this instanceof Fn ? this : context, args.concat(...arguments) ); }; }; 

在这里插入图片描述

this从objNum指向了test:

在这里插入图片描述

instanceof 方法

介绍

判断对象类型(引用数据类型),内部机制是判断构造函数的prototype属性是否出现在对象的原型链中的任何位置

基本数据类型在刚创建时会创建对应的包装类型对象(声明周期只有一瞬),基本数据类型不是对象 ,所以instanceof无法给出正确结果

typeof null返回object,因为js在底层存储变量时,会在变量的机器码的低位1-3位存储其类型信息:

机器码 类型信息
000 对象
1 整数
010 浮点数
100 字符串
110 布尔

null的所有机器码均为0,所以被当成对象。但null不具有任何对象的特性,也没有__proto__属性,所以instanceof无法正确判断null

实现流程

  1. 检验需要判断的是否是对象
  2. 获取类型的原型
  3. 获取需要判断的对象的原型
  4. 循环判断对象的原型是否和类型的原型指向一个地方,直到对象原型到达原型链终点null

代码

讯享网function myInstanceof(left, right) { 
   //left是需要判断的对象,right是构造函数 if(typeof left !== 'object' || left === null) return false;//判断left是不是对象 let proto = Object.getPrototypeOf(left), // 获取对象的原型,相当于left. __proto__ prototype = right.prototype; // 获取构造函数的 prototype 对象 while (true) { 
   // 判断构造函数的 prototype 对象是否在对象的原型链上 if (!proto) return false; if (proto === prototype) return true; proto = Object.getPrototypeOf(proto);//沿着原型链找 } } 

在这里插入图片描述

类型判断函数

介绍

  1. 数组、对象、null都会被typeof判断为object
  2. Object.prototype.toString.call()可以正确判断基本类型和原生引用类型,不能准确判断自定义类型
  3. toString是Object的原型方法,而Array、function等类型作为Object的实例,都重写了toString方法

实现流程

  1. 判断是否等于null
  2. 用typeof判断是否是基本类型,是则返回typeof判断的结果
  3. 引用类型用Object原型上的toString方法判断,截取类型再返回

代码

function getType(value) { 
    if (value === null) { 
    return value + ""; } if (typeof value === "object") { 
    let valueClass = Object.prototype.toString.call(value), type = valueClass.split(" ")[1].split("");//分割字符串,返回数组 type.pop(); return type.join("").toLowerCase(); } else { 
    return typeof value; } } 

在这里插入图片描述

new 操作符

介绍

new的作用是通过构造函数来创建一个实例对象,这个实例对象可以访问到构造函数原型上的属性

实现流程

  1. 创建一个新的空对象
  2. 设置原型,将对象的原型设置为函数的prototype对象
  3. 让函数的this指向这个对象,执行构造函数的代码(给这个新对象添加属性)
  4. 判断函数返回值类型,如果是值类型,返回创建的对象。如果是引用类型,返回这个引用类型的对象

代码

讯享网function objectFactory() { 
    let newObject = null; let constructor = Array.prototype.shift.call(arguments);//删除第一个参数并返回这个参数 let result = null; if (typeof constructor !== "function") { 
    console.error("type error"); return; } //新建原型为构造函数的prototype对象的空对象 newObject = Object.create(constructor.prototype); //给newObject增加constructor"属性"并调用,相当于newObject.constructor(arguments) //arguments在上面已经被删除了第一个参数,剩下传参部分 result = constructor.apply(newObject, arguments); //如果构造函数没有显式返回一个对象,则使用新建的空对象,否则返回指定对象 let flag = result && (typeof result === "object" || typeof result === "function"); return flag ? result : newObject; } 

在这里插入图片描述

Object.create

介绍

Object.create(proto,[propertiesObject])

proto:新创建对象的原型对象,如果不是null或非原始包装对象,则抛出异常

prop:可选,也是一个对象,用来设置新创建对象的自有可枚举属性

返回一个新对象,带着指定的原型对象和属性

实现流程

  1. 判断第一个参数的类型是否是object或null
  2. 创建新函数,新函数的原型指向第一个参数
  3. 创建新函数的实例
  4. 如果第二个参数不是undefined,将属性加入实例

代码

function create(proto, propertiesObject = undefined){ 
    if(!(obj instanceof Object) && proto !== null) { 
    throw Error('Error'); } function F(){ 
   } F.prototype = proto; let obj = new F(); if(propertiesObject !== undefined) { 
    Object.defineProperties(obj, propertiesObject); } return obj; } 

在这里插入图片描述

在这里插入图片描述

防抖函数

介绍

防抖是指当持续触发事件时,一定时间段内没有再触发事件,事件处理函数才会执行一次,如果设定时间到来之前,又触发了事件,就重新开始计时。可以通俗理解为:防止用户频繁触发一个事件,只执行该事件的最后一次

乘公交车,一直有人陆陆续续上车(事件触发),司机心想30s(时间间隔) 内没人继续上,再开车(函数执行)。

实现流程

  1. 闭包内设置定时器(指向定时器的变量不被垃圾回收机制回收)
  2. 在规定时间内再次触发,清除上次调用的计时器,重新设置定时器计时

代码

讯享网function debounce(fn, wait) { 
    let timer = null; return function() { 
   //存在闭包,实际上debounce只会执行一次,之后执行的都是这个返回函数 let context = this, args = arguments; if (timer) { 
   //在规定时间内再次触发,重新计时 clearTimeout(timer); timer = null; } //设置定时器,使事件间隔指定时间后执行 timer = setTimeout(() => { 
    fn.apply(context, args); }, wait); }; } 

在这里插入图片描述

节流函数

介绍

节流是指动作绑定事件后,动作触发事件,在这段事件内,如果动作又发生,则无视该动作,直到事件执行完后才能重新触发。可以通俗理解为:在指定的事件间隔内,只允许该函数执行一次

在公交车总站乘坐公交车,不管车上是否有人,不管是否有人上车,若规定了10分钟发一次车(时间间隔),那么只有在这辆车发车之后下辆车才会发车。

实现流程

  1. 设置时间戳(闭包会保存curTime时间戳,因为变量被返回的函数引用,所以无法被垃圾回收机制回收)
  2. 当触发事件的时候,取出当前的时间戳nowTime,减去之前的时间戳curTime
  3. 结果大于设置的时间周期,则执行函数,然后更新时间戳为当前时间戳:curTime = Date.now();
  4. 结果小于设置的时间周期,则不执行函数

代码

function throttle(fn, delay) { 
    let curTime = Date.now(); return function() { 
   //存在闭包,实际上throttle只会执行一次,之后执行的都是这个返回函数 let context = this, args = arguments, nowTime = Date.now(); //如果两次时间间隔超过了指定时间,则执行函数,否则忽略 if (nowTime - curTime >= delay) { 
    curTime = Date.now(); return fn.apply(context, args); } }; } 

在这里插入图片描述

柯里化

介绍

柯里化又称部分求值。一个柯里化的函数会首先接收一些参数,但不会立即求值,而是继续返回另一个函数,之前传入的参数在函数形成的闭包中被保存起来,等到函数真正被需要求值时,之前传入的所有参数都会被一次性用于求值

实现流程

接收函数和参数,判断当前传入的参数数量是否已经满足函数所需要参数数量,如果满足,执行函数,否则递归返回柯里化的函数等待参数的传入

代码

讯享网function curry(fn, ...args) { 
    return fn.length <= args.length ? fn(...args) : curry.bind(null, fn, ...args); } 

在这里插入图片描述

可以注意到,当参数数量满足函数所需数量时它就自动执行了,但如果我们想控制它的执行时间怎么办

实现可控制的执行时间的柯里化函数:参数足够时并不会执行,只有再调用一次函数它才会执行并返回结果

let newCurry = (fn,...params)=> { 
    let args = params || []; let fnLen = fn.length;//所需参数数量 return (...res)=> { 
    let allArgs = args.slice(0); allArgs.push(...res); //如果输入了参数(所以即使传入参数数量够,也不会执行函数,只有()才会执行函数)或当前参数数量还不够,递归返回柯里化的函数,等待下次参数传入 if(res.length > 0 || allArgs.length < fnLen){ 
    return newCurry.call(this,fn,...allArgs); }else{ 
    return fn.apply(this,allArgs); } } } 

在这里插入图片描述

应用场景

  • 参数复用,减少重复参数的传递
    在这里插入图片描述

比如说求四个数字之和,已经确定一个数字是1,那么只要再传入其它三个数字就行了,不用每次都再传入一次1

  • 延时执行
    像上面写的一样,参数数量满足要求,但并不想立即执行
  • 提前确认
讯享网//兼容性检测 const whichEvent = ( function () { 
    // 优先判断addEventListener if(window.addEventListener){ 
    return function(element,type,listener,useCapture){ 
    element.addEventListener(type,function(e){ 
    listener.call(element,e); },useCapture); } // 判断IE  }else if(window.attchEvent){ 
    return function(element,type,handler){ 
    element.attchEvent('on'+element,function(e){ 
    handler.call(element,e); }); } } } )(); 

总结

学习手写代码的过程中,用到了很多平常不怎么接触的知识,也对底层原理有了一些认识,更理解到了:要想学好一个知识点,就去用它,这个过程会产生很多光是看代码想不到的问题,解决了这些问题会对这个知识点了解得更加透彻。

小讯
上一篇 2025-02-23 23:52
下一篇 2025-04-08 08:28

相关推荐

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/39482.html