返回首页

说说你对this的理解?

问题解析

this 是 JavaScript 中最令人困惑的概念之一。与其他编程语言不同,JavaScript 的 this 不是在编写时绑定的,而是在运行时根据函数的调用方式动态确定的。理解 this 的绑定规则对于编写正确的 JavaScript 代码至关重要。

核心概念

this 是什么?

this 是函数运行时自动生成的一个内部对象,只能在函数内部使用,总指向调用它的对象。它的值是在函数被调用时确定的,而不是在函数定义时。

// this 的值取决于函数的调用方式,而不是定义方式
function showThis() {
    console.log(this);
}

showThis();              // window(浏览器)或 global(Node.js)

const obj = {
    show: showThis
};
obj.show();              // obj

showThis.call({a: 1});   // {a: 1}
showThis.apply([1, 2]);  // [1, 2]

详细解答

一、this 的绑定规则

1. 默认绑定(Default Binding)

function foo() {
    console.log(this);
}

foo();  // window(非严格模式)

// 严格模式下
function strictFoo() {
    'use strict';
    console.log(this);  // undefined
}
strictFoo();

// 注意:严格模式只在函数内部生效
function bar() {
    console.log(this);  // window(非严格模式)
}

(function() {
    'use strict';
    bar();  // window!bar 的定义不在严格模式下
})();

2. 隐式绑定(Implicit Binding)

// 作为对象方法调用
const person = {
    name: 'Alice',
    sayName: function() {
        console.log(this.name);
    }
};

person.sayName();  // "Alice"(this 指向 person)

// 只有最后一层影响 this
const anotherPerson = {
    name: 'Bob',
    friend: person
};

anotherPerson.friend.sayName();  // "Alice"(this 指向 person)

// 隐式丢失
const sayName = person.sayName;
sayName();  // undefined(this 指向全局对象,严格模式下)

// 回调函数中的隐式丢失
function doSomething(callback) {
    callback();
}
doSomething(person.sayName);  // undefined

3. 显式绑定(Explicit Binding)

function greet(greeting) {
    console.log(`${greeting}, ${this.name}`);
}

const person = { name: 'Alice' };

// call - 参数列表
greet.call(person, 'Hello');  // "Hello, Alice"

// apply - 参数数组
greet.apply(person, ['Hi']);  // "Hi, Alice"

// bind - 返回新函数
const greetAlice = greet.bind(person);
greetAlice('Hey');  // "Hey, Alice"

// bind 的柯里化
const greetHello = greet.bind(person, 'Hello');
greetHello();  // "Hello, Alice"

// 硬绑定的优先级高于隐式绑定
const obj = {
    name: 'Bob',
    greet: greet.bind(person)
};
obj.greet('Hi');  // "Hi, Alice"(bind 优先)

4. new 绑定(Constructor Binding)

function Person(name) {
    this.name = name;
    this.sayName = function() {
        console.log(this.name);
    };
}

const alice = new Person('Alice');
alice.sayName();  // "Alice"

// new 做了什么:
// 1. 创建一个空对象
// 2. 将对象的 __proto__ 指向 Person.prototype
// 3. 将 Person 中的 this 指向这个新对象
// 4. 执行构造函数代码
// 5. 返回这个新对象(除非构造函数返回了一个对象)

// 特殊情况:构造函数返回对象
function SpecialPerson(name) {
    this.name = name;
    return { name: 'Override' };  // 返回对象会覆盖默认返回
}

const special = new SpecialPerson('Alice');
console.log(special.name);  // "Override"

// 返回非对象则忽略
function NormalPerson(name) {
    this.name = name;
    return 123;  // 被忽略
}
const normal = new NormalPerson('Alice');
console.log(normal.name);  // "Alice"

二、箭头函数中的 this

// 箭头函数没有自己的 this,继承外层作用域的 this
const obj = {
    name: 'Alice',
    regularFunction: function() {
        console.log(this.name);  // "Alice"

        setTimeout(function() {
            console.log(this.name);  // undefined(this 指向全局对象)
        }, 100);

        setTimeout(() => {
            console.log(this.name);  // "Alice"(继承外层 this)
        }, 100);
    },
    arrowFunction: () => {
        console.log(this.name);  // undefined(继承定义时的 this)
    }
};

obj.regularFunction();
obj.arrowFunction();

// 箭头函数不能通过 call/apply/bind 改变 this
const arrow = () => console.log(this);
arrow.call({a: 1});  // window(忽略显式绑定)

// 箭头函数不能作为构造函数
const Arrow = () => {};
new Arrow();  // TypeError: Arrow is not a constructor

三、this 绑定优先级

// 优先级:new 绑定 > 显式绑定 > 隐式绑定 > 默认绑定

// 1. new 绑定 vs 显式绑定
function foo(something) {
    this.a = something;
}

const obj1 = {};

const bar = foo.bind(obj1);
bar(2);
console.log(obj1.a);  // 2

const baz = new bar(3);
console.log(obj1.a);  // 2(new 改变了 this 绑定)
console.log(baz.a);   // 3

// 2. 显式绑定 vs 隐式绑定
function sayName() {
    console.log(this.name);
}

const obj2 = {
    name: 'obj2',
    say: sayName
};

const obj3 = {
    name: 'obj3'
};

obj2.say();              // "obj2"(隐式绑定)
obj2.say.call(obj3);     // "obj3"(显式绑定优先)

// 3. 隐式绑定 vs 默认绑定
function test() {
    console.log(this);
}

const obj4 = {
    test: test
};

test();       // window(默认绑定)
obj4.test();  // obj4(隐式绑定优先)

深入理解

1. 常见 this 指向问题

// 问题1:对象方法中的嵌套函数
const obj = {
    name: 'Alice',
    friends: ['Bob', 'Charlie'],
    showFriends: function() {
        this.friends.forEach(function(friend) {
            // 这里的 this 指向全局对象,不是 obj
            console.log(`${this.name} is friends with ${friend}`);
        });
    },
    showFriendsFixed: function() {
        const self = this;  // 保存 this 引用
        this.friends.forEach(function(friend) {
            console.log(`${self.name} is friends with ${friend}`);
        });
    },
    showFriendsArrow: function() {
        this.friends.forEach(friend => {
            // 箭头函数继承外层 this
            console.log(`${this.name} is friends with ${friend}`);
        });
    }
};

// 问题2:DOM 事件处理函数
const button = document.getElementById('btn');

button.addEventListener('click', function() {
    console.log(this);  // button 元素
});

button.addEventListener('click', () => {
    console.log(this);  // 外层 this(通常是 window)
});

// 问题3:类中的方法
class Person {
    constructor(name) {
        this.name = name;
    }

    sayName() {
        console.log(this.name);
    }

    // 使用箭头函数确保 this 绑定
    sayNameArrow = () => {
        console.log(this.name);
    }
}

const person = new Person('Alice');
person.sayName();  // "Alice"

const sayName = person.sayName;
sayName();  // undefined(this 丢失)

const sayNameArrow = person.sayNameArrow;
sayNameArrow();  // "Alice"(this 被绑定)

2. this 与原型链

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

Animal.prototype.sayName = function() {
    console.log(this.name);
};

function Dog(name, breed) {
    Animal.call(this, name);
    this.breed = breed;
}

Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;

Dog.prototype.sayBreed = function() {
    console.log(this.breed);
};

const dog = new Dog('Buddy', 'Golden Retriever');
dog.sayName();   // "Buddy"(this 指向 dog)
dog.sayBreed();  // "Golden Retriever"(this 指向 dog)

最佳实践

1. 使用箭头函数避免 this 问题

const obj = {
    name: 'Alice',
    delayedLog: function() {
        // ✅ 使用箭头函数
        setTimeout(() => {
            console.log(this.name);  // "Alice"
        }, 100);
    }
};

2. 使用 bind 固定 this

class Component {
    constructor() {
        this.name = 'Component';
        // ✅ 在构造函数中绑定 this
        this.handleClick = this.handleClick.bind(this);
    }

    handleClick() {
        console.log(this.name);
    }
}

3. 使用类字段(Class Fields)语法

class Component {
    name = 'Component';

    // ✅ 箭头函数作为类字段,自动绑定 this
    handleClick = () => {
        console.log(this.name);
    }
}

4. 明确 this 的类型(TypeScript)

interface Person {
    name: string;
    greet(this: Person, greeting: string): void;
}

const person: Person = {
    name: 'Alice',
    greet(greeting: string) {
        console.log(`${greeting}, ${this.name}`);
    }
};

面试要点

  1. this 的绑定规则有哪些?

    • 默认绑定:独立函数调用,this 指向全局对象(严格模式 undefined)
    • 隐式绑定:作为对象方法调用,this 指向该对象
    • 显式绑定:call/apply/bind,this 指向指定的对象
    • new 绑定:构造函数调用,this 指向新创建的实例
  2. 箭头函数的 this 有什么特点?

    • 没有自己的 this,继承外层作用域的 this
    • 不能通过 call/apply/bind 改变 this
    • 不能作为构造函数使用
  3. this 绑定的优先级?

    • new 绑定 > 显式绑定 > 隐式绑定 > 默认绑定
  4. 如何解决回调函数中的 this 丢失问题?

    • 使用箭头函数
    • 使用 bind 绑定 this
    • 使用变量保存 this(self/that)
    • 使用类字段语法
  5. setTimeout 中的 this 指向?

    • 普通函数:指向全局对象(或严格模式 undefined)
    • 箭头函数:继承外层 this
  6. addEventListener 回调中的 this 指向?

    • 普通函数:指向触发事件的 DOM 元素
    • 箭头函数:继承外层 this