Home  >  Article  >  Web Front-end  >  In-depth analysis of this pointing problem in JavaScript

In-depth analysis of this pointing problem in JavaScript

巴扎黑
巴扎黑Original
2017-04-29 15:50:241106browse

There are many blogs explaining the issue of this pointing in JavaScript, and many people still ask.

Unlike many of our common languages, the this pointer in a JavaScript function is not determined when the function is defined, but when it is called. In other words, how the function is called determines what this points to.

In JavaScript, there are three common ways to call functions: direct call, method call and new call. In addition, there are some special calling methods, such as binding the function to the object through bind() and then calling it, calling through call(), apply(), etc. After es6 introduced arrow functions, when the arrow function is called, its this pointer is different. Let’s analyze the this pointer in these situations.

Directly call

Direct call is called through function name (...). At this time, this inside the function points to the global object. In the browser, the global object is window, and in NodeJs, the global object is global.

Let’s look at an example:

// 简单兼容浏览器和 NodeJs 的全局对象
const _global = typeof window === "undefined" ? global : window;

function test() {
    console.log(this === _global);    // true
}

test();    // 直接调用

One thing to note here is that direct calling does not refer to calling in the global scope. In any scope, calling a function directly through the function name (...) is called direct calling. For example, the following example also directly calls

(function(_global) {
    // 通过 IIFE 限定作用域

    function test() {
        console.log(this === _global);  // true
    }

    test();     // 非全局作用域下的直接调用
})(typeof window === "undefined" ? global : window);

The impact of bind() on direct calls

Another thing to note is the impact of bind(). The function of Function.prototype.bind() is to bind the current function to the specified object and return a new function. No matter how it is called, this new function always points to the bound object. Let’s look at an example:

const obj = {};

function test() {
    console.log(this === obj);
}

const testObj = test.bind(obj);
test();     // false
testObj();  // true

So what does bind() do? Let's simulate a bind() to understand how it affects this.

const obj = {};

function test() {
    console.log(this === obj);
}

// 自定义的函数,模拟 bind() 对 this 的影响
function myBind(func, target) {
    return function() {
        return func.apply(target, arguments);
    };
}

const testObj = myBind(test, obj);
test();     // false
testObj();  // true

As you can see from the above example, first, through the closure, the target, which is the bound object, is maintained; then when calling the function, the apply method is used on the original function to specify the this of the function. Of course the native bind() implementation may be different and more efficient. But this example shows what bind() can do.

The impact of call and apply on this

Function.prototype.apply() is used in the above example, and Function.prototype.call() is similar to it. For the usage of these two methods, please read the documentation yourself through the link. However, their first parameter all points to this when the specified function is run.

However, you still need to pay attention when using apply and call. If the directory function itself is a function bound to this object, then apply and call will not execute as expected, such as

const obj = {};

function test() {
    console.log(this === obj);
}

// 绑定到一个新对象,而不是 obj
const testObj = test.bind({});
test.apply(obj);    // true

// 期望 this 是 obj,即输出 true
// 但是因为 testObj 绑定了不是 obj 的对象,所以会输出 false
testObj.apply(obj); // false

It can be seen that bind() has a profound impact on functions, so use it with caution!

Method call

Method calling refers to calling its method function through an object. It is in the form of object.method function (...). In this case, this in the function points to the object on which the method is called. However, you also need to be aware of the effects of bind().

const obj = {
    // 第一种方式,定义对象的时候定义其方法
    test() {
        console.log(this === obj);
    }
};

// 第二种方式,对象定义好之后为其附加一个方法(函数表达式)
obj.test2 = function() {
    console.log(this === obj);
};

// 第三种方式和第二种方式原理相同
// 是对象定义好之后为其附加一个方法(函数定义)
function t() {
    console.log(this === obj);
}
obj.test3 = t;

// 这也是为对象附加一个方法函数
// 但是这个函数绑定了一个不是 obj 的其它对象
obj.test4 = (function() {
    console.log(this === obj);
}).bind({});

obj.test();     // true
obj.test2();    // true
obj.test3();    // true

// 受 bind() 影响,test4 中的 this 指向不是 obj
obj.test4();    // false

What needs to be noted here is that the last three methods are all predefined functions, and then attached to the obj object as its method. Again, the this pointer inside a function has nothing to do with the definition and is affected by the calling method.

When this in the method points to the global object

Note that what is said here is in the method rather than in the method call. This in the method points to the global object. If it is not because of bind(), it must be because the method is not called, such as

const obj = {
    test() {
        console.log(this === obj);
    }
};

const t = obj.test;
t();    // false

t is the test method of obj, but when t() is called, this points to the global.

The reason why this situation is mentioned in particular is because often after passing an object method as a callback to a function, it is found that the running result is not in line with expectations - because the impact of the calling method on this is ignored. For example, the following example is a problem that is particularly easy to encounter after encapsulating certain things on the page:

class Handlers {
    // 这里 $button 假设是一个指向某个按钮的 jQuery 对象
    constructor(data, $button) {
        this.data = data;
        $button.on("click", this.onButtonClick);
    }

    onButtonClick(e) {
        console.log(this.data);
    }
}

const handlers = new Handlers("string data", $("#someButton"));
// 对 #someButton 进行点击操作之后
// 输出 undefined
// 但预期是输出 string data

Obviously, after this.onButtonClick is passed into on() as a parameter, when the event is triggered, this function is called directly instead of a method call, so this will point to the global object. There are many ways to solve this problem

// 这是在 es5 中的解决办法之一
var _this = this;
$button.on("click", function() {
    _this.onButtonClick();
});

// 也可以通过 bind() 来解决
$button.on("click", this.onButtonClick.bind(this));

// es6 中可以通过箭头函数来处理,在 jQuery 中慎用
$button.on("click", e => this.onButtonClick(e));

However, please note that when using arrow functions as jQuery callbacks, you need to be careful about the use of this within the function. jQuery This in most callback functions (non-arrow functions) represents the call target, so you can write statements like $(this).text(), but jQuery cannot change the this pointer of arrow functions. The semantics of the same statement are completely different. .

 new 调用

  在 es6 之前,每一个函数都可以当作是构造函数,通过 new 调用来产生新的对象(函数内无特定返回值的情况下)。而 es6 改变了这种状态,虽然 class 定义的类用 typeof 运算符得到的仍然是 "function",但它不能像普通函数一样直接调用;同时,class 中定义的方法函数,也不能当作构造函数用 new 来调用。

  而在 es5 中,用 new 调用一个构造函数,会创建一个新对象,而其中的 this 就指向这个新对象。这没有什么悬念,因为 new 本身就是设计来创建新对象的。

var data = "Hi";    // 全局变量

function AClass(data) {
    this.data = data;
}

var a = new AClass("Hello World");
console.log(a.data);    // Hello World
console.log(data);      // Hi

var b = new AClass("Hello World");
console.log(a === b);   // false

 箭头函数中的 this

  先来看看 MDN 上对箭头函数的说明

An arrow function expression has a shorter syntax than a function expression and does not bind its own this, arguments, super, or new.target. Arrow functions are always anonymous. These function expressions are best suited for non-method functions, and they cannot be used as constructors.

  这里已经清楚了说明了,箭头函数没有自己的 this 绑定。箭头函数中使用的 this,其实是直接包含它的那个函数或函数表达式中的 this。比如

const obj = {
    test() {
        const arrow = () => {
            // 这里的 this 是 test() 中的 this,
            // 由 test() 的调用方式决定
            console.log(this === obj);
        };
        arrow();
    },

    getArrow() {
        return () => {
            // 这里的 this 是 getArrow() 中的 this,
            // 由 getArrow() 的调用方式决定
            console.log(this === obj);
        };
    }
};

obj.test();     // true

const arrow = obj.getArrow();
arrow();        // true

  示例中的两个 this 都是由箭头函数的直接外层函数(方法)决定的,而方法函数中的 this 是由其调用方式决定的。上例的调用方式都是方法调用,所以 this 都指向方法调用的对象,即 obj。

  箭头函数让大家在使用闭包的时候不需要太纠结 this,不需要通过像 _this 这样的局部变量来临时引用 this 给闭包函数使用。来看一段 Babel 对箭头函数的转译可能能加深理解:

// ES6
const obj = {
    getArrow() {
        return () => {
            console.log(this === obj);
        };
    }
}
// ES5,由 Babel 转译
var obj = {
    getArrow: function getArrow() {
        var _this = this;
        return function () {
            console.log(_this === obj);
        };
    }
};

  另外需要注意的是,箭头函数不能用 new 调用,不能 bind() 到某个对象(虽然 bind() 方法调用没问题,但是不会产生预期效果)。不管在什么情况下使用箭头函数,它本身是没有绑定 this 的,它用的是直接外层函数(即包含它的最近的一层函数或函数表达式)绑定的 this。

The above is the detailed content of In-depth analysis of this pointing problem in JavaScript. For more information, please follow other related articles on the PHP Chinese website!

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn