我有以下代码片段代表这个问题的答案:
您正在创建一个名为顾问的类,该类必须从 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
做了两件不同的事情:
它创建一个使用
Employee.prototype
作为其原型的对象它在
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.prototype
的constructor
属性应该指向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
(通常)。