Javascript 何时使用原型

IT技术 javascript performance prototype function-prototypes
2021-02-08 05:07:34

我想了解什么时候在 js 中使用原型方法是合适的。是否应该始终使用它们?或者是否存在不推荐使用它们和/或导致性能损失的情况?

在本网站上搜索 js 中命名空间的常用方法时,似乎大多数使用非基于原型的实现:简单地使用对象或函数对象来封装命名空间。

来自基于类的语言,很难不尝试绘制相似之处并认为原型就像“类”,而我提到的命名空间实现就像静态方法。

6个回答

原型是一种优化

很好地使用它们的一个很好的例子是 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这样的类提供了自己独特的实施接口的每一个成员。
  • 实现继承,您可以extendclass其中提供某些方法的默认实现。

在 JavaScript 中,原型继承是一种实现继承。因此,在那些情况下(在 C# 或 Java 中)您将从基类派生以获得默认行为,然后您通过覆盖对其进行小的修改,然后在 JavaScript 中,原型继承是有意义的。

但是,如果您在使用 C# 或 Java 中的接口的情况下,那么您不需要 JavaScript 中的任何特定语言功能。不需要显式声明代表接口的东西,也不需要将对象标记为“实现”该接口:

var duck = {
    quack: function() { ... }
};

duck.quack(); // we're satisfied it's a duck!

换句话说,如果每个“类型”的对象都有自己的“方法”定义,那么从原型继承就没有value。之后,这取决于您为每种类型分配了多少实例。但是在许多module化设计中,给定类型只有一个实例。

事实上,很多人已经提出实现继承是邪恶的也就是说,如果某个类型有一些常见的操作,那么如果它们不放入基类/超类中,而只是作为某个module中的普通函数公开,您将对象传递给该module,则可能会更清楚您希望他们进行操作。

+1 与 jQuery 的比较是我读过的关于何时以及为什么使用原型的第一个清晰简洁的解释。非常感谢你。
2021-03-18 05:07:34
在您的跟进中,您提到“这取决于您为每种类型分配的实例数量”。但是您引用的示例没有使用原型。分配实例的概念在哪里(您仍然会在这里使用“new”)?另外:说 quack 方法有一个参数 - 每次调用duck.quack(param) 是否会导致在内存中创建一个新对象(如果它有参数,可能无关紧要)?
2021-03-23 05:07:34
谢谢我接受了你的回答。但是我对您的观点(1)仍然有些困惑:我没有理解您所说的“一种鸭子的大量实例”是什么意思。就像你在 (3) 中所说的,每次调用 JS 函数时,都会在内存中创建一个对象——所以即使你只有一种类型的鸭子,每次调用鸭子的函数时,你不会分配内存吗(在在哪种情况下使用原型总是有意义的)?
2021-03-27 05:07:34
1.我的意思是,如果一种类型的鸭子有大量的实例,那么修改示例quack有意义的,使函数处于原型中,许多鸭子实例都链接到该原型。2.对象字面量语法{ ... }创建一个实例(不需要new和它一起使用)。3.调用任何函数 JS 都会导致在内存中至少创建一个对象——它被称为arguments对象并存储调用中传递的参数:developer.mozilla.org/en/JavaScript/Reference/...
2021-03-31 05:07:34
很好的解释。那么您是否同意,既然您认为原型是一种优化,那么它们总是可以用来改进您的代码?我想知道是否在某些情况下使用原型没有意义,或者实际上会导致性能下降。
2021-04-06 05:07:34

如果您希望声明对象的“非静态”方法,则应使用原型。

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.
@keatsKelleher 但是我们可以通过使用this示例在构造函数内部定义方法来为对象创建一个非静态方法,this.getA = function(){alert("A")}对吗?
2021-04-13 05:07:34

使用内置prototype对象的一个原因是,如果您要多次复制一个共享公共功能的对象。通过将方法附加到原型,您可以节省为每个new实例创建的重复方法但是,当您将方法附加到 时prototype,所有实例都可以访问这些方法。

假设您有一个基Car()类/对象。

function Car() {
    // do some car stuff
}

然后创建多个Car()实例。

var volvo = new Car(),
    saab = new Car();

现在,您知道每辆车都需要行驶、启动等。而不是将方法直接附加到Car()类(每个创建的实例占用内存),您可以将方法附加到原型(仅创建方法)一次),因此可以同时访问 newvolvosaab.

// 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'
@josh 感谢您指出这一点。我已经更新了我的答案,这样我就不会用对象文字覆盖原型,因为它应该从一开始就是这样。
2021-03-20 05:07:34
@29er - 按照我写这个例子的方式,你是对的。顺序很重要。如果我要保持这个例子不变,Car.prototype = { ... }那么必须在调用 a 之前出现,new Car()如这个 jsfiddle: jsfiddle.net/mxacA 所示至于你的论点,这将是正确的方法:jsfiddle.net/Embnp有趣的是,我不记得回答过这个问题 =)
2021-03-24 05:07:34
实际上这是不正确的。volvo.honk() 将不起作用,因为您完全替换了原型对象,而不是扩展它。如果你要做这样的事情,它会像你期望的那样工作: Car.prototype.honk = function() { console.log('HONK');} volvo.honk(); //'喇叭'
2021-04-01 05:07:34
@hellatan 您可以通过设置 constructor: Car 来解决这个问题,因为您用对象文字覆盖了原型属性。
2021-04-09 05:07:34

当您要为特定类型的对象创建大量副本并且它们都需要共享共同行为时,请将函数放在原型对象上。通过这样做,您可以通过每个函数的一个副本来节省一些内存,但这只是最简单的好处。

更改原型对象上的方法,或添加方法,会立即更改相应类型的所有实例的性质。

现在为什么要做所有这些事情主要是您自己的应用程序设计的一个功能,以及您需要在客户端代码中做的事情。(一个完全不同的故事是服务器内的代码;更容易想象在那里做更多大规模的“OO”代码。)

@opi 是的,你是对的 - 没有复制。相反,原型对象上的符号(属性名称)只是作为每个实例对象的虚拟部分自然地“存在”。人们不想为此烦恼的唯一原因是对象是短暂的和独特的,或者没有太多“行为”可以共享的情况。
2021-03-20 05:07:34
因此,当我使用原型方法(通过 new 关键字)实例化一个新对象时,该对象不会获得每个函数的新副本(只是一种指针)?如果是这种情况,您为什么不想使用原型?
2021-04-02 05:07:34

如果我用基于类的术语来解释,那么 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