this指向与call,apply,bind

「this」问题对于每个前端同学来说相信都不陌生,在平时开发中也经常能碰到,有时候因为「this」还踩过不少坑,并且「this」问题在面试题中出现的概率也非常高,我们一起来了解一下this的指向与callapplybind

this的指向

ES5中的this

「在ES5中,this一般指向函数调用时所在的执行环境,与函数定义的位置无关。也可以理解成this永远指向最后调用它的对象」

  • 在普通函数中的this总是指向它的「直接调用者」,默认情况下指向全局对象(浏览器为window)
  • 在严格模式下,没有直接调用者的函数中的thisundefined
  • Call, apply,bind函数,this指向的是绑定的对象
  • 对象函数调用,this指向调用它的那个对象
  • 构造函数中的this,指向该构造函数new出来的实例对象
var obj = {
    a:function(){
        console.log(this)
        console.log(this.b)
        console.log(this.c)
        console.log(this.a)
    },
    b:2,
    c:3
}
var b = obj.a
b()
// 结果:window,f(){...},undefined,undefined
obj.a()
// 结果:{a:..,b:2,c:3},2,3,f(){...}

/**
 * 解析:
 * b()调用,此时b函数所处的执行环境是全局环境,this指向window
 * obj.a()调用,此时a是作为对象方法进行调用,this指向调用对象obj
 */
ES6中的this

「在ES6中新增了一种箭头函数,箭头函数的this始终指向它定义时的this,而非执行时」

  • 箭头函数没有自己的this,它的this是继承来的,默认指向它定义时所在的对象,即箭头函数中的this指向外层代码的this
  • 不可以当作构造函数,也就是说,不可以用new命令调用,否则会抛出一个错误
  • 箭头函数内没有arguments对象,可以用rest参数代替
  • 不可以使用yield命令,因此箭头函数不能用作generator函数
  • 箭头函数没有自己的this,所以不能用callapplybind这些方法改变this指向
var obj = {
            hi: function(){
                console.log(this);
                return ()=>{
                    console.log(this);
                }
            },
            sayHi: function(){
                return function() {
                    console.log(this);
                    return ()=>{
                        console.log(this);
                    }
                }
            },
            say: ()=>{
                console.log(this);
            }
        }
        const hi = obj.hi()   //this->obj对象
        hi()   // this->obj对象
        const sayHi = obj.sayHi()
        const sayHiBack = sayHi()  //this->window
        sayHiBack() //this->window
        obj.say() //this->window

「输出结果依次为obj对象,obj对象,window,window,window」

解析:

1.第一个obj.hi()很好理解,hi为普通函数,this指向调用它的那个对象,即obj

2.第二个执行hi(),它其实是上一个执行后返回的函数,并且是箭头函数,箭头函数本身没有this,我们往他的上一级去查找,我们刚刚得出上一级的this为obj,所以这里的this也指向obj

3.第三个执行obj.sayHi(),这里没有打印this,而是返回了一个普通函数

4.第四个执行sayHi(),其实执行的是刚刚返回的那个普通函数,这里的this则指向调用它的那个对象,没有则指向window

5.第五个执行sa yHiBack(),指向的是刚刚第四次执行返回的箭头函数,OK,箭头函数我们往上一层找,也是window

5.第六个执行obj.say(),这里这个say()是一个箭头函数,当前代码块obj不存在this,只能往上一层查找,指向window

call, apply,bind的区别

我们都知道call,apply,bind都可以用来改变this指向,但这三个函数稍稍有些不同。

  • call与apply唯一的区别就是它们的传参方式不同,call从第二个参数开始都是传给函数的,apply只有两个参数,第二个参数是一个数组,传给函数的参数都写在这个数组里面
  • call与apply改变了函数的this指向后会立即执行,而bind是改变函数的this指向并返回这个这个函数,不会立即执行
  • call与apply的返回值是函数的执行结果,bind的返回值是改变了this指向的函数的拷贝
call

call() 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。(来自MDN)

「语法:」 fun.call(thisArg,arg1[,arg2,arg3...])

「解释:」 call方法用来为一个函数指定this对象,第一个参数是你想要指定的那个对象,后面都是传给该函数的参数,之间用逗号隔开

var person = {
  name:'南玖',
  gender: 'boy',
}
var speak = function(age,hobbit){
  console.log(`我是${this.name},今年${age}岁,爱好${hobbit},欢迎优秀的你关注~`)
}
speak.call(person,18,'前端开发') // 我是南玖,今年18岁,爱好前端开发,欢迎优秀的你关注~
apply

apply() 方法调用一个具有给定this值的函数,以及以一个数组(或类数组对象)的形式提供的参数。(来自MDN)

「语法:」 fun.apply(thisArg,[arg1,arg2,arg3...])
「解释:」 apply方法与call方法基本类似,不同的是,两者的参数形式,apply方法传递的是一个由若干个参数组成的数组。

var person = {
  name:'南玖',
  gender: 'boy',
}
function speak(age,hobbit){
  console.log(`我是${this.name},今年${age}岁,爱好${hobbit},欢迎优秀的你关注~`)
}
speak.apply(person,[18,'打篮球⛹️']) //我是南玖,今年18岁,爱好打篮球⛹️,欢迎优秀的你关注~
bind

bind() 方法会创建一个新函数。当这个新函数被调用时,bind() 的第一个参数将作为它运行时的 this,之后的一序列参数将会在传递的实参前传入作为它的参数。(来自于 MDN )

「语法:」 fun.bind(thisArg,arg1[,arg2,arg3...])()
「解释:」 bind方法用来为方法指定this对象并返回一个新的函数,它的参数与call函数一样。「它本身是不会调用的,需要自己手动调用。」

var person = {
  name:'南玖',
  gender: 'boy',
}
function speak(age,hobbit){
  console.log(`我是${this.name},今年${age}岁,爱好${hobbit},欢迎优秀的你关注~`)
}
speak.bind(person,18,'旅游⛱️')() //我是南玖,今年18岁,爱好旅游⛱️,欢迎优秀的你关注~

「注意这里需要自己再调用一次,因为bind只会返回这个改变了this指向的函数,并不会自己执行」

call,apply该用哪个?

  • 参数数量,顺序确定就用call,参数数量,顺序不确定就用apply
  • 参数数量少用call,参数数量多用apply
  • 参数集合已经是一个数组的情况,最好用apply

bind的应用场景

1.保存参数

我们先来看一道经典面试题

for(var i=1;i<6;i++){
  setTimeout(()=>{
    console.log(i)  // 6,6,6,6,6
  },i*1000)
}

相信大家都知道这里会打印出五个6,因为在执行settimeout回调函数时,i已经变成了6

那么如何让它打印出1,2,3,4,5呢?

当然方法有很多,比如闭包、将var改成let使它形成块级作用域,这里先不讲,后面单独讲闭包会提出来

我们也可以用bind来解决

for(var i=1;i<6;i++){
  setTimeout(function(i){
    console.log(i) // 1,2,3,4,5
  }.bind(null,i),i*1000)
}
2.回调函数this丢失问题
var student = {
  subject:['JS','VUE','REACT'],
  study: function(){
    setTimeout(function(){
      console.log(`我是南玖,我在学习${this.subject.join('、')}`)
    }.bind(this),0)
  }
}
student.study() //我是南玖,我在学习JS、VUE、REACT

想一想这里settimeout的回调如果不用bind绑定this,结果会怎样?

「结果是报错,因为不给settimeout回调函数绑定this的话,那它的this应该指向的是全局window,全局没有subject,调用join会报错」

模拟call

「思路:」

  1. 根据call的规则设置上下文对象,也就是this的指向。
  2. 通过设置context的属性,将函数的this指向到context上
  3. 通过隐式绑定执行函数并传递参数。
  4. 删除临时属性,返回函数执行结果
Function.prototype.myCall = function(context){
    // context指的是那个想要借方法的对象,并为它指定默认值,没传就是window
    var context = context || window
    // 将要借用的那个方法绑定在当前要使用该方法的对象的fn属性上
    context.fn = this
    // 这里的this指向你想要借用的那个方法也就是.myCall前面的调用者(这里的this指的是一个函数)
    console.log(this)
    //获取参数,也就是相当于call的参数列表
    var args = [...arguments].slice(1)
    // 将参数传给该函数并执行
    var res = context.fn(...args)
    // 删除该方法
    delete context.fn
    // 返回执行结果
    return res
}

模拟apply

「思路:」

  • 与call类似,主要区别是参数的处理
/* 
实现原理与call类似,主要是参数不同,apply接受一个参数数组
*/
Function.prototype.myApply = function (context){
    var context = context || window
    context.fn = this
    // 判断第二个参数是否为数组,不为数组需提示用户(报错提示)
    console.log(arguments.length)
    if(arguments.length > 2){
        throw new Error('只能传递两个参数')
    }else if(!(arguments[1] instanceof Array)){
        throw new Error('第二个参数需要是数组类型')
    }
    var res = context.fn(...arguments[1])
    delete context.fn
    return res
}

模拟bind

Function.prototype.myBind = function(context){
    var context = context || window
    var _this = this
    var args = [...arguments].slice(1)
//    这里返回的是一个函数
    var res = function(){
        return _this.apply(context,...args)
    }
    return res
}

this指向与call,apply,bind的更多相关文章

  1. this的指向问题 call apply bind 中的this

    在函数中this指向谁:     函数中的this指向谁,是由函数被调用的那一刻就确定下来的 平时确定一个函数中的this是谁,我们需要通过调用模式来确定 1. 函数调用模式 this ---> ...

  2. JavaScript中call,apply,bind方法的总结。

    why?call,apply,bind干什么的?为什么要学这个? 一般用来指定this的环境,在没有学之前,通常会有这些问题. var a = { user:"追梦子", fn:f ...

  3. call(),apply(),bind()与回调

    1.call(),apply(),bind()方法 JavaScript 中通过call或者apply用来代替另一个对象调用一个方法,将一个函数的对象上下文从初始的上下文改变为由 thisObj 指定 ...

  4. JS 的 call apply bind 方法

    js的call apply bind 方法都很常见,目的都是为了改变某个方法的执行环境(context) call call([thisObj[,arg1[, arg2[,   [,.argN]]]] ...

  5. javascript-this,call,apply,bind简述2

    上节我们一起研究了this这个小兄弟,得出一个结论,this指向调用this所在函数(或作用域)的那个对象或作用域.不太理解的朋友可以看看上节的内容,这次我们主要探讨一下call(),apply(), ...

  6. javascript-this,call,apply,bind简述1

    最近在系统的学习面向对象方面的知识,遇到的最大拦路虎就数this的指向,call,apply,bind函数的使用,单独抽出一天时间把这几个烦人的家伙搞定,去学习更深入的内容. 首先介绍一下this的一 ...

  7. call,apply,bind方法的总结

    why?call,apply,bind干什么的?为什么要学这个? 一般用来指定this的环境,在没有学之前,通常会有这些问题. var a = { user:"追梦子", fn:f ...

  8. JavaScript 之 call apply bind

    关键字 this 绑定的方法 this的动态切换,固然为JavaScript创造了巨大的灵活性,但也使得编程变得困难和模糊.有时,需要把this固定下来,避免出现意想不到的情况.JavaScript提 ...

  9. JavaScript中call,apply,bind方法的总结

    原文链接:http://www.cnblogs.com/pssp/p/5215621.html why?call,apply,bind干什么的?为什么要学这个? 一般用来指定this的环境,在没有学之 ...

随机推荐

  1. forEachRemaining

    ArrayList<Integer> arrayList=new ArrayList<>(); for (int i = 0; i <10; i++) { arrayLi ...

  2. AbpVnext使用分布式IDistributedCache Redis缓存(自定义扩展方法)

    AbpVnext使用分布式IDistributedCache缓存from Redis(带自定义扩展方法) 我的依赖包的主要版本以及Redis依赖如下 1:添加依赖 <PackageReferen ...

  3. MySQL-SQL基础-查询1

    #子查询-某些情况下,当进行查询的时候,需要的条件是另外一个select语句的结果,这个时候就要用到子查询.用于子查询的关键字主要包括: in.not in.=.!=.exists.not exist ...

  4. Linux常用命令(一)之文件处理命令

    分时的多用户.多任务的操作系统 多数的网络协议的支持(unix和tcp/ip协议是同时发展起来的),方便的远程管理(可以通过图形.命令行) 强大的内存管理和文件管理系统 大量的可用软件和免费软件(游戏 ...

  5. GoLang设计模式3 - 抽象工厂模式

    之前我们介绍了工厂设计模式,现在我们再看一下抽象工厂设计模式.抽象工程模式顾名思义就是对工厂模式的一层抽象,也是创建型模式的一种,通常用来创建一组存在相关性的对象. UML类图大致如下: 类图比较复杂 ...

  6. Hash值和位运算

    一.Hash 1.md5是hash算法,不可逆,还原的是暴力穷举的方式解析的:加盐之后穷举也不能还原: 2.压缩映射会有重复,即哈希冲突: 二.ConcurrentHashMap 1.putIfAbs ...

  7. RabbitMQ核心知识总结!

    本文已经收录到github仓库,此仓库用于分享Java相关知识总结,包括Java基础.MySQL.Spring Boot.MyBatis.Redis.RabbitMQ.计算机网络.数据结构与算法等等, ...

  8. HearthbuddyHelper已经开源

    https://github.com/V-arc/HearthbuddyHelper 只是为了稳定,没耗时多久写的一个中控,只是在逐渐堆加,并未进行重构. 通过写这个感受到自己对异步和wpf的理解还有 ...

  9. 1004. 最大连续1的个数 III

    1004. 最大连续1的个数 III 给定一个由若干 0 和 1 组成的数组 A,我们最多可以将 K 个值从 0 变成 1 . 返回仅包含 1 的最长(连续)子数组的长度. 示例 1: 输入:A = ...

  10. 在PHP中操作临时文件

    关于文件相关的操作,想必大家已经非常了解了,在将来我们刷到手册中相关的文件操作函数时也会进行详细的讲解.今天,我们先来了解一下在 PHP 中关于临时文件相关的一些内容. 获取 PHP 的默认临时创建文 ...