使用原型而不是在对象本身上声明属性有什么好处吗?

Is there any benefit to using prototype instead of declaring properties on the object itself?

本文关键字:属性 声明 什么 原型 对象      更新时间:2023-09-26

原型用于声明一类对象的属性和方法。使用原型的一个优点是它可以节省内存,因为类的所有实例都指向原型的属性和方法,从而节省内存并有效地允许类的所有实例将属性视为静态。

原型

用于通过原型链接进行继承。

我的问题很简单。当你可以做的时候,为什么要使用原型:

function car() {
    this.engine = "v8";
}
function mustang() {
    // nm, no good way to inherit without using prototypes
}

是吗?因此,原型的主要目的有三个:

  1. 节省内存
  2. 提供静态属性
  3. 是引用类型从超类继承的唯一方法

节省内存

是的,当您创建数百个 Car 实例并且它们都有自己的函数(有自己的闭包范围)时,您将消耗更多内存。

找不到它的参考,但有人建议 Chrome 优化使用原型的构造函数比构造函数更好地使用构造函数,构造函数体中的所有内容。

提供静态属性

静态更像是Date.now(),每个实例都有来自原型的成员,但可以在实例上调用。

是引用类型从超类继承的唯一方法

您可以在 Child 中使用 Parent.apply(this,arguments); 进行继承,但它会使扩展父函数变得更加复杂,并且不会使childInstance instanceof Parent真实。该代码的作用是运行父代码,并将要创建的子实例作为调用对象(this)。继承通常在两个地方完成。

  1. 在子正文Parent.apply(this,arguments);中,重新使用父初始化代码,并将父实例成员设置为子实例成员(例如:this.name)。
  2. 将 Child.prototype 设置为父原型Child.prototype=Object.create(Parent.prototype);Child.prototype.constructor=Child;的浅拷贝 这将确保共享的父成员在子实例上可用(如函数 getName)。

这些要点在这里有更详细的解释:https://stackoverflow.com/a/16063711/1641941

关于你的三点:

    原型
  1. 不一定性能更高,特别是对于变长或包含许多成员的原型链。原型越小,链越短,浏览器的编译器就越能优化它。最终,这个问题需要针对各个应用程序、它们的个人需求以及所针对的浏览器(性能差异很大)提出。
  2. 根据定义,静态成员需要对象。也就是说,静态成员属于对象本身,而不是特定实例。对象是在 JavaScript 中创建静态属性的唯一方法。请注意,对象文本是一种"特殊"类型的对象,本质上是静态的。
  3. 一个人可以实现他自己的对象类型,允许继承之类的东西(即jQuery.extend),但就引用类型而言,原型是创建继承的唯一方法。
原型

设计远不止于此。还可以在运行时使用方法和属性扩展类和已存在的对象实例。

这应该以一种非常容易理解的方式解释它:http://javascript.info/tutorial/inheritance

如果你关心遵循约定,以便人们(和你)真正理解你的代码,你不应该把this.engine="v8"放在构造函数中。原型旨在定义每辆车的属性,构造函数旨在定义单个实例。那么,为什么要在构造函数中放置对每个实例都正确的内容呢?这属于原型。把事情放在适当的位置是有话要说的,即使做这两件事最终都会完成同样的事情。您的代码将被您和其他人理解。

关于您的观点:

  1. 肯定会有性能提升,尤其是在函数方面 - 最好在原型上声明函数。
  2. 我想你的意思是说"公共"属性,以便通过编写some_instance.foo来检索信息。 "静态"属性/方法是不同的(见下文)。
  3. 正确。继承只能真正从原型发生。

让我解释一些事情,看看这是否有帮助。 在javascript中创建新的"类"是一个相当简单的过程。

var MyClass = new Function();
此时,引擎知道你的

新类,并且知道在创建你的"类"的新实例时"要做什么"(就性能而言)。

var my_instance = new MyClass();

如果要修改原型,可以这样做,并且知道每个实例都将更新,因为它们都共享相同的原型。

MyClass.prototype.name = "default name";
console.log( my_instance.name ); //=> default name

现在引擎知道有一个"name"属性需要一个字符串值......它会将这些资源分配给类的所有新实例和现有实例......非常方便。 请注意,像这样修改现有"类"的原型是一个昂贵的过程,不应该经常执行(但也不要害怕这样做)。

我真的不能说在实例上声明临时属性/方法的性能优缺点:

my_instance.foo = function() { /* this is not in the prototype chain */ };

我的猜测是,这对于引擎来说非常简单,除非您同时对数万个对象执行此操作,否则没什么大不了的。

使用原型 IMO 的主要好处是,您可以编写代码来扩展方法的功能,并且知道"类"的所有实例都将相应地更新:

var old_foo = MyClass.prototype.foo;
MyClass.prototype.foo = function() {
    /* new business logic here */
    // now call the original method.
    old_foo.apply(this, arguments);
};
关于"静态"属性

,您可以在"类"(构造函数)本身上声明这些属性:

// example static property
MyClass.num_instances = 0;

现在你可以创建这样的初始化/销毁方法:

MyClass.prototype.init = function() {
    this.constructor.num_instances++;
};
MyClass.prototype.destroy = function() {
    this.constructor.num_instances--;
};
// and call the init method any time you create a new instance
my_instance.init();
console.log( MyClass.num_instances ); //=> 1
var instance_2 = new MyClass();
instance_2.init();
console.log( MyClass.num_instances ); //=> 2
instance_2.destroy();
console.log( MyClass.num_instances ); //=> 1

希望有帮助。

(1)我不认为单独保存内存是使用.prototype的正当理由,除非你在复制对象方面变得非常极端。

(2)静态属性

的想法也不是使用.prototype(恕我直言)的真正理由,因为它的行为不像传统的静态属性。您(据我所知)在访问"static"属性之前总是需要一个对象实例,这使得它根本不是静态的。

function Car() {}
Car.prototype.Engine = "V8";
// I can't access Car.Engine... I'll always need an instance.
alert(new Car().Engine);
// or
var car1 = new Car();
alert(car1.Engine); //you always need an instance.
//unless you wanted to do
alert(Car.prototype.Engine); //this is more like a static property, but has an
//unintended consequence that every instance of Car also receives a .Engine
//behavior, so don't do this just to create a "static property."

应该注意的是,从传统的OO角度来看,这种"静态"概念不仅适用于属性,而且适用于所有成员,其中包括方法(函数)。

最好将原型(再次,恕我直言)视为注入的单例对象,这些对象具有附加到实例对象的行为。Car() 的所有实例都可以有自己的实例成员,但 Car() 的每个实例也将"自动"注入所有 Car.prototype 的成员/行为。从技术上讲,这并不相同,但我发现以这种方式考虑原型很方便。

//define Car and Car.GoFast
function Car() {}
Car.prototype.GoFast = function () { alert('vroom!'); };
var car1 = new Car();
var car2 = new Car();
car1.GoFast();
car2.GoFast(); //both call to same GoFast implementation on Car.prototype
//change the GoFast implementation
Car.prototype.GoFast = function () { alert('vvvvvrrrrroooooooooommmmm!!!!!'); };
car1.GoFast();
car2.GoFast(); //both have been "updated" with the new implementation because
//both car1 and car2 are pointing to the same (singleton) Car.prototype object!

Car.prototype 的行为类似于一个单例对象,其成员/行为已注入到 Car 类型的实例对象中。

(3)原型不应与继承混淆。您可以获得看似继承的行为,但事实并非如此。原型上的成员/行为保留在原型对象上。它们不会像真正的继承那样成为派生类的成员/行为。这就是为什么我将其描述得更像是将原型"注入"到您的实例中的原因。

function Car() {}
Car.prototype.Engine = "V8";
var car1 = new Car();
var car2 = new Car();
alert(car1.Engine); //alerts "V8"
//There is no "Engine" variable defined within the instance scope of 'car1'.
//Javascript searches the scope of car1's Type.prototype object to find 'Engine'.
//Equivalent to: Object.getPrototypeOf(car1).Engine
//But if we create 'Engine' within the scope of car1
car1.Engine = "V6"; //Car.prototype was never accessed or updated
alert(car1.Engine); //we get "V6"
alert(car2.Engine); //we still get "V8"
alert(Object.getPrototypeOf(car1).Engine); //we still get "V8"!

因此,直接回答这个问题:使用原型而不是在对象本身上声明属性有什么好处吗?

是的,当您想要在给定类型的实例对象之间共享行为实现时。巧合的是,您将减少内存占用,但这并不是使用原型的唯一原因。"创建静态属性"(它们不是),也不是继承(不是)。