Maison  >  Article  >  interface Web  >  Analyse approfondie de ce problème de pointage en JavaScript

Analyse approfondie de ce problème de pointage en JavaScript

巴扎黑
巴扎黑original
2017-04-29 15:50:241106parcourir

Il existe de nombreux blogs expliquant le problème de ce pointage en JavaScript, et beaucoup de gens le demandent encore.

Contrairement à beaucoup de nos langages courants, le pointeur this dans une fonction JavaScript n'est pas déterminé lorsque la fonction est définie, mais lorsqu'elle est appelée. En d’autres termes, la façon dont la fonction est appelée détermine ce vers quoi elle pointe.

En JavaScript, il existe trois manières courantes d'appeler des fonctions : l'appel direct, l'appel de méthode et le nouvel appel. De plus, il existe des méthodes d'appel spéciales, telles que lier la fonction à l'objet via bind() puis l'appeler, appeler via call(), apply(), etc. Après que es6 ait introduit les fonctions fléchées, lorsque la fonction flèche est appelée, son pointeur est différent. Analysons le pointeur this dans ces situations.

Appelez directement

L'appel direct est appelé via le nom de la fonction (...). À ce stade, cela à l'intérieur de la fonction pointe vers l'objet global. Dans le navigateur, l'objet global est window, et dans NodeJs, l'objet global est global.

Regardons un exemple :

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

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

test();    // 直接调用

Une chose à noter ici est que l'appel direct ne fait pas référence à l'appel dans la portée globale. Dans n'importe quelle portée, l'appel d'une fonction directement via le nom de la fonction (...) est appelé appel direct. Par exemple, l'exemple suivant appelle également directement

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

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

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

​L'impact de bind() sur les appels directs

Une autre chose à noter est l'impact de bind(). La fonction de Function.prototype.bind() est de lier la fonction actuelle à l'objet spécifié et de renvoyer une nouvelle fonction. Quelle que soit la manière dont elle est appelée, cette nouvelle fonction pointe toujours vers l'objet lié. Regardons un exemple :

const obj = {};

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

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

Alors, que fait bind() ? Simulons un bind() pour comprendre comment cela affecte cela.

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

Comme vous pouvez le voir dans l'exemple ci-dessus, d'abord, grâce à la fermeture, la cible, qui est l'objet lié, est conservée, puis lorsque la fonction est appelée, la méthode apply est utilisée sur la fonction d'origine pour spécifier le this de la fonction ; . Bien entendu, l’implémentation native de bind() peut être différente et plus efficace. Mais cet exemple montre ce que bind() peut faire.

​L'impact d'appeler et de postuler à ce sujet

Function.prototype.apply() est utilisé dans l'exemple ci-dessus et Function.prototype.call() lui est similaire. Pour utiliser ces deux méthodes, veuillez lire vous-même la documentation via le lien. Cependant, leur premier paramètre pointe vers cela lorsque la fonction spécifiée est exécutée.

Cependant, vous devez toujours faire attention lorsque vous utilisez apply et call. Si la fonction de répertoire elle-même est une fonction liée à cet objet, alors apply et call ne s'exécuteront pas comme prévu, comme

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

. On peut voir que bind() a un impact profond sur les fonctions, alors utilisez-le avec prudence !

Appel de méthode

L'appel de méthode fait référence à l'appel de sa fonction de méthode via un objet. Il se présente sous la forme d'une fonction object.method (...). Dans ce cas, this dans la fonction pointe vers l'objet sur lequel la méthode est appelée. Cependant, vous devez également être conscient des effets de 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

Ce qu'il faut noter ici, c'est que les trois dernières méthodes sont toutes des fonctions prédéfinies, puis attachées à l'objet obj en tant que méthode. Encore une fois, le pointeur this à l'intérieur d'une fonction n'a rien à voir avec la définition et est affecté par la méthode appelante.

Quand ceci dans la méthode pointe vers l'objet global

Notez que ce qui est dit ici se trouve dans la méthode plutôt que dans l'appel de méthode. Ceci dans la méthode pointe vers l'objet global. Si ce n'est pas à cause de bind(), cela doit être parce que la méthode n'est pas appelée, comme

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

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

. t est la méthode de test d'obj, mais lorsque t() est appelé, cela pointe vers le global.

La raison pour laquelle cette situation est mentionnée en particulier est que souvent, après avoir passé une méthode objet comme rappel à une fonction, il s'avère que le résultat en cours d'exécution n'est pas conforme aux attentes - car l'impact de la méthode appelante sur ce point est ignoré. Par exemple, l'exemple suivant est un problème particulièrement facile à rencontrer après avoir encapsulé certaines choses sur la 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

Évidemment, après que this.onButtonClick soit passé dans on() en tant que paramètre, lorsque l'événement est déclenché, cette fonction est appelée directement au lieu d'un appel de méthode, cela pointera donc vers l'objet global. Il existe de nombreuses façons de résoudre ce problème

// 这是在 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));

Cependant, veuillez noter que lorsque vous utilisez des fonctions fléchées comme rappels jQuery, vous devez faire attention à leur utilisation dans la fonction. jQuery Dans la plupart des fonctions de rappel (fonctions non fléchées), cela représente la cible de l'appel, vous pouvez donc écrire des instructions comme $(this).text(), mais jQuery ne peut pas modifier le pointeur this des fonctions fléchées. complètement différent.

 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。

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn