{"id":108734,"date":"2023-09-07T07:00:00","date_gmt":"2023-09-07T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=108734"},"modified":"2023-09-07T08:40:23","modified_gmt":"2023-09-07T15:40:23","slug":"20230907-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20230907-00\/?p=108734","title":{"rendered":"A freestanding JavaScript function that uses <CODE>this<\/CODE> is easily mistaken for a constructor"},"content":{"rendered":"<p>As a general rule in JavaScript, when you define a function with the <code>function<\/code> keyword (not with the arrow syntax), the <code>this<\/code> keyword is bound to the invoking object if the function was called as the result of the <code>.<\/code> (dot) operator. Otherwise, it is bound to <code>undefined<\/code> (in strict mode) or the global object (in non-strict mode).<\/p>\n<p>Being bound to <code>undefined<\/code> or the global object is not particularly interesting. In practice, a function uses <code>this<\/code> if it was intended to be called via the <code>.<\/code> (dot) operator, which means in practice that it was defined either by adding it to a prototype or by using the syntactic sugar of an ES6 class declaration.<\/p>\n<p>The only common case where a freestanding function (not stored on a prototype) uses <code>this<\/code> is when it is a constructor. In that case, the <code>this<\/code> keyword is bound to the object being constructed.<\/p>\n<p>Visual Studio Code has a static analyzer that looks for functions that look like constructors. When it finds one, it suggests, &#8220;This constructor function may be converted to a class declaration.&#8221; I don&#8217;t know the details of this static analyzer,\u00b9 but from observation, it appears that the logic for this rule is very simple: A function declared with the <code>function<\/code> keyword that writes to properties of <code>this<\/code> is probably a constructor. (Because there&#8217;s rarely any reason for a non-constructor to use <code>this<\/code>.)<\/p>\n<p>This rule generally works, except when it doesn&#8217;t.<\/p>\n<p>You can override the normal assignment of <code>this<\/code> by using methods like <code>call<\/code>, <code>apply<\/code>, or <code>bind<\/code>. The <code>call<\/code> and <code>apply<\/code> methods override the value of <code>this<\/code> for a single call of that function. The <code>bind<\/code> method returns a new function that calls the original function with the <code>this<\/code> overridden by the value you specify at bind time.<\/p>\n<p>Visual Studio Code&#8217;s static analyzer doesn&#8217;t know about these weirdos.<\/p>\n<p>But wait, there&#8217;s more.<\/p>\n<p>Some callers intentionally bind <code>this<\/code> to something strange when calling a callback function. For example, the <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/JavaScript\/Reference\/Global_Objects\/Array#iterative_methods\"> Array iterative methods<\/a> take an optional second parameter traditionally named <code>thisArg<\/code>. Before calling the callback function, the iterative method binds <code>this<\/code> to the value you passed as the <code>thisArg<\/code>, and your callback function can then use <code>this<\/code> to refer to that object.\u00b2 Another example is HTML DOM event handlers, which bind <code>this<\/code> to the element that generated the event.<\/p>\n<p>If you use either of these features, then the suggestion will misdetect your function as a constructor candidate.<\/p>\n<p>If you don&#8217;t want to disable the suggestion outright, it may be possible to make small adjustments to your code to avoid the misdetection.<\/p>\n<p>If your function is an event handler, then you can use <code>e.currentTarget<\/code> instead of <code>this<\/code> to access the element that generated the event. This is probably more idiomatic anyway, because <code>this<\/code> is easily lost when you do code factoring.<\/p>\n<pre>\/\/ original version\r\n\r\nfunction focusHandler(e) {\r\n    var item = findItemFromElement(this);\r\n    if (item.ready) {\r\n        this.className = \"ready\";\r\n    } else {\r\n        this.className = \"waiting\";\r\n    }\r\n}\r\n\r\n\/\/ refactored, bug introduced\r\n\r\nfunction focusHandler(e) {\r\n    var item = findItemFromElement(this);\r\n    updateClass(item);\r\n}\r\n\r\nfunction updateClass(item) {\r\n    if (item.ready) {\r\n        \/\/ Oops: \"this\" means something different here\r\n        this.className = \"ready\";\r\n    } else {\r\n        this.className = \"waiting\";\r\n    }\r\n}\r\n\r\n\/\/ alternate original version\r\n\r\nfunction focusHandler(e) {\r\n    var item = findItemFromElement(this);\r\n    if (item.ready) {\r\n        e.currentTarget.className = \"ready\";\r\n    } else {\r\n        e.currentTarget.className = \"waiting\";\r\n    }\r\n}\r\n\r\n\/\/ improper refactoring detected\r\n\r\nfunction focusHandler(e) {\r\n    var item = findItemFromElement(this);\r\n    updateClass(item);\r\n}\r\n\r\nfunction updateClass(item) {\r\n    if (item.ready) {\r\n        \/\/ error: \"e is not defined\"\r\n        e.currentTarget.className = \"ready\";\r\n    } else {\r\n        e.currentTarget.className = \"waiting\";\r\n    }\r\n}\r\n<\/pre>\n<p>The &#8220;<code>e<\/code> is not defined&#8221; error clues you in that the code you factored out also needed <code>e<\/code>, and you forgot to add it as a parameter.<\/p>\n<p>For the Array iterative methods, custom binding of <code>this<\/code> is one of those obscure features that you show off at parties, but rarely something you use in real code. If you really do want to pass a bonus parameter to the callback function, you can bind it explicitly.<\/p>\n<pre>\/\/ original version, false positive\r\nfunction accumulateTotal(e) {\r\n    this.total += e.value;\r\n}\r\n\r\nclass Something\r\n{\r\n    updateTotal(items) {\r\n        this.total = 0;\r\n        items.forEach(accumulateTotal, this);\r\n        return this.total;\r\n    }\r\n}\r\n\r\n\/\/ revised version, avoids false positive\r\nfunction accumulateTotal(o, e) {\r\n    o.total += e.value;\r\n}\r\n\r\nclass Something\r\n{\r\n    updateTotal(items) {\r\n        this.total = 0;\r\n        items.forEach(accumulateTotal.bind(null, this));\r\n        return this.total;\r\n    }\r\n}\r\n\r\n\/\/ revised version, avoids false positive, less obscure\r\n\r\nclass Something\r\n{\r\n    updateTotal(items) {\r\n        this.total = 0;\r\n        items.forEach(e =&gt; accumulateTotal(this, e));\r\n        return this.total;\r\n    }\r\n}\r\n<\/pre>\n<p>\u00b9 You can find the code that implements the &#8220;can be converted to a class constructor&#8221; suggestion <a href=\"https:\/\/github.com\/microsoft\/TypeScript\/blob\/00dc0b6674eef3fbb3abb86f9d71705b11134446\/src\/services\/suggestionDiagnostics.ts#L278\"> in the function <code>canBeConvertedToClass<\/code> in <code>suggestionDiagnostics.ts<\/code><\/a>.<\/p>\n<p>\u00b2 Offer not valid for functions which have already been bound to a <code>this<\/code>, either explicitly via <code>bind<\/code> or implicitly by virtue of being an arrow function.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Because that&#8217;s what constructors look like.<\/p>\n","protected":false},"author":1069,"featured_media":111744,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[],"class_list":["post-108734","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing"],"acf":[],"blog_post_summary":"<p>Because that&#8217;s what constructors look like.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/108734","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/users\/1069"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/comments?post=108734"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/108734\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media\/111744"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media?parent=108734"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=108734"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=108734"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}