葵花宝典——this指向问题

332 阅读6分钟

前言

this的概念:
  1. 在js中,this的意思为“这个;当前”,是一个指针型变量,它动态指向当前函数的运行环境。
  2. 在不同的场景中调用同一个函数,this的指向也可能会发生变化,但是它永远指向其所在函数的真实调用者;如果没有调用者,就指向全局对象window。

this的绑定规则总共有六种方式:分别为默认绑定、隐式绑定、隐式丢失、显示绑定(bind,apply,call)、new绑定、箭头函数。 我给大家总结了一个口诀,相信我,大家只要记住我这个口诀,就能彻底掌握js中this的指向。

先讲口诀:箭头函数、new、bind、 apply和call、欧比届点(obj.)、直接调用

按照口诀的顺序,只要满足前面某个场景,就可以确定 this 指向了。

一、箭头函数

箭头函数排在第一个是因为它的 this 不会被改变,所以只要当前函数是箭头函数,那么咱们就不用再看其他规则了。箭头函数没有this这个机制,写在箭头函数中的this也是它外层非箭头函数的this。

箭头函数的 this 是在创建它时外层 this 的指向。这里的重点有两个:

  1. 箭头函数没有自己的this指向,它会捕获自己定义所处的外层执行环境,并且继承这个this值,指向当前定义时所在的对象。箭头函数的this指向在被定义的时候就确定了,之后永远都不会改变。即使使用call()apply()bind()等方法改变this指向也不可以。
  2. 箭头函数内的 this 指向外层的 this

所以要知道箭头函数的 this 就得先知道外层 this 的指向,需要继续在外层应用六步口诀。

二、new绑定

当使用 new 关键字调用函数时,函数中的 this 一定是 JS 创建的新的实例对象。

Person.prototype.say = function(){
    console.log("hello " + this.name);
}
function Person(name){
    this.name = name;
    // 相当于下面
    // var this = {
    // 	name: name,
    // 	__proto__:Person.prototype
    // }
    // return this
}
var person1 = new Person("")
person1.say()

new的作用其实就是在构造函数中创建一个this对象,然后构造函数中的内容就相当于往this里面挂属性,另外还会放一个实例对象的隐式原型,其值就是构造函数的显示原型,最终return出这个this对象

三、bind

function foo(n,m){
    console.log(this.a,n,m);
}
var obj = {
    a: 2
}
var bar = foo.bind(obj,100,200)
bar() // 2 100 200

bind会返回一个新的函数体,这个函数体拥有了obj的词法作用域,并且foo中this会指向obj。

bind传参有下面三种方式

  1. 就是上面的例子,全部放进bind()中

  2. var bar = foo.bind(obj)

    bar(100,200) foo的参数可以全部写在返回的新函数中

  3. var bar = foo.bind(obj,100)

    bar(200) bind和返回的函数体都可以进行传参

    如果是下面这种情况

    var bar = foo.bind(obj,100,100)

    bar(200) // 2 100 100 这种情况不会覆盖

    在来看一个案例:

    function foo(){
        var a = 1
        function bar(){
            console.log(this.a);
        }
        var baz = bar.bind(foo)
        baz()
    }
    foo() // undefined
    

    这里使用bind就是让bar中的this指向了foo,但是foo是没有词法作用域,foo指向了全局作用域,因为foo在全局中声明,所以this最终指向了全局,而非foo,因此输出undefined。this不能引用一个词法作用域内部的内容.

    多次 bind 时只认第一次 bind 的值

    易错点

    function func() {
      console.log(this)
    }
    
    func.bind(1).bind(2)() // 1
    

    箭头函数中 this 不会被修改

    func = () => {
      // 这里 this 指向取决于外层 this
      console.log(this)
    }
    
    func.bind(1)() // Window,口诀 1 优先
    

    bind 与 new

    易错点

    function func() {
      console.log(this, this.__proto__ === func.prototype)
    }
    
    boundFunc = func.bind(1)
    new boundFunc() // Object   true,口诀 2 优先
    

    四、apply和call

    使用call、apply或bind方法人为干预他,让他代指谁

    call

    function foo(){
        console.log(this.a);
    }
    var obj = {
        a: 2
    }
    foo.call(obj) // 2
    

    这个call的用法就是把foo的this指向了obj

    当foo中有参数时

    function foo(n){
        console.log(this.a,n);
    }
    var obj = {
        a: 2
    }
    foo.call(obj,100) // 2 100
    

    call与foo共用这个括号

    apply:

    function foo(){
        console.log(this.a);
    }
    var obj = {
        a: 2
    }
    foo.apply(obj)
    

    无参数时与call用法相同

    传参时

    function foo(n,m){
        console.log(this.a,n,m);
    }
    var obj = {
        a: 2
    }
    foo.apply(obj,[100,200]) // 2 100 200
    

    apply传参是以数组的形式

    箭头函数中 this 不会被修改

    func = () => { // 这里 this 指向取决于外层 this,参考口诀 7 「不在函数里」 console.log(this) }

    func.apply(1) // Window,口诀 1 优先

    bind 函数中 this 不会被修改

    function func() {
      console.log(this)
    }
    
    boundFunc = func.bind(1)
    boundFunc.apply(2) // 1,口诀 3 优先
    

    五、obj

    当函数的引用有上下文对象时(当函数被某个对象所拥有时), 函数的this指向引用它的对象,也就是所说的隐式绑定。

    直接看下面这个案例:

    function foo(){
        console.log(this.a);
    }
    var obj = {
        a: 2,
        foo: foo
        // 等同于
        // foo: function foo(){
        //	console.log(this.a)
        // }
    }
    obj.foo() // 2
    
    

    obj中有个foo属性,然后这个属性的值又是一个函数体,就相当于foo这个函数体写在了foo这个key里面的value中,最后obj.foo()中obj.foo就是相当于foo这个函数体,然后()就是一个调用的意思

    根据隐式绑定的规则,foo这个函数被obj这个函数引用,等你obj再次调用这个函数时,函数中的this最终指向了obj这个对象,而非全局,这就是隐式绑定,因此最终打印2

    隐式丢失:隐式丢失是隐式绑定的一种,当一个函数被多个函数链式调用时,函数的this指向就近的那个对象,也就是就近原则。

    再来一个

    function foo(){
        console.log(this.a);
    }
    var obj = {
        a: 3,
        foo: foo()
    }
    obj.foo // undefined
    
    

    这里的对象中并非是引用这个foo函数,因为有个括号,是调用,所以不符合隐式绑定规则,只能是默认绑定,this还是指向了全局作用域,找不到a的值,输出undefined

    六、直接调用

    在函数不满足前面的场景,被直接调用时,this 将指向全局对象。在浏览器环境中全局对象是 Window,在 Node.js 环境中是 Global。也就是默认绑定。

    默认绑定:当一个函数独立调用,不带任何修饰符的时候,函数在哪个词法作用域下生效,函数中的this就指向哪里,(只要是默认绑定,this一定指向window)。

    先来个简单的例子

    function func() {
      console.log(this)
    }
    
    func() // Window
    

    来一个复杂的例子,外层的 outerFunc 就起个迷惑目的。

    function outerFunc() {
      console.log(this) // { x: 1 }
    
      function func() {
        console.log(this) // Window
      }
    
      func()
    }
    
    outerFunc.bind({ x: 1 })()
    
    
    

最后给大家来两道题目试试:

先背诵口诀在做题,“箭头函数、new、bind、 apply和call、 欧比届点(obj.)、直接调用。”

1、下面代码执行后,输出多少呢?

function func(num) {
    this.count++
  }
  
  func.count = 0
  func(1)
  console.log(func.count) 

答案: 输出0.

按照口诀,func()调用时属于第六类“直接调用”。this指向全局对象。this跟func一点关系都没有,所以func.count保持不变。很简单。

2.下列箭头函数中的this分别指向谁呢?

obj = {
  func() {
    const arrowFunc = () => {
      console.log(this.name)
    }

    return arrowFunc
  },

  name: "obj",
}

1.obj.func()()

2.func = obj.func
  func()()

3.obj.func.bind({ name: "newObj" })()()

4.obj.func.bind()()()

5.obj.func.bind({ name: "bindObj" }).apply({ name: "applyObj" })()

答案:

// obj
// undefined
// newObj
// undefined
// bindObj

是不是很简单,你学废了嘛?今天的分享就到这里啦!如果觉得本文对你有帮助的话,可以点个免费的赞赞嘛,感谢感谢!