任何人都可以提供一个特定的例子来展示Javascript的原型继承,它展示了什么时候使用传统的基于类的(经典)模型是有益的吗?
我见过的其他问题(例如Classical Vs prototypal inheritance,Why was JavaScript implemented using prototypal inheritance?,prototype based vs. class based inheritance)仅提供高级别讨论,而不是具体示例(最好是您在生产代码中使用的一个) )。
答案 0 :(得分:2)
原型继承优于经典继承有很多原因:
proto
属性,指向其原型对象(Object
对象具有原型null
)。由于JavaScript是动态的,您可以对原型对象进行更改,这些更改将反映在内部proto
属性指向该原型的每个对象上(即使在创建对象之后)。因此,它可以用于扩展一组对象的功能。还有很多原因。我会在回忆时不断更新。
这是一些显示经典继承的Java代码:
public class Employee {
public String name;
public String dept;
public Employee () {
this("", "general");
}
public Employee (String name) {
this(name, "general");
}
public Employee (String name, String dept) {
this.name = name;
this.dept = dept;
}
}
public class WorkerBee extends Employee {
public String[] projects;
public WorkerBee () {
this(new String[0]);
}
public WorkerBee (String[] projs) {
projects = projs;
}
}
public class Engineer extends WorkerBee {
public String machine;
public Engineer () {
dept = "engineering";
machine = "";
}
public Engineer (String mach) {
dept = "engineering";
machine = mach;
}
}
以下是等效的JavaScript代码:
function Employee (name, dept) {
this.name = name || "";
this.dept = dept || "general";
}
function WorkerBee (projs) {
this.projects = projs || [];
}
WorkerBee.prototype = new Employee;
function Engineer (mach) {
this.dept = "engineering";
this.machine = mach || "";
}
Engineer.prototype = new WorkerBee;
答案 1 :(得分:2)
我希望你能在下面的例子中看到的动态性和灵活性是javascript的用途 比静态类模型更有利。
在生成页面上使用的这个示例中,jQuery在ltIE8浏览器中根本不会生成动画。 由于它只发生在一个特定的页面中,因此破解它是没有意义的 jQuery核心也加载到其他页面上(更不用说它必须托管而不是从谷歌加载)。 相反,我做了ltIE8条件脚本块修改fx原型内联的cur方法,修复了它将返回NaN值的问题 用于动画步骤:
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js"></script>
<!--[if lt IE 8]>
<script type="text/javascript">
jQuery.fx.prototype.cur = function() {
var parsed, r;
if ( this.elem[this.prop] != null && (!this.elem.style || this.elem.style[this.prop] == null) ) {
return this.elem[ this.prop ];
}
r = this.elem.style[this.prop] || jQuery.css( this.elem, this.prop );
return isNaN( parsed = parseFloat( r ) ) ? !r || r === "auto" ? 0 : r : parsed;
};
</script>
<![endif]-->
另一个例子是我为模拟经典继承而创建的库的实现。具体实施 在示例中未在生产页面上使用,但我在生产页面上创建的所有“类”都是以这种方式生成的。重要的 这样做的好处是你可以添加和修改功能(例如本例中显示的内联别名),因为它没有用该语言进行硬编码。
var Person = function Person( name, age ){ //Declare the constructor
this.name = name || "John";
this.age = age || "20";
this.instanceId = "person"+this.constructor._getId(); //Create unique instance id
this.constructor._addInstance( this ); //Make this instance accessible from Person
}
.Inherits( Animal, Monkey )
.Class({
"static _instances": {},
"static _curId": 0,
"static _getId": function(){
return this._curId++; //In static methods "this" refers to the Person constructor, not to an instance.
},
"static _addInstance": function( instance ) {
this._instances[instance.instanceId] = instance;
},
"static alias byId": "getInstanceById", //Inline alias for Person.getInstanceById === Person.byId
"static getInstanceById": function( id ){
return ( id in this._instances ) && this._instances[id];
},
"alias greet": "sayHello", //alias for the instance method
"alias sayHi": "sayHello",
sayHello: function(){
return "hello from "+this.name;
},
eat: function(){
return this.__super__( "eat", "pizza" ); //Super method call, not really useful in this particular implementation
}
})
.Implements( whatever ); //emulating interfaces, whatever should be an object that describes how the methods must be implemented
//Instantiating and such works like regular js
var mike = new Person( "mike" );
mike.greet(); //"hello from mike"
mike.sayHi(); //"hello from mike"
mike.sayHello(); //"hello from mike"
mike === Person.byId( "person0" ); //true
没有实现使_underscore前缀方法实际上无法从外部访问,因为 在js-heavy页面上,开销不值得。仅限多重继承和超级方法 为上一代工作。
答案 2 :(得分:2)
我有几件事我会说是明显的优点。与强类型的基于类的语言相比,它们都代表了重要的安全弱点,但它们为熟练的用户提供了很多功能。
即使没有声明一个类,您也可以创建任何已实例化对象的特殊“子类”:
// Magic code
function child(src) {
function Child() {};
Child.prototype = src;
return new Child;
}
// Base object
var default_options = {
color: 'red',
size: 'large',
font: 'arial'
};
// Child object
var my_options = child(default_options);
my_options.size = 'small';
my_options.font = 'verdana';
my_options.color == 'red';
default_options.font == 'arial';
在支持__proto__
的浏览器上,这可能更容易:
var my_options = {
size: 'small',
font: 'verdana'
};
// When applying options:
my_options.__proto__ = default_options;
my_options.color == 'red';
这意味着你也可以传递简单的对象,然后通过将它们附加到完整的类原型来丰富它们:
my_options.__proto__ = OptionsProcessor.prototype;
当然,JavaScript继承如此强大的真正原因在于,您将要处理的环境已经很好地建立了数千个您可能想要增强的对象。假设您想在旧版浏览器上使用element.querySelectorAll
。使用经典继承,你运气不好但是使用JavaScript继承,这是一个简单的问题:
(HTMLElement || Object).prototype.querySelectorAll = function(selector) { ... }
这种polyfill比jQuery更有优势,因为你可以在整个应用程序中使用标准代码,只在我们需要时导入JavaScript。
假设我们希望每次使用querySelectorAll
时都知道如果想要用更快的函数替换它来进行更简单的查询。每次调用时我们都可以劫持函数并输出到控制台:
var oldFunction = HTMLElement.prototype.querySelectorAll;
HTMLElement.prototype.querySelectorAll = function(selector) {
console.log(selector);
oldFunction.prototype.apply(this, arguments);
};
JavaScript有很多类似数组的函数。 arguments
不是数组。 document.getElementsByTagName('div')
也不是。list.slice(0, 5)
。这意味着如果我们想要数组中的前5个项目,我们就不能使用Array.prototype.slice
。但是,您可以将list
应用于var divs = document.getElementsByTagName('div');
var first5divs = Array.prototype.slice.call(divs, 0, 5);
对象:
{{1}}
答案 3 :(得分:1)
在我看来,原型更灵活。
我只能让我的班级Bar的一个实例继承自Foo而不是Bar的所有实例。
我可以通过将Bar.prototype设置为null或其他一些对象值来决定我不再让Bar继承.Foo。
我可以在任何时候决定我希望Bar从Array而不是Foo继承。
古典语言确实有其优点。喜欢更好的封装。使用原型时,你必须做很多关闭魔法,以使对象的属性“作为”私有属性。
答案 4 :(得分:1)
我认为我们没有直接看到更多原型模式的最大原因是Javascript中的默认语法是伪古典像差而不是更有利的Object.create。如果你想真正看到原型模式闪耀搜索使用此功能的地方。以下示例来自Dojo工具包:
警告:自从我最初撰写此答案以来,我有点改变了自己对这种代码有多好的看法。虽然基本思想仍然存在,但你应该小心你有变异实例(“this”)属性的方法。这是因为如果通过委托者调用委托对象中的方法,那么最终可能会在委托者中而不是委托中设置变量,如果其他人最终直接访问委托,则可能会破坏某些不变量。
尽管你有不可变的对象,但整个想法是100%的好。
Dojo定义了一个通用的store接口(使用get(),add()等方法),例如,可以用来从服务器抽象REST API。我们想创建一个Cache函数来接收任何数据存储区并返回一个新版本来缓存对get()方法的任何调用(这允许我们将缓存与实现实际获取的特定于商店的行为分离开来())
第一个想法将涉及使用Javascript非常恐怖的事实来取代get方法:
//not the actual implementation. Things get more complicated w/ async code.
var oldGet = store.get;
store.get = function(id){
if(!cache[id]){ cache[id] = oldGet(id); }
return cache[id];
}
然而,这会破坏原始对象,因此您无法再访问原始方法,并且使并行添加其他修改变得更加棘手。
第二个想法是使用委托制作更强大的解决方案:
function Cache(obj){
this.obj = obj;
}
Cache.prototype = {
get: function(){
//do things involving this.obj...
}
};
这看起来很有希望,直到你记得生成的Cache对象需要实现商店界面。我们可以尝试手动添加所有方法:
Cache.prototype = {
//...
put: function(){ return this.obj.apply(this, arguments); },
//...
}
但这不仅麻烦且容易出错(它很容易忘记某些东西)它甚至不会像对象修改解决方案那样强大,因为我们无法访问原始对象中的方法商店界面。
嗯,执行这种“自动委托”的方法是继承,但在这种情况下它最初似乎没用,因为你需要为每个可能的商店类创建一个新的缓存子类,或者你需要某种类型的花哨的多重继承混合。输入原型继承以节省一天。我们可以轻松地创建一个新的obejct,为旧的添加功能而无需修改它或不得不使用类hieharchies
dojo.store.Cache = function(masterStore, cachingStore, options){
//...
return dojo.delegate(masterStore, {
//...
get: function(id, directives){
//...
}
//...
}
}
dojo.delegate是一个函数,它创建一个具有第二个参数中所有属性的新对象,其原型将是第一个参数。
非JS理论上的说法:原型继承可以在像Self这样的语言中更加激进地使用,它允许多个原型,并且还可以在运行时直接访问和修改原型。例如,可以通过将所有合适的方法委托给原型并在状态发生变化时更改原型,从GoF实现State模式。