Loại Uncaught LoạiError: Gọi bất hợp pháp


136

Khi tôi sử dụng requestAnimationFrameđể thực hiện một số hình ảnh động được hỗ trợ riêng với mã bên dưới:

var support = {
    animationFrame: window.requestAnimationFrame ||
        window.mozRequestAnimationFrame ||
        window.webkitRequestAnimationFrame ||
        window.msRequestAnimationFrame ||
        window.oRequestAnimationFrame
};

support.animationFrame(function() {}); //error

support.animationFrame.call(window, function() {}); //right

Gọi trực tiếp support.animationFramesẽ cho ...

Uncaught TypeError: Gọi bất hợp pháp

trong Chrome. Tại sao?

Câu trả lời:


194

Trong mã của bạn, bạn đang gán một phương thức riêng cho một thuộc tính của đối tượng tùy chỉnh. Khi bạn gọi support.animationFrame(function () {}), nó được thực thi trong ngữ cảnh của đối tượng hiện tại (tức là hỗ trợ). Để hàm requestAnimationFrame hoạt động đúng, nó phải được thực thi trong ngữ cảnh window.

Vì vậy, cách sử dụng chính xác ở đây là support.animationFrame.call(window, function() {});.

Điều tương tự cũng xảy ra với cảnh báo:

var myObj = {
  myAlert : alert //copying native alert to an object
};

myObj.myAlert('this is an alert'); //is illegal
myObj.myAlert.call(window, 'this is an alert'); // executing in context of window 

Một tùy chọn khác là sử dụng Function.prototype.bind () là một phần của tiêu chuẩn ES5 và có sẵn trong tất cả các trình duyệt hiện đại.

var _raf = window.requestAnimationFrame ||
        window.mozRequestAnimationFrame ||
        window.webkitRequestAnimationFrame ||
        window.msRequestAnimationFrame ||
        window.oRequestAnimationFrame;

var support = {
   animationFrame: _raf ? _raf.bind(window) : null
};

1
Kể từ Chrome 33, cuộc gọi thứ hai cũng thất bại với "Cuộc gọi bất hợp pháp". Rất vui khi xóa downvote sau khi câu trả lời được cập nhật !
Dan Dascalescu

@DanDascalescu: Tôi đang sử dụng chrome 33 và nó đang hoạt động với tôi.
Nemoy

1
Tôi vừa sao chép mã của bạn và nhận được lỗi gọi bất hợp pháp. Đây là screencast.
Dan Dascalescu

24
Bạn chắc chắn sẽ nhận được lỗi gọi bất hợp pháp, bởi vì stamtement đầu tiên myObj.myAlert('this is an alert');là bất hợp pháp. Sử dụng đúng là myObj.myAlert.call(window, 'this is an alert'). Xin vui lòng đọc câu trả lời đúng và cố gắng hiểu nó.
Nemoy

3
Nếu tôi không phải là người duy nhất ở đây bị mắc kẹt khi cố gắng để có được console.log.apply hoạt động theo cùng một cách, thì "cái này" phải là bàn điều khiển, không phải cửa sổ: stackoverflow.com/questions/8159233/ Thẻ
Alex

17

Bạn cũng có thể dùng:

var obj = {
    alert: alert.bind(window)
};
obj.alert('I´m an alert!!');

2
Điều này không trả lời đầy đủ câu hỏi. Tôi nghĩ nó nên là một bình luận, không phải là một câu trả lời.
Michał Perłakowski

2
Ngoài ra, điều quan trọng là liên kết với đối tượng thích hợp, ví dụ: khi làm việc với history.replaceState, người ta nên sử dụng: var realReplaceState = history.replaceState.bind(history);
DeeY

@DeeY: cảm ơn vì đã trả lời câu hỏi của tôi! Đối với những người trong tương lai, localStorage.clear yêu cầu bạn phải .bind(localStorage)không .bind(window).
Samyok Nepal

13

Khi bạn thực thi một phương thức (tức là hàm được gán cho một đối tượng), bên trong nó, bạn có thể sử dụng thisbiến để tham chiếu đến đối tượng này, ví dụ:

var obj = {
  someProperty: true,
  someMethod: function() {
    console.log(this.someProperty);
  }
};
obj.someMethod(); // logs true

Nếu bạn gán một phương thức từ đối tượng này sang đối tượng khác, thisví dụ , biến của nó đề cập đến đối tượng mới:

var obj = {
  someProperty: true,
  someMethod: function() {
    console.log(this.someProperty);
  }
};

var anotherObj = {
  someProperty: false,
  someMethod: obj.someMethod
};

anotherObj.someMethod(); // logs false

Điều tương tự xảy ra khi bạn gán requestAnimationFramephương thức windowcho đối tượng khác. Các hàm riêng, chẳng hạn như hàm này, có bảo vệ tích hợp để thực thi nó trong ngữ cảnh khác.

Có một Function.prototype.call()chức năng, cho phép bạn gọi một chức năng trong ngữ cảnh khác. Bạn chỉ cần truyền nó (đối tượng sẽ được sử dụng làm bối cảnh) làm tham số đầu tiên cho phương thức này. Ví dụ alert.call({})cho TypeError: Illegal invocation. Tuy nhiên, alert.call(window)hoạt động tốt, bởi vì bây giờ alertđược thực hiện trong phạm vi ban đầu của nó.

Nếu bạn sử dụng .call()với đối tượng của mình như thế:

support.animationFrame.call(window, function() {});

nó hoạt động tốt, bởi vì requestAnimationFrameđược thực thi trong phạm vi windowthay vì đối tượng của bạn.

Tuy nhiên, sử dụng .call()mỗi khi bạn muốn gọi phương thức này, không phải là giải pháp rất thanh lịch. Thay vào đó, bạn có thể sử dụng Function.prototype.bind(). Nó có tác dụng tương tự .call(), nhưng thay vì gọi hàm, nó tạo ra một hàm mới sẽ luôn được gọi trong ngữ cảnh được chỉ định. Ví dụ:

window.someProperty = true;
var obj = {
  someProperty: false,
  someMethod: function() {
    console.log(this.someProperty);
  }
};

var someMethodInWindowContext = obj.someMethod.bind(window);
someMethodInWindowContext(); // logs true

Nhược điểm duy nhất của Function.prototype.bind()nó là một phần của ECMAScript 5, không được hỗ trợ trong IE <= 8 . May mắn thay, có một polyfill trên MDN .

Như bạn có thể đã tìm ra, bạn có thể sử dụng .bind()để luôn luôn thực thi requestAnimationFrametrong bối cảnh window. Mã của bạn có thể trông như thế này:

var support = {
    animationFrame: (window.requestAnimationFrame ||
        window.mozRequestAnimationFrame ||
        window.webkitRequestAnimationFrame ||
        window.msRequestAnimationFrame ||
        window.oRequestAnimationFrame).bind(window)
};

Sau đó, bạn có thể chỉ cần sử dụng support.animationFrame(function() {});.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.