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

Isiah Meadows isiahmeadows at gmail.com
Tue Mar 19 05:31:47 UTC 2019


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
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20190319/05006e95/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/05006e95/attachment-0001.jpeg>


More information about the es-discuss mailing list