JS this 绑定的 4 种规则 —— 一次搞懂 this 指向

2 阅读3分钟

面试常出这种题:

const obj = {
  name: '张三',
  say: function() {
    return this.name;
  }
};

const say = obj.say;
console.log(obj.say());  // 张三
console.log(say());       // undefined 还是报错?

同一个函数,不同的调用方式,this 指向就完全不同。

一、this 是什么

核心真相:this 不是在函数定义时决定的,而是在函数调用时决定的

这句话是理解 this 的总纲,先记住。

同一个函数,不同的调用方式,this 就不同 —— 这就是所有 this 相关题目的本质。

二、4 种绑定规则(按优先级从低到高)

规则 1:默认绑定(优先级最低)

触发条件:函数独立调用

this 指向:

  • 非严格模式:全局对象(浏览器是 window)
  • 严格模式:undefined
function fn() {
  console.log(this);
}

fn();  
// 非严格模式:window
// 严格模式:undefined

常见陷阱:

const obj = {
  name: '张三',
  say: function() {
    return this.name;
  }
};

const say = obj.say;   // 把方法赋值给变量
say();                  // undefined —— 独立调用,this 是 window,window.name 是 undefined

踩坑点:函数被赋值给变量后独立调用,this 就丢了

规则 2:隐式绑定

触发条件:通过对象点语法调用 obj.fn()

this 指向:点号前的对象

const user = {
  name: '张三',
  say: function() {
    return this.name;
  }
};

user.say();  // "张三" —— this 是 user

关键:只看调用位置的最近一层

const parent = {
  name: '父',
  child: {
    name: '子',
    say: function() {
      return this.name;
    }
  }
};

parent.child.say();  // "子" —— this 是 child,不是 parent

常见陷阱:回调函数丢 this

const user = {
  name: '张三',
  say: function() {
    return this.name;
  }
};

setTimeout(user.say, 1000);  // undefined —— 传参时 say 变成独立函数

setTimeout(user.say, 1000) 相当于:

const fn = user.say;
setTimeout(fn, 1000);
fn();  // 独立调用,this 丢了

规则 3:显式绑定

触发条件:通过 call / apply / bind 手动指定 this。

this 指向:你传的第一个参数

function say() {
  return this.name;
}

const user = { name: '张三' };

say.call(user);       // "张三"
say.apply(user);      // "张三"
say.bind(user)();     // "张三"

差别:

  • call / apply:立即执行
  • bind:返回新函数,后续调用
const user = { name: '张三' };
const other = { name: '李四' };

user.say = function() { return this.name; };

user.say.call(other);   // "李四" —— call 覆盖了隐式绑定

规则 4:new 绑定(优先级最高)

触发条件:用 new 调用函数。

this 指向:new 出来的新对象

function Person(name) {
  this.name = name;
}

const p = new Person('张三');
console.log(p.name);  // "张三"

详细原理看[手写 new 那篇]

function Person(name) {
  this.name = name;
}

const bound = Person.bind({ name: '李四' });

const p = new bound('张三');
console.log(p.name);  // "张三" —— new 覆盖了 bind

即使 bind 到"李四",new bound() 还是会创建新对象,把 this 绑到新对象上。

三、优先级总结

new 绑定 > 显式绑定 (call/apply/bind) > 隐式绑定 (obj.fn) > 默认绑定 (fn)

四、箭头函数特例(没有 this)

箭头函数没有自己的 this,继承定义时外层作用域的 this

const user = {
  name: '张三',
  say: function() {
    const inner = () => {
      return this.name;
    };
    return inner();
  }
};

user.say();  // "张三"

关键:inner 是箭头函数,它的 this 继承自 say 的 this(即 user)。

对比:如果 inner 是普通函数

const user = {
  name: '张三',
  say: function() {
    const inner = function() {
      return this.name;
    };
    return inner();
  }
};

user.say();  // undefined —— inner 独立调用,默认绑定,window.name 是 undefined

这是箭头函数最有用的地方:解决回调里 this 丢失的问题

箭头函数无法被改变 this

call/apply/bind 对箭头函数无效:

const fn = () => this.name;

const user = { name: '张三' };

fn.call(user);  // undefined —— call 无法改变箭头函数的 this

五、面试题一览(输出什么?)

题 1:

const obj = {
  name: '张三',
  say: function() {
    return this.name;
  }
};

const say = obj.say;
say();  // ???

答案:undefined(默认绑定,this 是 window)

题 2:

const obj = {
  name: '张三',
  say: function() {
    return () => this.name;
  }
};

const fn = obj.say();
fn();  // ???

答案:"张三"

  • obj.say() 隐式绑定,this 是 obj
  • 返回的箭头函数继承 say 的 this,还是 obj

题 3:

function Person(name) {
  this.name = name;
  return { name: '李四' };
}

const p = new Person('张三');
console.log(p.name);  // ???

答案:"李四"

  • 构造函数显式返回对象时,new 会用返回的对象

六、面试怎么答

面试官问:"this 指向怎么判断?"

满分答法:

  1. 总纲:this 不是定义时决定,是调用时决定
  2. 4 种规则 + 优先级:new > 显式 > 隐式 > 默认
  3. 箭头函数特例:没有自己的 this,继承外层
  4. 实际判断:从高到低检查函数调用形式
new xxx()          → new 绑定
xxx.call/apply/bind → 显式绑定
obj.xxx()          → 隐式绑定
xxx()              → 默认绑定

总结

this 4 规则:

  • 默认绑定:独立调用,window/undefined
  • 隐式绑定:obj.fn(),this 是 obj
  • 显式绑定:call/apply/bind,指定 this
  • new 绑定:new 出的新对象

优先级:new > 显式 > 隐式 > 默认

箭头函数:没有 this,继承外层