Skip to content

09 面向对象:通过词法作用域和调用点理解 this 绑定

今天,我们来讲讲 JavaScript 中的 this。

讲 this 的资料有很多,其中不少已经把这个概念讲得很清楚了。但是为了课程的系统性,我今天也从对象和面向对象的角度来说一说。

从直观的印象来看,你可能觉得 this 指的是函数本身或它所在的范围,其实这样的理解都是不对的。在 JavaScript 中,this 是在运行时而不是编写时绑定的。所以要正确地使用它,需要考虑到函数调用时的执行上下文。

一、默认绑定

javascript
function aLogger() {
    console.log(this.a);
}

var a = 2;
aLogger(); // 2

在全局定义的函数,默认绑定到全局上下文,this 指向 window(非严格模式)。

注意:在 strict mode 下,默认绑定会返回 undefined

二、隐式绑定

当函数作为对象的方法调用时,this 绑定到该对象:

javascript
function aLogger() {
    console.log(this.a);
}

var obj = {
    a: 3,
    logger: aLogger
};

var a = 2;

obj.logger(); // 3

隐式绑定的问题:当把方法赋值给全局变量时,绑定会丢失:

javascript
var objLogger = obj.logger;
objLogger(); // 2(绑定丢失,指向全局)

三、显式绑定

使用 callapply 强制指定 this:

javascript
function logger() {
    console.log(this.a);
}

var obj = {
    a: 3
};

logger.call(obj); // 3
logger.apply(obj); // 3

四、硬性绑定

使用 ES5 的 bind 方法,无论如何调用,this 始终指向指定对象:

javascript
function logger() {
    console.log(this.a);
}

var obj = {
    a: 3
};

var hardBinding = logger.bind(obj);

hardBinding(); // 3
hardBinding.call(window); // 3(仍然指向 obj)

五、new 绑定

使用 new 创建实例时,this 指向新创建的对象:

javascript
function logger(a) {
    this.a = a;
    console.log(this.a);
}

var loggerA = new logger(2); // 2

new 绑定优先级高于 hard binding

javascript
function logger(a) {
    this.a = a;
}

var obj1 = {};
var hardBinding = logger.bind(obj1);
hardBinding(2);
console.log(obj1.a); // 2

var obj2 = new logger(3);
console.log(obj1.a); // 2
console.log(obj2.a); // 3

六、绑定优先级

绑定类型优先级说明
new 绑定最高构造函数创建新实例
显式绑定次之call/apply/bind
隐式绑定较低作为对象方法调用
默认绑定最低全局调用

七、箭头函数的 this

箭头函数的 this 不遵循普通绑定规则,而是在词法作用域中定义:

javascript
function logger() {
    return (a) => {
        console.log(this.a);
    };
}

var obj1 = { a: 2 };
var obj2 = { a: 3 };

var logger1 = logger.call(obj1);
logger1.call(obj2); // 2(箭头函数的 this 固定为 obj1)

箭头函数常用场景 - setTimeout

javascript
function logger() {
    setTimeout(() => {
        console.log(this.a);
    }, 1000);
}

var obj = { a: 2 };
logger.call(obj); // 2

八、软性绑定

自定义软性绑定,在 this 丢失时提供默认对象:

javascript
if (!Function.prototype.softBind) {
    Function.prototype.softBind = function(obj) {
        var fn = this,
            curried = [].slice.call(arguments, 1),
            bound = function() {
                return fn.apply(
                    (!this ||
                        (typeof window !== "undefined" && this === window) ||
                        (typeof global !== "undefined" && this === global)
                    ) ? obj : this,
                    curried.concat.apply(curried, arguments)
                );
            };
        bound.prototype = Object.create(fn.prototype);
        return bound;
    };
}

九、柯里化应用

bind 支持预设参数,实现函数的部分应用:

javascript
function fullFunc(x, y, z) {
    return x + y + z;
}

const partialFunc = fullFunc.bind(this, 1, 2);
partialFunc(9); // 12(1 + 2 + 9)

十、总结

this 的绑定是 JavaScript 中与闭包同等重要的概念:

  1. 默认绑定:非严格模式下指向全局,严格模式为 undefined
  2. 隐式绑定:作为对象方法调用时指向对象
  3. 显式绑定:通过 call/apply/bind 强制指定
  4. 硬性绑定:bind 始终保持 this 指向
  5. new 绑定:优先级最高,创建新实例
  6. 箭头函数:this 固定为词法作用域中的值

理解 this 的绑定规则,才能在 JavaScript 中熟练运用面向对象编程。