JavaScript中this指向问题

276 阅读5分钟

1. this绑定是什么

  • 在浏览器中测试this指向的是window
  • 在全局环境下,我们可以认为this就是指向window

2. 几种绑定规则

  1. 默认绑定

    1. 普通函数调用 : window
    2. 函数调用链(一个函数又调用另外一个函数) : window
    3. 将函数作为参数,传入到另一个函数中 : window
  2. 隐式绑定

    1. 通过对象调用函数:this会绑定到对象上。如果对象之间存在调用,若obj2中有obj1,那么obj2.obj1.foo() -> this指向为obj1
    2. 隐式丢失:看最终调用位置
     function foo(){
         console.log(this)
     }
    
     var obj1 = {
         name: "obj1",
         foo: foo
     }
    
     // 将obj1的foo赋值给bar
     var bar = obj1.foo
     bar()
    
  3. 显示绑定

    1. call、apply: 使用call、apply可以指定this绑定的对象
    // 实现一个bind函数
    
    // 方法一
    function foo(){
        console.log(this)
    }
    
    var obj = {
        name: 'hzy'
    }
    
    function bind(func,obj){
        return function(){
            func.apply(obj,argument)
        }
    }
    
    var bar = bind(foo,obj)
    
    bar() // obj
    bar() // obj
    
    // 方法二: Function.prototype.bind
    function foo(){
        console.log(this)
    }
    
    var obj = {
        name: 'hzy'
    }
    
    var bar = foo.bind(obj)
    
    bar() // obj
    bar() // obj
    
    1. 内置函数
      1. setTimeout : 默认指向window
      2. 数组的forEach :默认指向window,forEach最后一个参数可以指定this的绑定
      3. div点击: 我们添加点击事件中的函数this指定为获取的DOM元素
  4. new绑定

    1. 创建一个全新的对象
    2. 对这个对象执行Prototype的连接
    3. 这个新对象会绑定到函数调用的this上(这不完成this绑定)
    4. 如果函数没有返回其他对象,表达式会返回这个新对象
    5. 注意点:new和call、apply不可以同时使用
  5. 绑定优先级

    1. new绑定 -> 显示绑定(bind) -> 隐式绑定 -> 默认绑定
  6. this规则之外

    1. 忽略显示绑定: null,Undefined -> window
    2. 间接函数引用:创建一个函数间接引用,使用默认绑定规则
function foo(){
    console.log(this)
}


var obj1  = {
    name: 'obj1',
    foo: foo
}

var obj2 = {
    name: 'obj2'
}

obj1.foo() // obj1对象
(obj2.foo = obj1.foo)() // window
  1. ES6箭头函数
    1. 没有使用箭头函数,使用上层需要 var _this = this;
    2. 使用箭头函数,箭头函数的this默认是去上层查找

3. this题

  1. 题目一
var name = "window";
var person = {
  name: "person",
  sayName: function () {
    console.log(this.name);
  }
};
function sayName() {
  var sss = person.sayName;
  sss();
  person.sayName();
  (person.sayName)();  
  (b = person.sayName)();  
sayName();





function sayName() {
  var sss = person.sayName;
  // 独立函数调用,没有和任何对象关联
  sss(); // window
  // 关联
  person.sayName(); // person
  (person.sayName)(); // person
  (b = person.sayName)(); // window
}
  1. 题目二
var name = 'window'
var person1 = {
  name: 'person1',
  foo1: function () {
    console.log(this.name)
  },
  foo2: () => console.log(this.name),
  foo3: function () {
    return function () {
      console.log(this.name)
    }
  },
  foo4: function () {
    return () => {
      console.log(this.name)
    }
  }
}

var person2 = { name: 'person2' }

person1.foo1();  // person1
person1.foo1.call(person2); // person2

person1.foo2(); // window
person1.foo2.call(person2); // window

person1.foo3()(); // window
person1.foo3.call(person2)(); // window
person1.foo3().call(person2); // person2

person1.foo4()(); // person1
person1.foo4.call(person2)(); // person2
person1.foo4().call(person2); // person1



// 隐式绑定,肯定是person1
person1.foo1(); // person1
// 隐式绑定和显示绑定的结合,显示绑定生效,所以是person2
person1.foo1.call(person2); // person2

// foo2()是一个箭头函数,不适用所有的规则
person1.foo2() // window
// foo2依然是箭头函数,不适用于显示绑定的规则
person1.foo2.call(person2) // window

// 获取到foo3,但是调用位置是全局作用于下,所以是默认绑定window
person1.foo3()() // window
// foo3显示绑定到person2中
// 但是拿到的返回函数依然是在全局下调用,所以依然是window
person1.foo3.call(person2)() // window
// 拿到foo3返回的函数,通过显示绑定到person2中,所以是person2
person1.foo3().call(person2) // person2

// foo4()的函数返回的是一个箭头函数
// 箭头函数的执行找上层作用域,是person1
person1.foo4()() // person1
// foo4()显示绑定到person2中,并且返回一个箭头函数
// 箭头函数找上层作用域,是person2
person1.foo4.call(person2)() // person2
// foo4返回的是箭头函数,箭头函数只看上层作用域
person1.foo4().call(person2) // person1
  1. 题目三
var name = 'window'
function Person (name) {
  this.name = name
  this.foo1 = function () {
    console.log(this.name)
  },
  this.foo2 = () => console.log(this.name),
  this.foo3 = function () {
    return function () {
      console.log(this.name)
    }
  },
  this.foo4 = function () {
    return () => {
      console.log(this.name)
    }
  }
}
var person1 = new Person('person1')
var person2 = new Person('person2')

person1.foo1() // person1
person1.foo1.call(person2) // person2

person1.foo2() // person1
person1.foo2.call(person2) // person1

person1.foo3()() // window
person1.foo3.call(person2)() // window
person1.foo3().call(person2) // person2

person1.foo4()() // person1
person1.foo4.call(person2)() // person2
person1.foo4().call(person2) // person1





// 隐式绑定
person1.foo1() // peron1
// 显示绑定优先级大于隐式绑定
person1.foo1.call(person2) // person2

// foo是一个箭头函数,会找上层作用域中的this,那么就是person1
person1.foo2() // person1
// foo是一个箭头函数,使用call调用不会影响this的绑定,和上面一样向上层查找
person1.foo2.call(person2) // person1

// 调用位置是全局直接调用,所以依然是window(默认绑定)
person1.foo3()() // window
// 最终还是拿到了foo3返回的函数,在全局直接调用(默认绑定)
person1.foo3.call(person2)() // window
// 拿到foo3返回的函数后,通过call绑定到person2中进行了调用
person1.foo3().call(person2) // person2

// foo4返回了箭头函数,和自身绑定没有关系,上层找到person1
person1.foo4()() // person1
// foo4调用时绑定了person2,返回的函数是箭头函数,调用时,找到了上层绑定的person2
person1.foo4.call(person2)() // person2
// foo4调用返回的箭头函数,和call调用没有关系,找到上层的person1
person1.foo4().call(person2) // person1
  1. 题目四
var name = 'window'
function Person (name) {
  this.name = name
  this.obj = {
    name: 'obj',
    foo1: function () {
      return function () {
        console.log(this.name)
      }
    },
    foo2: function () {
      return () => {
        console.log(this.name)
      }
    }
  }
}
var person1 = new Person('person1')
var person2 = new Person('person2')

person1.obj.foo1()() // window
person1.obj.foo1.call(person2)() // window
person1.obj.foo1().call(person2) // person2

person1.obj.foo2()() // obj
person1.obj.foo2.call(person2)() // person2
person1.obj.foo2().call(person2) // obj







// obj.foo1()返回一个函数
// 这个函数在全局作用于下直接执行(默认绑定)
person1.obj.foo1()() // window
// 最终还是拿到一个返回的函数(虽然多了一步call的绑定)
// 这个函数在全局作用于下直接执行(默认绑定)
person1.obj.foo1.call(person2)() // window
person1.obj.foo1().call(person2) // person2

// 拿到foo2()的返回值,是一个箭头函数
// 箭头函数在执行时找上层作用域下的this,就是obj
person1.obj.foo2()() // obj
// foo2()的返回值,依然是箭头函数,但是在执行foo2时绑定了person2
// 箭头函数在执行时找上层作用域下的this,找到的是person2
person1.obj.foo2.call(person2)() // person2
// foo2()的返回值,依然是箭头函数
// 箭头函数通过call调用是不会绑定this,所以找上层作用域下的this是obj
person1.obj.foo2().call(person2) // obj

原文链接(coderwhy)