Thứ năm, 07/02/2019 | 00:00 GMT+7

JavaScript Biểu thức chính quy cho Người bình thường


Biểu thức chính quy, còn gọi là regex hoặc regexp, là một chủ đề khó giải quyết. Đừng cảm thấy xấu hổ nếu bạn chưa hoàn toàn thoải mái với việc viết các biểu thức chính quy của riêng mình, vì nó sẽ mất một số thời gian để làm quen. Tôi hy vọng rằng vào cuối bài viết này, bạn sẽ tiến gần hơn một bước đến việc làm rung chuyển các biểu thức của bạn trong JavaScript mà không cần phụ thuộc quá nhiều vào copypasta từ Stack Overflow.

Bước đầu tiên để viết một biểu thức chính quy là hiểu cách gọi nó. Trong JavaScript, biểu thức chính quy là một đối tượng tích hợp tiêu chuẩn . Do đó, ta có thể tạo một đối tượng RegExp mới theo một số cách:

  • Theo nghĩa đen, /expression/.match('string to test against')
  • Từ khóa new với đối số chuỗi, new RegExp('expression')
  • Từ khóa new với chữ, new RegExp(/expression/)

Tôi sẽ sử dụng kết hợp các phương pháp chỉ để cho thấy rằng về cơ bản chúng thực hiện cùng một công việc.

Mục tiêu của Cụm từ Thông dụng

Trong ví dụ của tôi, tôi sẽ làm việc với một chuỗi chứa tên, họ và domain của tôi. Trong thế giới thực, ví dụ cần nhiều suy nghĩ hơn. Có rất nhiều điểm tinh tế khi nói đến tên mà tôi sẽ không đề cập ở đây.

Giả sử tôi đang xây dựng trang tổng quan và muốn hiển thị tên của user đã đăng nhập. Tôi không có quyền kiểm soát dữ liệu được trả lại cho mình nên tôi phải làm với những gì tôi có.

Tôi cần chuyển đổi aaron.arney:alligator.io thành Aaron Arney [Alligator] .

Biểu thức chính quy phù hợp với nhiều logic vào một đối tượng cô đọng duy nhất. Điều này có thể và sẽ gây ra nhầm lẫn. Một phương pháp hay là chia nhỏ biểu thức của bạn thành một dạng mã giả. Điều này cho phép ta xem những gì cần xảy ra và khi nào.

  1. Extract tên
  2. Extract họ
  3. Extract domain
  4. Định dạng chuỗi thành định dạng mẫu mong muốn First Last [Domain]

Trùng với tên

Để khớp một chuỗi với một biểu thức chính quy, tất cả những gì bạn phải làm là chuyển chuỗi ký tự. Chữ i ở cuối biểu thức là một lá cờ. Cờ i đặc biệt là viết tắt của case insensitive . Điều đó nghĩa là biểu thức của ta với cách viết hoa bỏ qua trên chuỗi.

const unformattedName = 'aaron.arney:alligator.io';

const found = unformattedName.match(/aaron/i);

console.log(found);
// expected output: Array [ "aaron" ]

Điều đó hoạt động tốt, nhưng trong trường hợp của ta , đó không phải là một cách tiếp cận tốt vì tên của user không phải lúc nào cũng là “Aaron”. Đây là nơi ta khám phá các chuỗi đối sánh theo chương trình.

Hiện tại, hãy tập trung vào việc đối sánh một cái tên. Chia từ thành các ký tự riêng lẻ, bạn thấy gì?

Tên "Aaron" bao gồm năm ký tự alpha. Có phải mỗi tên chỉ có năm ký tự? Không, nhưng sẽ hợp lý khi cho rằng tên có thể nằm trong repository ảng từ 1 đến 15 ký tự. Để biểu thị một ký tự trong phạm vi az, ta sử dụng [az] .

Bây giờ, nếu ta cập nhật biểu thức của bạn để sử dụng lớp ký tự này…

const unformattedName = 'aaron.arney:alligator.io';

const found = unformattedName.match(/[a-z]/i);

console.log(found);
// expected output: Array [ "a" ]

Thay vì extract “aaron” từ chuỗi, nó chỉ trả về “a”. Điều này là tốt, vì các biểu thức chính quy cố gắng hết sức để khớp càng ít càng tốt. Để lặp lại ký tự trùng với một số lên đến giới hạn 15, ta sử dụng dấu ngoặc nhọn. Điều này cho biết biểu thức mà ta xem để trùng với mã thông báo trước, “az” của ta , khớp từ 1 đến 15 lần.

const unformattedName = 'aaron.arney:alligator.io';
const unformattedNameTwo = 'montgomery.bickerdicke:alligator.io';
const unformattedNameThree = 'a.lila:alligator.io';

const exp = new RegExp(/[a-z]{1,15}/, 'i');

const found = unformattedName.match(exp);
const foundTwo = unformattedNameTwo.match(exp);
const foundThree = unformattedNameThree.match(exp);

console.log(found);
// expected output: Array [ "aaron" ]

console.log(foundTwo);
// expected output: Array [ "montgomery" ]

console.log(foundThree);
// expected output: Array [ "a" ]

Trùng với họ

Việc extract họ sẽ dễ dàng như copy paste biểu thức đầu tiên của ta . Bạn sẽ nhận thấy rằng kết quả khớp vẫn trả về cùng một giá trị thay vì cả họ và tên.

Chia nhỏ ký tự chuỗi theo ký tự , có một dấu dừng đầy đủ để tách các tên. Để giải thích điều này, ta thêm điểm dừng đầy đủ vào biểu thức của ta .

Ta phải cẩn thận ở đây. Các . có thể nghĩa là một trong hai điều trong một biểu thức.

  • . - Phù hợp với bất kỳ ký tự nào ngoại trừ dòng mới
  • \. - Phù hợp a.

Sử dụng một trong hai version trong ngữ cảnh này sẽ tạo ra cùng một kết quả, nhưng không phải lúc nào cũng vậy. Các công cụ như eslint đôi khi sẽ đánh dấu trình tự thoát \ là không cần thiết, nhưng tôi nói rằng an toàn hơn là xin lỗi!

const unformattedName = 'aaron.arney:alligator.io';

const exp = new RegExp(/[a-z]{1,15}\.[a-z]{1,15}/, 'i');

const found = unformattedName.match(exp);

console.log(found);
// expected output: Array [ "aaron.arney" ]

Vì ta muốn chia chuỗi thành hai mục cũng như loại trừ điểm dừng hoàn toàn do biểu thức trả về, nên bây giờ ta có thể sử dụng capturing groups . Chúng được biểu thị bằng dấu ngoặc đơn () và bao quanh các phần của biểu thức mà bạn muốn được trả về. Nếu ta quấn chúng xung quanh các biểu thức họ và tên, ta sẽ nhận được kết quả mới.

Cú pháp để sử dụng các group bắt rất đơn giản: (expression) . Vì tôi chỉ muốn trả về họ và tên của bạn chứ không phải là điểm dừng đầy đủ, hãy đặt các biểu thức của ta trong ngoặc đơn.

const unformattedName = 'aaron.arney:alligator.io';

const exp = new RegExp(/([a-z]{1,15})\.([a-z]{1,15})/, 'i');

const found = unformattedName.match(exp);

console.log(found);
// expected output: Array [ "aaron.arney", "aaron", "arney" ]

Trùng với domain

Để extract “alligator.io”, ta sẽ sử dụng các lớp ký tự mà ta đã sử dụng cho đến nay. Tất nhiên với một số sửa đổi nhỏ.

Xác thực domain và TLD là một công việc khó khăn. Ta sẽ giả sử các domain mà ta phân tích cú pháp, luôn có > 3 && < 25 ký tự. TLD luôn > 1 && < 10 . Nếu ta cắm những thứ này vào, ta sẽ nhận được một số kết quả mới:

const unformattedName = 'aaron.arney:alligator.io';

const exp = new RegExp(/([a-z]{1,15})\.([a-z]{1,15}):([a-z]{3,25}\.[a-z]{2,10})/, 'i');

const found = unformattedName.match(exp);

console.log(found);
// expected output: Array [ "aaron.arney:alligator.io", "aaron", "arney", "alligator.io" ]

Một lối tắt

Tôi đã chỉ cho bạn “chặng đường dài” về cách diễn đạt. Bây giờ, tôi sẽ chỉ cho bạn cách bạn có thể có một cách diễn đạt ít dài dòng hơn mà vẫn nắm bắt được cùng một văn bản. Bằng cách sử dụng bộ định lượng + , ta có thể yêu cầu biểu thức của bạn lặp lại mã thông báo trước nhiều lần nhất có thể. Nó sẽ tiếp tục cho đến khi nó đi vào ngõ cụt, trong trường hợp của ta là điểm dừng hoàn toàn. Biểu thức này cũng giới thiệu cờ g , viết tắt của global . Nó cho biểu thức rằng ta muốn lặp lại tìm kiếm của bạn nhiều lần nhất có thể, thay vì ít lần nhất.

// With the global flag
'aaron.arney:alligator.io'.match(/[a-z]+/ig);
// expected output: Array(4) [ "aaron", "arney", "alligator", "io" ]

// Without the global flag
'aaron.arney:alligator.io'.match(/[a-z]+/i);
// expected output: Array(4) [ "aaron" ]

Định dạng kết quả

Để định dạng chuỗi, ta sẽ sử dụng phương thức replace trên đối tượng String . Phương thức replace có hai đối số:

  • RegExp | String - Đối tượng biểu thức chính quy hoặc chữ
  • RegExp | function - Một biểu thức chính quy hoặc một hàm
const unformattedName = 'aaron.arney:alligator.io';

// The "long" way
const exp = new RegExp(/([a-z]{1,15})\.([a-z]{1,15}):([a-z]{3,25}\.[a-z]{2,10})/, 'i');

unformattedName.replace(exp, '$1 $2 [$3]');
// expected output:  "aaron arney [alligator.io]"

// A slightly shorter way
unformattedName.replace(/([a-z]+)\.([a-z]+):([a-z]+\.[a-z]{2,10})/ig, '$1 $2 [$3]');
// expected output: "aaron arney [alligator.io]"

Trong đoạn mã trên, $1 , $2 , $3 là các mẫu đặc biệt được giải thích bằng phương pháp replace .

  • $1 - The first result from the match array => A reference to the first parenthesized group
  • $2 - The second result from the match array => A reference to the second parenthesized group
  • $n - vân vân và vân vân

Để viết hoa các từ, ta có thể sử dụng một regex khác. Thay vì định dạng kết quả như ta đã làm ở trên, ta sẽ truyền một hàm. Hàm viết hoa đối số được cung cấp và trả về nó.

Ở đây, tôi đang giới thiệu một số phần mới, anchors , alternation và một lớp nhân vật mới [^] .

  • [^abc] - Không phải a , b hoặc c
  • \b - Ranh giới từ
  • ab|cd - Logic "OR", trùng với ab hoặc cd
// Capitalize the words
"aaron arney [alligator.io]".replace(/(^\b[a-z])|([^\.]\b[a-z])/g, (char) => char.toUpperCase());
// expected output: "Aaron Arney [Alligator.io]"

Chia biểu thức này thành hai phần ..

  • (^\b[az]) - Chụp ký tự đầu tiên của chuỗi. ^ nói để trùng với phần đầu của chuỗi.
  • |([^\.]\b[az]) - HOẶC, kết hợp một từ mới không bắt đầu bằng dấu ngắt . , vì đây là TLD.

Tiếp tục khám phá của bạn

Đây chỉ là một phần nhỏ về sức mạnh của biểu thức chính quy. Ví dụ tôi đã làm qua là có thể ứng biến được, nhưng làm thế nào?

  • Diễn đạt có quá dài dòng không? Nó có quá đơn giản không?
  • Nó có bao gồm các trường hợp cạnh không?
  • Bạn có thể thay thế nó bằng một số thao tác chuỗi thông minh bằng cách sử dụng các phương thức root không?

Đây là nơi bạn lấy kiến thức đã học và cố gắng trả lời những câu hỏi đó. Khám phá các tài nguyên sau đây để giúp bạn trong hành trình và thử nghiệm!


Tags:

Các tin liên quan

Mẫu kế thừa nguyên mẫu JavaScript
2019-02-01
Các mẫu hướng đối tượng JavaScript: Mẫu nhà máy
2019-01-23
Đối tượng, Nguyên mẫu và Lớp trong JavaScript
2019-01-10
Thủ thuật với JavaScript Hủy cấu trúc
2018-11-26
Đừng sợ theo dõi JavaScript
2018-10-17
Làm phẳng mảng trong Vanilla JavaScript với flat () và flatMap ()
2018-09-28
Cách sử dụng các phương thức đối tượng trong JavaScript
2018-08-03
Xử lý lỗi trong JavaScript Sử dụng try ... catch
2018-08-03
Hiểu sự kiện trong JavaScript
2018-06-19
Lập lịch tác vụ trong JavaScript Sử dụng setTimeout & setInterval
2018-06-12