Không thể giải quyết bí ẩn các chức năng trong Javascript


16

Tôi đang cố gắng để hiểu đằng sau những bức màn của Javascript và bị mắc kẹt trong việc tìm hiểu việc tạo ra các đối tượng được xây dựng, đối tượngchức năng đặc biệt và mối quan hệ giữa chúng.

Khi tôi đọc rằng tất cả các đối tượng được xây dựng như Array, String, v.v. đều là phần mở rộng (được kế thừa) từ Object, tôi cho rằng Object là đối tượng được xây dựng đầu tiên được tạo và phần còn lại của các đối tượng được thừa hưởng từ nó. Nhưng nó không có nghĩa gì khi bạn biết rằng các Đối tượng chỉ có thể được tạo bởi các hàm nhưng sau đó các hàm cũng không là gì ngoài các đối tượng của Hàm. Nó bắt đầu giống như tình trạng khó xử của gà mái và gà.

Một điều cực kỳ khó hiểu khác là, nếu tôi console.log(Function.prototype)in một chức năng nhưng khi tôi in console.log(Object.prototype)nó sẽ in một đối tượng. Tại sao Function.prototypemột chức năng khi nó có nghĩa là một đối tượng?

Ngoài ra, theo tài liệu của Mozilla, mỗi javascript functionlà phần mở rộng của Functionđối tượng nhưng khi bạn console.log(Function.prototype.constructor)lại là một hàm. Bây giờ làm thế nào bạn có thể sử dụng một cái gì đó để tự tạo ra nó (Mind = blown).

Điều cuối cùng, Function.prototypelà một hàm nhưng tôi có thể truy cập constructorhàm bằng cách sử dụng Function.prototype.constructorđó có nghĩa Function.prototypelà một hàm trả về prototypeđối tượng


vì một hàm là một đối tượng, điều đó có nghĩa là Function.prototypecó thể là một hàm và có các trường bên trong. Vì vậy, không có bạn không thực hiện chức năng nguyên mẫu khi đi qua cấu trúc của nó. Cuối cùng hãy nhớ rằng có một công cụ diễn giải Javascript, vì vậy Object và Function có thể được tạo trong công cụ chứ không phải từ Javascript và tham chiếu đặc biệt như Function.prototypeObject.prototypecó thể được công cụ hiểu theo cách đặc biệt.
Walfrat

1
Trừ khi bạn đang tìm cách triển khai trình biên dịch JavaScript tuân thủ tiêu chuẩn, bạn thực sự không cần phải lo lắng về công cụ này. Nếu bạn đang tìm kiếm để làm một cái gì đó hữu ích được thực hiện, bạn không tham gia khóa học.
Jared Smith

5
FYI cụm từ thông thường trong tiếng Anh có nghĩa là "vấn đề nan giải của gà và gà" là "vấn đề gà và trứng", cụ thể là "xuất hiện trước, gà hay trứng?" (Tất nhiên câu trả lời là quả trứng. Động vật có trứng đã tồn tại hàng triệu năm trước khi có gà.)
Eric Lippert

Câu trả lời:


32

Tôi đang cố gắng để hiểu đằng sau những bức màn của Javascript và bị mắc kẹt trong việc tìm hiểu việc tạo ra các đối tượng được xây dựng, đối tượng và chức năng đặc biệt và mối quan hệ giữa chúng.

Nó rất phức tạp, dễ hiểu lầm và rất nhiều cuốn sách Javascript mới bắt đầu đã sai, vì vậy đừng tin vào mọi thứ bạn đọc.

Tôi là một trong những người triển khai công cụ JS của Microsoft vào những năm 1990 và trong ủy ban tiêu chuẩn hóa, và tôi đã phạm một số sai lầm khi kết hợp câu trả lời này. (Mặc dù tôi đã không làm việc này trong hơn 15 năm nhưng có lẽ tôi có thể được tha thứ.) Đó là công cụ khó khăn. Nhưng một khi bạn hiểu kế thừa nguyên mẫu, tất cả đều có ý nghĩa.

Khi tôi đọc rằng tất cả các đối tượng được xây dựng như Array, String, v.v. đều là phần mở rộng (được kế thừa) từ Object, tôi cho rằng Object là đối tượng được xây dựng đầu tiên được tạo và phần còn lại của các đối tượng được thừa hưởng từ nó.

Bắt đầu bằng cách vứt bỏ mọi thứ bạn biết về kế thừa dựa trên lớp. JS sử dụng kế thừa dựa trên nguyên mẫu.

Tiếp theo, hãy chắc chắn rằng bạn có một định nghĩa rất rõ ràng trong đầu về "thừa kế" nghĩa là gì. Mọi người đã sử dụng các ngôn ngữ OO như C # hoặc Java hoặc C ++ nghĩ rằng kế thừa có nghĩa là phân nhóm, nhưng thừa kế không có nghĩa là phân nhóm. Kế thừa có nghĩa là các thành viên của một điều cũng là thành viên của một điều khác . Điều đó không nhất thiết có nghĩa là có một mối quan hệ phụ giữa những điều đó! Vì vậy, nhiều hiểu lầm trong lý thuyết loại là kết quả của việc mọi người không nhận ra rằng có một sự khác biệt.

Nhưng nó không có nghĩa gì khi bạn biết rằng các Đối tượng chỉ có thể được tạo bởi các hàm nhưng sau đó các hàm cũng không là gì ngoài các đối tượng của Hàm.

Điều này chỉ đơn giản là sai. Một số đối tượng không được tạo bằng cách gọi new Fcho một số chức năng F. Một số đối tượng được tạo bởi thời gian chạy JS hoàn toàn không có gì. Có những quả trứng không được đẻ bởi bất kỳ con gà nào . Chúng chỉ được tạo bởi thời gian chạy khi nó khởi động.

Chúng ta hãy nói các quy tắc là gì và có thể sẽ giúp.

  • Mỗi đối tượng có một đối tượng nguyên mẫu.
  • Trong một số trường hợp nguyên mẫu có thể được null.
  • Nếu bạn truy cập một thành viên trên một đối tượng và đối tượng không có thành viên đó, thì đối tượng sẽ chuyển sang nguyên mẫu của nó hoặc dừng lại nếu nguyên mẫu là null.
  • Thành prototypeviên của một đối tượng thường không phải là nguyên mẫu của đối tượng.
  • Thay vào đó, prototypethành viên của một đối tượng hàm F là đối tượng sẽ trở thành nguyên mẫu của đối tượng được tạo bởi new F().
  • Trong một số triển khai, các trường hợp có được một __proto__thành viên thực sự đưa ra nguyên mẫu của họ. (Điều này hiện không được chấp nhận. Đừng dựa vào nó.)
  • Các đối tượng chức năng có được một đối tượng mặc định hoàn toàn mới được gán cho prototypekhi chúng được tạo.
  • Nguyên mẫu của một đối tượng chức năng là, tất nhiên Function.prototype.

Hãy tổng hợp lại.

  • Nguyên mẫu của ObjectFunction.prototype
  • Object.prototype là đối tượng nguyên mẫu đối tượng.
  • Nguyên mẫu của Object.prototypenull
  • Nguyên mẫu của FunctionFunction.prototype- đây là một trong những tình huống hiếm hoi mà Function.prototypethực sự là nguyên mẫu của Function!
  • Function.prototype là đối tượng nguyên mẫu hàm.
  • Nguyên mẫu của Function.prototypeObject.prototype

Giả sử chúng ta tạo một hàm Foo.

  • Nguyên mẫu của FooFunction.prototype.
  • Foo.prototype là đối tượng nguyên mẫu Foo.
  • Nguyên mẫu của Foo.prototypeObject.prototype.

Giả sử chúng ta nói new Foo()

  • Nguyên mẫu của đối tượng mới là Foo.prototype

Hãy chắc chắn rằng có ý nghĩa. Hãy vẽ nó. Hình bầu dục là trường hợp đối tượng. Các cạnh có __proto__nghĩa là "nguyên mẫu của" hoặc prototypecó nghĩa là " prototypetài sản của".

nhập mô tả hình ảnh ở đây

Tất cả thời gian chạy phải làm là tạo tất cả các đối tượng đó và gán các thuộc tính khác nhau của chúng cho phù hợp. Tôi chắc rằng bạn có thể thấy điều đó sẽ được thực hiện như thế nào.

Bây giờ hãy xem một ví dụ kiểm tra kiến ​​thức của bạn.

function Car(){ }
var honda = new Car();
print(honda instanceof Car);
print(honda.constructor == Car);

Cái này in cái gì?

Chà, instanceofcó nghĩa là gì? honda instanceof Carcó nghĩa là " Car.prototypebằng với bất kỳ đối tượng nào trên hondachuỗi nguyên mẫu?"

Vâng, đúng vậy. hondaNguyên mẫu là Car.prototypevậy, chúng ta đã hoàn thành. Điều này in đúng.

Còn cái thứ hai thì sao?

honda.constructorkhông tồn tại vì vậy chúng tôi tham khảo nguyên mẫu, đó là Car.prototype. Khi Car.prototypeđối tượng được tạo, nó sẽ tự động được cung cấp một thuộc tính constructorbằng Car, vì vậy điều này là đúng.

Bây giờ thì sao?

var Animal = new Object();
function Reptile(){ }
Reptile.prototype = Animal;
var lizard = new Reptile();
print(lizard instanceof Reptile);
print(lizard.constructor == Reptile);

Chương trình này in cái gì?

Một lần nữa, lizard instanceof Reptilecó nghĩa là "có Reptile.prototypebằng với bất kỳ đối tượng nào trên lizardchuỗi nguyên mẫu không?"

Vâng, đúng vậy. lizardNguyên mẫu là Reptile.prototypevậy, chúng ta đã hoàn thành. Điều này in đúng.

Bây giờ thì sao

print(lizard.constructor == Reptile);

Bạn có thể nghĩ rằng điều này cũng in đúng, vì lizardđã được xây dựng new Reptilenhưng bạn sẽ sai. Lý do nó ra.

  • Liệu lizardcó một constructorbất động sản? Không. Vì vậy, chúng tôi nhìn vào nguyên mẫu.
  • Nguyên mẫu của lizardReptile.prototype, đó là Animal.
  • Liệu Animalcó một constructorbất động sản? Không. Vì vậy, chúng tôi nhìn vào nguyên mẫu của nó.
  • Nguyên mẫu của AnimalObject.prototype, và Object.prototype.constructorđược tạo bởi thời gian chạy và bằng Object.
  • Vì vậy, điều này in sai.

Đáng lẽ chúng ta nên nói Reptile.prototype.constructor = Reptile;vào một lúc nào đó trong đó, nhưng chúng ta không nhớ!

Hãy chắc chắn rằng tất cả có ý nghĩa với bạn. Vẽ một số hộp và mũi tên nếu vẫn còn khó hiểu.

Một điều cực kỳ khó hiểu khác là, nếu tôi console.log(Function.prototype)in một chức năng nhưng khi tôi in console.log(Object.prototype)nó sẽ in một đối tượng. Tại sao Function.prototypemột chức năng khi nó có nghĩa là một đối tượng?

Nguyên mẫu hàm được định nghĩa là một hàm mà khi được gọi sẽ trả về undefined. Chúng tôi đã biết đó Function.prototypeFunctionnguyên mẫu, đủ kỳ lạ. Vì vậy, Function.prototype()là hợp pháp, và khi bạn làm điều đó, bạn sẽ nhận undefinedlại. Vì vậy, nó là một chức năng.

Các Objectnguyên mẫu không có tài sản này; nó không thể gọi được Nó chỉ là một vật thể.

khi bạn console.log(Function.prototype.constructor)lại là một chức năng.

Function.prototype.constructorchỉ là Function, rõ ràng. Và Functionlà một chức năng.

Bây giờ làm thế nào bạn có thể sử dụng một cái gì đó để tự tạo ra nó (Mind = blown).

Bạn đang suy nghĩ quá mức này . Tất cả những gì được yêu cầu là thời gian chạy tạo ra một loạt các đối tượng khi nó khởi động. Các đối tượng chỉ là các bảng tra cứu liên kết các chuỗi với các đối tượng. Khi thời gian chạy khởi động, tất cả những gì phải làm là tạo ra một vài đối tượng chục trống, và sau đó bắt đầu giao prototype, __proto__, constructor, và vân vân thuộc tính của từng đối tượng cho đến khi họ làm cho đồ thị mà họ cần để thực hiện.

Sẽ rất hữu ích nếu bạn lấy sơ đồ mà tôi đã đưa cho bạn ở trên và thêm constructorcác cạnh vào nó. Bạn sẽ nhanh chóng thấy rằng đây là một biểu đồ đối tượng rất đơn giản và thời gian chạy sẽ không có vấn đề gì khi tạo nó.

Một bài tập tốt sẽ là tự làm. Ở đây, tôi sẽ bắt đầu với bạn. Chúng ta sẽ sử dụng my__proto__có nghĩa là "đối tượng nguyên mẫu của" và myprototypecó nghĩa là "thuộc tính nguyên mẫu của".

var myobjectprototype = new Object();
var myfunctionprototype = new Object();
myfunctionprototype.my__proto__ = myobjectprototype;
var myobject = new Object();
myobject.myprototype = myobjectprototype;

Và như thế. Bạn có thể điền vào phần còn lại của chương trình để xây dựng một tập hợp các đối tượng có cấu trúc liên kết tương tự như các đối tượng tích hợp Javascript "thực" không? Nếu bạn làm như vậy, bạn sẽ thấy nó cực kỳ dễ dàng.

Các đối tượng trong JavaScript chỉ là các bảng tra cứu liên kết các chuỗi với các đối tượng khác . Đó là nó! Không có phép thuật ở đây. Bạn đang bị trói buộc trong các nút thắt bởi vì bạn đang tưởng tượng các ràng buộc không thực sự tồn tại, giống như mọi đối tượng phải được tạo bởi một nhà xây dựng.

Các hàm chỉ là các đối tượng có khả năng bổ sung: được gọi. Vì vậy, đi qua chương trình mô phỏng nhỏ của bạn và thêm một thuộc .mycallabletính cho mọi đối tượng cho biết liệu nó có thể gọi được hay không. Nó đơn giản như vậy.


9
Cuối cùng, một lời giải thích ngắn gọn, súc tích, dễ hiểu về JavaScript! Thông minh! Làm thế nào bất cứ ai trong chúng ta có thể bị nhầm lẫn? :) Mặc dù trong tất cả sự nghiêm túc, phần cuối cùng về các đối tượng là bảng tra cứu thực sự là chìa khóa. Có một phương pháp cho sự điên rồ --- nhưng nó vẫn là sự điên rồ ...
Greg Burghardt

4
@GregBurghardt: Ban đầu tôi đồng ý nó có vẻ phức tạp, nhưng sự phức tạp là hậu quả của các quy tắc đơn giản. Mọi đối tượng đều có a __proto__. Các __proto__nguyên mẫu đối tượng là null. Các __proto__của new X()X.prototype. Tất cả các đối tượng hàm có nguyên mẫu hàm __proto__ngoại trừ chính nguyên mẫu hàm. ObjectFunctionnguyên mẫu hàm là các hàm. Các quy tắc này đều đơn giản và chúng xác định cấu trúc liên kết của biểu đồ của các đối tượng ban đầu.
Eric Lippert

6

Bạn đã có nhiều câu trả lời xuất sắc rồi, nhưng tôi chỉ muốn đưa ra một câu trả lời ngắn gọn và rõ ràng cho câu trả lời của bạn về cách tất cả những điều này hoạt động, và câu trả lời đó là:

MA THUẬT!!!

Thực sự, đó là nó.

Những người thực hiện các công cụ thực thi ECMAScript phải thực hiện các quy tắc của ECMAScript, nhưng không tuân thủ chúng trong quá trình thực hiện.

Đặc tả ECMAScript nói rằng A thừa hưởng từ B nhưng B là một thể hiện của A? Không vấn đề gì! Tạo A đầu tiên với một con trỏ nguyên mẫu của NULL, tạo B làm ví dụ của A, sau đó sửa con trỏ nguyên mẫu của A để trỏ đến B sau đó. Dễ như ăn bánh.

Bạn nói, nhưng chờ đã, không có cách nào để thay đổi con trỏ nguyên mẫu trong ECMAScript! Nhưng, đây là điều: mã này không chạy trên công cụ ECMAScript, mã này là công cụ ECMAScript. Nó không có quyền truy cập vào bên trong các đối tượng mà mã ECMAScript chạy trên công cụ không có. Tóm lại: nó có thể làm bất cứ điều gì nó muốn.

Nhân tiện, nếu bạn thực sự muốn, bạn chỉ phải thực hiện việc này một lần: sau đó, ví dụ, bạn có thể kết xuất bộ nhớ trong của mình và tải kết xuất này mỗi khi bạn khởi động công cụ ECMAScript của mình.

Lưu ý rằng tất cả những điều này vẫn được áp dụng, ngay cả khi chính công cụ ECMAScript được viết bằng ECMAScript (ví dụ như thực tế đối với Mozilla Narcissus chẳng hạn). Ngay cả khi đó, mã ECMAScript thực hiện công cụ vẫn có quyền truy cập đầy đủ vào công cụ mà nó đang triển khai , mặc dù tất nhiên nó không có quyền truy cập vào công cụ mà nó đang chạy .


3

Từ thông số kỹ thuật ECMA 1

ECMAScript không chứa các lớp thích hợp như các lớp trong C ++, Smalltalk hoặc Java, nhưng thay vào đó, hỗ trợ các hàm tạo tạo đối tượng bằng cách thực thi mã phân bổ lưu trữ cho các đối tượng và khởi tạo tất cả hoặc một phần của chúng bằng cách gán giá trị ban đầu cho các thuộc tính của chúng. Tất cả các hàm bao gồm các hàm tạo là các đối tượng, nhưng không phải tất cả các đối tượng đều là các hàm tạo.

Tôi không thấy làm thế nào nó có thể rõ ràng hơn nữa !!! </sarcasm>

Xa hơn nữa chúng ta thấy:

Nguyên mẫu Một nguyên mẫu là một đối tượng được sử dụng để thực hiện kế thừa cấu trúc, trạng thái và hành vi trong ECMAScript. Khi một hàm tạo tạo một đối tượng, đối tượng đó hoàn toàn tham chiếu nguyên mẫu liên kết của hàm tạo với mục đích giải quyết các tham chiếu thuộc tính. Nguyên mẫu liên quan của hàm tạo có thể được tham chiếu bởi biểu thức constructor.prototype của chương trình và các thuộc tính được thêm vào nguyên mẫu của đối tượng được chia sẻ, thông qua kế thừa, bởi tất cả các đối tượng chia sẻ nguyên mẫu.

Vì vậy, chúng ta có thể thấy rằng một nguyên mẫu là một đối tượng, nhưng không nhất thiết là một đối tượng chức năng.

Ngoài ra, chúng tôi có chuẩn độ thú vị này

http: //www.ecma-i Intl.org/ecma-262/8.0/index.html#sec-object-objects

Hàm tạo đối tượng là đối tượng nội tại% Object% và giá trị ban đầu của thuộc tính Object của đối tượng toàn cục.

Hàm tạo Hàm là đối tượng nội tại% Hàm% và giá trị ban đầu của thuộc tính Hàm của đối tượng toàn cục.


Bây giờ nó làm. ECMA6 cho phép bạn tạo các lớp và khởi tạo các đối tượng từ chúng.
ncmathsadist

2
@ncmathsadist Các lớp ES6 chỉ là một cú pháp cú pháp, ngữ nghĩa là như nhau.
Hamza Fatmi

1
sarcasmBiệt danh của bạn nếu không, văn bản này thực sự khá mờ đối với người mới bắt đầu.
Robert Harvey

đúng, tôi sẽ thêm vào sau, cần phải thực hiện một số hoạt động đào
Ewan

1
erm? để chỉ ra rằng nó không rõ ràng từ tài liệu
Ewan

1

Các loại sau bao gồm mọi giá trị trong JavaScript:

  • boolean
  • number
  • undefined(bao gồm giá trị đơn undefined)
  • string
  • symbol ("những thứ" độc đáo trừu tượng được so sánh bằng tham chiếu)
  • object

Mọi đối tượng (tức là mọi thứ) trong JavaScript đều có một nguyên mẫu, đó là một loại đối tượng.

Nguyên mẫu chứa các hàm, cũng là một loại đối tượng 1 .

Các đối tượng cũng có một hàm tạo, là một hàm và do đó là một loại đối tượng.

lồng nhau

Tất cả đều là đệ quy, nhưng việc triển khai có thể thực hiện tự động bởi vì, không giống như mã JavaScript, nó có thể tạo các đối tượng mà không phải gọi các hàm JavaScript (vì các đối tượng chỉ là bộ nhớ mà việc triển khai thực hiện).

Hầu hết các hệ thống đối tượng trong nhiều ngôn ngữ gõ động là vòng tròn 2 như thế này. Ví dụ, trong Python, các lớp là các đối tượng và lớp của các lớp là type, do đó, đây typelà một thể hiện của chính nó.

Ý tưởng tốt nhất là chỉ sử dụng các công cụ mà ngôn ngữ cung cấp, và không suy nghĩ quá nhiều về cách họ đến đó.

1 Hàm khá đặc biệt vì chúng có thể gọi được và chúng là các giá trị duy nhất có thể chứa dữ liệu mờ (cơ thể của chúng và có thể là một bao đóng).

2 Nó thực sự giống như một dải băng bị tra tấn, phân nhánh uốn cong về phía sau, nhưng "hình tròn" là đủ gần.

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.