关于 this 指向,可能有一部分人都是模糊的,本文对常见情况下的 this 指向作出总结,让你不再皱眉。
先了解
一个基本概念:普通函数的 this 指向不是在定义的时候确定,而是在调用的时候确定。
两个注意事项:
- 所有例子在浏览器环境(window 对象同时也是全局对象)运行,而不是 node 环境(global)。
-
非严格模式指向 Window 的对应严格模式是 undefined。(
Window.setTimeout
等除外,由setTimeout
调用的代码运行在与所在函数完全分离的执行环境上,即使在严格模式下仍然指向 Window)
接下来从一般形式函数调用、方法调用、apply 和 call 调用、箭头函数、class 等理清指向问题。
1. 一般形式函数调用
所谓一般形式函数调用就是 函数名()
,this 指向全局对象。
function test() {
console.log(this.name) // fang
}
var name = 'fang'
// let、const 声明的变量不是 Window 属性
const age = 1
console.log(this) // Window
console.log(this.age) // undefined
test()
2. 方法调用
一个函数被设置为对象(非全局对象)的属性值时,就是方法调用,this 指向对象自身。
-
当函数属于对象外层属性的属性值
function test() { 'use strict' console.log(this) // obj console.log(this.name) // fang } var name = 'wang' const obj = { name: 'fang', fun: test } obj.fun()
-
当函数属于深层对象的属性值,要明确知道它的使用者是谁
function test() { console.log(this) // obj2 console.log(this.name) // zhang } var name = 'wang' const obj1 = { name: 'fang', obj2: { name: 'zhang', fun: test } } obj1.obj2.fun()
-
当把对象的属性值函数赋值给一个新的变量,就会变成一般形式函数调用
function test() { console.log(this) // Window console.log(this.name) // wang } var name = 'wang' const obj1 = { name: 'fang', obj2: { name: 'zhang', fun: test } } var t = obj1.obj2.fun t()
-
当属性值函数内部还有函数
function test() { console.log(this) // Window console.log(this.name) // wang } var name = 'wang' const obj = { name: 'fang', foo: function() { console.log(this) // obj test() // test 是一般形式函数调用 } } obj.foo() // foo 是方法调用
3. call、apply 和 bind 用来改变 this 指向
-
call:
fun.call(thisArg[, arg1[, arg2[, ...]]])
,第一个参数是要绑定给 this 的值,后面传入参数列表。const obj = { name: 'fang' } function a() { console.log(this) } a.call(obj) // obj a.call(null) // 如果第一个参数是 null、undefined,指向 Window(下同)
-
apply:
fun.apply(thisArg[, [arg1, arg2, ...]])
,可接收两个参数,第一个是绑定给 this 的值,第二个是数组。const obj = { name: 'fang' } function a() { console.log(this) } a.apply(obj) // obj a.apply(undefined) // Window
-
bind:
fun.bind(thisArg[, arg1[, arg2[, ...]]])
,与call
相似,但是返回的是一个函数,需要手动去执行。const obj1 = { name: 'fang' } const obj2 = { name: 'wang' } function a() { console.log(this) } const b = a.bind(obj1) const c = a.bind(obj2) // 需要手动执行 b() // obj1 a() // Window,函数a不会受影响 c() // obj2 b.call(null) // obj1(绑定关系一旦确认给新变量,新变量继续使用 call、apply、bind 不会再次改变指向) c.bind(obj2)() // obj2
4. 箭头函数
箭头函数没有自己的 this,看其定义时外层是否有函数,如果有,外层函数的 this 就是内部箭头函数的 this,如果没有,则 this 指向 Window。
-
箭头函数外层没有函数
const arrow = () => { console.log(this.name) // wang } var name = 'wang' const obj = { name: 'fang', foo: function() { console.log(this) // obj arrow() // 看定义时 } } obj.foo()
var name = 'wang' const obj1 = { name: 'fang', obj2: { name: 'zhang', fun: () => { console.log(this) // Window } } } obj1.obj2.fun()
-
箭头函数外层有函数,注意外层函数的 this 指向按照之前规则判断
var name = 'wang' const obj = { name: 'fang', foo: function() { console.log(this) // obj const arrow = () => { console.log(this) // 指向外层函数 this } arrow() } } obj.foo()
-
箭头函数能否被改变指向?
var name = 'wang' const obj1 = {name: 'zhang'} const obj2 = { name: 'fang', foo: function() { console.log(this) // obj2 const arrow = () => { console.log(this.name) // fang } arrow.call(obj1) // 箭头函数不会改变 this 指向 } } obj2.foo()
5. class 类(es6 严格模式)
-
创建类实例后,再去调用类的方法,this 指向实例对象
class A { constructor({ age, name }) { this.name = name this.age = age } test() { console.log(this) // {name:'fang', age:1} } } const a = new A({ age: 1, name: 'fang' }) a.test()
-
直接通过 prototype 对象调用 test,指向 prototype
class A { constructor({ age, name }) { this.name = name this.age = age } test() { console.log(this) } } const a = new A({ age: 1, name: 'fang' }) a.test() // {name:'fang', age:1} console.log(A.prototype) // {constructor:f, test:f} A.prototype.test() // prototype(想想看是不是可以理解为方法调用)
-
子类创建一个实例后,指向子类实例对象,包括子类调用父类的方法
class A { constructor() { this.name = 1 this.age = 1 this.sex = 0 } test1(){ console.log(this) } } class B extends A { constructor({name,age}) { super() // super 必须置于 this 前 this.name = name // 如果不写,继承父类的属性 1 this.age = age // 如果不写,继承父类的属性 1 } test2() { console.log(this) } } const b = new B({ name: 3, age: 3 }) b.test2() // {age:3,name:3,sex:0} // 父类的方法被子类调用 b.test1() // {age:3,name:3,sex:0}
-
子类通过 prototype 调用的指向与父类是有区别的
class A { constructor() { this.name = 1 this.age = 1 this.sex = 0 } test1(){ console.log(this) } } class B extends A { constructor({name,age}) { super() // super 必须置于 this 前 this.name = name // 如果不写,继承父类的属性 1 this.age = age // 如果不写,继承父类的属性 1 } test2() { console.log(this) } } console.log(B.prototype) // A {constructor: ƒ, test2: ƒ} 注意与父类prototype的区别 B.prototype.test1() // A {constructor: ƒ, test2: ƒ} B.prototype.test2() // A {constructor: ƒ, test2: ƒ}
6. vue 中的 this
一般来说,在 vue 生命周期函数或自定义方法中 this 指向的是 vue 实例,但是要注意下面的3种情况。
-
回调函数 then 链式写法用普通函数,this 指向 undefined,可使用
_this=this
获取 vue 实例let _this = this // vue实例 /* eslint-disable */ request().then( function (res){ console.log(this) // undefined console.log(_this) // vue实例 })
-
setTimeout 执行普通函数指向 Window ,可使用箭头函数获取 vue 实例
setTimeout(() => { console.log(this) // vue 实例 }, 2000)
-
不应该使用箭头函数来定义 method 函数,箭头函数绑定了父级作用域的上下文,this 指向 undefined。
methods: { todo: () => console.log(this) // undefined }
以上理解如果有不对之处请指出。
网友评论