Javascript原型仅用于函数

Javascript prototype used only for functions?

本文关键字:函数 用于 原型 Javascript      更新时间:2023-09-26

我最近一直在研究Javascript,并认为我已经弄清楚了原型功能,直到我遇到了一个小问题。

发现如果你创建一个对象的两个新实例,并且在这两个对象中的任何一个中,你改变了一个变量的值,它在另一个对象中也会改变,这不是我当时正在寻找的效果,不得不将属性放在构造函数中,而不是让它们在每个实例中保持独立。

什么时候需要让每个对象实例共享属性(字符串、布尔值等)?

原型真的只用于声明函数吗? 你在构造函数中声明了所有属性?

举个例子:

function Animal(name) {
    this.name = name;
}
Animal.prototype = {
    color: null,
    walk: function() {}
};
var dog = new Animal('Sam');
var horse = new Animal('Bob');
dog.color = 'black';
horse.color = 'chestnut';
"

dog"对象颜色现在是"栗子",因为该属性位于原型中并在两个实例之间共享。哪里需要这种效果?乍一看,我能从中看到的只是调试头痛,还是在 Javascript 中使用它有很好的用途?这让我想起了糟糕的全球变化。

如果大多数情况下您不想在一个实例中更改属性,并且在所有其他实例中它也更改,那么您是否只使用原型来声明函数?

你不应该在原型中分配变量(实际上你正在用自定义对象覆盖Animal.prototype)。要定义实例属性,请在构造函数中定义它们,如下所示:

function Animal(name) {
    this.name = name;
    this.color = null;
}
// Instance method
Animal.prototype.walk = function() {};
// Static method
Animal.run = function() {};

// Inheritance example
function Dog(name) {
    // Call parent constructor
    Animal.apply(this, arguments);
}
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.walk = function() {
    // Call parent method (if you need)
    Animal.prototype.walk.apply(this, arguments);
    ...
};

恕我直言,您误解了原型的行为。
解释所有内容将是一个太长的故事,但是关于在原型上设置的属性,如果更改实例上的属性就是更改所有实例的此值,则确实是一个无限的错误来源。
看看,使用jsfiddle,jsbin,cssdeck或任何你喜欢的测试站点,在你的马/狗的例子上:当你改变马的颜色时,狗的颜色不会改变。它仍然是黑色的。

为什么会这样?
当 dog 的实例对属性进行 READ 访问时,它将在原型上搜索,然后在原型的原型上搜索,依此类推,直到我们到达原型链的末端。在找到的第一个值上,将返回该值,如果在没有原型上找不到属性,则返回 undefined。
因此,在创建实例后,所有动物颜色都将为空。
现在,如果您对实例的属性执行 WRITE 访问权限,则会在此实例上创建一个全新的属性,并为其分配提供的值。这样,原型将保留其引用函数,并且实例将获得其值。救济。

在原型上定义属性仍然很有用,因为:
1) 确保所有实例都将为这些属性提供有效值(默认值)。
2)您可以通过分配一次在所有实例(静态)之间共享的属性(更重要的是:一个大对象)来节省内存。Expl :狗的默认图像。
3) 在某些情况下,您可能希望一次更改所有实例的值。在这种情况下,更改原型上的值就可以做到这一点。
4)你允许JavaScript解释器通过将一个类(C++经典的含义)与你的JavaScript类相关联来优化你的代码:当你分配原型上存在的属性时,类不会被"破坏",JS解释器可以继续使用后台类来表示实例。如果性能很重要,那是非常重要的一点。如果没有,就算了:-)

所以只是一个小例子:如果你定义一个动物,那么每个实例都有它的颜色和名称:在构造函数中而不是在原型上设置它们是有意义的。但是对于腿计数,例如,它不会从一个实例更改为另一个实例,如果您创建子类,则始终可以更改子原型上的值,以使子类更改此计数:

function Animal(name, color) {
    this.name = name;
    this.color = null;
}    
Animal.prototype.legCount = 4; // most animal have 4 legs (...)
// Dog Class, inheriting from Animal
function Dog(name) {    Animal.apply(this, arguments);    }
// Set Animal as Dog's prototype's prototype.
// so that we can safely change Dog's prototype.
Dog.prototype = Object.create(Animal.prototype); 
// Duck class
function Duck(name) {    Animal.apply(this, arguments);    }
// same inheritance scheme
Duck.prototype = Object.create(Animal.prototype);
// ... but we change the legCount, for the ducks only
Duck.prototype.legCount = 2;

高级答案:

如果您希望原型属性保持不变,即使如果我们尝试在实例上为此属性赋值,请将它作为原型上的只读属性。

Object.defineProperty(Duck.prototype,'legCount', { get : function() { return 2 } } );
var duck = new Duck(...) ;
duck.legCount = 5;
console.log (duck.legCount) ; // --> output is still 2

如果您希望在实例上设置值,请将值更改为所有实例(不显式更改原型的属性值),然后 在原型属性定义的设置器中完成它。

 // using a closure
 var duckLegCount = 2;
 Object.defineProperty(Duck.prototype,'legCount', { 
                            get : function()   { return duckLegCount } ,
                            set : function (x) { duckLegCount = x    }    } );
 var duck1 = new Duck(...);
 var duck2 = new Duck(...);
 duck1.legCount = 12;
 console.log ( duck2.legCount ) ;  // output is 12