Douglas Crockford, a prominent luminary in the JavaScript realm, celebrated for his contributions to JSON and his seminal work "JavaScript: The Good Parts", recently made a startling proclamation in an interview:
“The ideal step forward for JavaScript today is to retire it. Two decades back, I was among the scarce champions of JavaScript. Its clever amalgamation of nested functions and dynamic objects was revolutionary. I dedicated a decade towards rectifying its imperfections. I achieved a minor victory with ES5. However, since then, there has been a profound interest in adding more layers to the language rather than refining it. Hence, JavaScript, akin to other prehistoric languages, has evolved into an impediment to progress. Our focus should shift towards the next language, which should resonate more with E than with JavaScript.”
Being a seasoned web developer proficient in PHP, JavaScript, and TypeScript, this assertion evokes a blend of sentiments in me. While Crockford's plea for a new language is persuasive, I am also aware that we do not inhabit an ideal world where we can simply transition to a new language once we start experiencing the constraints of the existing one. That's why, in my perspective, TypeScript embodies a pragmatic and efficient antidote to several of JavaScript's predicaments.
Let's delve into the unique aspects of JavaScript that stir concern among several developers, including Crockford, and examine how TypeScript can alleviate these issues.
JavaScript, the omnipresent language of the web, has certain eccentricities that can lead to unforeseen outcomes, bewilderment, and bugs. Here are some of its most criticized facets:
Global Variables: By default, JavaScript's scope is global, potentially leading to conflicts in variable naming. Observe this instance:
var x = 10;
function test() {
var x = 20;
console.log(x); // Outputs 20
}
test();
console.log(x); // Outputs 10
Unrequested Type Conversion: In an attempt to be helpful, JavaScript converts types automatically, which can be perplexing:
console.log(5 + "5"); // Outputs "55"
console.log(5 - "5"); // Outputs 0
== vs ===: JavaScript possesses two equality operators, each abiding by different rules. While ==verifies equality after performing any essential type coercion, === checks for equality without type coercion.
Semicolon Insertion: JavaScript automatically inserts semicolons in certain scenarios, potentially causing unexpected behavior.
this
Keyword: The this
keyword's behavior in JavaScript is unpredictable as it's contingent on how a function is invoked, not on how it's defined.
Null and Undefined: JavaScript has both null
and undefined
values, which can cause confusion as they are similar but not exactly alike. They signify the absence of a value, but subtle differences exist in their utilization.
Prototypal Inheritance: Instead of the more familiar classical inheritance seen in other object-oriented languages, JavaScript employs prototypal inheritance. While powerful, it can confuse developers more accustomed to classical inheritance.
Absence of Modules: Prior to ES6, JavaScript lacked a built-in module system, making code organization and reuse more challenging. Although ES6 introduced modules, a significant amount of JavaScript code still doesn't employ them.
Callback Hell: Before the advent of Promises and async/await syntax, handling asynchronous code in JavaScript often led to nested callbacks, yielding hard-to-read, "pyramid" shaped code.
Non-existence of Integer Type: JavaScript doesn't possess a built-in integer type. It uses the Number
type for both integers and floating-point values, leading to precision errors when dealing with considerably large numbers.
No Block Scope until ES6: Before ES6, JavaScript didn't support block scope due to its reliance on var
for declaring variables, leading to issues with variable hoisting and unexpected values within loops. ES6 largely addressed this with the introduction of let
and const
.
Verbose and Intricate Asynchronous Syntax: Despite advancements with promises and async/await, compared to languages with built-in support for asynchronous programming like C# or Python's asyncio library, JavaScript's asynchronous syntax can still be verbose and complex.
Silent Failures: Often, JavaScript fails silently, i.e., it doesn't throw errors or stop execution when things go wrong. While occasionally convenient, it can lead to elusive bugs.
API Inconsistencies: The methods built into JavaScript can be inconsistent in their naming and usage. For instance, Array's push()
modifies the original array and returns the new length, while concat()
also modifies the original array but returns the new array.
NaN Peculiarities: NaN
stands for 'Not a Number', but in JavaScript, typeof NaN
returns "number"
. Moreover, NaN
is the only JavaScript value that is unequal to itself.
Sparse Standard Library: Compared to other languages, JavaScript's standard library is relatively sparse. It lacks features like a standard function for copying objects, leading developers to depend on utility libraries like lodash, or write their own utility functions.
Performance: While JavaScript engines have considerably improved over time, JavaScript is generally slower than statically-typed, compiled languages.
Developed by Microsoft, TypeScript can be perceived as JavaScript's super-powered alter ego. It's a superset of JavaScript that incorporates static types, and it compiles down to plain JavaScript, which can run wherever JavaScript does. By counteracting many of JavaScript's quirks, TypeScript can significantly boost the development experience. Let's examine this closer:
Static Typing: One of TypeScript's main enhancements over JavaScript is static typing. While JavaScript is dynamically typed and performs type checking at runtime, TypeScript allows developers to specify variable types and executes type checking at compile-time, reducing the likelihood of type-related bugs in the running code. Here's an illustration:
let myVar: number;
myVar = 5; // Valid
myVar = "hello"; // Error: Type '"hello"' is not assignable to type 'number'.
This type annotation enables TypeScript to catch the bug before the code execution.
Class Features: TypeScript supports class-based object-oriented programming. It introduces class decorators, which are a stage 2 proposal for JavaScript, and simplifies the task of managing and organizing code in an object-oriented manner.
Interfaces: TypeScript introduces interfaces, allowing developers to define the structure of an object, making it simpler to ensure objects possess the correct format:
interface Point {
x: number;
y: number;
}
let myPoint: Point = { x: 10, y: 20 }; // Valid
myPoint = { x: "hello", y: 20 }; // Error: Type 'string' is not assignable to type 'number'.
Generics: TypeScript supports generics, a feature that enables components to work with any data type and not restrict to a single datatype. This makes the code more reusable and flexible:
function identity<T>(arg: T): T {
return arg;
}
let output1 = identity<string>("myString"); // type of output will be 'string'
let output2 = identity<number>(100); // type of output will be 'number'
Tooling Support: Thanks to TypeScript's static typing, IDEs can provide superior tooling support, with features like autocomplete, type inference, and refactoring tools, enhancing developer productivity.
Enhanced Collaboration: In larger codebases or teams, TypeScript's features can facilitate easier collaboration. The defined interfaces and types act as self-documentation, making the code more comprehensible.
Downlevel Compilation: TypeScript offers support for the latest and evolving JavaScript features like async/await and decorators to target older JavaScript versions (ES3/ES5), enabling developers to write future JavaScript today.
Strict Null Checking: TypeScript has a strict null checking feature. When enabled, variables can't be null
or undefined
unless explicitly declared, helping to prevent common null
and undefined
errors.
While TypeScript undoubtedly adds complexity and a build step to the process, the benefits often outweigh the costs. It takes the dynamic and flexible nature of JavaScript, introduces the safety net of static typing, and offers several features leading to more reliable, maintainable code. In a world where JavaScript can't be entirely replaced, TypeScript proves to be a practical and efficient solution.
While Douglas Crockford's call for a new language is logical from a purist's viewpoint, the practical solution is in leveraging tools like TypeScript. TypeScript provides a bridge, allowing us to write safer, more robust code while still employing the existing JavaScript infrastructure. Even if the concept of completely retiring JavaScript seems too radical, TypeScript aids us in mitigating its shortcomings and advancing towards a future where coding in JavaScript, or rather TypeScript, becomes a more enjoyable experience.
As we navigate the vast world of web development, the journey often sparks more questions and insights. If you have any queries or thoughts about the topics we've discussed, or if you're interested in diving deeper into object-oriented programming and design patterns in PHP and TypeScript, I'm here to assist. I'm currently working on a book that delves into these topics in more detail.
You can reach out to me at [email protected] or follow https://twitter.com/zhukmax for any inquiries or to pre-order the book. As a token of appreciation for your interest, I'll also provide a special discount for early bird orders. Let's continue to learn and grow in this dynamic field of web development together.