2017-03-28 39 views
1

我有下面的代碼片段代表了這個問題的答案:的Javascript原型構造解釋

您正在創建一個名爲類顧問,從Employee類爲MustInherit。 Consultant類必須修改繼承的PayEmployee方法。未來的顧問實例必須用重寫的方法創建。

function Employee() {} 

Employee.prototype.PayEmployee = function (){ 
    alert(‘Hi there!’); 
} 

function Consultant() { 
    Employee.call(this);       // X 
} 
Consultant.prototype = new Employee(); 
Consultant.prototype.constructor = Consultant; // X 

Consultant.prototype.PayEmployee = function() 
{ 
    alert(‘Pay Consultant’); 
} 

我真的不能把我的頭圍繞這個。爲什麼標有「X」的行是必需的?我不能不寫這些代碼而沒有這些代碼,它會完全一樣嗎?根據我的測試中,它也就沒有什麼區別:

有人能解釋我是什麼這兩個代碼之間的區別是,什麼我爲什麼要使用一個比其他?

在此先感謝

+0

*目前*他們可能沒有任何區別。嘗試在基類'員工'上定義其他屬性和方法,但不是'顧問',我相信你會發現一些東西:) – Jokester

回答

3

不能我只是寫沒有這些行的代碼,這將是完全一樣的?

不,你在一般情況下確實需要它們兩個。是的,在某些特定情況下,您可以在沒有其中一個或兩個的情況下離開。

首先,請注意,該代碼在其中存在常見錯誤。這是不正確的:

Consultant.prototype = new Employee(); // INCORRECT 

它應該是:

Consultant.prototype = Object.create(Employee.prototype); 

new Employee做兩個不同的東西:

  1. 它創建一個使用Employee.prototype作爲其原型對象

  2. 它運行中的代碼10,他的工作就是通過設置屬性和這樣的this

初始化的Employee實例,但我們不希望出現這種情況。第二部分呢。 Consultant.prototype不是Employee實例,它是一個對象,該對象將變得經由new Consultant創建的對象的原型(這將是Employee一個實例,並且還Consultant)。 (這是JavaScript的構造函數和prototype財產標準原型繼承離開。你可以做標準的原型繼承在JavaScript中,但是當你做什麼,你不使用構造函數中,prototype財產,或new

Object.create只是做了這兩個任務中的第一個:使用Employee.prototype作爲原型創建一個對象。所以我們最終得到一個Consultant.prototype上的對象,它使用Employee.prototype作爲它的原型,但是沒有調用Employee,並讓它運行其每個實例的初始化代碼。 (我們稍後會在第二行中標註X.)

但是爲什麼我們不想Employee執行其每個實例的初始化?因爲Consultant.prototype不是Employee實例,所以將它初始化爲一個實例是沒有意義的。有一件事:如果Employee需要使用參數來初始化它返回的對象呢?構建Consultant.prototype時我們會通過什麼?我們沒有任何特定於實例的信息在此時傳遞它。

因此,相反,我們只是做#1以上(通過Object.create),並留下#2(主叫Employee),直到我們構造一個實例,通過(下文)調用從ConsultantEmployee.call(this)


要將標線:

Employee.call(this); 

這條線是必不可少給Employee的機會做創建的實例的初始化。沒有它,Employee還沒有機會完成它的工作:初始化Employee部分的實例。例如,假設Employee看起來是這樣的:

function Employee() { 
    this.paySchedule = "biweekly"; 
} 

如果沒有ConsultantEmployee.call(this);線,該屬性會從通過new Consultant創建一個實例會丟失。

你的第二個標記行:

Consultant.prototype.constructor = Consultant; 

我們需要這樣做,因爲Consultant.prototypeconstructor屬性應該指向Consultant,但如果我們不這樣做,它會指向Employee

它並沒有太多用處,因爲儘管constructor在規範中被定義爲默認設置,但在規範中沒有用於使用它。在ES2015中改變了(又名「ES6」):現在,有時使用constructor屬性(例如,在promise中)。


下面是一個例子,說明Employee有參數的情況。我也切換到PayEmployeepayEmployee在與JavaScript中的壓倒性的慣例,唯一的構造函數最初皚皚保持:

function Employee(name, title) { 
 
    this.name = name; 
 
    this.title = title; 
 
} 
 

 
Employee.prototype.payEmployee = function() { 
 
    console.log("Time to pay " + this.name + " (" + this.title + ")"); 
 
}; 
 

 
function Consultant(name) { 
 
    Employee.call(this, name, "Consultant"); 
 
} 
 
Consultant.prototype = Object.create(Employee.prototype); 
 
Consultant.prototype.constructor = Consultant; 
 

 
Consultant.prototype.payEmployee = function() { 
 
    console.log("Time to pay " + this.name + " (" + this.title + ") -- remember, you're paying the gross, consultants handle their own tax."); 
 
}; 
 

 
var e = new Employee("Joe Bloggs", "Engineer"); 
 
e.payEmployee(); 
 

 
var c = new Consultant("John Smith"); 
 
c.payEmployee();


最後,我會如果我沒有指出ES2015的話,我們有class的語法,如果需要的話可以在較老的環境下進行轉換:

class Employee { 
 
    constructor(name, title) { 
 
     this.name = name; 
 
     this.title = title; 
 
    } 
 
    payEmployee() { 
 
     console.log("Time to pay " + this.name + " (" + this.title + ")"); 
 
    } 
 
} 
 

 
class Consultant extends Employee { 
 
    constructor(name) { 
 
     super(name, "Consultant"); 
 
    } 
 
    payEmployee() { 
 
     console.log("Time to pay " + this.name + " (" + this.title + ") -- remember, you're paying the gross, consultants handle their own tax."); 
 
    } 
 
} 
 

 
const e = new Employee("Joe Bloggs", "Engineer"); 
 
e.payEmployee(); 
 

 
const c = new Consultant("John Smith"); 
 
c.payEmployee();


你問到Consultant.prottype = new Employee();是否永遠是錯的。讓我們一版本使用new Employee是什麼,以及消除你與X標誌着兩行代碼,並加入到員工的一個功能,它看起來像它應該工作,但最終導致了種種困惑:

function Employee() { 
 
    this.staff = []; 
 
} 
 

 
Employee.prototype.payEmployee = function() { 
 
    console.log("Paying employee"); 
 
}; 
 

 
function Consultant(name) { 
 
} 
 
Consultant.prototype = new Employee(); 
 

 
Consultant.prototype.payEmployee = function() { 
 
    console.log("Time to pay " + this.name + " (" + this.title + ") -- remember, you're paying the gross, consultants handle their own tax."); 
 
}; 
 

 
var jill1 = new Employee(); 
 
jill1.staff.push("Bob"); 
 
jill1.staff.push("Jane"); 
 

 
var john1 = new Employee(); 
 
john1.staff.push("Russell"); 
 
john1.staff.push("Mohammed"); 
 

 
console.log("Jill's staff (1): " + jill1.staff.join(", ")); 
 
console.log("John's staff (1): " + john1.staff.join(", ")); 
 

 
// so far so good, but what if we use Consultant instead? 
 

 
var jill2 = new Consultant(); 
 
jill2.staff.push("Bob"); 
 
jill2.staff.push("Jane"); 
 

 
var john2 = new Consultant(); 
 
john2.staff.push("Russell"); 
 
john2.staff.push("Mohammed"); 
 

 
console.log("Jill's staff (2): " + jill2.staff.join(", ")); 
 
console.log("John's staff (2): " + john2.staff.join(", ")); 
 

 
// ??? how is it they both have all four staff?!

的問題是,staff陣列上Consultant.prototype,不是每個實例,所以無論我們使用的情況下,我們總是訪問相同的陣列。

這就是爲什麼我們不使用用於初始化實例功能(例如,構造函數)來初始化一個構造函數的prototype屬性的對象。

我應該注意到,在標準原型繼承中,實際創建一個實例成爲另一個實例的原型將是常見和正常的。但是JavaScript的構造函數和它們的屬性並不是標準的原型繼承,它們是基於類的OOP和原型OOP的混合。你可以在JavaScript中完成純原型的OOP(還有很多),但是當你這樣做時,你不使用構造函數,它們的prototype屬性或new。您使用工廠功能和Object.create(通常)。

+0

非常感謝你的偉大的解釋!感謝你,我現在明白這背後的邏輯。 我不明白的是,如果這條線 'Consultant.prototype = new Employee();' 是真的總是錯誤的,或者如果這是可用於像沒有參數的問題代碼的情況。 當我保留這一行,並且不要用你建議的行代替它時,我仍然可以訪問你在答案中提到的'this.paySchedule'。這是爲什麼? 感謝您的解釋! –

+1

@ T.Bob:如果'員工'不接受任何參數或做任何其他假設它正在完成其主要工作(設置*實例*)的任何其他事情,那麼以機械方面,使用「新員工」功能。但它不正確,'Employee'很容易開始不做任何特定於實例的事情,但隨後稍後再編輯以執行特定於實例的工作。所以最好從一開始就做好。 –

+1

@ T.Bob:Re'paySchedule':如果你使用'new Employee'來設置'Constructor。原型「,你將在'Constructor.prototype'上得到'paySchedule' - 因此你會在所有的'Constructor'實例中看到它。如果你在'Constructor'中也有'Employee.call(this)',那麼'Employee'也會將'paySchedule'加入到實例中。我將添加一個示例,說明如果使用'new Employee'而不是'Object.create(Employee.prototype)'*和*'Employee.call(this)'關閉以及它如何可能會出錯,會發生什麼情況。 :-) –