JavaScript中我理解的 this

324 阅读5分钟

在 JavaScript 里,this 关键字是一个相当重要且有些复杂的概念。它的指向并非固定不变,而是取决于函数的调用方式。理解 this 的指向和应用场景,对于编写高效、可维护的 JavaScript 代码至关重要。接下来,我们将深入探讨 浏览器this 的五种常见情况,Node.js中两种常见情况,并介绍 this 在鸭子类型中的有趣应用和this 几种可能导致内存泄漏的情况及原因。

一、浏览器里this 的五种情况分析

1. 函数执行时的 this 指向

函数执行时,this 的指向取决于方法前面是否有 “点”。如果没有 “点”,在非严格模式下 this 指向 window,在严格模式下则为 undefined;如果有 “点”,“点” 前面是谁,this 就指向谁。

const fn = function fn() { 
    console.log(this)
}
let obj = { 
    name: 'rx', 
    fn: fn 
}
fn(); // 非严格模式下 this 指向 window
obj.fn(); // this 指向 obj

2. 事件绑定方法中的 this 指向

当给当前元素的某个事件行为绑定方法时,事件触发时方法中的 this 通常指向当前元素本身(但要排除 attachEvent)。

document.body.addEventListener('click'function () {
    console.log(this); // this 指向 document.body
});

3. 构造函数中的 this 指向

在构造函数体中,this 指向当前类的实例。

function Factory() {
    this.name = 'raferxu';
    this.age = 12;
    console.log(this); // this 指向 Factory 的实例
}
let f = new Factory();

4. 箭头函数中的 this 指向

箭头函数没有自己的执行主体,它所用到的 this 都是其所处上下文中的 this

let demo = { 
    name: 'DEMO', 
    fn() { 
        console.log(this); // this 指向 demo 
        setTimeout(function () { 
            console.log(this); // this 指向 window 
        }, 1000); 
        setTimeout(() => { 
            console.log(this); // this 指向 demo 
        }, 1000); 
    } 
}; 
demo.fn();

5. 通过 callapplybind 改变 this 指向

可以基于 Function.prototype 上的 callapplybind 方法来改变函数中 this 的指向。

function func(x, y) { 
    console.log(this, x, y); 
} 
let obj = { name: 'OBJ' }; 
// 使用 call 方法 
func.call(obj, 10, 20); 
// 使用 apply 方法 
func.apply(obj, [10, 20]);

二、Node.js里this 的两种情况分析

1. 全局作用域中的 this

在 Node.js 的全局作用域里,this 指向的是 global 对象,这和在浏览器环境中 this 指向 window 对象有所不同。

// 在 Node.js 全局作用域中
console.log(this === global); // 输出: true

// 定义一个全局变量
this.myGlobalVariable = 'Hello, Node.js';
console.log(global.myGlobalVariable); // 输出: Hello, Node.js

2. 模块作用域中的 this

在 Node.js 的模块作用域里,this 指向的是 module.exports 对象。需要注意的是,这和在浏览器中的情况不一样,在浏览器里全局作用域中的 this 指向 window 对象。

// 模块文件 example.js
console.log(this === module.exports); // 输出: true

this.myModuleVariable = 'Module variable';
console.log(module.exports.myModuleVariable); // 输出: Module variable

三、this 的好玩应用:鸭子类型

鸭子类型是一种编程概念,即 “如果它走起来像鸭子、叫起来也像鸭子,那么它就是鸭子”。在 JavaScript 中,类数组对象虽然不是真正的数组,但它们具有类似数组的结构和操作。我们可以利用 this 和 call 方法让类数组对象使用数组的方法。

function func() {
    [].forEach.call(arguments, item => {
        console.log(item);
    });
}
func(10, 20, 30);

在上述代码中,arguments 是一个类数组对象,通过 [].forEach.call(arguments, ...) 让它可以使用数组的 forEach 方法。

四、this 可能导致内存泄漏的情况及原因

this 本身不会导致内存泄漏,但在闭包、构造函数、定时器或异步操作等场景中,由于对 this 的不当引用,可能会间接引发内存泄漏问题。为了避免内存泄漏,需要正确处理 this 的指向,并在不需要时及时清理相关的引用和资源。

1. 闭包中对 this 的不当引用

当在闭包中引用 this 时,如果闭包的生命周期过长,且持有对外部对象的引用,可能会导致外部对象无法被垃圾回收,从而造成内存泄漏。

function Outer() {
    this.data = new Array(1000000).fill('data');

    // 定义一个闭包函数
    this.innerFunction = function() {
        // 闭包中引用了 this
        console.log(this.data);
    }.bind(this);

    // 模拟事件绑定
    document.addEventListener('click', this.innerFunction);
}

const outerInstance = new Outer();

在上述代码中,innerFunction 是一个闭包,它通过 bind(this) 绑定了 Outer 实例的 this。同时,innerFunction 被添加为 click 事件的监听器,只要事件监听器存在,innerFunction 就不会被销毁,从而导致 outerInstance 无法被垃圾回收,即使它不再被其他地方使用。

解决方法:在不需要事件监听器时,及时移除它。

function Outer() {
    this.data = new Array(1000000).fill('data');

    this.innerFunction = function() {
        console.log(this.data);
    }.bind(this);

    document.addEventListener('click', this.innerFunction);

    // 提供一个方法来移除事件监听器
    this.destroy = function() {
        document.removeEventListener('click', this.innerFunction);
    };
}

const outerInstance = new Outer();
// 在不需要时调用 destroy 方法
outerInstance.destroy();

2. 构造函数中对 this 的循环引用

如果在构造函数中创建了对 this 的循环引用,会导致对象无法被垃圾回收。

function Circular() {
    this.self = this;
}

const circularInstance = new Circular();

在这个例子中,circularInstance 的 self 属性指向了它自身,形成了一个循环引用。这使得垃圾回收机制无法判断该对象是否可以被回收,从而导致内存泄漏。

解决方法:避免在对象内部创建对自身的循环引用。

3. 定时器或异步操作中对 this 的引用

在定时器或异步操作中,如果不正确处理 this 的指向,可能会导致对象无法被销毁。

function TimerExample() {
    this.data = 'some data';

    // 定时器中使用 this
    this.timer = setInterval(() => {
        console.log(this.data);
    }, 1000);
}

const timerInstance = new TimerExample();

在上述代码中,定时器会一直运行,只要定时器存在,timerInstance 就无法被垃圾回收。

解决方法:在不需要定时器时,及时清除它。

function TimerExample() {
    this.data = 'some data';

    this.timer = setInterval(() => {
        console.log(this.data);
    }, 1000);

    // 提供一个方法来清除定时器
    this.destroy = function() {
        clearInterval(this.timer);
    };
}

const timerInstance = new TimerExample();
// 在不需要时调用 destroy 方法
timerInstance.destroy();

总结

通过对浏览器 this 的五种情况分析、Node.js this 的两种情况分析以及了解鸭子类型的应用和this 可能导致内存泄漏的情况及原因,我们对 this 关键字有了更深入的理解。this 虽然复杂,但只要掌握了它的规律和应用场景,就能在 JavaScript 编程中灵活运用,编写出更加高效、健壮的代码。希望本文能帮助你更好地掌握 this 关键字,提升你的 JavaScript 编程能力。