JS中this的应用场景,再了解下apply、call和bind!

this的应用场景,再了解下apply、call和bind

  • 一、谈谈对this对象的理解
  • 二、this的应用场景
    • 1、作为普通函数被调用
    • 2、使用call、apply和bind被调用
    • 3、作为对象方法被调用
    • 4、在class方法中被调用
    • 5、箭头函数中被调用
  • 三、apply、call和bind
    • 1、apply、call和bind的共同用法
    • 2、apply
    • 3、call
    • 4、bind
    • 5、做个小结
  • 四、写在最后

在写程序时,我们都知道this很好用,但是却很容易导致乱用。就像我刚开始学习箭头函数时,我知道这个箭头指代的是this,但是却不知道它往哪里指,所以在写程序时,就会想当然的乱写,导致有时候因为一个数据获取不到而疯狂找错,这无形之中要增加很大的时间成本,不懂原理胡来总是很容易事后两行泪(T_T)

在下面的这边文章中,将讲解关于this的几大应用场景以及了解在面试中经常会被问到的apply、bind和call究竟是什么,接下来开始进入本文的讲解。

一、谈谈对this对象的理解

  • this ,函数执行的上下文,总是指向函数的直接调用者(而非间接调用者),可以通过 applycallbind 改变 this 的指向。

  • 如果有 new 关键字,this 指向 new 出来的那个对象。

  • 在事件中,this 指向触发这个事件的对象,特殊的是,IE 中的 attachEvent 中的 this 总是指向全局对象 window

  • 对于匿名函数或者直接调用的函数来说,this 指向全局上下文(浏览器为 windowNodeJSglobal),剩下的函数调用,那就是谁调用它, this 就指向谁。

  • 对于 es6 的箭头函数,箭头函数的指向取决于该箭头函数声明的位置,在哪里声明, this 就指向哪里。

二、this的应用场景

在程序中,this主要有以下5种应用场景:

  • 作为普通函数被调用
  • 使用 callapplybind 被调用
  • 作为对象方法被调用
  • class 方法中被调用
  • 箭头函数中被调用

1、作为普通函数被调用

this 作为普通函数被调用时,指向 window 全局。

function fn1(){console.log(this);
}fn1(); //window

2、使用call、apply和bind被调用

this 使用 callapplybind 被调用时,直接指向作用域内的内容。

function fn1(){console.log(this);
}fn1(); //windowfn1.call({ x : 100 }); //{x : 100}
fn1.apply({x : 200}); //{x : 200}const fn2 = fn1.bind({ x : 200 });
fn2(); //{ x : 200 }

3、作为对象方法被调用

从下面代码中可以得出,当 this 放在 sayHi() 方法里面时,此时作为 zhangsan 对象的方法被调用,指向的是当前的对象。而放在 wait() 方法时,里面还有一个定时器,定时器里面还有一个函数,所以第二个 this 是作为普通函数被调用,指向 window 全局。

const zhangsan = {name: '张三',sayHi(){//this 即当前对象console.log(this);},wait(){setTimeout(function(){//this === windowconsole.log(this);});}
}

4、在class方法中被调用

从以下代码中可以看出,当 thisclass 中被调用时,指向的是整个对象。

class People{constructor(name){this.name = name;this.age = 20;}sayHi(){console.log(this);}
}const zhangsan = new People('张三');
zhangsan.sayHi(); //zhangsan 对象

5、箭头函数中被调用

看到以下代码,细心的小伙伴不难发现,跟我们上面第3点看到的似乎有点类似,主要区别在于定时器中的函数改为了箭头函数。当改为箭头函数时,此时的this指向的是zhangsan这一个整个对象,而不再是指向全局。

const zhangsan = {name: '张三',sayHi(){//this 即当前对象console.log(this);},waitAgain(){setTimeout(() => {//this 即当前对象console.log(this);});}
}

讲完箭头函数,我们来梳理下箭头函数和普通函数的区别,以及箭头函数是否能当做是构造函数的问题。

(1)箭头函数和普通函数定义

普通函数通过 function 关键字定义, this 无法结合词法作用域使用,在运行时绑定,只取决于函数的调用方式,在哪里被调用,调用位置。(取决于调用者,和是否独立运行)

箭头函数使用被称为 “胖箭头” 的操作 => 定义,箭头函数不应用普通函数 this 绑定的四种规则,而是根据外层(函数或全局)的作用域来决定 this ,且箭头函数的绑定无法被修改( new 也不行)。

(2)箭头函数和普通函数的区别

  • 箭头函数常用于回调函数中,包括事件处理器或定时器。
  • 箭头函数和 var self = this ,都试图取代传统的 this 运行机制,将 this 的绑定拉回到词法作用域。
  • 箭头函数没有原型、没有 this 、没有 super,没有 arguments ,没有 new.target
  • 箭头函数不能通过 new 关键字调用。
    • 一个函数内部有两个方法:[[Call]][[Construct]],在通过 new 进行函数调用时,会执行 [[construct]] 方法,创建一个实例对象,然后再执行这个函数体,将函数的 this 绑定在这个实例对象上。
    • 当直接调用时,执行 [[Call]] 方法,直接执行函数体。
    • 箭头函数没有 [[Construct]] 方法,不能被用作构造函数调用,当使用 new 进行函数调用时会报错。
function foo() { return (a) => { console.log(this.a); } 
}var obj1 = { a: 2 
}var obj2 = { a: 3 
}let bar1 = foo.call(obj1); //2
let bar2 = bar.call(obj2); 
console.log(bar2); //undefind

(3)this绑定的四大规则

this绑定四大规则遵循以下顺序:

New 绑定 > 显示绑定 > 隐式绑定 > 默认绑定

下面一一介绍四大规则。

  • 默认绑定:没有其他修饰( bindapplycall ),在非严格模式下定义指向全局对象,在严格模式下定义指向 undefined
function foo() { console.log(this.a); 
}
var a = 2; 
foo(); //undefined
  • 隐式绑定:调用位置是否有上下文对象,或者是否被某个对象拥有或者包含,那么隐式绑定规则会把函数调用中的 this 绑定到这个上下文对象。而且,对象属性链只有上一层或者最后一层在调用位置中起作用。
function foo() { console.log(this.a); 
}
var obj = { a: 2, foo: foo, 
}
obj.foo(); // 2
  • 显式绑定:通过在函数上运行 callapply ,来显式的绑定 this
function foo() { console.log(this.a); 
}
var obj = { a: 2 
};
foo.call(obj); //2

显示绑定之硬绑定

function foo(something) { console.log(this.a, something); return this.a + something; 
}
function bind(fn, obj) { return function() {return fn.apply(obj, arguments); }; 
}
var obj = { a: 2 
}
var bar = bind(foo, obj);
console.log(bar); //f()
  • New 绑定new 调用函数会创建一个全新的对象,并将这个对象绑定到函数调用的 thisNew 绑定时,如果是 new 一个硬绑定函数,那么会用 new 新建的对象替换这个硬绑定 this
function foo(a) { this.a = a; 
}
var bar = new foo(2); 
console.log(bar.a); //2

三、apply、call和bind

1、apply、call和bind的共同用法

先说下三者的共同用法,三者的共同用法就是可以改变函数的this指向,并将函数绑定到上下文中。接下来讲述一个应用场景加深理解:

let obj1 = {hobby: 'running',add(favorite){console.log(`在我的业余时间里,我喜欢${favorite},但同时我也喜欢${this.hobby}`);}
}let obj2 = {hobby: 'learning'
}obj1.add('reading'); //在我的业余时间里,我喜欢reading,但同时我也喜欢running

可以看到在最后一行代码中,我们调用了 obj1 中的 add 函数,并传入了一个参数 readingadd 函数中的 this 指的是他所在的对象 obj1 ,所以 this.hobby 就是 running , 但是我们如果想获得 obj2 中的hobby, 又该怎么处理呢?这就涉及到我们平常所听到的 applycallbind

接下来开始讲解 applycallbind

2、apply

(1)语法: Array.prototype.apply(this, [args1, args2])

(2)传入参数:

第一个参数:传入 this 需要指向的对象,即函数中的 this 指向谁,就传谁进来;

第二个参数:传入一个数组,数组中包含了函数需要的实参。

(3)apply的作用:①调用函数;指定函数中 this 的指向。

(4)代码演示:

/*** * @description 实现apply函数,在函数原型上封装myApply函数, 实现和原生apply函数一样的效果*/Function.prototype.myApply = function(context){// 存储要转移的目标对象_this = context ? Object(context) : window;// 在转移this的对象上设定一个独一无二的属性,并将函数赋值给它let key = Symbol('key');_this[key] = this;// 将数组里存储的参数拆分开,作为参数调用函数let res = arguments[1] ? _this[key](...arguments[1]) : _this[key]();// 删除delete _this[key];// 返回函数返回值return res;
}

(5)前情回顾

实现了 myApply 之后,我们继续引用刚开始关于爱好的那个例子,来修改 this 的指向。

let obj1 = {hobby: 'running',add(...favorite){ //...favorite意味着可以接收多个参数console.log(`在我的业余时间里,我喜欢${favorite},但同时我也喜欢${this.hobby}`);}
}let obj2 = {hobby: 'learning'
}obj1.add.myApply(obj2, ['reading', 'working']); // 输出结果:在我的业余时间里,我喜欢reading,working,但同时我也喜欢learning

obj1.add.myApply(obj2, ['reading', 'working']) 这一行代码, 第一个参数将 obj1 中的 add 函数的 this 指向了 obj2 , 第二个参数以数组形式传入多个参数,作为 obj1 中的 add 函数传入的参数, 所以最后能将 readingworking 都输出。

3、call

(1)语法: Array.prototype.call(this, args1, args2)

(2)传入参数:

第一个参数:传入 this 需要指向的对象,即函数中的 this 指向谁,就传谁进来;

其余参数: 除了第一个参数,其他的参数需要传入几个,就一个一个传递进来即可。

(3)call的作用:①调用函数;指定函数中 this 的指向。

(4)代码演示:

/*** * @description 实现apply函数,在函数原型上封装myApply函数, 实现和原生apply函数一样的效果*/Function.prototype.myCall = function(context){// 存储要转移的目标对象let _this = context ? Object(context) : window;// 在转移this的对象上设定一个独一无二的属性,并将函数赋值给它let  key = Symbol('key');_this[key] = this;// 创建空数组,存储多个传入参数let args = [];// 将所有传入的参数添加到新数组中for(let i =1; i < arguments.length; i++){args.push(arguments[i]);}// 将新数组拆开作为多个参数传入,并调用函数let res = _this[key](...args);// 删除delete _this[key];// 返回函数返回值return res;
}

(5)前情回顾

实现了 myCall 之后,我们继续引用刚开始关于爱好的那个例子,来修改 this 的指向。

let obj1 = {hobby: 'running',add(...favorite){ //...favorite意味着可以接收多个参数console.log(`在我的业余时间里,我喜欢${favorite},但同时我也喜欢${this.hobby}`);}
}let obj2 = {hobby: 'learning'
}obj1.add.myCall(obj2, 'reading', 'working');// 输出结果:在我的业余时间里,我喜欢reading,working,但同时我也喜欢learning

obj1.add.myCall(obj2, 'reading', 'working') 这一行代码, 第一个参数将 obj1 中的 add 函数的 this 指向了 obj2 , 第二个参数通过依次传入多个参数的形式,作为 obj1 中的 add 函数传入的参数, 所以最后能将 readingworking 都输出。

讲到这里,我们来梳理下 callapply 的区别:

callapply 唯一的区别就是在于给函数传入参数的形式不同call 是将多个参数逐个传入, 而apply 是 将多个参数放在一个数组中,一起传入。

4、bind

(1)语法: Array.prototype.bind(this, args1, args2)

(2)传入参数:

第一个参数:传入 this 需要指向的对象,即函数中的 this 指向谁,就传谁进来;

其余参数: 除了第一个参数,其他参数的传递可以像 apply 一样的数组类型,也可以像 call 一样的逐个传入;但需注意的是后面需要加个小括号进行其余参数的传递。

(3)call的作用:①克隆当前函数,返回克隆出来的新函数;新克隆出来的函数,该函数的this被指定了。

(4)代码演示:

/*** @description 实现Bind函数,在函数原型上封装myBind函数 , 实现和原生bind函数一样的效果* */Function.prototype.myBind = function(context){// 存储要转移的目标对象let _this = context ? Object(context) : window;// 在转移this的对象上设定一个独一无二的属性,并将函数赋值给它let key = Symbol('key');_this[key] = this;// 创建函数闭包return function(){// 将所有参数先拆分开,再添加到新数组中,以此来支持多参数传入以及数组参数传入的需求let args = [].concat(...arguments);// 调用函数let res = _this[key](...args);// 删除delete _this[key];// 返回函数返回值return res;}
}

(5)前情回顾

实现了 myBind 之后,我们继续引用刚开始关于爱好的那个例子,来修改 this 的指向。

let obj1 = {hobby: 'running',add(...favorite){ //...favorite意味着可以接收多个参数console.log(`在我的业余时间里,我喜欢${favorite},但同时我也喜欢${this.hobby}`);}
}let obj2 = {hobby: 'learning'
}obj1.add.myBind(obj2)(['reading', 'working']);// 输出结果:在我的业余时间里,我喜欢reading,working,但同时我也喜欢learning

通过以上我们可以看到, bind 有点类似 applycall 的结合,只不过它返回的是一个函数,需要自身再进行一次调用, 而传给这个函数的参数形式有两种方式,可以是像 apply 一样的数组形式, 也可以是像 call 一样的逐个传入的形式。

大家不要觉得这个后面加个小括号太麻烦,这就是 bind 的强大之处,有时候 bind也会经常运用在函数柯里化中。

讲到这里,关于this的相关知识就讲完啦!接下来我们来做个总结。

5、做个小结

  • this 取什么样的值,是在函数执行时确定的,不是在函数定义的时候确定的。

  • applycallbind 三者都是函数的方法,都可以改变函数的 this 指向。

  • applycall 都是改变函数 this 指向,并传入参数后立即调用执行该函数。

  • bind 是在改变函数 this 指向后,并传入参数后返回一个新的函数,不会立即调用执行。

  • apply 传入的参数是数组形式的,call 传入的参数是按顺序的逐个传入并以逗号隔开, bind 传入的参数既可以是数组形式,也可以是按顺序逐个传入。

四、写在最后

关于 this 的指向问题在前端的面试中尤为常见,大家可以按照上文中的顺序把 this 的知识点串联起来一起理解!同时,本文内容为本人理解所整理,可能会存在边界歧义等问题。如果有不理解或者有误的地方欢迎私聊我或加我微信指正~

  • 公众号:星期一研究室
  • 微信:MondayLaboratory

如果这篇文章对你有用,记得点个赞再走哦~

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

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

相关文章

【BCVP更新】StackExchange.Redis 的异步开发方式

有哪些习惯坚持LESS IS MORE,SIMPLER IS BETTER THAN MORE你一定会有很大的收获各种小问题&#xff1f;如果你之前用过Redis的话&#xff0c;肯定会使用过StackExchange.Redis&#xff0c;我之前很久就用过&#xff0c;在.netfw的时候&#xff0c;当时并发还比较小&#xff0c;…

map容器实现一对多

一&#xff1a;需求描述 我们希望一个数字或则其他字符串可以对应 一串数&#xff0c; #include<iostream> #include<map> #include<vector> using namespace std; int main(){map<int,vector<int> > m;map<int,vector<int> >:: i…

解决异步问题,教你如何写出优雅的promise和async/await,告别callback回调地狱!

解决异步问题——promise、async/await一、单线程和异步1、单线程是什么2、为什么需要异步3、使用异步的场景二、promise1、promise的三种状态2、三种状态的表现和变化&#xff08;1&#xff09;状态的变化&#xff08;2&#xff09;状态的表现3、then和catch对状态的影响&…

使用 Visual Studio 2019 批量添加代码文件头

应用场景介绍在我们使用一些开源项目时&#xff0c;基本上都会在每个源代码文件的头部看到一段版权声明。一个项目或解决方案中源代码文件的个数少则几十&#xff0c;多则几千甚至更多&#xff0c;那么怎么才能给这么多文件方便地批量添加或者修改一致的文件头呢&#xff1f;在…

7-3 模板题 (10 分)(思路+详解)

一:题目 二&#xff1a;思路 1.读题读不懂&#xff0c;那就分析给出的示例&#xff0c;本题意思就是给出一串数&#xff0c;然后找出找出该元素之后&#xff0c;第一个大于 该元素的下标&#xff08;这一串数的下标是从一开始的&#xff09;如果找不到比起大的&#xff0c;那就…

提升对前端的认知,不得不了解Web API的DOM和BOM

了解Web API的DOM和BOM引言正文一、DOM操作1、DOM的本质2、DOM节点操作&#xff08;1&#xff09;property形式&#xff08;2&#xff09;attribute形式3、DOM结构操作&#xff08;1&#xff09;新增/插入节点&#xff08;2&#xff09;获取子元素列表&#xff0c;获取父元素&a…

Dapr微服务应用开发系列1:环境配置

题记&#xff1a;上篇Dapr系列文章简要介绍了Dapr&#xff0c;这篇来谈一下开发和运行环境配置本机开发环境配置安装Docker为了方便进行Dapr开发&#xff0c;最好&#xff08;其实不一定必须&#xff09;首先在本机&#xff08;开发机器&#xff09;上安装Docker。安装方式可以…

leetcode704二分法:(左闭右闭+左闭右开)

前言 又重温了一遍<肖生客的救赎> 其中安迪的一句话一直回荡我的脑中&#xff1a;“人生可以归结为一种简单的选择&#xff1a;不是忙着活&#xff0c;就是忙着死。” 多深刻&#xff0c;多简单&#xff0c;又多令人深省&#xff0c; 哪有那么多选择 哪有那么多时间去花…

你真的理解事件绑定、事件冒泡和事件委托吗?

一文了解Web API中的事件绑定、事件冒泡、事件委托引言正文一、事件绑定1、事件和事件绑定时什么&#xff1f;2、事件是如何实现的&#xff1f;二、事件冒泡1、事件模型2、事件模型解析&#xff08;1&#xff09;捕获阶段&#xff08;2&#xff09;目标阶段&#xff08;3&#…

欢迎来到 C# 9.0(Welcome to C# 9.0)

翻译自 Mads Torgersen 2020年5月20日的博文《Welcome to C# 9.0》&#xff0c;Mads Torgersen 是微软 C# 语言的首席设计师&#xff0c;也是微软 .NET 团队的项目群经理。C# 9.0 正在成形&#xff0c;我想和大家分享一下我们对下一版本语言中添加的一些主要特性的想法。对于 C…

367. 有效的完全平方数(二分法)

一&#xff1a;题目 二:思路 完全平方数:若一个数能表示成某个整数的平方的形式&#xff0c;则称这个数为完全平方数 思路:1.我们将num先折半,因为它是某个整数的平方&#xff0c;而这个数的范围肯定不会超过num的一半 2.那么这就相当于在[left,num/2]中查找某个数&#xff0c…

跨越跨域大山,前端不得不知道的Ajax

AJAX和跨域引言正文一、AJAX请求1、模拟get和post请求2、封装一个简易的AJAX二、状态码1、xhr.readyState2、xhr.status三、跨域1、同源策略&#xff08;1&#xff09;同源策略是什么&#xff08;2&#xff09;为什么浏览器会有同源策略&#xff1f;&#xff08;3&#xff09;同…

译 | Azure 应用服务中的程序崩溃监控

点击上方蓝字关注“汪宇杰博客”原文&#xff1a;Yun Jung Choi, Puneet Gupta翻译&#xff1a;汪宇杰应用程序崩溃经常发生。崩溃是指代码中的异常未得到处理并终止进程。这些未处理的异常也称为二次机会异常&#xff08;second chance exceptions&#xff09;。当您的应用程序…

解决浏览器存储问题,不得不了解的cookie,localStorage和sessionStorage

浏览器存储方式一、浏览器存储的方式二、cookie、localStorage和sessionStorage1、cookie、localStorage和sessionStorage是什么&#xff1f;&#xff08;1&#xff09;cookie&#xff08;2&#xff09;localStorage&#xff08;3&#xff09;sessionStorage2、cookie、localSt…

使用Seq搭建免费的日志服务

Seq简介Seq是老外开发的一个针对.NET平台非常友好的日志服务。支持容器部署&#xff0c;提供一个单用户免费的开发版本。官网&#xff1a;https://datalust.co/seq使用文档&#xff1a;https://docs.datalust.co/docsSeq主体功能如下所示&#xff1a;支持主流的编程语言&#x…

leetcode27:移除元素(暴力+双指针)

一&#xff1a;题目 二&#xff1a;暴力双指针 1&#xff1a;暴力解法 (1):思路 1.在数组当中 我们想要删除一个元素 得靠覆盖也就是后面的元素往前覆盖其想要删除的元素 但是注意的是我们真实的数组中的元素个数是不变的 因为我们只是将后面的元素移到起前面 并未真正的删除…

三分钟Docker-推送本地镜像到仓库

在上篇文章中&#xff0c;我们完成了应用程序容器化&#xff0c;把webapi项目构建镜像并容器化运行。本文将会演示如何把自己构建的镜像上传到docker官网的仓库和自己私有仓库本地镜像推送到官网的registry1.创建仓库点击Docker Desktop图标->Repositories-》create 跳转到…

你知道304吗?图解强缓存和协商缓存

http协议—常见状态码&#xff0c;请求方法&#xff0c;http头部&#xff0c;http缓存一、http状态码1、引例阐述2、状态码分类3、常见状态码4、关于协议和规范二、http 方法1、传统的methods2、现在的methods3、Restful API&#xff08;1&#xff09;Restful API是什么&#x…

leetcode844. 比较含退格的字符串(栈+双指针)

一:题目 二:思路代码 1:利用栈 (1):思路 1.利用栈 我们将字符串中的单个元素都入栈 当遇到’#的时候将将栈顶元素弹出 (2):上码&#xff08;方法一&#xff09; class Solution { public:/**思路:1.利用栈 我们将字符串中的单个元素都入栈 当遇到#的时候将将栈顶元素弹出*…

efcore技巧贴-也许有你不知道的使用技巧

前言.net 环境近些年也算是稳步发展。在开发的过程中&#xff0c;与数据库打交道是必不可少的。早期的开发者都是DbHelper一撸到底&#xff0c;到现在的各种各样的ORM框架大行其道。孰优孰劣谁也说不清楚&#xff0c;文无第一武无第二说的就是这个理。没有什么最好的&#xff0…