[英]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
原型是 JS 實現繼承的一種方式,因為Emp(name)
是基函數,原型被引用到同一個函數本身。 是這樣的嗎?
employee.constructor
和employee.prototype.constructor
有什么不同?
為什么jack.prototype
undefined
? 即,如果它是從函數Emp(name)
繼承的,為什么它不引用該函數?
我怎樣才能清楚地預測(無需在控制台中輸入)原型或構造函數或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()
對象來完成的。 現在它基本上是一個名為Title
的Foo
對象。 這不是我們想要的,因為現在我們無法訪問Title
中的消息部分。 我們可以通過將構造函數重置為Title
來使其正確擴展Foo()
Title.prototype.constructor = Title;
現在我們又面臨一個問題。 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
之類的關鍵字,而是使用prototype
和constructor
。
如果您想創建一個 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.