Node.js
Node.js được viết với JavaScript dùng để lập trình về phần server. Cho phép JavaScript tạo ứng dụng từ backend đến frontend.
Sự xuất hiện của Node.js tác động lớn đến hệ sinh thái JavaScript, giới thiệu về npm manager package và phổ biến format cho từng module JS. Lập trình viên đã bắt đầu tạo nhiều tools và phát triển các cách tiếp cận mới giữa trình duyệt, server và các app, ứng dụng.
JavaScript ES2015+
Trong 2015, phiên bản thứ sáu của ECMAScript được ra mắt dưới cái tên ES2015 (hay ES6). Đây là phiên bản mới bao gồm những bổ sung quan trọng tới ngôn ngữ này, khiến nó dễ dàng và khả thi hơn để xây dựng những ứng dụng web đầy tham vọng. Sự cải thiện không chỉ dừng với ES2015, hàng năm, đều có một phiên bản mới được ra mắt.
Khai báo biến
JavaScript nay có thêm hai cách để khai báo biến là: let và const
let
gần giống như var
. let giới hạn phạm vi của biến trong khối (thay vì function), điều này giảm thiểu cơ hội lỗi:
// ES5 for (var i = 1; i < 5; i++) { console.log(i); } // <-- logs the numbers 1 to 4 console.log(i); // <-- 5 (variable i still exists outside the loop) // ES2015 for (let j = 1; j < 5; j++) { console.log(j); } console.log(j); // <-- 'Uncaught ReferenceError: j is not defined'
Sử dụng const để định nghĩa biến – mà nó không nhảy sang giá trị khác (hay còn gọi là hằng). Giá trị thường sử dụng const là chuỗi hoặc số, dưới đây
const name = 'Bill'; name = 'Steve'; // <-- 'Uncaught TypeError: Assignment to constant variable.' // Gotcha const person = { name: 'Bill' }; person.name = 'Steve'; // person.name is now Steve. // As we're not changing the object that person is bound to, JavaScript doesn't // complain.
Arrow functions
Arrow functions cho cú pháp rõ ràng hơn để khai báo các hàm ẩn danh (lambdas), loại bỏ từ khóa hàm và trả về giá trị khi body function chỉ có một biểu thức. Điều này cho phép bạn viết code function một cách đẹp hơn.
// ES5 var add = function(a, b) { return a + b; } // ES2015 const add = (a, b) => a + b;
Một feature khác của arrow function là nó cho phép kế thừa giá trị của this
từ phần trước đấy mà nó định nghĩa:
function Person(){ this.age = 0; // ES5 setInterval(function() { this.age++; // |this| refers to the global object }, 1000); // ES2015 setInterval(() => { this.age++; // |this| properly refers to the person object }, 1000); } var p = new Person();
Cải thiện cú pháp Class
Cung cấp cú pháp gọn gàng hơn cho developer code hướng đối tượng với prototypes.
class Person { constructor(name) { this.name = name; } greet() { console.log(`Hello, my name is ${this.name}`); } }
Promises/ Async functions
Bản chất không đồng bộ của JavaScript từ lâu đã là một khó khăn; bất cứ cứng dụng nào đều có nguy cơ rới vào địa ngục callback khi làm việc với Ajax requests.
May mắn thay, ES2015 đã thêm promises
. Promises đại diện cho giá trị không tồn tại lúc đầu (trong thời điểm load) nhưng có thể sau này, giúp việc quản lý các function không đồng bộ dễ dàng hơn mà không cần tới các callback lồng ghép.
ES2017 giới thiệu async functions, cho phép xử lý code không đồng bộ như thể nó đồng bộ:
async function doAsyncOp () { var val = await asynchronousOperation(); console.log(val); return val; };
Modules
Một tính năng nổi bật khác được bổ sung trong ES2015 là định dạng module, làm cho định nghĩa và cách sử dụng module thành một phần của ngôn ngữ.
Code Linting
Linters là công cũ phân tích mã code của bạn và so sánh nó với một quy tắc, kiểm tra lỗi, format và các phướng pháp khác. Mặc dù việc dùng linter được khuyến khích với mọi người, nó đặc biệt hữu dụng nếu bạn mới bắt đầu.
Khi cấu hình đúng cho Edior/IDE code của bạn, bạn có thể nhận feedback lập tức khi bạn gặp lỗi cú khi đang học các tính năng ngôn ngữ mới.
Modular Code
Các Web app hiện đại có thể có hàng nghìn (trăm nghìn) dòng code. Làm việc với kích thước như vậy gần như không thể nếu không có một cơ chế để tổ chức gọn gàng và chia nhỏ, viết các đoạn mã chuyên biệt và biệt lập, có thể sử dụng lại khi cần thiết. Đây là công việc của modules.
CommonJS modules
Một số định dạng module đã xuất hiện trong nhiều năm gần đây, phổ biến nhất đó là CommonJS. Đó là định dạng module mặc định trong Node.js.
Nó khiến cho việc sử dụng đối tượng module
được export từ file Javascript and function require()
để import function nơi bạn muốn.
// lib/math.js function sum(x, y) { return x + y; } const pi = 3.141593 module.exports = { sum: sum, pi: pi }; // app.js const math = require("lib/math"); console.log("2π = " + math.sum(math.pi, math.pi));
ES2015 Modules
ES2015 giới thiệu một cách để định nghĩa và sử dụng component ngay trong ngôn ngữ, điều mà trước đây chỉ có thể với các thư viện bên thứ ba. Bạn có thể có các tệp file riêng với chức năng bạn muốn, và chỉ xuất một số thành phần nhất định trong app của bạn.
Đây là một ví dụ:
// lib/math.js export function sum(x, y) { return x + y; } export const pi = 3.141593;
Ở đây chúng ta có một module xuất function và biến. Ta có thể include file này ở một chỗ khác và sử dụng những function đã xuất.
// app.js import * as math from "lib/math"; console.log("2π = " + math.sum(math.pi, math.pi));
Hoặc ta có thể rõ ràng chỉ import những cái mà ta cần:
// otherApp.js import {sum, pi} from "lib/math"; console.log("2π = " + sum(pi, pi));
Quản lý gói
Các ngôn ngữ khác từ lâu đã có kho lưu trữ và trình quản lý gói riêng để giúp việc tìm kiếm và cài đặt các thư viện và thành phần của bên thứ ba dễ dàng hơn. Node.js đi kèm với trình quản lý gói và kho lưu trữ của riêng nó, npm
.
Trong npm repository Bạn có thể dễ dàng tìm và download các module bên thứ ba, sử dụng chúng trong project của bạn với câu lệnh npm install <package>
. Các gói được tải xuống trong thư mục node_modules
, trong đó chứa packages và dependencies.
Các gói mà bạn tải xuống có thể được đăng ký trong file package.json, cùng với thông tin về dự án của bạn.
Ví dụ về package.json file
{ "name": "demo", "version": "1.0.0", "description": "Demo package.json", "main": "main.js", "dependencies": { "mkdirp": "^0.5.1", "underscore": "^1.8.3" }, "devDependencies": {}, "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "author": "Sitepoint", "license": "ISC" }
Xây dựng hệ thống & trình chạy nhiệm vụ
Gói và chuyển đổi module chỉ là hai trong số các quy trình xây dựng mà chúng tôi có thể cần trong các dự án của mình. Những thứ khác bao gồm rút gọn mã (để giảm kích thước tệp), các công cụ để phân tích và có thể là các tác vụ không liên quan gì đến JavaScript, như tối ưu hóa hình ảnh hoặc xử lý CSS / HTML.
Việc quản lý các tác vụ có thể trở thành một việc tốn nhiều công sức và chúng ta cần một cách để xử lý nó một cách tự động, có thể thực thi mọi thứ bằng các lệnh đơn giản hơn. Hai công cụ phổ biến nhất cho việc này là Grunt.js và Gulp.js, cung cấp cách sắp xếp các nhiệm vụ của bạn thành các nhóm theo cách có thứ tự.
Single Page Applications (SPAs)
Kiến trúc cấp cao, phổ biến nhất cho các ứng dụng web được gọi là SPA, viết tắt của Single Page Application. SPA là những đốm màu lớn của JavaScript chứa mọi thứ mà ứng dụng cần để hoạt động bình thường. UI được render toàn bộ ở phía client, vì vậy không cần tải lại. Điều duy nhất thay đổi là dữ liệu bên trong ứng dụng, thường được xử lý bằng API từ xa thông qua Ajax hoặc một phương thức giao tiếp không đồng bộ khác.
Một nhược điểm của cách tiếp cận này là ứng dụng mất nhiều thời gian hơn để tải lần đầu tiên. Tuy nhiên, khi nó đã được tải, quá trình chuyển đổi giữa các chế độ xem (trang) thường nhanh hơn rất nhiều, vì nó chỉ là dữ liệu thuần túy được gửi giữa máy khách và máy chủ.
Universal / Isomorphic Applications
Mặc dù các SPA cung cấp trải nghiệm người dùng tuyệt vời, tùy thuộc vào nhu cầu của bạn, chúng có thể không phải là giải pháp tối ưu – đặc biệt nếu bạn cần thời gian phản hồi ban đầu nhanh hơn hoặc lập chỉ mục tối ưu bởi các công cụ tìm kiếm.
Có một cách tiếp cận khá gần đây để giải quyết những vấn đề này, được gọi là các ứng dụng Isomorphic JavaScript (hoặc Universal). Trong kiểu kiến trúc này, hầu hết mã có thể được thực thi cả trên máy chủ và máy khách.
Bạn có thể chọn những gì bạn muốn hiển thị trên máy chủ để tải trang đầu nhanh hơn . Sau đó, máy khách sẽ tiếp nhận việc rendor trong khi người dùng tương tác với ứng dụng. Bởi vì các trang được hiển thị ban đầu trên máy chủ, các công cụ tìm kiếm có thể lập chỉ mục chúng đúng cách.
Deployment
Với các ứng dụng JavaScript hiện đại, mã bạn viết không giống với mã bạn deploy cho sản phẩm: bạn chỉ deploy kết quả của quá trình xây dựng của mình. Quy trình làm việc để thực hiện điều này có thể thay đổi tùy thuộc vào quy mô dự án của bạn, số lượng dev đang làm việc trên đó và đôi khi là các công cụ / thư viện bạn đang sử dụng.
Ví dụ: nếu bạn đang làm việc một mình trong một dự án đơn giản, mỗi khi bạn sẵn sàng triển khai, bạn chỉ cần chạy build process và tải các tệp kết quả lên máy chủ web. Bạn chỉ cần upload các files kết quả.
Cấu trúc thư mục có thể sẽ như này
├── dist │ ├── app.js │ └── index.html ├── node_modules ├── src │ ├── lib │ │ ├── login.js │ │ └── user.js │ ├── app.js │ └── index.html ├── gulpfile.js ├── package.json └── README
Bạn có tất cả các tệp ứng dụng của mình trong một thư mục src
, được viết bằng ES2015+, import các gói được cài đặt bằng npm
và các module của riêng bạn từ một thư mục lib
.
Sau đó, bạn có thể chạy Gulp, nó sẽ thực thi cấu trúc từ gulpfile.js
để build project – gói tất cả các mô-đun thành một tệp (bao gồm cả những mô-đun được cài đặt bằng npm), chuyển đổi ES2015 + sang ES5, thu nhỏ tệp kết quả, v.v.. Sau đó, bạn có thể cấu hình nó để xuất kết quả trong một thư mục dist
thuận tiện.
Giờ đây, bạn tải các tệp từ thư mục dist
lên máy chủ web mà không cần phải lo lắng về phần còn lại của các tệp, vốn chỉ hữu ích cho việc phát triển.
Kết luận
Việc chuyển đổi từ các trang web đơn giản sang các ứng dụng JavaScript hiện đại có vẻ khó khăn nếu bạn không còn phát triển web trong những năm gần đây, nhưng tôi hy vọng bài viết này hữu ích như một điểm khởi đầu. Tôi đã liên kết đến các bài viết chuyên sâu hơn về từng chủ đề nếu có thể để bạn có thể khám phá thêm.
Và hãy nhớ rằng nếu tại một thời điểm nào đó, sau khi xem xét tất cả các tùy chọn có sẵn, mọi thứ dường như quá tải và lộn xộn, chỉ cần ghi nhớ nguyên tắc KISS và chỉ sử dụng những gì bạn nghĩ bạn cần chứ không phải mọi thứ bạn có sẵn. Cuối cùng, giải quyết vấn đề mới là điều quan trọng, không phải sử dụng mọi thứ mới nhất.