📜 ⬆️ ⬇️

Small utility for CoffeeScript developer

CoffeeScript is truly an amazing language that allows you to look at JavaScript from a completely different and much more attractive side. A long time ago, when I was just starting to engage in the front-end - I was literally forced into writing snares on it (corporate standard), but now I cannot write in the original language.

For the time (more than two years), spent at the helm of this preprocessor, has accumulated quite a lot of "hoteles" that I would like to see in JS (the benefit is the experience of communicating with other languages), some of which I managed to put into practice, sometimes clumsily, but as it is - CoffeeScript allows you to almost invent your designs. I also want to tell about these "hotelkeepers" in article, I ask under kat.

Namespaces


The first thing I would like to see is the namespace . What developer does not want to see neatly located code not only in the file system, but also in the class hierarchy? Googling prompted several solutions, and the most elegant was the use of object literals as space names:

namespace MyApplication:Some: class Any #      window.MyApplication.Some.Any namespace global: class Some #      window.Some 

The following happens: We call the namespace function and send {MyApplication:{Some: _}} object there {MyApplication:{Some: _}}
')
The only "but" - this solution had some problems, namely - it required a strict sequence of connecting files (first with the MyApplication space, then with MyApplication.Some, etc.), as well as hardcore regulars with the receipt of the class name. I tried to get rid of their fatal flaws , and the result was the following code:

 window.namespace = -> args = arguments[0] target = global || window loop for subpackage, obj of args target = target[subpackage] or= {} args = obj break unless typeof args is 'object' Class = args target = window if arguments[0].hasOwnProperty 'global' name = if !Class.name? # IE Fix args.toString().match(/^function\s(\w+)\(/)[1] else Class.name proto = target[name] or undefined target[name] = Class if proto? for i of proto target[name][i] = proto[i] 

The code that has already been tested on combat projects does not slow down too much and has brought nothing but a huge heap of pleasure.

Import classes \ functions


Of course, I would like the presence of operators, such as using , use , import , but alas - you can only implement these at the level of the preprocessor itself, but not at all at the language level. But in the case of CoffeeScript, it turns out that there are some properties of the language itself that make it possible to realize import almost beautifully:

 {Any} = MyApplication.Some #  MyApplication.Some.Any   Any {Any: Test} = MyApplication.Some #  MyApplication.Some.Any   Test 

these operations are similar, let's say use in php:

 use MyApplication\Some\Any; use MyApplication\Some\Any as Test; 


This behavior is of course documented (point: "Destructuring Assignment" example number 3 in off- site documentation ), but, honestly, I was very surprised when I noticed a similar construction in someone's code. When I read the documentation, I simply did not notice it.

Constants


There are several popular options for implementing constants in CoffeeScript.

Option 1


 class Some @MY_DEFINE = 42 @getVar: -> @MY_DEFINE getVar: -> @contructor.MY_DEFINE #  Some.MY_DEFINE 

In this embodiment, there are both pros and cons:
A constant (theoretical, because it is just a variable) can be obtained from anywhere by contacting Some.MY_DEFINE
- It is not always convenient to use
“This is a variable (i.e., it can be overwritten), and using __defineGetter__ and similar constructs for creating getters will only complicate reading.

Option number two


 class Some MY_DEFINE = 42 @getVar: -> MY_DEFINE getVar: -> MY_DEFINE 

advantages and disadvantages:
+ Inside the class looks great, readable and very comfortable to use.
- Disposable, because it is limited only to the current (and nested) scope, it is impossible to get its value outside the class
- It is impossible to implement getters / setters to protect the value from changes

There are other options, such as using javascript scripts const MY_DEFINE = 42 in reverse single quotes (where the letter is “E”), or adding functions to the Function prototype that will register constants with getters / setters, but these are not very popular techniques, so I’m I'll keep them silent and better offer my version (a bit more close to reality):

Third option


 class Ajax define AJAX_UNSENT: 0 define AJAX_OPENED: 1 define AJAX_HEADERS_RECEIVED: 2 define AJAX_LOADING: 3 define AJAX_READY: 4 request: -> #   if xhr.status is AJAX_READY #  - 

The implementation of the function itself:

 window.define = (args) -> for name, val of args continue if window[name]? do (name, val) -> unless window.__defineGetter__? # IE Fix return window[name] = val window.__defineGetter__ name, -> val window.__defineSetter__ name, -> throw new Error "Can not redeclare define #{name}. Define already exists." #         window.defined = (name) -> return window.__lookupGetter__(name)? && window[name]? 

The following happens: Call the function where we are passing the object. Next, we register a getter in the window , which will return the desired value and the setter, which blocks the possibility of rewriting the constant.
Pros:
+ Inside the class also looks very nice and readable
+ Can get anywhere code
- Hanging in the window - globals were never a good solution, but I do not think that this is so significant in the light of possible gains in readability and convenience of the code, and the problems of collisions are solved with the usual prefixes.

Private variables


This example is only as a bonus and just as an idea. I don’t really like this implementation myself, but so far I couldn’t think of a better one:

 class Some test = $private 'test' constructor: -> @[test] = 23 console.log(new Some) 

What happens here: Inside the class, we declare var test variable, the value of which will be the string [private test] (returned by the function $ private). Next, we simply use this variable as the name for our real variable. And since our name begins with an invisible character, access to a variable is rather difficult to obtain, especially if the prefix is ​​generated from random invisible characters.

Implementation:

 window.$private = (name) -> unless defined 'ZERO_WIDTH_SPACE' define ZERO_WIDTH_SPACE: '​' #     ,    return "#{ZERO_WIDTH_SPACE}[private #{name}]" 

As a result:
+ Real Private Variables
+ In classes of quite large size can help a lot, because clears the interface of this class from unnecessary methods \ properties that should not be made public
- Very cumbersome and ugly
- It is inconvenient to use
- You have to add the prefix "$", because this is a keyword and it is reserved

Class names


Sometimes you want to get the class name or to distinguish between an array and an object. Such situations are quite common, and for such cases I hid for myself a small function:

 nameof [] # 'Array' nameof {} # 'Object' nameof SomeClass # 'SomeClass' 

The implementation itself looks like this:

 window.nameof = (cls) -> if typeof cls is 'object' cls = cls.constructor else if typeof cls isnt 'function' return typeof cls name = unless cls.name? cls.toString().match(/function\s(\w+)\(/)[1] else cls.name return name 


Abstract methods


Probably the most elegant and simple solution:

 class Some abstractMethod: abstract class Any extends Some (new Any).abstractMethod() # Error 'Can not call abstract method' #   class Any2 extends Some abstractMethod: -> console.log 42 (new Any).abstractMethod() # 42 

The implementation is the simplest and most obvious:

 window.abstract = -> throw new Error 'Can not call abstract method' 


Epilogue


I cited several interesting examples of how to improve readability (in my opinion) and how to use the code with a couple of small functions. Some of them may well fit for the organization of a serious code, some, like small helpers, but in general they fulfill one role - they add some language constructs to the language itself. That is why I want to warn you, the reader. Maybe all this is good and beautiful, and even convenient in practice, but sugar is good in moderation, you should not overuse such crazy functions.

PS I apologize for the “JavaScript” hub, alas, for CoffeeScript is missing.

UPD: Added constant rollback for IE, out of harm's way =)

Source: https://habr.com/ru/post/235205/


All Articles