我想了解何时在js中使用原型方法是合适的。它们应该一直使用吗?或者是否存在不使用它们和/或导致性能损失的情况?
在js中搜索关于命名空间的常用方法的网站时,似乎大多数都使用非基于原型的实现:只需使用对象或函数对象来封装命名空间。
来自基于类的语言,很难不尝试绘制并行,并认为原型就像“类”,我提到的命名空间实现就像静态方法。
答案 0 :(得分:128)
原型是优化。
使用它们的一个很好的例子是jQuery库。每次使用$('.someClass')
获取jQuery对象时,该对象都有许多“方法”。库可以通过返回一个对象来实现:
return {
show: function() { ... },
hide: function() { ... },
css: function() { ... },
animate: function() { ... },
// etc...
};
但这意味着内存中的每个jQuery对象都会有几十个包含相同方法的命名槽,一遍又一遍。
相反,这些方法是在原型上定义的,并且所有jQuery对象都“继承”该原型,以便以非常小的运行时成本获得所有这些方法。
jQuery如何正确使用它的一个至关重要的部分是程序员隐藏它。它纯粹是一种优化,而不是在使用库时需要担心的事情。
JavaScript的问题在于,裸构造函数要求调用者记住用new
作为前缀,否则它们通常不起作用。这没有充分的理由。 jQuery通过隐藏普通函数$
后面的无意义来实现它,所以你不必关心如何实现对象。
因此,您可以方便地使用指定的原型创建对象,ECMAScript 5包含标准函数Object.create
。它的大大简化版本看起来像这样:
Object.create = function(prototype) {
var Type = function () {};
Type.prototype = prototype;
return new Type();
};
它只关注编写构造函数然后用new
调用它的痛苦。
你什么时候会避开原型?
有用的比较是流行的OO语言,如Java和C#。这些支持两种继承:
implement
interface
,以便该类为接口的每个成员提供自己的唯一实现。extend
class
提供某些方法的默认实现。在JavaScript中,原型继承是一种实现继承。因此,在(在C#或Java中)您将从基类派生以获得默认行为的情况下,您可以对通过覆盖进行少量修改,然后在JavaScript中,原型继承是有意义的。
但是,如果您处于使用C#或Java接口的情况,那么您在JavaScript中不需要任何特定的语言功能。不需要显式声明代表接口的东西,也不需要将对象标记为“实现”该接口:
var duck = {
quack: function() { ... }
};
duck.quack(); // we're satisfied it's a duck!
换句话说,如果对象的每个“类型”都有自己的“方法”定义,那么继承原型就没有价值。之后,它取决于您为每种类型分配的实例数。但在许多模块化设计中,只有一个给定类型的实例。
事实上,it has been suggested by many people that implementation inheritance is evil。也就是说,如果某个类型有一些常见的操作,那么如果它们没有被放入基类/超类中,则可能会更清楚,而是在某些模块中作为普通函数公开,您将对象传递给它你希望他们继续操作。
答案 1 :(得分:44)
如果您希望声明对象的“非静态”方法,则应使用原型。
var myObject = function () {
};
myObject.prototype.getA = function (){
alert("A");
};
myObject.getB = function (){
alert("B");
};
myObject.getB(); // This works fine
myObject.getA(); // Error!
var myPrototypeCopy = new myObject();
myPrototypeCopy.getA(); // This works, too.
答案 2 :(得分:16)
使用内置prototype
对象的一个原因是,您将多次复制一个将共享通用功能的对象。通过将方法附加到原型,您可以节省每个new
实例创建的重复方法。但是当您将方法附加到prototype
时,所有实例都可以访问这些方法。
假设你有一个基类Car()
类/对象。
function Car() {
// do some car stuff
}
然后您创建多个Car()
个实例。
var volvo = new Car(),
saab = new Car();
现在,您知道每辆车都需要开车,开启等等。您可以将方法附加到Car()
类(每个创建的实例占用内存),而不是将方法直接附加到volvo
类。而是原型(仅创建方法一次),因此可以为新saab
和// just mapping for less typing
Car.fn = Car.prototype;
Car.fn.drive = function () {
console.log("they see me rollin'");
};
Car.fn.honk = function () {
console.log("HONK!!!");
}
volvo.honk();
// => HONK!!!
saab.drive();
// => they see me rollin'
提供对这些方法的访问权。
{{1}}
答案 3 :(得分:12)
当您要创建特定类型对象的大量副本时,将函数放在原型对象上,并且它们都需要共享常见行为。通过这样做,你可以通过只使用每个函数的一个副本来节省一些内存,但这只是最简单的好处。
更改原型对象上的方法或添加方法会立即改变相应类型的所有实例的性质。
现在正好为什么你做所有这些事情主要取决于你自己的应用程序设计,以及你需要在客户端代码中做的事情。 (完全不同的故事是服务器内的代码;更容易想象在那里做更大规模的“OO”代码。)
答案 4 :(得分:2)
如果我在课程基础上解释那么Person是课,walk()是Prototype方法。因此,只有在使用此实例化新对象后,walk()才会存在。
因此,如果你想创建像Person这样的对象的副本你可以创建许多用户Prototype是一个很好的解决方案,因为它通过共享/继承内存中每个对象的相同功能副本来节省内存。
静态在这种情况下没有那么大的帮助。
function Person(){
this.name = "anonymous";
}
// its instance method and can access objects data data
Person.prototype.walk = function(){
alert("person has started walking.");
}
// its like static method
Person.ProcessPerson = function(Person p){
alert("Persons name is = " + p.name);
}
var userOne = new Person();
var userTwo = new Person();
//Call instance methods
userOne.walk();
//Call static methods
Person.ProcessPerson(userTwo);
所以这更像是实例方法。 对象的方法类似于静态方法。
https://developer.mozilla.org/en/Introduction_to_Object-Oriented_JavaScript
答案 5 :(得分:0)
这里只包含一个视频链接以供参考,不使用原型时:https://youtu.be/JCXZhe6KsxQ?t=2m30s
这是 Ben Lesh 在 NGConf 上的演讲,为什么 rxjs 删除了修补原型(可链接函数)以支持可管道函数。