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
-
如果你把一个函数当成参数传递到另一个函数的时候,也会发生隐式丢失的问题,且与包裹着它的函数的this指向无关。在非严格模式下,会把该函数的this绑定到window上,严格模式下绑定到undefined。
-
如果
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
// ''
- 箭头函数中没有 this 绑定,必须通过查找作用域链来决定其值,如果箭头函数被非箭头函数包含,则 this 绑定的是最近一层非箭头函数的 this,否则,this 为 undefined。
- // 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()()
- // 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()
- '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'
- 箭头函数的
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'
-
总结
OK👌,来总结一下箭头函数需要注意的点吧:
- 它里面的
this
是由外层作用域来决定的,且指向函数定义时的this
而非执行时 - 字面量创建的对象,作用域是
window
,如果里面有箭头函数属性的话,this
指向的是window
- 构造函数创建的对象,作用域是可以理解为是这个构造函数,且这个构造函数的
this
是指向新建的对象的,因此this
指向这个对象。 - 箭头函数的
this
是无法通过bind、call、apply
来直接修改,但是可以通过改变作用域中this
的指向来间接修改。
- 它里面的
-
避免使用的场景
- 使用箭头函数定义对象的方法
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