JS中的this问题

106 阅读5分钟

“我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第1篇文章,点击查看活动详情

this的指向问题

面向对象语言中 this 表示当前对象的一个引用。

但在 JavaScript 中 this 不是固定不变的,它会随着执行环境的改变而改变。

讨论this的原理,一定离不开this环境

判断this的诀窍:

1. this的指向在函数创建的时候是决定不了的,在调用的时候才能决定,谁调用的就指向谁

2. this永远指向最后调用它的对象

3. apply、call、bind会改变this的指向

4. new操作符会改变函数this的指向问题

this的绑定规则(优先级从低到高)

  1. 默认绑定
  2. 隐式绑定
  3. 显示绑定
  4. new绑定

默认绑定

默认绑定最大的特点就是独立函数的调用fn()

情况一: 普通函数独立调用

this默认绑定全局对象Window,而严格模式下,全局对象无法使用默认的绑定,无法直接使用this,所以在输出的时候,直接报错了

// 非严格模式下
var name = 'pig'
function fn() {
    name: 'zhuzhu',
    console.log(this.name)
}
fn()  // pig

// 严格模式下
'use strict'
var name = 'pig'
function fn() {
    name: 'zhuzhu',
    console.log(this.name)
}
fn()  // Uncaught TypeError: Cannot read properties of undefined (reading 'name')

情况二: 函数定义在对象中,但被独立调用

var name = 'pig'
const a = {
    name: 'zhuzhu',
    fn: function() {
        console.log(this.name)
    }
}
var b = a.fn
b()  // pig

情况三: 函数在另一个函数中被调用

var name = 'pig'
const a = {
    name: 'zhuzhu',
    fn: function() {
        console.log(this.name)
    }
}
function b(params) {
    params()
}
b(a.fn)  // pig

隐式绑定

隐式绑定的特点是函数被某个对象调用obj.fn(),此时的this指向该对象

案例一

var name = 'pig'

function fn() {
    console.log(this.name)
}

const a = {
    name: 'zhuzhu',
    fn: fn
}
fn() // pig
a.fn() // zhuzhu

上述的案例一也说明了隐式绑定优先于默认绑定

将案例一做一个简单的改变,我们来看看案例二

var name = 'pig'

function fn() {
    console.log(this.name)
}

const a = {
    fn: fn
}
a.fn() // undefined

让我们把案例一和案例二结合起来看,不难发现当函数引用有上下文对象时,隐式绑定规则会把函数调用中的this绑定到这个上下文对象

可以将案例二和显示绑定的情况二好好再了解一下,相信你很快就能理解

案例三

var name = 'pig'

function fn() {
    console.log(this.name)
}

const a = {
    name: 'zhuzhu',
    fn: fn
}

const b = {
    name: 'cute',
    a: a
}
b.a.fn()  // zhuzhu

将案例三和案例一好好对比一下,你会发现在调用fn的引用链上,this永远指向最后一个引用它的对象

显示绑定

使用applycallbind实现显示绑定

var name = 'pig';
function fn() {
    console.log(this.name);
}
const obj = { 
    name: 'zhuzhu',
    fn
};
obj.fn();  // zhuzhu
obj.fn.apply({ name: 'apply优先级高于隐式绑定'})  // apply优先级高于隐式绑定
obj.fn.call({ name: 'call优先级高于隐式绑定'})  // call优先级高于隐式绑定
obj.fn.bind({ name: 'bind优先级高于隐式绑定'})  // 返回对象 fn
obj.fn.bind({ name: 'bind优先级高于隐式绑定'})()  // bind优先级高于隐式绑定

注意: 第一个bind和第二个bind打印结果的不同,这是因为bind()方法创建一个新的函数,所以返回结果也是一个函数;而callapply返回的是一个新的对象

new绑定

通过new关键字调用一个函数构造器,这个时候this是在调用这个构造器时创建出来的对象

// 函数
function fn(params) {
    this.name = params
}
const a = new fn('pig')
console.log(a.name)  // pig

// 构造器 
class Person { 
    constructor() { 
        this.name = "zhuzhu"; 
    } 
} 
const b = new Person(); // Person类中的this指向对象b 
console.log(b.name); // zhuzhu

new绑定优先于隐式绑定

const a = {
    fn: function(name) {
        this.name = name
        console.log(this.name)
    }
}
a.fn('pig')  // pig
const b = new a.fn('zhuzhu')  // zhuzhu

new绑定优先于显示绑定

new绑定优先于显示绑定中的bind,但不能与call/apply一起使用,原因是new的对象需要是函数或构造器,而call/apply返回的是对象

function fn() {
    console.log(this)
}
const a = fn.bind({ name: 'bind'})
a()  // {name: 'bind'}
new a()  // fn {}

const b = fn.apply({ name: 'apply'})  // {name: 'apply'}
new b;  // Uncaught TypeError:b is not a constructor

const c = fn.call({ name: 'call'})  // {name: 'call'} 
new c;  // Uncaught TypeError:c is not a constructor
/**
    当尝试使用对象或变量作为构造函数
    但该对象或变量不是构造函数时
    会发生 JavaScript 异常“不是构造函数”。
*/

优先级规律总结:

new > bind > call/apply > 隐式绑定 > 默认绑定

箭头函数的this指向

箭头函数没有this指向,它的this是根据外层(函数或者全局)作用域决定的,不适用上面4条规则

箭头函数表达式更适用于那些本来需要匿名函数的地方,并且它不能用作构造函数,不能使用new调用

如果在箭头函数中使用this,this关键字将指向箭头函数定义位置中的this

箭头函数内部指向通过作用域链查找,一般是指向父级的this,没有父级会一直往上找

使用箭头函数的注意事项:

  1. 使用箭头函数,函数内部没有arguments,this指向window
const add = (a,b) => {
    console.log(arguments)  // arguments is not defined
    return a+b;
}
console.log(add(1,3))  // 4
  1. 没有this指向,箭头函数不能使用new关键字实例化对象,function函数也是一个对象,但是箭头函数不是一个对象,它其实是语法糖

改变this的指向

  1. 使用箭头函数
  2. 使用bind、call、apply
  3. new实例化一个对象

参考

克里斯蒂亚L juejin.cn/post/713608…