Javascript原型构造函数解释



我有以下代码片段代表这个问题的答案:

您正在创建一个名为顾问的类,该类必须从 Employee 类继承。顾问类必须修改继承的 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"的行是必要的?难道我不能在没有这些行的情况下编写代码,它会完全相同吗?根据我的测试,这不会有什么区别:

  • https://jsfiddle.net/yk84g8f4/1/
  • https://jsfiddle.net/66jxp9p9/

有人可以解释一下这两个代码之间的区别是什么,以及为什么我应该使用一个而不是另一个?

提前致谢

我不能在没有这些行的情况下编写代码,它会完全相同吗?

不,在一般情况下,您确实需要它们。是的,在某些特定情况下,您可以在没有其中一个或两个的情况下逃脱。

首先,请注意该代码中有一个常见错误。这是不正确的:

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

它应该是:

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

new Employee做了两件不同的事情:

  1. 它创建一个使用Employee.prototype作为其原型的对象

  2. 它在Employee中运行代码,其工作是通过设置属性等来初始化Employee的实例this

但我们还不想要第二部分。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),直到我们构建一个实例,通过从Consultant调用Employee.call(this)(更多内容见下文)。


到您标记的行:

Employee.call(this);

此行对于Employee有机会对正在创建的实例进行初始化至关重要。没有它,Employee就没有机会完成它的工作:初始化实例的Employee部分。例如,假设Employee如下所示:

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

如果没有Consultant中的Employee.call(this);行,则通过new Consultant创建的实例中将缺少该属性。

您的第二行标记:

Consultant.prototype.constructor = Consultant;

我们需要这样做,因为Consultant.prototypeconstructor属性应该指向Consultant,但是如果我们不这样做,它将指向Employee

它过去并不重要,因为尽管constructor在规范中定义了默认以这种方式设置,但规范中没有任何内容用于使用它。这在 ES2015(又名"ES6")中发生了变化:现在,有时使用constructor属性(例如,在 promise 中)。


以下示例说明了Employee具有参数的情况。我还PayEmployee切换到payEmployee,以符合 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 标记的两行,并向 Employee 添加一个功能,该功能似乎应该有效,但最终会导致各种混乱:

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的构造函数及其prototype属性不是标准的原型继承,它们是基于类的OOP和原型OOP的混合体。你可以在 JavaScript 中做纯粹的原型 OOP(很多人都这样做),但是当你这样做时,你不使用构造函数、它们的prototype属性或new。您使用工厂函数和Object.create(通常)。

最新更新