JavaScript中的this指向与函数封装艺术:从迷惑到精通

171 阅读3分钟

前言:this的迷惑行为大赏

大家好,我是你们的老朋友FogLetter,今天我们来聊聊JavaScript中最让人迷惑又最重要的概念之一——this。相信不少同学在初学JavaScript时,都曾被this的"善变"折磨得死去活来。它时而指向window,时而指向某个对象,有时候干脆undefined给你看。

但别担心,今天我将用最生动的方式,带大家彻底搞懂this的指向规则,以及如何用callapplybind来驯服这个"善变"的家伙。最后我们还会聊聊如何用这些知识来写出更优雅的封装代码。

一、this的四种基本指向规则

1. 默认绑定:普通函数调用

var name = 'a';
function fn(){
    var name = 'b';
    console.log(this.name);
} 
fn(); // 输出什么?

这里fn()是直接调用的,没有绑定任何对象,此时this指向全局对象(浏览器中是window)。所以输出'a'

注意:在严格模式下("use strict"),这种情况this会是undefined,可以避免意外的全局污染。

2. 隐式绑定:方法调用

let obj = {
    name: 'fog',
    fn: function() {
        console.log(this.name);
    }
}
obj.fn(); // 输出 'fog'

当函数作为对象的方法调用时,this指向调用它的对象。这里obj.fn()中的this就是obj

但有个坑:

const fn2 = obj.fn;
fn2(); // 输出 'a'

为什么?因为赋值后fn2是直接调用的,没有通过obj,所以this又回到了默认绑定规则。

3. 显式绑定:call/apply/bind

var a = {
    name: 'fog',
    fn: function(a,b) {
        console.log(this.name);
        console.log(a,b);
    }
}
const b = a.fn;
b.call(a,1,2);  // fog 1 2
b.apply(a,[1,2]); // fog 1 2
b.bind(a,1,2)(); // fog 1 2

这三种方法都可以显式指定this的值:

  • call:立即执行,参数逐个传递
  • apply:立即执行,参数以数组形式传递
  • bind:返回一个新函数,需要再调用一次

4. new绑定:构造函数调用

function Person(name,age) {
    this.name = name;
    this.age = age;
}
const haha = new Person('haha',18);
console.log(haha.name); // 'haha'

使用new调用函数时:

  1. 创建一个新对象
  2. 这个新对象会绑定到函数的this
  3. 如果函数没有返回其他对象,就自动返回这个新对象

二、箭头函数:this的例外

箭头函数没有自己的this,它会捕获所在上下文的this值,作为自己的this值。

var a = {
    func2: function() {
        setTimeout(() => {
            this.func1(); // this正确指向a
        }, 1000)
    }
}

对比普通函数:

var a = {
    func2: function() {
        setTimeout(function() {
            this.func1(); // 报错,this指向window
        }, 1000)
    }
}

三、实战:封装一个按钮组件

让我们用this和原型链的知识,封装一个按钮组件:

// button.js
function Button(id) {
    this.element = document.querySelector(`#${id}`);
    this.bindEvent();
}

Button.prototype.bindEvent = function() {
    this.element.addEventListener('click', this.setBgColor.bind(this));
}

Button.prototype.setBgColor = function() {
    this.element.style.backgroundColor = '#1abc9c';
}

// 使用
new Button('button');

为什么需要bind(this)?

如果不绑定,事件处理函数中的this会指向触发事件的DOM元素。通过bind(this),我们确保在setBgColor方法中,this仍然指向Button实例。

四、this指向的优先级

当多种规则同时适用时,优先级如下:

  1. new绑定:var obj = new Foo()
  2. 显式绑定:call/apply/bind
  3. 隐式绑定:obj.foo()
  4. 默认绑定:foo()

五、常见陷阱与最佳实践

1. 回调函数中的this丢失

// 错误示范
var obj = {
    data: 'important',
    fetchData: function() {
        ajaxCall(function(response) {
            console.log(this.data); // undefined
        });
    }
}

// 正确做法1:使用箭头函数
var obj = {
    data: 'important',
    fetchData: function() {
        ajaxCall((response) => {
            console.log(this.data); // 'important'
        });
    }
}

// 正确做法2:使用bind
var obj = {
    data: 'important',
    fetchData: function() {
        ajaxCall(function(response) {
            console.log(this.data); // 'important'
        }.bind(this));
    }
}

2. 多层嵌套中的this

var obj = {
    name: 'outer',
    inner: {
        name: 'inner',
        logName: function() {
            console.log(this.name);
        }
    }
}

obj.inner.logName(); // 'inner'

记住:this指向最后调用它的对象,与定义位置无关。

结语

this的指向看似复杂,但只要掌握了四种基本规则,配合箭头函数和call/apply/bind的使用,就能轻松驾驭。在封装组件或类时,合理使用这些知识可以让代码更加健壮和优雅。

记住,理解this的关键在于看函数是如何被调用的,而不是在哪里定义的。多练习,多思考,很快你就能对this的指向了如指掌了!

希望这篇文章对你有帮助,如果觉得不错,别忘了点赞收藏哦~我们下期再见!