首页 > 其他分享 >重拾JS-手写bind(延伸作用域理解,有助于面试)

重拾JS-手写bind(延伸作用域理解,有助于面试)

时间:2024-11-26 11:59:38浏览次数:10  
标签:函数 指向 作用域 bind JS 参数 apply

简言

最近在做前端知识的复习和整理,有了一些自己新的体会。更多在于记录,通过反复的温习,写笔记改变自己以前学习知识点的误区

关于Bind,Apply,Call

大家本能知道,当函数调用他们的时候就会将函数中的this,显示指向他们的第一个参数(新对象),那么为什么大家在面试或者其他场景下仍然会因为这个this指向而发愁呢?

答案是,大家没理解this在什么场景下才会作用

作用域,上下文

因为js是一门脚本语言,因此它不具备静态编译的特征,很多代码需要在程序执行过程中才会正确理解他的含义

但是js神奇之处在于,js有着静态(作用域)动态(执行上下文),两种表现。

静态 - 程序运行之前

当程序运行之前,程序会扫描所有的代码,会先以函数为范围划分作用域(全局作用域,函数作用域)

作用域:以一个函数区域为边界的范围

然后各个作用域会将各自的变量和函数做提升,提升到当前作用域的顶层,当然变量提升也仅仅只局限于使用var声明的变量,且只提升变量的声明

console.log(a); // undefined,因为只提升了a变量的声明,相当于 var a; console.log(a); a = 1;
var a = 1;

function o (){
    console.log(b); // undefined,只提升了b变量的声明
    var b = 2;
}

o();

接下来我们再看看下面这个例子

console.log(a);
a = 1;

function o (){
    console.log(b); 
    b = 2;
}
o();

执行结果后,a输出1,b输出b is not defined,这是为什么呢?

对比上述例子我们可以得到结论:

  1. 如果在全局作用域下没有使用var声明变量,则当程序运行前扫描,会将没有使用声明标识符的变量直接绑定到window上,而在程序运行时执行的console.log(a)相当于执行console.log(window.a)
  2. 如果在函数作用域下没有使用var声明的变量,则变量不会进行变量提升,也不会被绑定在window上,因为函数已经将b限制在函数的作用域内了,外部无法访问。因此当函数o执行时,调用console.log(b),当前函数作用域下没有找到b的声明,那么就会根据作用域链,向函数作用域外查找,发现全局作用域中也没有找到b的声明,则认定b未被定义

那么这就是在静态情况下,程序帮开发者做的预扫描,根据作用域范围,提升作用域中的变量以及函数

动态 - 执行上下文(this)

那么在动态情况下,this又是怎么表现的呢。大家也都知道this有几个指向,那也到这里就结束了,一做起面试题来就跪了,this指向又分为隐式指向显式指向

隐式指向

这里有个儿歌启蒙法:

家门口前有条河,上面有座桥,里面有群鸭

如果程序没有执行的时候,程序肯定静态的按家门口前有条河上面有座桥里面有群鸭,来一条条解析语句,那么这里有个问题,静态解析的时候,上面有座桥,里面有群鸭,在我们不知道的前提下,怎么知道上面里面代指是什么地方呢,因此我们就需要唱出家门口前有条河,那么这个家门口前的河就是thisthis的上面有座桥,this的里面有群鸭

总结就是:

  1. this是在程序运行时读取上下文获取的
  2. this指向调用(this)所在函数的那个对象

当程序运行时,函数真正被调用的时候,this就会指向那个调用函数的对象

第一点就是大家平时忽略最多的,因此总是做面试题时,找不到this指向

显式指向

显式改变this指向,这里大家都知道使用call,bind,apply

call,apply,bind的区别

  1. 传参方式不同
  2. 执行结果不同

传参方式不同

call和bind从第二个参数开始使用的是依次传入,而apply第二个参数接收的是一个参数ArrayList

执行结果不同

call和bind会将this指向到新对象后,立即执行原函数的函数体内容,而bind则会将这个原函数执行过程封装成一个函数作为结果返回,因此bind最后会返回一个函数

接下来我们就讲讲怎么去手写一个Bind

手写Bind

首先我们要确定以下需求

  1. bind要被挂载到什么地方 - Function.prototypes
  2. bind的输入(第一个参数为this所指向的新对象,第二个参数~最后一个参数依次传入)
  3. bind的输出(返回一个函数,这个函数中返回原函数改变this指向后的执行结果)
  4. 原函数改变this指向需要构建新的apply
  5. apply挂载到什么地方 - Function.prototypes
  6. apply的输入(第一个参数为新对象,也就是bind的第一个参数, 第二个参数为传入的参数列表-来源于bind第二个参数~最后一个参数)
  7. apply的输出(执行原函数-调用bind的那个函数)并返回函数执行结果
  8. 做apply的优化
// 1. bind要被挂载到 Function.prototypes
Function.prototype.myBind = function () {
  // 8. 做apply的优化
  if (typeof this !== "function") {
    // 8-1. 如果this不是函数,则抛出错误
    throw new TypeError("this is not a function");
  }

  // 2. bind的输入(第一个参数为this所指向的新对象,第二个参数~最后一个参数依次传入)
  const args = Array.prototype.slice.call(arguments); // arguments是一个类数组,并不是一个真正的数组,因此需要转换成真正的数组
  // 3-1. 获取新的this, 同时确保newThis一定有值
  const newThis = args.shift() || window;

  // 3-2, 获取原函数
  const _this = this; // 当原函数调用mybind的时候,this就指向原函数

  // 3. bind的输出(返回一个函数,这个函数中返回原函数改变this指向后的执行结果)
  return function () {
    // 3-3. 返回原函数改变this指向后的执行结果
    return _this.myApply(newThis, args);
  };
};

// 4. 原函数改变this指向需要构建新的apply
// 5. apply挂载到什么地方 - Function.prototypes
Function.prototype.myApply = function (context) {
  // 8. 做apply的优化
  if (typeof this !== "function") {
    // 8-1. 如果this不是函数,则抛出错误
    throw new TypeError("this is not a function");
  }

  // 6. apply的输入(第一个参数为新对象,也就是bind的第一个参数, 第二个参数为传入的参数列表-来源于bind第二个参数~最后一个参数)
  // 6-1 传入的对象一定有值
  context = context || window;

  // 6-2 将调用myApply的函数(this)隐式添加到context对象上,这里的context就是上面的newThis
  // 6-3 为了防止context上已经有fn,因此使用Symbol取唯一值
  const fn = Symbol();
  context[fn] = this;

  // 7. apply的输出(执行原函数-调用bind的那个函数)并返回函数执行结果
  let result = arguments[1] ? context[fn](...arguments[1]) : context[fn]();
  delete context[fn];

  return result;
};

结语

后面一段时间我会持续更新,希望多多三连

标签:函数,指向,作用域,bind,JS,参数,apply
From: https://www.cnblogs.com/laowangcoding/p/18569836

相关文章

  • Android中序列化方式:Serialzable接口、Parcelabel接口、Binder(一)
    Serializable接口Java提供的一个序列化空接口,为对象提供标准的序列化和反序列化操作,使用方法只需要在类的声明中指定一个标识publicclassUserimplementsSerializable{ privatestaticfinallongserialVersionUID=519067123721295773L; publicintuserId; pub......
  • 不用h标签、css和js怎么实现多个字的字体连续放大的效果?
    不用<h1>到<h6>标签、CSS和JavaScript实现多个字的字体连续放大的效果,在纯HTML中是无法实现的。HTML本身只提供语义和结构,控制样式和动态效果都需要CSS和JavaScript。连续放大的字体效果本质上是动态改变字体大小,这必须通过CSS和/或JavaScript来完成。虽然有一些HTML标签的默......
  • HTML-CSS-JS-day02:复合标签
    HTML常用标签之复合标签一、列表1)无序列表标签:<ultype=""><li></li><litype=""></li><li></li>      .....     </ul>属性:type列表样式-disc实心圆-circle空心圆......
  • 请使用原生的js实现斐波那契数列
    functionfibonacci(n){if(n<=0){return0;}elseif(n===1){return1;}else{leta=0;letb=1;lettemp;for(leti=2;i<=n;i++){temp=a+b;a=b;b=temp;}returnb......
  • 使用js实现摩斯密码的加密和解密
    constmorseCodeMap={'A':'.-','B':'-...','C':'-.-.','D':'-..','E':'.','F':'..-.','G':'--.',......
  • 请为什么说js是单线程,而不是多线程呢?
    JavaScript的单线程性质主要源于其最初的设计目标:操作浏览器中的DOM(文档对象模型)。如果JavaScript是多线程的,并且多个线程同时尝试修改DOM,就可能会出现竞态条件,导致DOM处于不一致或损坏的状态。想象一下,一个线程试图添加一个元素,而另一个线程同时试图删除同一个元素的父元素,这会导......
  • 说说用原生js实现封装一个选项卡的功能
    functioncreateTabs(tabContainerId,contentContainerId){consttabContainer=document.getElementById(tabContainerId);constcontentContainer=document.getElementById(contentContainerId);consttabHeaders=tabContainer.querySelectorAll('[dat......
  • 【前端】Next.js 性能优化技巧,让你的网站速度提升 50%!
    前言在当今互联网时代,网站的加载速度和性能直接关系到用户的满意度和留存率。特别是在竞争激烈的市场环境中,即使是几秒钟的延迟也可能导致用户流失。Next.js作为一款广受好评的React框架,不仅提供了强大的开发工具和丰富的功能,还内置了许多性能优化机制,帮助开发者构建高......
  • 理解 Python 作用域和命名空间
    在学习Python编程时,掌握作用域(Scope)和命名空间(Namespace)这两个关键概念是非常重要的。这不仅能帮助你更好地理解变量的生命周期与可见性,还能提高你编写高质量代码的能力。本文将深入探讨这两者的含义、区别以及如何在实际编码中应用。什么是命名空间?命名空间是一个存储变量......
  • HTML静态网页成品作业(HTML+CSS+JS)——动漫火影忍者网页设计制作(5个页面)
    ......