Chắc hẳn ai cũng biết về javascript. Và dưới đây là top 10 lỗi javascript thường gặp được rollbar thu thập với hơn 1000 project.
1. Uncaught TypeError: Cannot read property
Nếu bạn là developer JavaScript, có thể bạn đã nhìn thấy lỗi nhiều như cơm bữa. Điều này xảy ra khi bạn đọc thuộc tính hoặc gọi phương thức trên đối tượng chưa được xác định. Bạn có thể kiểm tra điều này rất dễ dàng trong F12 của trình duyệt.
Điều này có thể xảy ra vì nhiều lý do, nhưng một lý do phổ biến là khởi tạo không đúng trạng thái trong khi hiển thị các thành phần giao diện người dùng. Hãy xem xét một ví dụ về cách điều này có thể xảy ra trong một ứng dụng trong thực tế. Tôi sẽ chọn React, nhưng cùng một nguyên tắc khởi tạo không đúng cũng áp dụng cho Angular, Vue hoặc bất kỳ framework khác.
class Quiz extends Component {
componentWillMount() {
axios.get('/thedata').then(res => {
this.setState({items: res.data});
});
}
render() {
return (
<ul>
{this.state.items.map(item =>
<li key={item.id}>{item.name}</li>
)}
</ul>
);
}
}
Có hai điều quan trọng nhận ra ở đây:
1. Trạng thái của một thành phần (ví dụ this.state ) bắt đầu là undefined .
2. Khi bạn fetch data không đồng bộ, thành phần sẽ render ít nhất một lần trước khi dữ liệu được tải - bất kể nó có được gọi trong hàm hay không, componentWillMount hoặc componentDidMount . Khi Quiz hiển thị lần đầu tiên, this.state.items là không xác định. Điều này có nghĩa là ItemList nhận các mục như undefined, và bạn nhận được một lỗi - "Uncaught TypeError: Cannot read property ‘map’ of undefined" trong console.
Điều này rất dễ sửa. Cách đơn giản nhất: Khởi tạo trạng thái với các giá trị mặc định hợp lý trong hàm.
class Quiz extends Component {
// Added this:
constructor(props) {
super(props);
// Assign state itself, and a default value for items
this.state = {
items: []
};
}
componentWillMount() {
axios.get('/thedata').then(res => {
this.setState({items: res.data});
});
}
render() {
return (
<ul>
{this.state.items.map(item =>
<li key={item.id}>{item.name}</li>
)}
</ul>
);
}
}
Với code thực tế trong ứng dụng của bạn có thể khác, nhưng tôi hy vọng tôi đã cung cấp cho bạn đủ đầu mối để khắc phục hoặc tránh sự cố này trong ứng dụng của bạn. Nếu không, hãy tiếp tục đọc vì tôi sẽ trình bày thêm các ví dụ về các lỗi liên quan bên dưới.
2. TypeError: ‘undefined’ is not an object
Đây là lỗi xảy ra trong Safari khi bạn đọc thuộc tính hoặc gọi phương thức trên đối tượng không xác định. Bạn có thể kiểm tra điều này rất dễ dàng trong F12 của Safari. Về cơ bản, điều này giống với lỗi trên cho Chrome, nhưng Safari sử dụng thông báo lỗi khác.
3. TypeError: null is not an object
Đây là lỗi xảy ra trong Safari khi bạn đọc thuộc tính hoặc gọi phương thức trên đối tượng rỗng. Bạn có thể kiểm tra điều này rất dễ dàng trong F12 của Safari.
Điều thú vị là, trong JavaScript, null và undefined không giống nhau, đó là lý do tại sao chúng ta thấy hai thông báo lỗi khác nhau. Undefined thường là một biến chưa được gán, trong khi null có nghĩa là giá trị trống. Để xác minh chúng không bằng nhau, hãy thử sử dụng toán tử '===':
Một cách mà lỗi này có thể xảy ra trong ví dụ thực tế là nếu bạn thử sử dụng phần tử DOM trong JavaScript của bạn trước khi phần tử được tải. Đó là vì DOM API trả về null cho các tham chiếu đối tượng trống.
Bất kỳ mã JS nào thực hiện và giao dịch với các phần tử DOM sẽ thực thi sau khi các phần tử DOM đã được tạo. Mã JS được diễn giải từ trên xuống dưới như được trình bày trong HTML. Vì vậy, nếu có thẻ trước phần tử DOM, mã JS trong thẻ tập lệnh sẽ thực thi khi trình duyệt phân tích cú pháp trang HTML. Bạn sẽ nhận được lỗi này nếu các phần tử DOM chưa được tạo trước khi tải tập lệnh.
Trong ví dụ này, tôi có thể giải quyết vấn đề bằng cách thêm trình xử lý sự kiện sẽ thông báo cho tôi khi trang đã sẵn sàng. Khi addEventListener được kích hoạt, phương thức init() có thể sử dụng các phần tử DOM.
<script>
function init() {
var myButton = document.getElementById("myButton");
var myTextfield = document.getElementById("myTextfield");
myButton.onclick = function() {
var userName = myTextfield.value;
}
}
document.addEventListener('readystatechange', function() {
if (document.readyState === "complete") {
init();
}
});
</script>
<form>
<input type="text" id="myTextfield" placeholder="Type your name" />
<input type="button" id="myButton" value="Go" />
</form>
4. (unknown): Script error
Lỗi Tập lệnh xảy ra khi một lỗi JavaScript không được thực hiện vượt qua các ranh giới của phạm chính sách gốc . Ví dụ: nếu bạn lưu trữ mã JavaScript của mình trên CDN, bất kỳ lỗi không xác định nào (các lỗi bong bóng lên trình xử lý window.onerror, thay vì bị bắt trong try-catch) sẽ được báo cáo đơn giản là "Lỗi tập lệnh" thay vì có chứa hữu ích thông tin. Đây là biện pháp bảo mật của trình duyệt nhằm ngăn chặn truyền dữ liệu trên các tên miền nếu không sẽ không được phép giao tiếp.
Để nhận được thông báo lỗi , hãy làm như sau:
Gửi Access-Control-Allow-Origin header
Đặt header Access-Control-Allow-Origin thành *, biểu thị rằng tài nguyên có thể được truy cập đúng từ bất kỳ miền nào. Bạn có thể thay thế * bằng tên miền của mình nếu cần: ví dụ: Access-Control-Allow-Origin: www.example.com . Tuy nhiên, việc xử lý nhiều tên miền trở nên phức tạp và có thể không đáng giá nếu bạn đang sử dụng CDN do các vấn đề về bộ nhớ đệm có thể phát sinh. Xem thêm tại đây .
Dưới đây là một số ví dụ về cách đặt tiêu đề này trong các môi trường khác nhau: Apache
Trong các thư mục chứa các tệp JavaScript của bạn, hãy tạo .htaccess với các nội dung sau:
Header add Access-Control-Allow-Origin "*"
Nginx
Thêm chỉ thị add_header vào location block phục vụ các tệp JavaScript của bạn:
location ~ ^/assets/ {
add_header Access-Control-Allow-Origin *;
}
Haproxy
Thêm phần sau vào phần phụ trợ nội dung của bạn nơi tệp JavaScript được phân phối:
rspadd Access-Control-Allow-Origin:\ *
Đặt crossorigin = "anonymous" trên thẻ script Trong code HTML của bạn, đối với mỗi tập lệnh mà bạn đã đặt header Access-Control-Allow-Origin, hãy đặt crossorigin="anonymous" trên thẻ SCRIPT. Đảm bảo rằng bạn xác minh rằng header đang được gửi cho tệp script trước khi thêm thuộc tính crossorigin vào thẻ script. Trong Firefox, nếu thuộc tính crossorigin có mặt trong header Access-Control-Allow-Origin, kịch bản trên sẽ không xảy ra.
5. TypeError: Object doesn’t support property
Đây là lỗi xảy ra trong IE khi bạn gọi một phương thức không xác định. Bạn có thể kiểm tra điều này trong F12 của IE.
Điều này tương đương với lỗi “TypeError: ‘undefined’ is not a function” trong Chrome. Các trình duyệt khác nhau có thể có các thông báo lỗi khác nhau cho cùng một lỗi logic.
Đây là một vấn đề phổ biến đối với IE trong các ứng dụng web sử dụng tính năng tạo namespace JavaScript. Trong trường hợp này, vấn đề là 99,9% thời gian là do IE không có khả năng ràng buộc các phương thức trong namespace hiện tại với từ khóa this . Ví dụ, nếu bạn có JS namespace tên Rollbar với phương thức isAwesome. Thông thường, nếu bạn đang ở trong namespace tên Rollbar bạn có thể gọi phương thức isAwesome với cú pháp sau:
this.isAwesome();
Chrome, Firefox và Opera sẽ vui vẻ chấp nhận cú pháp này. IE thì lại không. Do đó, để an toàn nhất khi sử dụng tính năng đặt tên JS là luôn luôn tiền tố với tên namespace thực tế.
Rollbar.isAwesome();
6. TypeError: ‘undefined’ is not a function
Đây là lỗi xảy ra khi bạn gọi 1 hàm không xác định.
Khi các kỹ thuật lập trình JavaScript và các mẫu thiết kế ngày càng trở nên tinh vi trong những năm qua, đã có sự gia tăng tương ứng về phạm vi tự tham chiếu trong callbacks và closures, một điều khá phổ biến của sự lỗi này.
Xem xét đoạn mã ví dụ này:
function clearBoard(){
alert("Cleared");
}
document.addEventListener("click", function(){
this.clearBoard(); // what is “this” ?
});
Nếu bạn thực hiện đoạn mã trên và sau đó bấm vào trang, nó dẫn đến lỗi sau "Uncaught TypeError: this.clearBoard is not a function". Lý do là hàm ẩn danh đang được thực thi nằm trong ngữ cảnh của document, trong khi clearBoard được định nghĩa trong window.
Một giải pháp truyền thống, tương thích với trình duyệt cũ chỉ đơn giản là lưu tham chiếu của bạn vào this trong một biến mà sau đó có thể được kế thừa bằng cách đóng. Ví dụ:
var self=this; // save reference to 'this', while it's still this!
document.addEventListener("click", function(){
self.clearBoard();
});
Ngoài ra, trong các trình duyệt mới hơn, bạn có thể sử dụng phương thức bind() để chuyển tham chiếu thích hợp:
document.addEventListener("click",this.clearBoard.bind(this));
7. Uncaught RangeError: Maximum call stack
Đây là lỗi xảy ra khi bạn gọi hàm đệ quy không có kết thúc.
Nó cũng có thể xảy ra nếu bạn chuyển một giá trị cho một hàm nằm ngoài phạm vi. Nhiều hàm chỉ chấp nhận một phạm vi số cụ thể cho các giá trị đầu vào của chúng. Ví dụ, Number.toExponential(digits) và N umber.toFixed(digits) chấp nhận các chữ số từ 0 đến 20, và Number.toPrecision(digits) chấp nhận các chữ số từ 1 đến 21.
var a = new Array(4294967295); //OK
var b = new Array(-1); //range error
var num = 2.555555;
document.writeln(num.toExponential(4)); //OK
document.writeln(num.toExponential(-2)); //range error!
num = 2.9999;
document.writeln(num.toFixed(2)); //OK
document.writeln(num.toFixed(25)); //range error!
num = 2.3456;
document.writeln(num.toPrecision(1)); //OK
document.writeln(num.toPrecision(22)); //range error!
8. TypeError: Cannot read property ‘length’
Đây là lỗi xảy ra do thuộc tính độ dài của biến không xác định
Bạn thường tìm thấy chiều dài được xác định trên một mảng, nhưng bạn có thể chạy vào lỗi này nếu mảng không được khởi tạo hoặc nếu tên biến được ẩn trong một ngữ cảnh khác. Hãy hiểu lỗi này với ví dụ sau.
var testArray= ["Test"];
function testFunction(testArray) {
for (var i = 0; i < testArray.length; i++) {
console.log(testArray[i]);
}
}
testFunction();
Khi bạn khai báo một hàm với các tham số, các tham số này trở thành các tham số cục bộ. Điều này có nghĩa rằng ngay cả khi bạn có các biến có tên testArray , các tham số có cùng tên trong một hàm sẽ vẫn được coi là cục bộ.
Bạn có hai cách để giải quyết vấn đề của mình:
a. Loại bỏ các tham số trong câu lệnh khai báo hàm (nó chỉ ra bạn muốn truy cập vào các biến được khai báo bên ngoài hàm, vì vậy bạn không cần tham số cho hàm của bạn):
var testArray = ["Test"];
/* Precondition: defined testArray outside of a function */
function testFunction(/* No params */) {
for (var i = 0; i < testArray.length; i++) {
console.log(testArray[i]);
}
}
testFunction();
b. Gọi hàm truyền qua mảng mà chúng ta đã khai báo:
var testArray = ["Test"];
function testFunction(testArray) {
for (var i = 0; i < testArray.length; i++) {
console.log(testArray[i]);
}
}
testFunction(testArray);
9. Uncaught TypeError: Cannot set property
Khi chúng ta cố gắng truy cập một biến không xác định, nó luôn trả về undefined và chúng ta không thể lấy hoặc thiết lập bất kỳ thuộc tính nào của undefined . Trong trường hợp đó, một ứng dụng sẽ trả ra "Uncaught TypeError cannot set property of undefined."
Nếu đối tượng test không tồn tại, lỗi sẽ ném “Uncaught TypeError cannot set property of undefined.”
10. ReferenceError: event is not defined
Lỗi này được trả ra khi bạn cố gắng truy cập một biến không xác định hoặc nằm ngoài phạm vi hiện tại.
Nếu bạn gặp lỗi này khi sử dụng hệ thống xử lý sự kiện, hãy đảm bảo bạn sử dụng đối tượng sự kiện được truyền vào dưới dạng tham số. Các trình duyệt cũ hơn như IE cung cấp casc biến global và Chrome tự động đính kèm biến sự kiện vào trình xử lý. Firefox sẽ không tự động thêm nó. Các thư viện như jQuery cố gắng bình thường hóa hành vi này. Tuy nhiên, cách tốt nhất là sử dụng phương thức được chuyển vào hàm xử lý sự kiện của bạn.
document.addEventListener("mousemove", function (event) {
console.log(event);
})