我想了解什么时候在 js 中使用原型方法是合适的。是否应该始终使用它们?或者是否存在不推荐使用它们和/或导致性能损失的情况?
在本网站上搜索 js 中命名空间的常用方法时,似乎大多数使用非基于原型的实现:简单地使用对象或函数对象来封装命名空间。
来自基于类的语言,很难不尝试绘制相似之处并认为原型就像“类”,而我提到的命名空间实现就像静态方法。
我想了解什么时候在 js 中使用原型方法是合适的。是否应该始终使用它们?或者是否存在不推荐使用它们和/或导致性能损失的情况?
在本网站上搜索 js 中命名空间的常用方法时,似乎大多数使用非基于原型的实现:简单地使用对象或函数对象来封装命名空间。
来自基于类的语言,很难不尝试绘制相似之处并认为原型就像“类”,而我提到的命名空间实现就像静态方法。
原型是一种优化。
很好地使用它们的一个很好的例子是 jQuery 库。每次使用 获取 jQuery 对象时$('.someClass')
,该对象都有数十种“方法”。该库可以通过返回一个对象来实现这一点:
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!
换句话说,如果每个“类型”的对象都有自己的“方法”定义,那么从原型继承就没有value。之后,这取决于您为每种类型分配了多少实例。但是在许多module化设计中,给定类型只有一个实例。
事实上,很多人已经提出实现继承是邪恶的。也就是说,如果某个类型有一些常见的操作,那么如果它们不放入基类/超类中,而只是作为某个module中的普通函数公开,您将对象传递给该module,则可能会更清楚您希望他们进行操作。
如果您希望声明对象的“非静态”方法,则应使用原型。
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.
使用内置prototype
对象的一个原因是,如果您要多次复制一个共享公共功能的对象。通过将方法附加到原型,您可以节省为每个new
实例创建的重复方法。但是,当您将方法附加到 时prototype
,所有实例都可以访问这些方法。
假设您有一个基Car()
类/对象。
function Car() {
// do some car stuff
}
然后创建多个Car()
实例。
var volvo = new Car(),
saab = new Car();
现在,您知道每辆车都需要行驶、启动等。而不是将方法直接附加到Car()
类(每个创建的实例占用内存),您可以将方法附加到原型(仅创建方法)一次),因此可以同时访问 newvolvo
和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'
当您要为特定类型的对象创建大量副本并且它们都需要共享共同行为时,请将函数放在原型对象上。通过这样做,您可以通过每个函数的一个副本来节省一些内存,但这只是最简单的好处。
更改原型对象上的方法,或添加方法,会立即更改相应类型的所有实例的性质。
现在,您为什么要做所有这些事情主要是您自己的应用程序设计的一个功能,以及您需要在客户端代码中做的事情。(一个完全不同的故事是服务器内的代码;更容易想象在那里做更多大规模的“OO”代码。)
如果我用基于类的术语来解释,那么 Person 是类,walk() 是 Prototype 方法。所以 walk() 只有在你用 this 实例化新对象后才会存在。
因此,如果您想创建像 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