使用原型而不是在对象本身上声明属性有什么好处吗?
Is there any benefit to using prototype instead of declaring properties on the object itself?
原型用于声明一类对象的属性和方法。使用原型的一个优点是它可以节省内存,因为类的所有实例都指向原型的属性和方法,从而节省内存并有效地允许类的所有实例将属性视为静态。
原型用于通过原型链接进行继承。
我的问题很简单。当你可以做的时候,为什么要使用原型:
function car() {
this.engine = "v8";
}
function mustang() {
// nm, no good way to inherit without using prototypes
}
是吗?因此,原型的主要目的有三个:
- 节省内存
- 提供静态属性
- 是引用类型从超类继承的唯一方法
节省内存
是的,当您创建数百个 Car 实例并且它们都有自己的函数(有自己的闭包范围)时,您将消耗更多内存。
找不到它的参考,但有人建议 Chrome 优化使用原型的构造函数比构造函数更好地使用构造函数,构造函数体中的所有内容。
提供静态属性
静态更像是Date.now()
,每个实例都有来自原型的成员,但可以在实例上调用。
是引用类型从超类继承的唯一方法
您可以在 Child 中使用 Parent.apply(this,arguments);
进行继承,但它会使扩展父函数变得更加复杂,并且不会使childInstance instanceof Parent
真实。该代码的作用是运行父代码,并将要创建的子实例作为调用对象(this
)。继承通常在两个地方完成。
- 在子正文
Parent.apply(this,arguments);
中,重新使用父初始化代码,并将父实例成员设置为子实例成员(例如:this.name)。 - 将 Child.prototype 设置为父原型
Child.prototype=Object.create(Parent.prototype);Child.prototype.constructor=Child;
的浅拷贝 这将确保共享的父成员在子实例上可用(如函数 getName)。
这些要点在这里有更详细的解释:https://stackoverflow.com/a/16063711/1641941
关于你的三点:
- 原型
- 不一定性能更高,特别是对于变长或包含许多成员的原型链。原型越小,链越短,浏览器的编译器就越能优化它。最终,这个问题需要针对各个应用程序、它们的个人需求以及所针对的浏览器(性能差异很大)提出。
- 根据定义,静态成员需要对象。也就是说,静态成员属于对象本身,而不是特定实例。对象是在 JavaScript 中创建静态属性的唯一方法。请注意,对象文本是一种"特殊"类型的对象,本质上是静态的。
- 一个人可以实现他自己的对象类型,允许继承之类的东西(即
jQuery.extend
),但就引用类型而言,原型是创建继承的唯一方法。
设计远不止于此。还可以在运行时使用方法和属性扩展类和已存在的对象实例。
这应该以一种非常容易理解的方式解释它:http://javascript.info/tutorial/inheritance
如果你关心遵循约定,以便人们(和你)真正理解你的代码,你不应该把this.engine="v8"
放在构造函数中。原型旨在定义每辆车的属性,构造函数旨在定义单个实例。那么,为什么要在构造函数中放置对每个实例都正确的内容呢?这属于原型。把事情放在适当的位置是有话要说的,即使做这两件事最终都会完成同样的事情。您的代码将被您和其他人理解。
关于您的观点:
- 肯定会有性能提升,尤其是在函数方面 - 最好在原型上声明函数。
- 我想你的意思是说"公共"属性,以便通过编写
some_instance.foo
来检索信息。 "静态"属性/方法是不同的(见下文)。 - 正确。继承只能真正从原型发生。
让我解释一些事情,看看这是否有帮助。 在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"!
因此,直接回答这个问题:使用原型而不是在对象本身上声明属性有什么好处吗?
是的,当您想要在给定类型的实例对象之间共享行为实现时。巧合的是,您将减少内存占用,但这并不是使用原型的唯一原因。"创建静态属性"(它们不是),也不是继承(不是)。
- 获取HTML属性中CSS声明的值
- 如何在AngularJS工厂中正确声明对象属性
- 在样式表中声明元素后,删除该元素的悬停属性
- 在Aurelia computeds中,当设置依赖关系时,如何声明对对象属性的依赖关系
- 什么's;var'声明的变量和'这'在Javascript中创建了属性
- 如何区分未声明和未定义的 JavaScript 对象属性
- "无法读取属性'id'未定义的“;解析工厂中声明的数组时
- 无法使用typescript访问angular中另一个控制器中声明的$rootscope属性
- 属性初始值设定项在声明之前未定义
- 如何使用ES6在Ember中声明可观察性或计算属性
- 使用原型而不是在对象本身上声明属性有什么好处吗?
- 对象属性的分配顺序是否与声明顺序相同
- Javascript - 在变量声明中使用大括号将多个变量分配给对象属性
- 在 onClick 属性中声明变量,并在 onClick 中使用多个命令
- SCRIPT5039:重新声明常量属性 - IE 9 Javascript 问题
- Javascript 数组声明(可能误用长度属性?)
- 在 angularjs 控制器范围内声明函数和属性,但不附加到$scope
- ES7 类:在构造函数之外声明属性
- jQuery验证插件,用属性声明需要的规则不工作
- 具有开放端构造函数参数的参数属性声明