一文让你拿捏 this、call、apply、bind

前言:前端现在越来越卷,找工作拿高薪更是不易,因为想要在这个特别卷的时代,我们要想进大厂,拿高薪,首先最重要的就是,知识点得掌握扎实,做到举一反三。

因此 this、call、apply、bind 还不是很清楚的,文章干货很多,请先收藏,慢慢阅读,让你直接 拿捏 这些重要经典知识点。

1、this的指向

​ this的指向是面试中必考且比较难得一个点,本文以最简单最好理解的方式来解读this指向的问题。

​ 在ES5中,this的指向是原则:永远指向最后调用它的那个对象 这一点非常关键:理解this,必须牢牢记住这句话。

看看一些例子:

例1:

var name = 'windowName';
function example(){
    var name = 'object';
    console.log(this.name);  //windowName
}
example()  

这里为什么输出的是 window 而不是object ,原因在于: 函数 example 在调用时,直接调用 example() ,前面没有对象,相对于这样调用 window.example() ;又:this 永远指向最后调用它的那个对象 ,所以输出 window。

例2:

var name = "windowName";
var o = {
   name : "object",
   fn : function(){
       console.log(this.name);  //object
   }
}
o.fn()
//window.a.fn()

在这里打印的是 object ,因为函数 是 对象 o 调用的。

这里换成 window.a.fn() 也是同样的结果,因为最后还是 对象 a 调用的,印证了那句话 :**this永远指向它最后调用的对象 **。

例3:面试中很坑的一个例子。

var name = "windowName";
var o = {
   name : "object",
   fn : function(){
       console.log(this.name);  //windowName
   }
}
const f = o .fn;
f()

这里就让人很懵,大家肯定都很好奇原因,首先要明白 const f = o .fn; 这一步是声明变量f 并赋值的操作,在这里只是将对象 o 的方法 fn 赋值给 f 并没有调用,而最后调用f() ,(实际是调用o .fn()) ,是在window对象下调用的,因此this 指向的是 window , 所有输出的是 :windowName

2、改变this的指向?

通常改变this的方法有一下几种方法:

  • 使用call、apply、bind。
  • ES 6 中的箭头函数。
  • 函数内部一般使用 that = this
  • new 实例化一个对象。
  • 隐式绑定

2.1 使用 apply、call、bind

1、使用call

var person = {
    name:'王涵宇'
}
var o = {
   name : "object",
   fn : function(){
       console.log(this.name);  //王涵宇
   }   
}
o.fn.call(person)

2、使用apply

/*
	以call实例为例
*/
o.fn.apply(person)  //王涵宇

3、使用bind

/*
	以call实例为例
*/
o.fn.bind(person)()  // 王涵宇
//注意这里的调用方式与上面的不同

2.2 使用 ES 6 中的箭头函数。

使用箭头函数:

var name = "windowName";
var o = {
   name : "object",
   fn1 : function(){
       console.log(this.name);  //object
   },
    fn2 : function(){
        setTimeout(()=>{
            this.fn1()
        },1000)
    }
}
o .fn2();

不使用箭头函数:

var name = "windowName";
var o = {
   name : "object",
   fn1 : function(){
       console.log(this.name);  //Uncaught TypeError: this.fn1 is not a function
   },
    fn2 : function(){
        setTimeout(function(){
            this.fn1()
        },1000)
    }
}
o .fn2();

会报错,因为 最后是 window对象 调用 setTimeOut ,而 window对象上没有 fn1 这个方法。

2.3 函数内部使用 that = this

var name = "windowName";
var o = {
   name : "object",
   fn1 : function(){
       console.log(this.name);  //  object
   },
    fn2 : function(){
    	let that = this;  //保存this
        setTimeout(()=>{
            that.fn1()
        },1000)
    }
}
o .fn2();

2.4 new 实例化一个对象

var name = "windowName";
function getName(){
	this.name = '王涵宇';
    console.log(this); //getName {name: '王涵宇'}  this指向这个函数(对象)
}
new getName()

2.5 隐式绑定

说白了:就是谁调用指向谁。

const obj = {
    name :'王涵宇',
    getName : function(){
        console.log(this);
        console.log(this.name)   //王涵宇
    }
}
obj.getName() 

3、实现call、apply、bind

1、实现call

1、这三种方法都在函数的 原型上 接受两个参数

Function.prototype.myCall = function(obj,...args){
    //这里默认不传obj。就是给 window
    obj = obj || window;
    args = args ? args : [];
    //给obj添加一个独一无二的属性,防止与调用函数中有相同的属性变量
    const fn = Symbol();
    obj[fn] = this;
    //通过隐式绑定的方式调用函数
    //在这里用一个变量保存调用的结果,完了后删除这个属性,防止造成影响
    const res = obj[fn](...args);
    delete obj[fn](...args);
    return res;
}
const obj1 = {
    name:'ustinian',
    getName:function(age){
        console.log(`${this.name}今年${age}了!`)   //王涵宇今年22了!
    }
}
const obj2 = {
    name:'王涵宇',
}
obj1.getName.myCall(obj2,22)  

2、实现apply

Function.prototype.myApply = function(obj,args){
    //这里默认不传obj。就是给 window
	obj = obj || window;
    args = args ? args : [];
    //给obj添加一个独一无二的属性,防止与调用函数中变量属性重名
    const fn = Symbol();
    obj[fn] = this;
    //隐式绑定的方式调用函数
    const res = obj[fn](args);
    delete obj[fn](args);
    return res;
}

3、实现bind

img

bind方法的第一个参数this是可以不传的,分2种情况

  • 直接调用bind之后的方法
var f = function () { console.log('不传默认为'+this)  };f.bind()()



// 不传默认为 Window 

所以直接调用绑定方法时候 apply(that, 建议改为 apply(that||window,,其实不改也可以,因为不传默认指向window

  • 使用new实例化被绑定的方法
重点在于弄清楚标准的bind方法在new的时候做的事情,就可以清晰的实现

这里我们需要看看 new 这个方法做了哪些操作 比如说 var a = new b()

  1. 创建一个空对象 a = {},并且this变量引用指向到这个空对象a
  2. 继承被实例化函数的原型 :a.__proto__ = b.prototype
  3. 被实例化方法bthis对象的属性和方法将被加入到这个新的 this 引用的对象中: b的属性和方法被加入的 a里面
  4. 新创建的对象由 this 所引用 :b.call(a)
    // 原生JS 的实现
    Function.prototype._bind = function(context) {
        if (typeof this !== 'function') {
            throw new Error('error callback');
        }

        var self = this;
        var args = Array.prototype.slice.call(arguments, 1);
        // 注意:这里如果直接将 fnBind.prototype = this.prototype
        // ,我们直接修改 fnBind.prototype 的时候,也会直接修改绑定函数的 prototype。
        // 解决方案:这个时候,我们可以通过一个空函数来进行中转
        var fnOP = function() {};

        var fnBind = function() {
            var bindArgs = Array.prototype.slice.call(arguments);
            return self.apply(this instanceof fnOP
                            ? this
                            : context
                            , args.concat(bindArgs));
        }
        // 通过间接地去修改这个中间函数的原型对象,new 的方式来避免直接修改原型上面的属性和对象参数信息
        fnOP.prototype = this.prototype;
        // 这里不就是原型链继承的实现吗?
        fnBind.prototype = new fnOP();
        return fnBind;
    }

    // ES6的写法一
    Function.prototype._bind = function(context, ...args) {
      let self = this;
      return function() {
        self.apply(context, args.concat(Array.prototype.slice.call(arguments)));
      }
    }

    // ES6的写法二
    Function.prototype._bind = function(context) {
      context = context || window;
      let args = Array.prototype.slice.call(arguments, 1);
      let self = this;
      // 构建一个中间(空)的函数,维护原型之间的关系
      let Empty  = function(){};
      let F = function() {
        self.apply(this instanceof Empty 
                    ? this
                    : context
                    , args.concat(Array.prototype.slice.call(arguments)))
      }
      // 通过中间函数维护原型关系
      Empty .prototype = this.prototype;
      F.prototype = new Empty ();

      return F;
    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wadee.w

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值