this
以下代码只要涉及到window,必须在浏览器环境下执行
this的几种模式:
- 方法调用模式下,
this总是指向调用它所在方法的对象,this的指向与所在方法的调用位置有关,而与方法的声明位置无关 - 函数调用下,
this指向window,调用方法没有明确对象的时候,this指向window,如setTimeout、匿名函数等 - 构造函数调用模式下,
this指向被构造的对象 apply、call、bind调用模式下,this指向第一个参数- 箭头函数,在声明的时候绑定
this,而非取决调用的位置 - 严格模式下,如果
this没有被执行环境(execution context)定义,那this是undefined
优先级
| 优先级 | 绑定方式 | 示例 |
|---|---|---|
| 1 (最高) | new 绑定 | new Foo() |
| 2 | 显式绑定 | foo.call(obj), foo.bind(obj) |
| 3 | 隐式绑定 | obj.foo() |
| 4 (最低) | 默认绑定 | foo() (非严格模式下指向 window/global) |
var obj = {
name: 'obj',
foo: function () {
console.log(this)
}
}
var f = new obj.foo() // foo {}new优先级高于隐式绑定的
function foo() {
console.log(this)
}
var bar = foo.bind('aaa')
var obj = new bar() // foo {}方法调用模式
var test = function () {
console.log(this.x)
}
var x = '2'
var obj = {
x: '1',
fn: test
}
obj.fn() // 1
test() // 2可以看出,this指向调用它所在方法的对象,test方法在obj对象下,所以this指向obj,test在window对象下,所以this指向window。也可以看出:this和声明位置无关,和调用位置有关
let obj1 = {
a: 222
}
let obj2 = {
a: 111,
fn: function () {
console.log(this.a)
}
}
obj1.fn = obj2.fn
obj1.fn() // 222这个其实不难理解,虽然obj1.fn是从obj2.fn赋值而来,但是调用函数的是obj1,所以this指向obj1
函数调用模式
var a = 1
function fn1() {
console.log(this.a) // 1
}
fn1() // 1
window.b = 2
function fn2() {
console.log(this.b) // 2
}
fn2() // 可以理解为 window.fn()需要在浏览器下运行代码,因为window是浏览器的全局对象
匿名函数,setTimeout
;(function () {
console.log('1', this) // window
})()
setTimeout(() => {
console.log('2', this) // window
}, 0)
setTimeout(function () {
console.log('3', this) // window
}, 0)构造函数模式
var flag = undefined
function Fn() {
flag = this
}
var obj = new Fn()
console.log(flag === obj) // true这个this指向obj,内部原理还是用apply把this指向obj的,回忆一下js中new一个对象的过程
call apply bind
call和apply作用完全一样,唯一区别就是参数
var obj = {
name: '111',
getName: function () {
console.log(this.name)
}
}
var otherObj = {
name: '222'
}
var name = '333'
obj.getName() // 111
obj.getName.call() // 333
obj.getName.call(otherObj) // 222
obj.getName.apply() // 333
obj.getName.apply(otherObj) // 222
obj.getName.bind(this)() // 333
obj.getName.bind(otherObj)() // 222不传参数,call和apply和bind会把this指向window,那些333由此而来
箭头函数
关于ES6的箭头函数,官方解释是:箭头函数里面的this是山下文(context),外部作用域的this就是箭头函数的this
let obj = {
a: 222,
fn: function () {
setTimeout(() => console.log(this.a), 0)
}
}
obj.fn() // 222由于fn是由obj调用的,fn内部的this指向obj,箭头函数寻找this,会往外层函数fn找,发现fn的this是obj,于是,这个箭头函数的this就永久的寄生在obj了
如果不用箭头函数
let obj = {
a: 222,
fn: function () {
setTimeout(function () {
console.log(this.a)
})
}
}
obj.fn() // 输出:undefined (在非严格模式浏览器中可能是 100 等全局变量)普通函数作为回调被setTimeout调用时,由于js引擎在全局环境触发的,此时内部的this会丢失,指向全局对象(window或global),而全局对象找不到a,所以是undefined
var name = 'window'
var A = {
name: 'A',
sayHello: () => {
console.log(this.name)
}
}
A.sayHello() // 输出的是 window var 会把变量挂载到 window全局变量上那么如何永远绑定A呢
var name = 'window'
var A = {
name: 'A',
sayHello: function () {
var s = () => console.log(this.name)
return s // 返回的是箭头函数S
}
}
var satHello = A.sayHello() // A.sayHello()返回的是箭头函数S
satHello() // 执行箭头函数Scall、apply、bind方法对于箭头函数来说都只是传入参数,对他的this毫无影响
var globalObject = this
var foo = () => this
console.log(globalObject === foo()) // true
var obj = { foo: foo }
console.log(foo.call(obj) === globalObject) // true
foo = foo.bind(obj)
console.log(foo() === globalObject) // true严格模式
非严格模式下,this默认指向全局对象window
// 非严格模式下
function f() {
return this
}
console.log(f() === window) // true严格模式下,this为undefined
'use strict'
function f() {
return this
}
console.log(f() === window) // true示例一
globalThis.a = 100
function fn() {
return {
a: 200,
m: function () {
console.log(this.a)
},
n: () => {
console.log(this.a)
},
k: function () {
return function () {
console.log(this.a)
}
}
}
}
const fn0 = fn()
fn0.m() // 200 this指向 { a, m, n}
fn0.n() // 100 this指向 globalThis
fn0.k()() // 100 this指向globalThis
const context = { a: 300 }
const fn1 = fn().call(context) // 改变 箭头函数 this指向
fn1.m() // 200 this指向 { a, m, n }
fn1.n() // 300 this指向 context
fn1.k().call(context) // 300 this指向 context前三个就讲最后一个
fn0.k()返回了一个匿名普通函数。随后执行()是独立函数调用。在非严格模式下,独立调用的函数this始终指向全局对象
fn1.m():
关键点:fn().call(context)只是在"生产"这个对象的过程改变了this指向,并没有把生产出来的m方法锁死在context上,所以当fn1.m()的时候,m内部的this指向fn1,而fn1长:{ a: 200, m: function...}
fn1.n():
m是普通函数,n是箭头函数,它在定义的那一刻就看准了当时的this(也就是fn执行时的this),因为用了.call(context)执行了fn,导致n定义时的环境就是context。所以输出了300
fn1.k():
fn1.k() 返回一个普通函数,接着我们使用 .call(context) 显式地将这个返回函数的 this 指定为 context,所以输出 300。
示例2
globalThis.a = 100
const person1 = {
a: 'person1',
foo1: function () {
console.log(this.a)
},
foo2: () => {
console.log(this.a)
},
foo3: function () {
return function () {
console.log(this.a)
}
},
}
const person2 = {
a: 'person2',
}
person1.foo1() // person1
person1.foo1.call(person2) // person2
person1.foo2() // 100
person1.foo2.call(person2) // 100
person1.foo3()() // 100
person1.foo3.call(person2)() // 100person1.foo2().call(person2):箭头函数完全无视call、apply、bind等方法,this指向外层作用域的this,即globalThis,因为箭头函数被创建时,已经把this固定了foo3.call(person2)只改变了foo3内部的this,但返回的是全新创建的函数,这个函数并没有绑定person2,所以直接调用时this是全局对象->100
示例3
let length = 10
function fn() {
return this.length + 1
}
const obj = {
length: 5,
test1: function () {
return fn()
}
}
obj.test2 = fn
console.log(obj.test1()) // window 的窗口数
console.log(fn() === obj.test2()) // false