Re: Proposal For A New Alternative Keyword To “this” For Classes

Michael Luder-Rosefield rosyatrandom at gmail.com
Tue Mar 19 06:30:33 UTC 2019


I'm getting deja vu again

On Tue, 19 Mar 2019 at 14:31 Isiah Meadows <isiahmeadows at gmail.com> wrote:

> UX workflows aren't all of JS. Classes exist for many more reasons than
> that, and 99% of my classes are for abstracting non-trivial business logic
> and ensuring *those* are easily testable, not directly UI/UX.
>
> -----
>
> Isiah Meadows
> contact at isiahmeadows.com
> www.isiahmeadows.com
>
>
> On Sun, Mar 17, 2019 at 2:35 AM kai zhu <kaizhu256 at gmail.com> wrote:
>
>> *rant warning*
>>
>> -1 because classes generally have little-value in UX-workflow programming.
>>
>> the example class RequestManager (given in this discussion),
>> realistically has little reusability-value -- its no better than employing
>> a throwaway static-function (both are equally likely to get rewritten each
>> time UX-workflow features are added.).
>>
>> for example, a common feature-request is adding visual-progress-bar.
>>  there is no "simple" way to extend RequestManager to do this, other than
>> significant-refactoring of the base-class (and risk breaking
>> class-dependencies downstream).
>>
>> some other common UX feature-requests that would likely invalidate
>> "reusability" of your class-based design include:
>>
>> 1. needing to upload a binary-file (social-images, receipt-signatures,
>> screenshots, etc...)
>> 2. needing to upload multiple binary-files in parallel (keeping track of
>> timeouts of each)
>> 3. needing to upload multiple binary-files in parallel (keeping track of
>> timeouts of each),
>>     and then download their thumbnail-previews from server to visually
>> confirm uploads were correct
>> 4. needing to make parallel http-requests from 3rd-party sources and
>> "joining" the response-data
>> 5. needing the sign-up-page to additionally pre-validate
>>     email / username / mobile-number / credit-card / etc... before
>> form-submission to server
>>
>> many frontend-engineers with experience "extending" products with
>> additional UX-workflow features, know its rarely as simple as modifying
>> some class-methods and be done with it -- it oftentimes require rewriting
>> nearly every-piece-of-code that touches the given workflow needing
>> enhancement.
>>
>> p.s. -- here's a "simple" fully-working UX-example [1] on how to add
>> visual-progress-bar to http-requests.  if i had to additionally add some of
>> the other UX-features mentioned above, it would likely entail me completely
>> rewriting the throwaway static-function, rather than waste time trying to
>> extend it.
>>
>> [1] https://jsfiddle.net/kaizhu256/t9ubdenf/
>>
>> ```html
>> <style>
>> /* jslint utility2:true */
>> /* csslint ignore:start */
>> *,
>> *:after,
>> *:before {
>>     box-sizing: border-box;
>> }
>> /* csslint ignore:end */
>> body {
>>     background: #eee;
>>     font-family: Arial, Helvetica, sans-serif;
>>     font-size: small;
>> }
>> input {
>>     width: 100%;
>> }
>> textarea {
>>     font-family: Consolas, Menlo, monospace;
>>     font-size: smaller;
>>     overflow: auto;
>>     width: 100%;
>> }
>> </style>
>>
>> <div id="ajaxProgressDiv1" style="background: #d00; height: 5px; left: 0;
>> margin: 0; padding: 0; position: fixed; top: 0; transition: background
>> 500ms, width 1500ms; width: 0%; z-index: 1;"></div>
>>
>> <label>ajax-request</label><br>
>> <textarea id="input1" style="height: 10rem;">{
>>     "method": "GET",
>>     "url": "https://api.github.com/orgs/octokit/repos",
>>     "headers": {
>>         "accept": "application/vnd.github.v3+json"
>>     },
>>     "data": "hello world!"
>> }</textarea><br><br>
>>
>> <button id="submit1">submit ajax-request</button><br><br>
>>
>> <label>ajax-response</label><br>
>> <textarea id="output1" style="height: 20rem;"></textarea><br><br>
>>
>> <script>
>> /*jslint browser*/
>> (function () {
>>     "use strict";
>>     var local;
>>     local = {};
>>     window.local = local;
>>
>>     local.ajax = function (opt, onError) {
>>     /*
>>      * simple, throwaway ajax-function that can be easily rewritten
>>      * to accomodate new [async] ux-features
>>      */
>>         var resHandler;
>>         var xhr;
>>         opt.headers = opt.headers || {};
>>         opt.method = opt.method || "GET";
>>         xhr = new XMLHttpRequest();
>>         // open url
>>         xhr.open(opt.method, opt.url);
>>         // set req-headers
>>         Object.entries(opt.headers).forEach(function (entry) {
>>             xhr.setRequestHeader(entry[0], entry[1]);
>>         });
>>         // send data
>>         xhr.send(opt.data);
>>         // init request-handling
>>         resHandler = function (evt) {
>>         /*
>>          * this function will handle ajax-response
>>          */
>>             switch (evt.type) {
>>             case "abort":
>>             case "error":
>>                 // decrement ajaxProgressCounter
>>                 local.ajaxProgressCounter = Math.max(
>>                     local.ajaxProgressCounter - 1,
>>                     0
>>                 );
>>                 onError(new Error(evt.type), xhr);
>>                 break;
>>             case "load":
>>                 // decrement ajaxProgressCounter
>>                 local.ajaxProgressCounter = Math.max(
>>                     local.ajaxProgressCounter - 1,
>>                     0
>>                 );
>>                 onError(null, xhr);
>>                 break;
>>             }
>>             // increment ajax-progress-bar
>>             local.ajaxProgressUpdate();
>>         };
>>         // increment ajaxProgressCounter
>>         local.ajaxProgressCounter = local.ajaxProgressCounter || 0;
>>         local.ajaxProgressCounter += 1;
>>         // increment ajax-progress-bar
>>         local.ajaxProgressUpdate();
>>         xhr.addEventListener("abort", resHandler);
>>         xhr.addEventListener("error", resHandler);
>>         xhr.addEventListener("load", resHandler);
>>         xhr.addEventListener("loadstart", resHandler);
>>         xhr.addEventListener("progress", resHandler);
>>     };
>>
>>     local.ajaxProgressUpdate = function () {
>>     /*
>>      * this function will update ajax-progress-bar
>>      */
>>         var ajaxProgressDiv1;
>>         // init state
>>         local.ajaxProgressCounter = local.ajaxProgressCounter || 0;
>>         local.ajaxProgressState = local.ajaxProgressState || 0;
>>         ajaxProgressDiv1 = document.querySelector(
>>             "#ajaxProgressDiv1"
>>         );
>>         // init ajaxProgressDiv1StyleBackground
>>         local.ajaxProgressDiv1StyleBackground = (
>>             local.ajaxProgressDiv1StyleBackground
>>             || ajaxProgressDiv1.style.background
>>         );
>>         // show ajaxProgress
>>         ajaxProgressDiv1.style.background = (
>>             local.ajaxProgressDiv1StyleBackground
>>         );
>>         // increment ajaxProgress
>>         if (local.ajaxProgressCounter > 0) {
>>             local.timerIntervalAjaxProgressHide = (
>>                 local.timerIntervalAjaxProgressHide
>>                 || setInterval(local.ajaxProgressUpdate, 2000)
>>             );
>>             // this algorithm will indefinitely increment ajaxProgressBar
>>             // with successively smaller increments without ever reaching
>> 100%
>>             if ((ajaxProgressDiv1.style.width.slice(0, -1) | 0) > 95) {
>>                 ajaxProgressDiv1.style.width = "0%";
>>                 local.ajaxProgressState = 0;
>>             }
>>             local.ajaxProgressState += 1;
>>             ajaxProgressDiv1.style.width = Math.max(
>>                 100 - 75 * Math.exp(-0.125 * local.ajaxProgressState),
>>                 ajaxProgressDiv1.style.width.slice(0, -1) | 0
>>             ) + "%";
>>         } else {
>>             // finish ajaxProgress
>>             ajaxProgressDiv1.style.width = "100%";
>>         }
>>         // cleanup timerTimeout
>>         clearTimeout(local.timerTimeoutAjaxProgressHide);
>>         // hide ajaxProgress
>>         local.timerTimeoutAjaxProgressHide = setTimeout(function () {
>>             ajaxProgressDiv1.style.background = "transparent";
>>             local.ajaxProgressCounter = 0;
>>             local.ajaxProgressState = 0;
>>             // reset ajaxProgress
>>             clearInterval(local.timerIntervalAjaxProgressHide);
>>             local.timerIntervalAjaxProgressHide = null;
>>             setTimeout(function () {
>>                 if (!local.ajaxProgressState) {
>>                     ajaxProgressDiv1.style.width = "0%";
>>                 }
>>             }, 500);
>>         }, (
>>             local.ajaxProgressCounter > 0
>>             ? local.timeoutDefault
>>             : 1000
>>         ));
>>     };
>>
>>     // init event-handling
>>     document.querySelector(
>>         "#submit1"
>>     ).addEventListener("click", function () {
>>         var output1;
>>         output1 = document.querySelector(
>>             "#output1"
>>         );
>>         // reset #output1
>>         output1.innerHTML = "";
>>         try {
>>             local.ajax(JSON.parse(document.querySelector(
>>                 "#input1"
>>             ).textContent), function (err, xhr) {
>>                 // handler err
>>                 if (err) {
>>                     output1.textContent = err.stack;
>>                     return;
>>                 }
>>                 // output response-headers
>>                 output1.textContent = (
>>                     "status-code:\n"
>>                     + xhr.status
>>                     + "\n\nresponse-headers:\n"
>>                     + xhr.getAllResponseHeaders()
>>                     + "\n\nresponse-text:\n"
>>                     + xhr.responseText
>>                 );
>>             });
>>         // handler errCaught
>>         } catch (errCaught) {
>>             output1.textContent = errCaught.stack;
>>         }
>>     });
>> }());
>> </script>
>> ```
>>
>>
>> On 12 Mar 2019, at 15:49, guest271314 <guest271314 at gmail.com> wrote:
>>
>> ```class RequestManager {
>>   __THIS__(method) {
>>     return new Proxy(method, {
>>       apply: (target, thisArg, argumentsList) => {
>>         return method.apply(thisArg, [...argumentsList, {
>>           THIS: this
>>         }])
>>       }
>>     })
>>   }
>>   constructor() {
>>     this.successMessage = "Xhr successful.";
>>   }
>>   makeRequest() {
>>     var oReq = new XMLHttpRequest();
>>     oReq.addEventListener("load", this.__THIS__(this.responseHandler));
>>     oReq.open("GET", "data:,");
>>     oReq.send();
>>   }
>>
>>   responseHandler(e, {THIS} = {}) {
>>     console.log(this, e);
>>     window.alert(THIS.successMessage)
>>   }
>> }
>>
>> var reqManager = new RequestManager();
>>
>> reqManager.makeRequest();```
>>
>> On Mon, Mar 11, 2019 at 8:59 AM john larson <johnlarsondev1 at gmail.com>
>> wrote:
>>
>>> First of all, thank you all for taking the time to review the proposal
>>> and sharing your valuable opinions. I would like to note that the proposal
>>> aims not to add a new capability that was not possible to do before but
>>> rather move the standard forward on the way for a modern, better and easier
>>> to use language for all the developers. Advancements of the language, or
>>> any language in that matter, throughout the last decade also followed a
>>> similar path because we were already able to do everything in one way or
>>> the other. We actually strive for the same thing, a better Javascript.
>>>
>>>
>>> That being said, let me try to clarify my proposal further by walking
>>> you through my thought process:
>>>
>>>
>>> Normally if I try to write a class similar to the sample code I have
>>> given in my first email using an object oriented programming language like
>>> Java, C# etc., I would be writing something similar to the following:
>>>
>>>     class RequestManager
>>>     {
>>>         string successMessage = "Xhr successful.";
>>>
>>>
>>>         void makeRequest()
>>>         {
>>>             var oReq = new XMLHttpRequest();
>>>             oReq.addEventListener("load", responseHandler);
>>>             oReq.open("GET", "*www.google.com*");
>>>             oReq.send();
>>>         }
>>>
>>>
>>>         void responseHandler()
>>>         {
>>>             window.alert(successMessage);
>>>         }
>>>
>>>     }
>>>
>>>
>>> As you can see, I do not even have to use a special keyword for
>>> referring to methods from inside the class. Because they are already in
>>> lexical scope. Now, if this can be accomplished in Javascript without
>>> hitting some limitation/restriction due to the current state of the
>>> language, I think it would be the ideal solution. (This limitation might be
>>> the class syntax being just a syntactical sugar or some other reason that
>>> I cannot foresee right now and that would require a breaking change.) And
>>> I would happily change the proposal that way: “A no-keyword alternative for
>>> the “this””. If I should summarize this approach, I can say that every
>>> method of the class is going to assume the behavior we now have with arrow
>>> functions, but without requiring the use of the “this” and the arrow
>>> function syntax.
>>>
>>>
>>> As contrary to the ideal solution, the last thing I would want would be
>>> to use a context-dependant keyword like the “this” to refer to
>>> methods/properties of the object and then try to set the context right by
>>> using binding or arrow functions. This referral should be lexical, not
>>> context-dependant. If I have the intent of referring to the instance
>>> method/property, that intent should manifest itself right there where I am
>>> using this method/property. I shouldn’t be looking at if this takes place
>>> inside an arrow function, or if the enclosing method is called with a
>>> binding or not. Why should I care about the enclosing of the call,
>>> right?
>>>
>>> By the way, MDN also mentions the following about the use of arrow
>>> functions: “Arrow function expressions are ill suited as methods”.
>>>
>>> *@Yulia:* Thanks for pointing out the decorator approach. But that also
>>> seems to deal with the enclosing and tries to solve the problem with a
>>> “context binding” approach. The only difference is the way it determines
>>> the binding. I am against this binding approach all together. Only the
>>> lexical scope of the code should be taken into consideration.
>>>
>>>
>>>
>>> So far, I have laid out what I think the ideal solution is and what I
>>> think the problematic state we are in right now. And as a middle-ground,
>>> in case the ideal solution cannot be applied, I proposed a new keyword to
>>> use instead of the “this” so that it will always refer to the instance,
>>> regardless of execution context binding. In which case, when you
>>> replace the “this” in the problematic sample code in my initial email, it
>>> will work just fine. Let’ assume for the sake of this example that the new
>>> keyword is “self”:
>>>
>>> class RequestManager{
>>>
>>>
>>>     constructor(){
>>>         *self*.successMessage = "Xhr successful.";
>>>     }
>>>
>>>
>>>
>>>
>>>     makeRequest() {
>>>         var oReq = new XMLHttpRequest();
>>>         oReq.addEventListener("load", *self*.responseHandler);
>>>         oReq.open("GET", "*www.google.com*");
>>>         oReq.send();
>>>     }
>>>
>>>
>>>     responseHandler() {
>>>         window.alert(*self*.successMessage);
>>>     }
>>> }
>>>
>>>
>>> var reqManager = new RequestManager();
>>> reqManager.makeRequest();
>>>
>>>
>>> As you can see, self.responseHandler will always point to the
>>> responseHandler method no matter whether the enclosing is a method, an
>>> arrow function or if it is called using a bind syntax or not.
>>>
>>> I would be happy to further address your concerns about this explanation
>>> if you have any.
>>>
>>>
>>>
>>> On Sun, Mar 10, 2019 at 10:30 PM guest271314 <guest271314 at gmail.com>
>>> wrote:
>>>
>>>> This is probably not the pattern that is being proposed though outputs
>>>> the expected result
>>>>
>>>>     ```class RequestManager {
>>>>       constructor() {
>>>>         this.successMessage = "Xhr successful.";
>>>>         RequestManager.THIS = this;
>>>>       }
>>>>
>>>>       makeRequest() {
>>>>         var oReq = new XMLHttpRequest();
>>>>         oReq.addEventListener("load", this.responseHandler);
>>>>         oReq.open("GET", "");
>>>>         oReq.send();
>>>>       }
>>>>
>>>>       responseHandler(e) {
>>>>         console.log(e, this); // `e`: event, `this`: XMLHttpRequest
>>>> instance
>>>>         console.log(RequestManager.THIS.successMessage);
>>>>       }
>>>>
>>>>     }
>>>>
>>>>     var reqManager = new RequestManager();
>>>>
>>>>     reqManager.makeRequest();```
>>>>
>>>> On Sat, Mar 9, 2019 at 11:42 AM john larson <johnlarsondev1 at gmail.com>
>>>> wrote:
>>>>
>>>>> *Summary of the problem:*
>>>>>
>>>>> “this” keyword in Javascript is context dependent. And this is one of
>>>>> the culprits of most subtle and latent errors in Javascript. Moreover, use
>>>>> of “this” cannot be avoided if we are using classes and trying to reference
>>>>> instance properties.
>>>>>
>>>>> When “this” is used in callback functions or in functions given
>>>>> to forEach as argument, IDEs rightfully cannot raise any design-time
>>>>> errors, giving developers the false sense of security, but we get run-time
>>>>> errors because “this” is undefined.
>>>>>
>>>>> There seem to be two work-arounds:
>>>>> 1.      Using arrow functions
>>>>>
>>>>> 2.      Using .bind(this) syntax
>>>>>
>>>>> Just assuming we forgot to use an arrow function or a .bind(), the IDE
>>>>> will not be able to raise an error and we will encounter the error in
>>>>> run-time.
>>>>>
>>>>>
>>>>> *What I propose:*
>>>>>
>>>>> I am proposing a new keyword that will be the alternative of "this"
>>>>> and will always point to the instance of the class. The name of the new
>>>>> keyword can be chosen with consensus from the community such that it would
>>>>> minimize/eliminate collision in existing codebases.
>>>>>
>>>>>
>>>>> Here is a sample js code:
>>>>>
>>>>>
>>>>> class RequestManager{
>>>>>
>>>>>
>>>>>
>>>>>     constructor(){
>>>>>
>>>>>         this.successMessage = "Xhr successful.";
>>>>>
>>>>>     }
>>>>>
>>>>>
>>>>>
>>>>>
>>>>>
>>>>>     makeRequest() {
>>>>>
>>>>>         var oReq = new XMLHttpRequest();
>>>>>
>>>>>         oReq.addEventListener("load", this.responseHandler);
>>>>>
>>>>>         oReq.open("GET", "www.google.com");
>>>>>
>>>>>         oReq.send();
>>>>>
>>>>>     }
>>>>>
>>>>>
>>>>>
>>>>>     responseHandler() {
>>>>>
>>>>>         window.alert(this.successMessage);
>>>>>
>>>>>     }
>>>>>
>>>>> }
>>>>>
>>>>>
>>>>>
>>>>> var reqManager = new RequestManager();
>>>>>
>>>>> reqManager.makeRequest();
>>>>>
>>>>>
>>>>>
>>>>> This piece of code will alert “undefined” because “this” is undefined
>>>>> in the callback function in strict mode.
>>>>>
>>>>> Now let’s assume a new keyword is used insetead of “this” that will
>>>>> always point to the class instance.
>>>>>
>>>>> As per its implementation, as described on
>>>>> https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes
>>>>> :
>>>>>
>>>>> *“JavaScript classes, introduced in ECMAScript 2015, are primarily
>>>>> syntactical sugar over JavaScript's existing prototype-based inheritance.
>>>>> The class syntax does not introduce a new object-oriented inheritance model
>>>>> to JavaScript.”*
>>>>>
>>>>> So with the new keyword introduced, behind the scenes, previous class
>>>>> could be interpreted as a piece of code along the lines of:
>>>>>
>>>>>
>>>>> var RequestManager = function () {
>>>>>
>>>>>     var self = this;
>>>>>
>>>>>     self.successMessage = "Xhr successful";
>>>>>
>>>>>
>>>>>
>>>>>     self.makeRequest = function () {
>>>>>
>>>>>         var oReq = new XMLHttpRequest();
>>>>>
>>>>>         oReq.addEventListener("load", responseHandler);
>>>>>
>>>>>         oReq.open("GET", "www.google.com");
>>>>>
>>>>>         oReq.send();
>>>>>
>>>>>     };
>>>>>
>>>>>
>>>>>
>>>>>     var responseHandler = function () {
>>>>>
>>>>>         window.alert(self.successMessage);
>>>>>
>>>>>     };
>>>>>
>>>>> };
>>>>>
>>>>> var reqManager = new RequestManager();
>>>>>
>>>>>
>>>>>
>>>>> reqManager.makeRequest();
>>>>>
>>>>>
>>>>>
>>>>> I believe this way, we would not have to resort to work-arounds for
>>>>> such a fundamental construct of the language and this would ease
>>>>> developers’ lives as someone forgetting to have used an arrow function or
>>>>> the .bind(this) syntax will not be a problem anymore.
>>>>>
>>>>>
>>>>> Best Regards,
>>>>>
>>>>> John
>>>>>
>>>>>
>>>>> <https://www.avast.com/sig-email?utm_medium=email&utm_source=link&utm_campaign=sig-email&utm_content=webmail&utm_term=icon> Virus-free.
>>>>> www.avast.com
>>>>> <https://www.avast.com/sig-email?utm_medium=email&utm_source=link&utm_campaign=sig-email&utm_content=webmail&utm_term=link>
>>>>> _______________________________________________
>>>>> es-discuss mailing list
>>>>> es-discuss at mozilla.org
>>>>> https://mail.mozilla.org/listinfo/es-discuss
>>>>>
>>>> _______________________________________________
>> es-discuss mailing list
>> es-discuss at mozilla.org
>> https://mail.mozilla.org/listinfo/es-discuss
>>
>>
>> _______________________________________________
>> es-discuss mailing list
>> es-discuss at mozilla.org
>> https://mail.mozilla.org/listinfo/es-discuss
>>
> _______________________________________________
> es-discuss mailing list
> es-discuss at mozilla.org
> https://mail.mozilla.org/listinfo/es-discuss
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20190319/354fca47/attachment-0001.html>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: Screenshot 2019-03-10 at 08.45.13.jpeg
Type: image/jpeg
Size: 19739 bytes
Desc: not available
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20190319/354fca47/attachment-0001.jpeg>


More information about the es-discuss mailing list