簡體   English   中英

試圖理解 JavaScript 中原型和構造函數的區別

[英]Trying to understand the difference between prototype and constructor in JavaScript

我是 JavaScript 新手,並試圖理解這個概念。 我已經閱讀了很多關於原型和構造函數的文章,但無論我走到哪里,我都會感到困惑。

當人們同時談論構造函數和原型時,就會產生混淆。

在下面的例子中

var employee = function Emp(name) {
    this.name = name;
}
var jack = new employee("Jack Dwain");

employee.constructor // gives Function()

employee.prototype // gives Emp {}

employee.prototype.constructor // gives Emp(name)

jack.constructor // gives Emp(name)

jack.prototype // gives undefined
  1. 原型是 JS 實現繼承的一種方式,因為Emp(name)是基函數,原型被引用到同一個函數本身。 是這樣的嗎?

  2. employee.constructoremployee.prototype.constructor有什么不同?

  3. 為什么jack.prototype undefined 即,如果它是從函數Emp(name)繼承的,為什么它不引用該函數?

  4. 我怎樣才能清楚地預測(無需在控制台中輸入)原型或構造函數或prototype.constructor ......會產生什么?

如果您習慣於在其他 OOP 語言中輕松擴展對象,那么很難圍繞這個概念來思考,但我會盡我所能解釋這些的用途和什么是什么。 我假設您熟悉其他 OOP 語言。 如我錯了請糾正我。

所有函數都有原型Function() 它們繼承了Function的所有基本功能,例如toString()valueOf()

然后是構造函數。 這就是你用來初始化對象的東西。

p = new Foo();

所以在這種情況下,我們有兩件事。

  • Function為原型的function Foo (Foo)
  • Foo()作為構造函數的Function對象(p)

(還跟着我?)

Foo()構造函數可以覆蓋Function構造函數的一些基本功能,或者保持原樣並充分利用它。

如果你熟悉 OOP 原理,原型是基類,構造函數是你當前的類。 在 OOP 中,上面將是class Foo extends Function

您還可以從原型和構造函數的整個設置開始繼承,在共享功能的同時制作更復雜的對象。

例如這個:

// Make an object initialiser extending Function. In OOP `class Foo extends Function`

function Foo(bar) {
    this.baz = bar;
}
Foo.prototype.append = function(what) {
    this.baz += " " + what;
};
Foo.prototype.get() {
    return this.baz
}

現在讓我們說我們想要不同的方法來讓baz離開那里。 一種用於控制台日志記錄,另一種用於將其放在標題欄上。 我們可以為我們的類Foo做一件大事,但我們不這樣做,因為我們需要用為不同實現而制作的新類做完全不同的事情。 他們唯一需要分享的是baz項目和 setter 和 getter。

所以我們需要擴展它以使用 OOP 術語。 在 OOO 中,這將是所需的最終結果class Title extends Foo(){} 那么讓我們來看看如何到達那里。

function Title(what) {
    this.message = what;
}

此時Title函數如下所示:

  • 原型函數
  • 構造函數標題

因此,要使其擴展Foo ,我們需要更改原型。

Title.prototype = new Foo();
  • 原型 Foo
  • 構造函數 Foo

這是通過針對原型初始化一個新的Foo()對象來完成的。 現在它基本上是一個名為TitleFoo對象。 這不是我們想要的,因為現在我們無法訪問Title中的消息部分。 我們可以通過將構造函數重置為Title來使其正確擴展Foo()

Title.prototype.constructor = Title;
  • 原型 Foo
  • 構造函數標題

現在我們又面臨一個問題。 Foo的構造函數沒有被初始化,所以我們最終得到一個未定義的this.baz

為了解決這個問題,我們需要打電話給父母。 在 Java 中,您可以在 PHP $parent->__construct($vars)中使用super(vars)來做到這一點。

在Javascript中我們要修改Title類的構造函數來調用父對象的構造函數。

所以Title類的構造函數會變成

function Title(what) {
    Foo.call(this,what);
    this.message = what;
}

通過使用Function對象屬性Foo繼承,我們可以在Titl對象中初始化Foo對象。

現在你有了一個正確繼承的對象。

因此,它不像其他 OOP 語言那樣使用像extend之類的關鍵字,而是使用prototypeconstructor

如果您想創建一個 javascript對象,您可以簡單地聲明一個新對象並為其賦予屬性(我選擇將自己對象化):

var myself= {
    name:"Niddro",
    age:32
};

此方法允許您制作一個對象。 如果你想要的是一個描述一個人的原型,你可以在其中聲明幾個具有相同設置的人。 要創建原型,您可以使用構造函數,如下所示:

//Constructor
function generalNameForObject(param1, param2,...) {
    //Give the object some properties...
}

我有一個原型(配方),我想調用 person ,它應該包含屬性名稱和年齡,我將使用構造函數來制作它:

function person(name,age) {
    this.name=name;
    this.age=age;
}

上面的構造函數描述了我的人員對象的原型。

通過調用構造函數創建一個新人:

var myself = new person("Niddro",31);
var OP = new person("rajashekar thirumala",23);

一段時間過去了,我意識到我已經過生日了,所以我需要更改原型的屬性:

myself.age=32;

如果要在構造函數中添加屬性,則需要手動將其添加到構造函數中:

function person(name,age,rep) {
    this.name=name;
    this.age=age;
    this.reputation=rep;
}

相反,您可以通過執行以下操作向原型添加屬性(這里“原型”是一個實際命令,而不僅僅是一個名稱):

function person(name,age,rep) {
    this.name=name;
    this.age=age;
}
person.prototype.reputation=105;

請注意,這將為創建的所有對象添加 105 的聲譽。

我希望這能讓您對構造函數和原型之間的關系有更多的了解。

employee.constructor //給出 Function()

在 JavaScript 中,函數也是對象,可以使用它自己的構造函數來構造,即Function 因此,您可以編寫以下代碼來獲取 Function 的實例。

var employee2 = new Function('a', 'b', 'return a+b');

當您使用函數文字創建函數時也會發生同樣的情況,就像您的情況一樣。 並且這個對象的構造函數屬性也引用了相同的原生函數對象/類。

employee.prototype // 給 Emp {}

JavaScript 中的每個對象都有一個與之關聯的原型。 雖然只有函數對象原型可以通過.prototype直接訪問。 當您使用new關鍵字創建對象時,相同的原型會復制到其對象原型上。 這種復制主要負責繼承/擴展。 盡管原型被復制了,但它不像 Function 對象那樣直接可訪問。 它可以通過.__proto__以非標准方式使用。 以下代碼將返回 true。

jack.__proto__==employee.prototype

employee.prototype.constructor //給出 Emp(name)

正如Object.prototype.constructor的文檔中所說。 這將返回對創建實例原型的 Object 函數的引用。 這里引用的對象是employee.prototype 而not employee 這有點復雜,但對象employee.prototype 的原型是由函數 Emp(name) 創建的

jack.constructor //給出 Emp(name)

如上一點所述,這個對象原型是由函數 Emp(name) 在您使用 new Emp() 創建對象時創建的,

jack.prototype //給出未定義的

jack 不是一個函數對象,所以你不能像那樣訪問它的原型。 您可以訪問(不是標准方式)jack 的原型,如下所示。

jack.__proto__

構造函數:

function Foo(x) {
    this.x =x;
}

Foo是構造函數。 構造函數是一個函數。

有兩種方法可以使用這個構造函數Foo

“對象是通過在 new 表達式中使用構造函數創建的;例如,new Date(2009,11) 創建一個新的 Date 對象。在不使用 new 的情況下調用構造函數會產生依賴於構造函數的結果。例如,Date() 會生成一個字符串表示當前日期和時間,而不是對象。”

來源ECMA-262

這意味着如果Foo返回一些東西(通過return "somevalue"; ),那么typeof Foo()是返回值的類型。

另一方面,當你打電話

var o = new Foo();

JavaScript 實際上只是

var o = new Object();
o.[[Prototype]] = Foo.prototype;
Foo.call(o);

原型:

當您調用oa時,javascript 首先檢查a是否是對象o自己的屬性。 如果不是,javascript 將查找屬性鏈以a .

有關屬性鏈的更多信息,請查看mdn

構造函數的prototype屬性有一個非常強大的特性,它在類中不可用。 如果它有用,那就另當別論了。 構造函數的prototype屬性可以更改在原型鏈中鏈接到該原型的每個實例的屬性。

長話短說:

注意:這不是一個精確的定義,總結的目的只是為了讓你對構造函數和原型有一個感覺。

如果您使用帶有new關鍵字的構造函數,則構造函數和原型具有相似的目的,即使它們完全不同。 構造函數初始化對象的屬性,因此它提供屬性。 原型還通過屬性鏈(基於原型的繼承)提供屬性。

原型只是一個對象,構造函數是指向創建該對象的函數的指針。

構造函數是一個指針。 它指向創建您從中檢索構造函數的點的 Function()。 (即構造函數只是對 Function() 的引用,我們可以根據需要多次調用它。)

構造函數的用途之一是幫助您創建對象的復制副本。 由於構造函數屬性是對創建對象的函數的引用,因此只要您擁有該對象的副本,它將始終指向原始構造函數。 https://coderwall.com/p/qjzbig/understanding-constructor-and-prototype

使用對象構造器:通常,單獨創建的對象在許多情況下都是有限的。 它只創建一個對象。

有時我們喜歡有一個“對象類型”,它可以用來創建一個類型的許多對象。

創建“對象類型”的標准方法是使用對象構造函數:

function person(first, last, email ) {
  this.first_name = first;
  this.last_name = last;
  this.e_mail = email;
}
var myFather = new person("Ibm", "Muh", "ibm@gmail.com");

上面的函數(person)是一個對象構造函數。 一旦有了對象構造函數,就可以創建相同類型的新對象:

var myFather = new person("Sul", "Ahm", "sul@gmail.com");

每個 JavaScript 對象都有一個原型。 原型也是一個對象。

所有 JavaScript 對象都從它們的原型繼承它們的屬性和方法。

使用 2 種創建對象的方法來創建對象,即(1)對象字面量,或(2)使用 new Object(),從名為 Object.prototype 的原型繼承。 使用 new Date() 創建的對象繼承 Date.prototype。

Object.prototype 位於原型鏈的頂端。

所有 JavaScript 對象(日期、數組、正則表達式、函數......)都繼承自 Object.prototype。 https://www.w3schools.com/js/js_object_prototypes.asp

關鍵字原型是 Function() 對象的屬性。

原型的值是創建該特定對象的對象構造函數。 讓我們看幾個原型:

Boolean.prototype // returns Object Boolean
String.prototype // returns Object String with methods such as "toUpperCase"
Function.prototype // returns function() {} or function Empty() {}

創建原型:

創建對象原型的標准方法是使用對象構造函數:

function Person(first, last, age, eyecolor) {
  this.firstName = first;
  this.lastName = last;
  this.age = age;
}
var myFather = new Person("John", "Doe", 50);

使用構造函數,您可以使用 new 關鍵字從相同的原型創建新對象,如上所示:

構造函數是 Person 對象的原型。 使用大寫首字母命名構造函數被認為是一種很好的做法。

向原型添加屬性

不能像向現有對象添加新屬性一樣向原型添加新屬性,因為原型不是現有對象。

示例:Person.nationality = "English";

要將新屬性添加到原型,您必須將其添加到構造函數:

function Person(first, last, age, eyecolor) {
  this.firstName = first;
  this.lastName = last;
  this.age = age;
  this.eyeColor = eyecolor;
  this.nationality = "English";
}

所有本機和復雜對象都檢索到它們的原始構造函數,在這種情況下就是它們自己。 唯一的例外是 Function 原型,它返回創建它的 Function() 函數。 不要將它與構造函數混淆,因為它不一樣。

Function.prototype === Function.constructor // returns false, Function.constructor is function Function(){}

還有一個額外的屬性__proto__ ,它引用實例對象的內部 [[proto]] 屬性。 與 Function() 對象不同,每個 Object 都有一個__proto__ 不建議更新實例對象的原型,因為原型並不意味着在運行時更改(您應該能夠看到誰是誰的原型,否則您需要花費額外的計算來確保沒有循環引用)。

然而事實是,這種方法在許多情況下可能是錯誤的。 在 Javascript 中,當您將方法綁定到 this 關鍵字時,您只是將該方法提供給該特定實例,並且它與該構造函數的對象實例實際上沒有任何關系,就像靜態方法一樣。 請記住,函數是 Javascript 中的一等公民,我們可以像處理對象一樣處理它們,在這種情況下,我們只是向函數對象的實例添加屬性。 這只是故事的一部分,您還必須知道,通過 this 附加的任何方法都會為我們創建的每個新實例重新聲明,如果我們希望創建如此多的實例,這可能會對應用程序的內存使用產生負面影響。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM