js系列之——call/apply/bind基础重写以及this的指向问题

写在前面

距离上次更新已经过去了三个多月了,一方面是因为回到学校有一定的科研论文压力,另一方面可能短暂的认为总结整理博文发布,除了自己要十分清晰之外还要有很好的表述能力才能够做到对看到的一部分人有帮助,所以暂时停止了继续整理。 目前学硕小论文已经发表(水过),想起自己的初衷还是为了能够记录自己的前端挖坑过程,加深自己对知识点的理解总结能力,同时部分大厂的秋招已经明目张胆的开始了,所以还是决定继续进行整理记录,希望会有一个好的结果!
第一次就贡献给一道经典的面试题:js中关于this的指向以及call、apply和bind的区别


一、this的指向

1:在 ES5 中,其实 this 的指向,始终坚持一个原理:this 永远指向最后调用它的那个对象。
2:函数的this在调用时绑定的,完全取决于函数的调用位置。为了搞清楚this的指向是什么,必须知道相关函数是如何调用的
3:我们先通过几个小例子来熟悉接触this。

核心:this永远指向最后调用它的那个对象(记住理解这句话,this已经了解了一半了!)

	//例1:
    var name = "windowsName";
    function a() {
        var name = "Cherry";
        console.log(this.name);          // windowsName
        console.log("inner:" + this);    // inner: Window
    }
    a();
    console.log("outer:" + this)         // outer: Window

调用结果:
在这里插入图片描述
分析:最后调用 a 的地方是 a();,前面没有调用的对象那么就是全局对象 window,这就相当于是 window.a();注意,这里我们没有使用严格模式,如果使用严格模式的话,全局对象就是 undefined,那么就会报错 Uncaught TypeError: Cannot read property ‘name’ of undefined。


  //例2:
  var name = "windowsName";
  var a = {
    name: "ajia",
    fn : function () {
      console.log(this.name);      // ajia
    }
  }
  a.fn();

函数 fn 是对象 a 调用的,所以打印的值就是 a 中的 name 的值。然后在对例子进行一个小小的改动,此时要牢记一句话:this 永远指向最后调用它的那个对象。


var name = "windowsName";
    var a = {
        name: "ajia",//这里如果没有进行name属性的定义,那么下面就会打印 undefined
        fn : function () {
            console.log(this.name);      // ajia
        }
    }
    window.a.fn();

最后调用函数fn的不是window而是对象a,所以打印的理所应当是ajia没错吧!我们继续往下看!


    var name = "windowsName";
    var a = {
        name : 'ajia',
        fn : function () {
            console.log(this.name);      // windowsName
        }
    }
    var f = a.fn;
    f();

这里虽然将 a 对象的 fn 方法赋值给变量 f 了,但是没有调用,由于刚刚的 f 并没有调用,所以 fn() 最后仍然是被 window 调用的。所以 this 指向的也就是 window。


二、函数调用的方式

1):普通函数调用模式

    var name = "windows111";
    function a() {
        var name = "Cherry";
        console.log(this.name);    // windows111
        console.log(this);    //  Window
    }
    a();
    console.log(this)         //  Window

严格模式中,普通函数中的this则表现不同,表现为undefined。这样一个最简单的函数,不属于任何一个对象,就是一个函数,这样的情况在 JavaScript 的在浏览器中的非严格模式默认是属于全局对象 window 的,在严格模式,就是 undefined。

2):函数作为方法调用

更多的情况是将函数作为对象的方法使用

    var name = "windowsName";
    var ajia = {
        name: "Ajia",
        fn : function () {
            console.log(this.name);      //Ajia
        }
    }
    ajia.fn();

定义一个对象 ajia,对象 ajia 有一个属性(name)和一个方法(fn)。然后对象 ajia通过 . 方法调用了其中的 fn 方法。然后我们一直记住的那句话**“this 永远指向最后调用它的那个对象”**,所以在 fn 中的 this 就是指向 ajia 的。

3):构造函数调用(使用new关键字)
如果函数调用前使用了 new 关键字, 则是调用了构造函数。这看起来就像创建了新的函数,但实际上 JavaScript 函数是重新创建的对象:

function Student(name){
    this.name = name;
    console.log(this); // {name: '阿佳'}
    // return this;     // 相当于返回了
}
var result = new Student('阿佳');

插播一个经典的new的面试题:(伪代码表示)

var a = new myFunction("ajia","Tom");

new myFunction{
    var obj = {};
    obj.__proto__ = myFunction.prototype;
    var result = myFunction.call(obj,"ajia","Tom");
    return typeof result === 'obj'? result : obj;
}

1:创建一个空对象 obj;
2:将新创建的空对象的隐式原型指向其构造函数的显示原型。
3:使用 call 改变 this 的指向
4:如果无返回值或者返回一个非对象值,则将 obj 返回作为新对象;如果返回值是一个新对象的话那么直接直接返回该对象。

总之:在 new 的过程中,我们是使用 call 改变了 this 的指向。

4):call、apply、bind 调用模式(作为函数方法调用)

在 JavaScript 中, 函数是对象。JavaScript 函数有它的属性和方法。call() 和 apply() 是预定义的函数方法。 两个方法可用于调用函数,两个方法的第一个参数必须是对象本身。在 JavaScript 严格模式(strict mode)下, 在调用函数时第一个参数会成为 this 的值, 即使该参数不是一个对象。在 JavaScript 非严格模式(non-strict mode)下, 如果第一个参数的值是 null 或 undefined, 它将使用全局对象替代。
(参考链接:https://juejin.cn/post/6844903496253177863)

var doSth = function(name){
    console.log(this);
    console.log(name);
}
doSth.call(2, '阿佳'); // Number{2}, '阿佳'

三、怎样改变this的指向

改变this的方法:
1:使用 ES6 的箭头函数
2:在函数内部使用 _this = this
3:使用 apply、call、bind
4:new 实例化一个对象

1)使用 ES6 的箭头函数

众所周知,ES6 的箭头函数是可以避免 ES5 中使用 this 的坑的。箭头函数的 this 始终指向函数定义时的 this,而非执行时。箭头函数中没有this绑定,必须通过查找作用域链来决定其值。 如果箭头函数被非箭头函数包含,则this绑定的是最近一层非箭头函数的this,否则this的值则被设置为全局对象。

1:箭头函数没有自己的this、super、arguments
2:不能使用new来调用
3:没有原型对象
4:不可以改变this的绑定
5:形参名称不能重复

    var name = "windowsName";
    var a = {
        name : "ajia",
        func1: function () {
            console.log(this.name)     
        },

        func2: function () {
            setTimeout(  function () {
                this.func1()
            },100);
        }
    };
    a.func2()     // this.func1 is not a function
   

在不使用箭头函数的情况下,是会报错的,因为最后调用 setTimeout 的对象是 window,但是在 window 中并没有 func1 函数。

  //替换原来的func2如下:
  func2: function () {
     setTimeout( () => {
         this.func1()
            },100);
        }
  a.func2()     // ajia

使用箭头函数进行改造,可以看到函数执行可以正常的得到name:ajia


2)在函数内部使用 _this = this

    var name = "windowsName";
    var a = {
        name : "ajia",
        func1: function () {
            console.log(this.name)     
        },
        func2: function () {
            let _this = this;
            setTimeout( function() {
                _this.func1()
            },100);
        }
    };
    a.func2()       // ajia

1:我们是先将调用这个函数的对象保存在变量 _this 中,然后在函数中都使用这个 _this,这样 _this 就不会改变了。
2:在 func2 中,首先设置 let _this = this;,这里的 this 是调用 func2 的对象 a,为了防止在 func2 中的 setTimeout 被 window 调用而导致的在 setTimeout 中的 this 为 window。我们将 this(指向变量 a) 赋值给一个变量 _this,这样,在 func2 中我们使用 _this 就是指向对象 a 了。
(参考链接:https://juejin.cn/post/6844903496253177863)


3)使用 apply、call、bind

    var a = {
        name : "ajia,

        func1: function () {
            console.log(this.name)
        },

        func2: function () {
            setTimeout(  function () {
                this.func1()
            }.apply(a),100); //这里apply和call是一样的,区别稍后给出
    };
        func3: function () {
            setTimeout(  function () {
                this.func1()
            }.bind(a)(),100); //这里bind和上面两种的区别在于bind返回一个函数,不能立即执行,需要进行函数调用才可以!没有其余的区别。
    };
    a.func2()            // ajia
    a.func3()            // ajia

四、bind、apply和call的区别


这里我们只说结论性的区别,至于三者的用法大家可以去MDN上面进行详细的学习总结!

MDN文档连接地址:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function/call


三者都是用来改变this指向的方法,apply和call功能一样,只是传入的参数列表形式不同,call 需要把参数按顺序传递进去,而 apply 则是把参数放在数组里bind 是创建一个新的函数,我们必须要手动去调用才可以。

1:apply() 方法调用一个函数, 其具有一个指定的this值,以及作为一个数组(或类似数组的对象)提供的参数。
2:call()方法其实 apply 和 call 基本类似,他们的区别只是传入的参数不同,需要传递一个数组。虽然是一个数组,但是也相当于给fn一个个的进行传递。
3:bind()方法会返回一个改变this指向的新函数,注意这里强调的是新函数,后续调用才会执行。而apply和call则是立即调用的。

fn.call(obj,1,2)  //改变fn中的this,并且把fn立即执行。
fn.apply(obj,[1,2]) //改变fn中的this,并且把fn立即执行。
fn.bind(obj,1,2)  //改变fn中的this,fn不立即执行。
//此时需要找一个变量进行接受
let new_fn = fn.bind(obj,1,2)
fn()   //此时就会成功的改变this

这里就不进行具体的实例的展示,大家可以自行的进行尝试。牢记基本的基础概念即可!算了还是附上几个找来的小案例!


    var a ={
        name : "Cherry",
        fn : function (a,b) {
            console.log( a + b)
        }
    }
    var b = a.fn;
    b.apply(a,[1,2])     // 3

    var a ={
        name : "Cherry",
        fn : function (a,b) {
            console.log( a + b)
        }
    }

    var b = a.fn;
    b.call(a,1,2)       // 3

    var a ={
        name : "Cherry",
        fn : function (a,b) {
            console.log( a + b)
        }
    }

    var b = a.fn;
    b.bind(a,1,2)()           // 3

五、当然也会区分严格模式和非严格模式:

call 或者 apply( 或者 bind) 调用:严格模式下,绑定到指定的第一个参数。非严格模式下,null和undefined,指向全局对象(浏览器中是window),其余值指向被new Object()包装的对象。
在这里插入图片描述
在这里插入图片描述

总结以及参考文章

以上就是今天要整理的内容,总结看来题目描述的是关于call、bind、apply的基础概念以及相应的重写上面,整理着确实发现好像有点跑偏,重心跑到了this的指向以及函数调用的方式上面,但是总体上来是有相应的逻辑性在里面的,对于bind等的重写也是前面面试中常常遭遇的问题,今天就暂时写到这里。详细的重写问题描述会放在下一篇整理里,谢谢大家,欢迎指点! 关于参考的一些好的文章链接我放在下面。

掘金深度好文:https://juejin.cn/post/6844903496253177863#heading-7
关于this指向好文:https://juejin.cn/post/6844903746984476686#heading-5
CSD文章:https://blog.csdn.net/hexinyu_1022/article/details/82795517

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值