this指向和bind—call—apply

this 关键字

this是在函数运行时,函数内部自动生成的一个对象;

  • 每一个函数内部都有一个关键字是 this

  • 可以让我们直接使用的

  • 重点: 函数内部的 this 只和函数的调用方式有关系,和函数的定义方式没有关系

  • 函数内部的 this 指向谁,取决于函数的调用方式

  • 全局定义的函数直接调用,this => window

  • 构造函数中的this,每个构造函数在new之后都会返回一个对象,这个对象就是this,也就是context上下文。

    function fn() {
      console.log(this)
    }
    fn()
    // 此时 this 指向 window
    
  • 对象内部的方法调用,this => 调用者

    var obj = {
      fn: function () {
        console.log(this)
      }
    }
    obj.fn()
    // 此时 this 指向 obj
    
  • 定时器的处理函数,this => window

    setTimeout(function () {
      console.log(this)
    }, 0)
    // 此时定时器处理函数里面的 this 指向 window
    
  • 事件处理函数,this => 事件源

    div.onclick = function () {
      console.log(this)
    }
    // 当你点击 div 的时候,this 指向 div
    
  • 自调用函数,this => window

    (function () {
      console.log(this)
    })()
    // 此时 this 指向 window
    

问题: 箭头函数中的this是如何查找的了?

答案: 向外层作用域中, 一层层查找this, 直到有this的定义

const obj = {
   a() {
      setTimeout(function () {
        setTimeout(function () {
          console.log(this); // window
        })

        setTimeout(() => {
          console.log(this); // window
        })
      })

      setTimeout(() => {
        setTimeout(function () {
          console.log(this); // window
        })

        setTimeout(() => {
          console.log(this); // obj
        })
      })
   }
}

obj.a()

那继续更新this的指向问题

const o1 = {
    text: 'o1',
    fn: function () {
      return this.text
    }
  }
  const o2 = {
    text: 'o2',
    fn: function () {
      return o1.fn()
    }
  }
  const o3 = {
    text: 'o3',
    fn: function () {
      var fn = o1.fn
      return fn()
    }
  }
  console.log(o1.fn()); // o1
  console.log(o2.fn()); // o1
  console.log(o3.fn()); // undefined
  // 答案是 ol ol undefined  下面来分析一下代码:
  // 第一个 console 最简单,输出 o1 不难理解。 难点在第二个和第三个 console 上,关键还是看调用 this 的那个函数;
  // 第二个 console 中的 o2.fn 最终调用的还是 o1.fn() ,因此运行结果仍然是 o1;
  // 第三个 console 中的 o3.fn 通过 var fu = o1.fn() 的赋值进行了“裸奔“调用,因此这里的 this 指向 window, 运行结果当然是 undefined

问: 如果需要让 console.log(o2.fn())语句, 输出 o2 该怎么做?

const o1 = {
    text: 'o1',
    fn: function () {
      return this.text
    }
  }
 
  // 修改 o2 让 console.log(o2.fn())语句, 输出o2
  const o2 = {
    text: 'o2',
    fn: o1.fn
  }

  const o3 = {
    text: 'o3',
    fn: function () {
      var fn = o1.fn
      return fn()
    }
  }
  console.log(o1.fn()); // o1
  console.log(o2.fn()); // o2
  console.log(o3.fn()); // undefined
  
  // 问:如果我们需要让 console.log(o2.fn())语句, 输出 o2 该怎么做?
  // 一般会想到用call,apply,bind,当然这里不用的话怎么处理
  
  // 以上方法同样应用了那个重要的结论 this 指向最后调用它的对象。
  //  在上面的代码中,如果提前进行了赋值操作,将函数 fu 挂载到 o2 对象上,和最终作为 o2 对象的方法被调用

call 和 apply 和 bind

  • 刚才我们说过的都是函数的基本调用方式里面的 this 指向
  • 我们还有三个可以忽略函数本身的 this 指向转而指向别的地方
  • 这三个方法就是 call / apply / bind
  • 是强行改变 this 指向的方法

说明call,apply是ES5中的语法,bind是ES6新引入的:

  • 都是用来改变函数的this对象的指向
  • 第一个参数都是this要指向的对象
  • 都可以利用后续参数进行传参

call

  • call 方法是附加在函数调用后面使用,可以忽略函数本身的 this 指向

  • 语法: 函数名.call(要改变的 this 指向,要给函数传递的参数1,要给函数传递的参数2, ...)

var obj = { name: 'fqniu' }
function fn(a, b) {
  console.log(this)
  console.log(a)
  console.log(b)
}
fn(1, 2)
fn.call(obj, 1, 2)
  • fn() 的时候,函数内部的 this 指向 window
  • fn.call(obj, 1, 2) 的时候,函数内部的 this 就指向了 obj 这个对象
  • 使用 call 方法的时候
    • 会立即执行函数
    • 第一个参数是你要改变的函数内部的 this 指向
    • 第二个参数开始,依次是向函数传递参数

apply

  • apply 方法是附加在函数调用后面使用,可以忽略函数本身的 this 指向

  • 语法: 函数名.apply(要改变的 this 指向,[要给函数传递的参数1, 要给函数传递的参数2, ...])

var obj = { name: 'fqniu' }
function fn(a, b) {
  console.log(this)
  console.log(a)
  console.log(b)
}
fn(1, 2)
fn.apply(obj, [1, 2])
  • fn() 的时候,函数内部的 this 指向 window
  • fn.apply(obj, [1, 2]) 的时候,函数内部的 this 就指向了 obj 这个对象
  • 使用 apply 方法的时候
    • 会立即执行函数
    • 第一个参数是你要改变的函数内部的 this 指向
    • 第二个参数是一个 数组,数组里面的每一项依次是向函数传递的参数

bind

  • bind 方法是附加在函数调用后面使用,可以忽略函数本身的 this 指向

  • 和 call / apply 有一些不一样,就是不会立即执行函数,而是返回一个已经改变了 this 指向的函数

  • 语法: var newFn = 函数名.bind(要改变的 this 指向); newFn(传递参数)

var obj = { name: 'fqniu' }
function fn(a, b) {
  console.log(this)
  console.log(a)
  console.log(b)
}
fn(1, 2)
var newFn = fn.bind(obj)
newFn(1, 2)
  • bind 调用的时候,不会执行 fn 这个函数,而是返回一个新的函数
  • 这个新的函数就是一个改变了 this 指向以后的 fn 函数
  • fn(1, 2) 的时候 this 指向 window
  • newFn(1, 2) 的时候执行的是一个和 fn 一模一样的函数,只不过里面的 this 指向改成了 obj

加强理解这三者关系

	 const apple = {
        name: "苹果",
        color: "红色",
        type: function () {
          console.log(this.name + ', ' + this.color);
        }
      }
      const orange = {
        name: "橘子",
        color: "yellow",
      }
      // apple.type(); // 苹果, 红色

      // 使用call, apply以及bind方法实现, 并从中得到它们三者的区别:
      apple.type.call(orange); // 橘子, yellow
      apple.type.apply(orange); // 橘子, yellow
      apple.type.bind(orange); // 没有输出内容
      // bind 改为如下写法:
      let abind =  apple.type.bind(orange);
      abind()  // // 橘子, yellow
      
	 const apple = {
        name: "苹果",
        color: "红色",
        type: function (agr0, arg1) {
          console.log(this.name + ', ' + this.color + ','+ agr0 + ',' + arg1);
        }
      }
      const orange = {
        name: "橘子",
        color: "yellow",
      }
      // apple.type(); // 苹果, 红色

      // 使用call, apply以及bind方法实现, 并从中得到它们三者的区别:
      apple.type.call(orange, 1,2); // 橘子, yellow,1,2
      apple.type.apply(orange, [1,2]); // 橘子, yellow,1,2
      apple.type.bind(orange, 1,2); // 没有输出内容 
      // bind 改为如下写法:
      let abind =  apple.type.bind(orange);
      abind()  // 橘子, yellow,undefined,undefined
      

手写apply和call和bind

	  // 手写apply 
      Function.prototype.myApply = function (context) {
        var context = context || window
        context.fn = this
        var result
        // 需要判断是否存储第二个参数
        // 如果存在,就将第二个参数展开
        if (arguments[1]) {
          result = context.fn(...arguments[1])
        } else {
          result = context.fn()
        }
        delete context.fn
        return result
      }
	   /*
		主要区别就是获取参数不同,因为apply的带二个参数为数组,数组中包含函数需要的各项参数值,其余内容实现myCall相同, 如下call
	   */
	   
      //手写call
      Function.prototype.myCall = function (context) {
        var context = context || window //如果第一个参数为空则默认指向window对象
        // 给 context 添加一个属性
        // getValue.call(a, 'b', '24') => a.fn = getValue
        context.fn = this
        // 将 context 后面的参数取出来
        var args = [...arguments].slice(1)  //存放参数的数组
        // getValue.call(a, 'b', '24') => a.fn('b', '24')
        var result = context.fn(...args)
        // 删除 fn 删除掉临时添加的方法,否则就无缘无故多了个fn
        delete context.fn
        return result
      }
	  /*
		因为call方法是每一个函数都拥有的,所以需要在Function.prototype上定义myCall,
		传递的参数obj即为call方法的第一个参数,说明this的指向,如果没有该参数,则指向默认为window对象。
		args为一个存放除第一个参数以外的其余参数的数组(arguments为函数中接收到的多有参数,
		[...arguments]可以将arguments类数组转换为真正的数组,详细讲解可以查看ES6语法)。
	  */
	  
      // 手写bind
      Function.prototype.myBind = function (context) {
        if (typeof this !== 'function') {
          throw new TypeError('Error')
        }
        var _this = this
        var args = [...arguments].slice(1)
        // 返回一个函数
        return function Fun() {
          // 因为返回了一个函数,我们可以 new F(),所以需要判断
          if (this instanceof Fun) {
            return new _this(...args, ...arguments)
          }
          return _this.apply(context, args.concat(...arguments))
        }
      }
      /*
      return function是因为bind返回的是一个函数,并且这个函数不会执行,需要再次调用,
      那么当调用的时候,依旧可以对这个函数进行传递参数,即为支持柯里化形式传参,
      所以需要在返回的函数中声明一个空的数组接收调用bind函数返回的函数时传递的参数,
      之后对两次的参数使用concat()方法进行连接,调用ES5中的apply方法。
      */

context.say = this这里没看懂,怎么改变this指向的?

举个例子
obj.call(obj1)
mycall 就是例子中的 call;
context 就是 obj1;
this 指向的是 obj;
意思就是:在obj1上模拟一个say方法和obj一样的东西 完了去执行 做到改变this;

 // 所有函数对象都有两个方法:apply和call,这两个方法可以让我们构建一个参数数组传递给调用函数,也允许改变this值。
 // 使sayName中的this指向b, 改变this的指向
 
      var name = 'window';
      var obj = {
        name: 'obj',
        sayName: function () {
          console.log(this.name);
        }
      }
      var b = {
        name: 'abcd'
      };

      //改变this指向
      var newobj = obj.sayName;
      newobj(); // window  注意:this指向全局  
      obj.sayName(); // obj  注意:this 指向 obj  
      newobj.call(b); // abcd  注意:this指向 b  改变this的指向并且执行调用函数
      // 这样就是改变了obj.sayName的this,打印出来的是b中的name

再补充理解 bind— call — apply 这三者的关系

// 用一句话总结:它们都是用来改变相关函数this指向的
// 但 call apply 是直接进行相关函数调用的, bind 不会执行相关函数, 而是返回一个新 函数,这个新的函数已经自动绑定了新的指向
// 开发者可以手动调用它 再说具体点,就是 call app 之间的区别主要体现在参数设定上;
const foo = {
      name: 'fqniu',
      fun: function () {
        console.log(this.name)
      }
 }
 const bar = {
      name: 'niuniu'
 }
 foo.fun.call(bar) // niuniu

理解new 操作符到底具体做了什么?

	// 一、理解 new 操作符到底具体做了什么呢?
    /* 
      1、创建一个新的对象
      2、将构造函数的this指向这个新的对象
      3、为这个对象添加属性和方法
      4、最终返回新的对象
    */

    // 二、代码理解如下:
    var obj = {}
    obj.__proto__ = Foo.prototype
    Foo.call(obj)

如果在构造函数中出现了显式 return 的情况,那么需要注意,其可以细分为两种场景

	// 注意点
    // 需要指出的是,如果在构造函数中出现了显式 return 的情况,那么需要注意,其可以细分为两种场景
    // 场景1
    function Foo1(){
      this.name = "fqniu"
      const o = {}
      return o
    }
    const instance1 = new Foo1()
    console.log(instance1.name); // undefined 这里的 instance1 返回的是空对象 o

    // 场景1
    function Foo2(){
      this.name = "fqniu"
      return 1
    }
    const instance2 = new Foo2()
    console.log(instance2.name); // fqniu 这里的 instance2 返回的是目标对象实例 this
    // 所以,如果构造函数中显式饭回一个值
    // 1、如果返回的是一个对象(返回复杂类型),那么 this 就指向这个返回的对象
    // 2、如果返回的不是一个对象(返回基本类型),那么 this 仍然指向实例

箭头函数中的 this

 const foo1 ={
    fun:function(){
      setTimeout(function(){
        console.log(this);
      });
    }
  }
  console.log(foo1.fun());  // window
  
  const foo2 ={
    fun:function(){
      setTimeout(() => {
        console.log(this);
      });
    }
  }
  console.log(foo2.fun()); // foo2 的 fun

显式绑定和隐式绑定谁的优先级更高

我们常常把通过 call 、apply 、bind、 new 对this进行绑定的情况称为显式绑定,而把根据调用关系确定this指向的情况称为隐式绑定;

那么显式绑定和隐式绑定谁的优先级更高呢? 关于这个问题的答案,那么会在接下来的例题中为大家揭晓

function foo(a) {
    this.a = a
  }
  const obj1 = {}
  var bar = foo.bind(obj1)
  bar(2)
  console.log(obj1.a); // 2
  // 上述代码 通过bind将bar函数中的this绑定为obj1对象。
  // 执行bar(2)后, obj1.a值为2, 即执行bar(2)后, obj1对象为 {a: 2}
  
  // 当再使用bar作为构造函数时,例如执行以下代码,则会输出3
  var bat = new bar(3)
  console.log(bat.a); // 3
  // bar 函数本身是通过 bind 法构造的函数,其内部已经将 this 绑定为 obj1,
  // 当它再次作为构造函数通过 new 调用时,返回的实例就已经与 obj1 解绑了。
  // 也就是说,new 绑定修改了 bind 绑定中this指向
  // 因此 new 绑定的优先级比显式 bind 定的更高。

  function foo(){
    return a => {
      console.log(this.a);
    }
  }
  const obj1 = {
    a: 1
  }
  const obj2 = {
    a: 2
  }
  const bar = foo.call(obj1)
  bar.call(obj2) // 打印的为 1
  // 以上代码的输出结果为1, 由于foo中的 this 绑定到了obj1上,所以 bar(引用箭头函数)中的this 会绑定到 obj1 上,箭头函数的绑定无法被修改


  // 如果将foo完全写成如下的箭头函数的形式,则会输入123
  var a = 123
  const foo = () => a => {
    console.log(this.a);
  }
  const obj1 = {
    a: 1
  }
  const obj2 = {
    a: 2
  }
  const bar = foo.call(obj1)
  bar.call(obj2) // 打印的为 123

  // 如果将 a 的声明修改一下呢
  const a = 123
  const foo = () => a => {
    console.log(this.a);
  }
  const obj1 = {
    a: 1
  }
  const obj2 = {
    a: 2
  }
  const bar = foo.call(obj1)
  bar.call(obj2) // 打印的为 undefined
  // 答案为 undefined, 原因是使用 const 声明的变量不会挂载到 window 全局对象上。
  // 因此,this指向 window 时,自然也找不到 a变量了 关于 const let 等后续会进行介绍
  
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值