this 小记

212 阅读5分钟

this的5种绑定方式

  • 默认绑定(非严格模式下this指向全局对象, 严格模式下this会绑定到undefined)
  • 隐式绑定(当函数引用有上下文对象时, 如 obj.foo()的调用方式, foo内的this指向obj)
  • 显示绑定(通过call()或者apply()方法直接指定this的绑定对象, 如foo.call(obj))
  • new绑定
  • 箭头函数绑定(this的指向由外层作用域决定的)
function foo () {
  console.log(this.a)
};
var obj = { a: 1, foo };
var a = 2;
var foo2 = obj.foo;
var obj2 = { a: 3, foo2: obj.foo }

obj.foo();
foo2();
obj2.foo2();
// 1 2 3
// foo3()发生了隐式丢失,调用者是obj2,使得foo()中的this指向obj2
function foo () {
  console.log(this.a)
}
function doFoo (fn) {
  console.log(this)
  fn()
}
var obj = { a: 1, foo }
var a = 2
doFoo(obj.foo)
w 2
function foo () {
  console.log(this.a)
}
function doFoo (fn) {
  console.log(this)
  fn()
}
var obj = { a: 1, foo }
var a = 2
var obj2 = { a: 3, doFoo }

obj2.doFoo(obj.foo)

// { a:3, doFoo: f }
// 2
  1. 如果你把一个函数当成参数传递到另一个函数的时候,也会发生隐式丢失的问题,且与包裹着它的函数的this指向无关。在非严格模式下,会把该函数的this绑定到window上,严格模式下绑定到undefined。

  2. 如果call、apply、bind接收到的第一个参数是空或者null、undefined的话,则会忽略这个参数

function foo () {
  console.log(this.a)
}
var a = 2
foo.call()
foo.call(null)
foo.call(undefined)
// 2 2 2
var obj1 = {
  a: 1
}
var obj2 = {
  a: 2,
  foo1: function () {
    console.log(this.a)
  },
  foo2: function () {
    setTimeout(function () {
      console.log(this)
      console.log(this.a)
    }, 0)
  }
}
var a = 3

obj2.foo1()
obj2.foo2()
// 2 w 3
var obj1 = {
  a: 1
}
var obj2 = {
  a: 2,
  foo1: function () {
    console.log(this.a)
  },
  foo2: function () {
    function inner () {
      console.log(this)
      console.log(this.a)
    }
    inner()
  }
}
var a = 3
obj2.foo1()
obj2.foo2()
// 2 w 3
function Person (name) {
  this.name = name
  this.foo1 = function () {
    console.log(this.name)
  }
  this.foo2 = function () {
    return function () {
      console.log(this.name)
    }
  }
}
var person1 = new Person('person1')
person1.foo1()
person1.foo2()()
// person1 
// '' 
  1. 箭头函数中没有 this 绑定,必须通过查找作用域链来决定其值,如果箭头函数被非箭头函数包含,则 this 绑定的是最近一层非箭头函数的 this,否则,this 为 undefined。
  2. // w o o
var obj = {
  name: 'obj',
  foo1: () => {
    console.log(this.name)
  },
  foo2: function () {
    console.log(this.name)
    return () => {
      console.log(this.name)
    }
  }
}
var name = 'window'
obj.foo1()
obj.foo2()()

  1. // o w
var name = 'window'
var obj1 = {
	name: 'obj1',
	foo: function () {
		console.log(this.name)
	}
}

var obj2 = {
	name: 'obj2',
	foo: () => {
		console.log(this.name)
	}
}

obj1.foo()
obj2.foo()

  1. 'person1' 'person1' 'window'
var name = 'window'
function Person (name) {
  this.name = name
  this.foo1 = function () {
    console.log(this.name)
  }
  this.foo2 = () => {
    console.log(this.name)
  }
}
var person2 = {
  name: 'person2',
  foo2: () => {
    console.log(this.name)
  }
}
var person1 = new Person('person1')
person1.foo1()
person1.foo2()
person2.foo2()

  • person1.foo2()为箭头函数,this由外层作用域决定,且指向函数定义时的this而非执行时,在这里它的外层作用域是函数Person,且这个是构造函数,并且使用了new来生成了对象person1,所以此时this的指向是为person1
var name = 'window'
function Person (name) {
  this.name = name
  this.foo1 = function () {
    console.log(this.name)
    return function () {
      console.log(this.name)
    }
  }
  this.foo2 = function () {
    console.log(this.name)
    return () => {
      console.log(this.name)
    }
  }
  this.foo3 = () => {
    console.log(this.name)
    return function () {
      console.log(this.name)
    }
  }
  this.foo4 = () => {
    console.log(this.name)
    return () => {
      console.log(this.name)
    }
  }
}
var person1 = new Person('person1')
person1.foo1()() // 'person1' 'window'
person1.foo2()() // 'person1' 'person1'
person1.foo3()() // 'person1' 'window'
person1.foo4()() // 'person1' 'person1'

  1. 箭头函数的this无法通过bind、call、apply直接修改,但是可以通过改变作用域中this的指向来间接修改。
var name = 'window'
var obj1 = {
  name: 'obj1',
  foo1: function () {
    console.log(this.name)
    return () => {
      console.log(this.name)
    }
  },
  foo2: () => {
    console.log(this.name)
    return function () {
      console.log(this.name)
    }
  }
}
var obj2 = {
  name: 'obj2'
}
obj1.foo1.call(obj2)() // 'obj2' 'obj2'
obj1.foo1().call(obj2) // 'obj1' 'obj1'
obj1.foo2.call(obj2)() // 'window' 'window'
obj1.foo2().call(obj2) // 'window' 'obj2'

  1. 总结

    OK👌,来总结一下箭头函数需要注意的点吧:

    • 它里面的this是由外层作用域来决定的,且指向函数定义时的this而非执行时
    • 字面量创建的对象,作用域是window,如果里面有箭头函数属性的话,this指向的是window
    • 构造函数创建的对象,作用域是可以理解为是这个构造函数,且这个构造函数的this是指向新建的对象的,因此this指向这个对象。
    • 箭头函数的this是无法通过bind、call、apply直接修改,但是可以通过改变作用域中this的指向来间接修改。
  2. 避免使用的场景

    • 使用箭头函数定义对象的方法
    let obj = {
        value: 'LinDaiDai',
        getValue: () => console.log(this.value)
    }
    obj.getValue() // undefined
    
    • 定义原型方法
    function Foo (value) {
        this.value = value
    }
    Foo.prototype.getValue = () => console.log(this.value)
    
    const foo1 = new Foo(1)
    foo1.getValue() // undefined
    
    
    • 构造函数使用箭头函数
    const Foo = (value) => {
        this.value = value;
    }
    const foo1 = new Foo(1)
    // 事实上直接就报错了 Uncaught TypeError: Foo is not a constructor
    console.log(foo1);
    
    
    • 作为事件的回调函数
    const button = document.getElementById('myButton');
    button.addEventListener('click', () => {
        console.log(this === window); // => true
        this.innerHTML = 'Clicked button';
    });
    
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'

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'

  • person1.obj.foo1()()返回的是一个普通的匿名函数,调用它的是window,所以打印出window
  • person1.obj.foo1.call(person2)()中是使用.call(person2)改变第一层函数中的this,匿名函数和它没关系,依旧是window调用的,所以打印出window
  • person1.obj.foo1().call(person2)是通过.call(person2)改变匿名函数内的this,所以绑定有效,因此打印出person2
  • person1.obj.foo2()()第一层为普通函数,第二层为匿名箭头函数。首先让我们明确匿名箭头函数内的this是由第一层普通函数决定的,所以我们只要知道第一层函数内的this是谁就可以了。而这里,第一层函数最后是由谁调用的呢 🤔️?是由obj这个对象,所以打印出obj
  • person1.obj.foo2.call(person2)()中使用.call(person2)改变了第一层函数中的this指向,所以第二层的箭头函数会打印出person2
  • person1.obj.foo2().call(person2)中使用.call(person2)想要改变内层箭头函数的this指向,但是失败了,所以还是为外层作用域里的this,打印出obj
function foo() {
  console.log( this.a );
}
var a = 2;
(function(){
  "use strict";
  foo();
})();
 
// 2