Tắt Quảng Cáo [X] Đăng Nhập để ẩn Tất Cả Quảng Cáo #j2team_share # [Javascript] Function là một Object? Đúng là như vậy, ắt hẳn chúng ta đều biết điều này khi làm việc với JS. Nhưng mà nếu nó là một object vậy thì sao khi mình chạy một object kiểu như này *obj()* thì ăn ngay exception *"obj is not a 'function'"*. Chắc là phải có chiêu trò gì đó để tụi JS nó chạy một "object" chứ nhề ?. Chúng ta cùng lật tẩy "chiêu trò" của tụi JS nào. Bài viết này dựa trên những gì mình tìm hiểu được, có gì sai sót mong mọi người hoan hỷ góp ý ha. ## typeof có đang lừa chúng ta? Trong JS thì đây là cách để kiểm tra kiểu dữ liệu của một giá trị. Nếu chúng ta tìm google về các kiểu dữ liệu trong JS thì sẽ nhận được danh sách sau: *undefined, null, boolean, string, symbol, number, bigint và Object. * Nào chúng ta cùng thử xem **typeof** của một function là gì: function foo() {} console.log( typeof foo); // -> 'function' - WHAT? Rõ ràng làm gì có kiểu nào tên là function. Hmm, thôi quay tài liệu của ECMAScript về **typeof** thôi. **Thuật toán của typeof:** với val là giá trị truyền vào * if Type(val) = Undefined return "undefined" * if Type(val) = Null return "object" *// cái này từng làm anh/em ta hoang mang đây ...* * if Type(val) = Object (does not implement Call method) return "object" * if Type(val) = Object (implements Call method) return "function" Vậy thì nếu như một *Object* có implements phương thức *Call* thì nó sẽ được gọi là "function". Có vẻ như *typeof* không hẵn trả về kiểu dữ liệu nhỉ. ## Function object "function" là một object nhưng mà đã được implements phương thức **Call**, chúng được gọi là function object. Vậy thì phương thức **Call** là cái gì?. Nó có phải là *foo.call()* hay không? Tất nhiên là không rồi, một Object trong ECMAScript sẽ có nhiều phương thức gọi là internal, tức là chúng chỉ được sử dụng ở JS engine thôi. Chúng ta không thể gọi ra và xài ở runtime được. Vậy thì ta có thể suy luận rằng, muốn một object trở thành một function thì JS phải có cách nào đó "nhét" cái method **Call** này vào trong object ấy. ## Function Definitions Nào hãy quay trở lại với cách khai báo một hàm: **function** *foo***()** **{ ***return 0* **}** Lật tài liệu của ECMAScript ra xem nó xử lý như nào khi gặp một khai báo hàm như thế này. (Thuật toán bên dưới đã được mình lược bớt các đoạn nằm ngoài phạm vi bài viết) Trước tiên, chúng ta nên tách cú pháp kia thành một pattern: **function** *<Name>* **(** *<Parameters> ***) {** *<FunctionBody>** *}** **Thuật toán của engine:** - Tạo object *F* từ hàm *OrdinaryFunctionCreate*(Function.prototype, Parameters, FunctionBody). - Gán tên cho function thông qua hàm *SetFunctionName*(F, name). - Gắn constructor thông qua hàm *MakeConstructor*(F). - Trả về *F.* Ở đây chúng ta sẽ có 3 hàm internal khác, *OrdinaryFunctionCreate*, *SetFunctionName*, *MakeConstructor*. - *MakeConstructor*: Hàm này dùng để tạo constructor khi chúng ta tạo object thông qua từ khóa new. Vì phạm vi bài viết nằm ở function bình thường thôi nên phần này ta bỏ qua. - *SetFunctionName*: Hàm này tạo một property tên "name" và gắn <Name> vào. Đây là lý do khi chúng ta gọi *foo.name* thì nó trả về "foo" - *OrdinaryFunctionCreate*: Hàm này sẽ tạo ra một object với prototype là Function, object này cũng sẽ gồm các thông tin khác về <Parameters>, <FunctionBody>. ## OrdinaryFunctionCreate Lật tẩy tiếp hàm *OrdinaryFunctionCreate* chúng ta sẽ được thuật toán như sau: - Tạo object F từ hàm OrdinaryObjectCreate(Function.prototype) - Gán nội dung cho phương thức F.[[Call]]. *// -> Đây là điểm khác biệt giữa object và một function object* - Trả về F Về *OrdinaryObjectCreate*, hàm này sẽ trả về một object bình thường với prototype truyền vào. Do ở đây JS dùng Function.protype nên sẽ có phương thức call từ prototype. Và tất nhiên foo.call() là từ prototype, không phải phương thức Call trong function object F. Ok, đến đây chúng ta đã biết cách mà JS implements phương thức **Call** như nào. Vậy thì làm sao để JS thực thi một function? ## Function Call Đây là một Left-Hand-Side Expressions, là cách để thực thi một function: vd foo(). Nào, chúng ta lại lật tài liệu của ECMAScript xem có gì khi chúng ta gọi hàm. **Thuật toán của function call: **với func là hàm cần thực thi - Nếu Type(func) != Object -> ném **TypeError** exception - Nếu func chưa implements phương thức Call -> ném **TypeError** exception - Trả về *func.Call()* Nếu function này hợp lệ thì phương thức **Call** sẽ được gọi và chạy hàm. Vì chi tiết về **Call** quá dài nên mình sẽ không nói chi tiết thêm, nếu mọi người quan tâm thì mình sẽ làm về **Call** . Và đó cũng là lý do tại sao khi gọi hàm bậy bạ trên một kiểu dữ liệu khác hoặc một object bình thường thì chúng ta ăn ngay exception "hello"() //-> Uncaught TypeError: "hello" is not a function let obj = {}; obj() //-> Uncaught TypeError: obj is not a function Tới đây chúng ta có thể nói rằng: Đúng thật là function là một object nhưng một object thì không thể được thực thi như một function. *Cảm ơn mọi người đã theo dõi.*