JavaScript 中几种不同的基于 prototype 继承方式的区别

JavaScript 中几种不同的基于 prototype 继承方式的区别

普通属性的继承

第一种方式

来自于 MDN 对象模型的细节

function Employee1 (name, dept) {
  console.log('Employee constructor')
  this.name = name || "";
  this.dept = dept || "general";
  this.work = function() {console.log("Employee.work")}
  this.workAsEmployee = function() { this.work() }
}

function WorkerBee1 (projs) {
  console.log('WorkerBee constructor')
  this.projects = projs || [];
  this.work = function() {console.log("WorkerBee.work")}
  this.workAsBee = function() { this.work() }
}
WorkerBee1.prototype = new Employee1;

function Engineer1 (mach) {
  console.log('Engineer constructor')
  this.dept = "engineering";
  this.machine = mach || "";
  this.work = function() {console.log("Engineer.work")}
  this.workAsEngineer = function() { this.work() }
}

Engineer1.prototype = new WorkerBee1;

e1 = new Engineer1()
e1.work()
console.log(Employee1.prototype.isPrototypeOf(e1))
console.log(WorkerBee1.prototype.isPrototypeOf(e1))
console.log(e1)

Alt text

第二种方式

来自于 YUI 的实现,利用中间对象传递 prototype

function extend(Child, Parent) {
  var F = function () { };
  F.prototype = Parent.prototype;
  Child.prototype = new F();
  Child.prototype.constructor = Child;
  Object.defineProperty(Child, "super", { "value": Parent.prototype })
}

function Employee2 (name, dept) {
  console.log('Employee constructor')
  this.name = name || "";
  this.dept = dept || "general";
  this.work = function() {console.log("Employee.work")}
  this.workAsEmployee = function() { this.work() }
}

function WorkerBee2 (projs) {
  console.log('WorkerBee constructor')
  this.projects = projs || [];
  this.work = function() {console.log("WorkerBee.work")}
  this.workAsBee = function() { this.work() }
}
extend(WorkerBee2, Employee2);

function Engineer2 (mach) {
  console.log('Engineer constructor')
  this.dept = "engineering";
  this.machine = mach || "";
  this.work = function() {console.log("Engineer.work")}
  this.workAsEngineer = function() { this.work() }
}
extend(Engineer2, WorkerBee2);

e2 = new Engineer2()
e2.work()

console.log(Employee2.prototype.isPrototypeOf(e2))
console.log(WorkerBee2.prototype.isPrototypeOf(e2))
console.log(e2)

Alt text

可以看到,区别主要在于,直接 Child.prototype = new Parent() 会把定义在 Parent 里面的方法也带到 prototype 里面去。 另外,这种方式并没有执行父类的构造函数。

对于定义在 prototype 里面的方法呢

下面对上面的方法定义进行一点改进,把方法定义在 prototype 里,类似正常的 OO 编程中在类里面定义方法。

第一种方式改进

function Employee3 (name, dept) {
  console.log('Employee constructor')
  this.name = name || "";
  this.dept = dept || "general";
}
Employee3.prototype.work = function() {console.log("Employee.work")}
Employee3.prototype.workAsEmployee = function() { this.work() }

function WorkerBee3 (projs) {
  console.log('WorkerBee constructor')
  this.projects = projs || [];
}
WorkerBee3.prototype = new Employee3;
WorkerBee3.prototype.work = function() {console.log("WorkerBee.work")}
WorkerBee3.prototype.workAsBee = function() { this.work() }

function Engineer3 (mach) {
  console.log('Engineer constructor')
  this.dept = "engineering";
  this.machine = mach || "";
}
Engineer3.prototype = new WorkerBee3;
Engineer3.prototype.work = function() {console.log("Engineer.work")}
Engineer3.prototype.workAsEngineer = function() { this.work() }

e3 = new Engineer3()
e3.work()

console.log(Employee3.prototype.isPrototypeOf(e3))
console.log(WorkerBee3.prototype.isPrototypeOf(e3))
console.log(e3)

Alt text

第二种方式的改进

function extend(Child, Parent) {
  var F = function () { };
  F.prototype = Parent.prototype;
  Child.prototype = new F();
  Child.prototype.constructor = Child;
  Object.defineProperty(Child, "super", { "value": Parent.prototype })
}

function Employee4 (name, dept) {
  console.log('Employee constructor')
  this.name = name || "";
  this.dept = dept || "general";
}
Employee4.prototype.work = function() {console.log("Employee.work")}
Employee4.prototype.workAsEmployee = function() { this.work() }

function WorkerBee4 (projs) {
  console.log('WorkerBee constructor')
  this.projects = projs || [];

}
extend(WorkerBee4, Employee4);
WorkerBee4.prototype.work = function() {console.log("WorkerBee.work")}
WorkerBee4.prototype.workAsBee = function() { this.work() }

function Engineer4 (mach) {
  console.log('Engineer constructor')
  this.dept = "engineering";
  this.machine = mach || "";
}
extend(Engineer4, WorkerBee4);
Engineer4.prototype.work = function() {console.log("Engineer.work")}
Engineer4.prototype.workAsEngineer = function() { this.work() }

e4 = new Engineer4()
e4.work()

console.log(Employee4.prototype.isPrototypeOf(e4))
console.log(WorkerBee4.prototype.isPrototypeOf(e4))
console.log(e4)

Alt text

注意观察 constructor__proto__ 属性。

要执行所有构造函数

上述第二种方法,都没有执行父类的构造函数,也就没有真正的继承父类的初始化数据。为了弥补这一点,如下两种写法都可以达到目的。

利用 super 变量

function extend(Child, Parent) {
  var F = function () { };
  F.prototype = Parent.prototype;
  Child.prototype = new F();
  Child.prototype.constructor = Child;
}

function Employee5 (name, dept) {
  console.log('Employee constructor')
  this.name = name || "";
  this.dept = dept || "general";
}
Employee5.prototype.work = function() {console.log("Employee.work")}
Employee5.prototype.workAsEmployee = function() { this.work() }

function WorkerBee5 (projs) {
  console.log('WorkerBee constructor')

  this.super = Employee5;
  this.super();

  this.projects = projs || [];

}
extend(WorkerBee5, Employee5);
WorkerBee5.prototype.work = function() {console.log("WorkerBee.work")}
WorkerBee5.prototype.workAsBee = function() { this.work() }

function Engineer5 (mach) {
  console.log('Engineer constructor')

  this.super = WorkerBee5;
  this.super();

  this.dept = "engineering";
  this.machine = mach || "";
}
extend(Engineer5, WorkerBee5);
Engineer5.prototype.work = function() {console.log("Engineer.work")}
Engineer5.prototype.workAsEngineer = function() { this.work() }

e5 = new Engineer5()
e5.work()

console.log(Employee5.prototype.isPrototypeOf(e5))
console.log(WorkerBee5.prototype.isPrototypeOf(e5))
console.log(e5)

Alt text

类似,但是用 Parent.apply 方法

function extend(Child, Parent) {
  var F = function () { };
  F.prototype = Parent.prototype;
  Child.prototype = new F();
  Child.prototype.constructor = Child;
}

function Employee6 (name, dept) {
  console.log('Employee constructor')
  this.name = name || "";
  this.dept = dept || "general";
}
Employee6.prototype.work = function() {console.log("Employee.work")}
Employee6.prototype.workAsEmployee = function() { this.work() }

function WorkerBee6 (projs) {
  console.log('WorkerBee constructor')
  Employee6.apply(this)
  this.projects = projs || [];
}
extend(WorkerBee6, Employee6);
WorkerBee6.prototype.work = function() {console.log("WorkerBee.work")}
WorkerBee6.prototype.workAsBee = function() { this.work() }

function Engineer6 (mach) {
  console.log('Engineer constructor')
  WorkerBee6.apply(this)
  this.dept = "engineering";
  this.machine = mach || "";
}
extend(Engineer6, WorkerBee6);
Engineer6.prototype.work = function() {console.log("Engineer.work")}
Engineer6.prototype.workAsEngineer = function() { this.work() }

e6 = new Engineer6()
e6.work()

console.log(Employee6.prototype.isPrototypeOf(e6))
console.log(WorkerBee6.prototype.isPrototypeOf(e6))
console.log(e6)

Alt text

长距离打车如何省钱?

学JavaScript的时候写的一个用于在长距离打车时,计算多远抬一次表最省钱的抬表方案的小程序,参数可以根据需要随意修改。

X轴是距离,Y轴是费用,不同的颜色表示不同的抬表方案

大致的简单结论是20公里以上的时候,每15公里抬一次表,能省出10%左右吧。

taxi 下面代码存为taxi.html,然后打开即可在浏览器中看到结果。


<html>
<script type="text/javascript" src="http://www.walterzorn.com/scripts/wz_jsgraphics.js"></script>

<body>
<script type="text/javascript">
<!--
function getPrice(distance, night) {
 var unitPrice = 2.0,
 basicDistance = 3,
 basicPrice = 10,
 extDistance = 15,
 extPrice = 3.0;

 var price = 0.0;
 if ( distance == 0 ) return 0;
 if ( distance <= basicDistance ) return basicPrice;

 if ( distance <= extDistance ) {
 price = basicPrice + unitPrice * (distance - basicDistance);
 return price;
 }

 price += basicPrice + unitPrice*(extDistance-basicDistance) + extPrice*(distance-extDistance)
 return price;
}

function getPriceWithReset(distance, night, reset) {
 if ( reset == 0 ) {
 return getPrice(distance, night);
 } else {
 return getPrice(reset, night) * Math.floor(distance/reset) + getPrice(distance % reset, night);
 }
}

var coordXUnit = 8, coordYUnit = 3,
basicX = 100, basicY = 50,
coordMaxX = 101, coordMaxY = 251;

function drawString(str, x, y) {
 jg.drawString(str, basicX+x*coordXUnit, basicY+(coordMaxY-y)*coordYUnit);
}

function drawPoint(x, y) {
 drawLine(x, y, x, y);
}

function drawLine(x1, y1, x2, y2) {
 jg.drawLine(basicX + x1*coordXUnit, basicY + (coordMaxY-y1)*coordYUnit, basicX + x2*coordXUnit, basicY + (coordMaxY-y2)*coordYUnit);
}

function drawPolyline(ax, ay) {
 var axx = new Array(), ayy = new Array();

 for (var e in ax) {
 axx.push(basicX + (ax[e] * coordXUnit) );
 }

 for (var e in ay) {
 ayy.push(basicY + (coordMaxY - ay[e])*coordYUnit);
 }

 jg.drawPolyline(axx, ayy);
}

function drawCoordinate() {
 jg.setColor("#ee8800");
 drawLine(0, 0, coordMaxX, 0);
 drawLine(0, 0, 0, coordMaxY);

 drawString(0, -2, -2);
 for (var i = 1; i < coordMaxX; i++) {
 if (i%5==0) {
 // draw grid
 jg.setStroke(Stroke.DOTTED);
 drawLine(i, 0, i, coordMaxY);
 // draw mark
 jg.setStroke(0);
 drawLine(i, 0, i, -2);
 drawString(i, i-1, -3);
 } else {
 drawLine(i, 0, i, -1);
 }
 }

 for (var i = 1; i < coordMaxY ; i++) {
 if (i%5==0) {
 // draw grid
 jg.setStroke(Stroke.DOTTED);
 drawLine(0, i, coordMaxX, i);
 // draw mark
 jg.setStroke(0);
 drawLine(0, i, -2, i);
 drawString(i, -7, i+1);
 } else {
 drawLine(0, i, -1, i);
 }
 }
}

function drawPriceLine(night, reset) {
 var ax = new Array(), ay = new Array();

 for (var d=0; d<70; d++) {
 ax.push(d);
 ay.push(getPriceWithReset(d, 0, reset));
 }
 drawPolyline(ax, ay);
}

/////////////// main section ///////////////
var jg = new jsGraphics();
jg.setFont("verdana,geneva,sans-serif", "10px", Font.PLAIN);

drawCoordinate();
drawString("reset:", 0, -10);
jg.setColor("#000000");
drawPriceLine(0, 0);
drawString("0", 5, -10);
jg.setColor("#FF0000");
drawPriceLine(0, 10);
drawString("10", 10, -10);
jg.setColor("#00FF00");
drawPriceLine(0, 15);
drawString("15", 15, -10);
jg.setColor("#0000FF");
drawPriceLine(0, 20);
drawString("20", 20, -10);

///////////////// from wangjing to my home, 35KM /////////////////
jg.setColor("#880000");
var xp = new Array(), yp = new Array();
for ( var i=10; i<30; i++) {
 xp.push(i);
 yp.push(getPriceWithReset(35, 0, i));
}
drawPolyline(xp, yp);

drawString(getPriceWithReset(35, 0, 15), 0, -20);
jg.paint();
//-->
</script>
</body>
</html>