原型构造函数和私有属性的区别

3

考虑第一个情况:

function f(){
       console.log("inside the function : " + f.myVar);
}
f.prototype.myVar = 1;
var myObject = new f();
console.log("from the object : " + myObject.myVar);

这是输出结果:

inside the function : undefined
from the object : 1

问题:为什么我的变量 myVar 在函数中不可用?如果它存储在对象原型中,应该可以在 f() 中访问。

现在考虑这种情况:

function f(){
       console.log("inside the function : " + this.myVar);
}
f.prototype.myVar = 1;
var myObject = new f();
console.log("from the object : " + myObject.myVar);

输出结果如下:

inside the function : 1
from the object : 1

问题:为什么我得到了不同的结果?如果'this'指的是对象,那么f.myVar不意味着访问myObject中的myVar吗?

现在看这种情况:

function f(){
       console.log("inside the function : " + f.myVar);
       console.log("inside the function using 'this' : " + this.myVar);
}
f.myVar = 1;
var myObject = new f();
console.log("from the  object : " + myObject.myVar);

输出:

inside the function : 1
inside the function using 'this' : undefined
from the object : undefined

如果我不使用原型来设置属性,它就不应该在实例化的对象中可用。但是如果我像这样编写脚本,它会产生奇怪的结果:

function f(){
       console.log("inside the function : " + f.myVar);
}
f.myVar = 1;
var myObject = new f();
console.log("from the first object : " + myObject.myVar);
var strangeObject = myObject.constructor;
console.log("from the strange object : " + strangeObject.myVar);

输出:

inside the function : 1
from the first object : undefined
from the strange object : 1

“f.myVar”存储在哪里?这是谁的变量?我不明白上述所有情况之间有什么区别。非常感谢您提供完整的澄清。
编辑:主要问题是我不知道这到底意味着什么。
function f(){}
f.someVar = someValue;

因为在其他语言中,函数是一个抽象的概念,在被调用之前实际上并不存在。而在JS中,函数默认情况下被认为是对象。那么根据上面的脚本,我应该有一个像这样的对象:

{someVar : sameValue}

实际上,我认为这应该与以下内容相同:
function f(){this.someVar = someValue;} //should create {someVar : someValue}

如果是这种情况,通过调用"new f()"实例化的每个对象都必须包含这个"someVar",但实际上并没有。

1
老问题了,欢迎来到 JS 中的 OOP。但你很幸运,现在你可以采用 ES6 类 :D - Leo
@CS,首先和主要的是,当你使用new f()创建对象时,只需设置原型f.prototype,如果在代码中添加someVar属性而不是简单的f.prototype,则此属性不会添加到创建的对象中。 - Grundy
@Grundy 是的,我也看到它不行。但我的问题是为什么不行呢?! - CS.
因为直接添加到函数对象的属性,在我们创建对象并将此函数用作构造函数时不会被使用,在这种情况下,只使用原型中的属性。 - Grundy
所以,正如我所说:f.myVar保存在函数对象中,并且用于继承的函数有两种类型:第一种保存在f.prototype中,第二种是当f作为构造函数调用时创建的,例如new f() - Grundy
显示剩余4条评论
4个回答

2

首先,让我们看一下“原型(prototype)”这个词的定义。我认为在讨论JavaScript中如何创建新对象时,记住这点很重要。

原型(pro·to·type)

名词

  1. 某物的第一个、典型或初步模型,尤指机器,其他形式都是从它们开发或复制而来。

动词

  1. 制作(产品)的原型。

原型就是从中复制出另一种形式的模型。

当你在JavaScript中创建一个新对象时,正是这样的过程。

var obj = new MyObject();

在上面的代码中,有许多事情发生了,但在问题的背景下,有两件事情是相关的:
  1. 原型被应用于一个新对象。
  2. 函数MyObject被调用,并将this设置为新对象。
有了这个知识,让我们来看一下你所描述的不同形式的变量设置:
function MyObject() {}
MyObject.myProperty = 'MyProperty';

重要的是要明白,在JavaScript中,函数本身就是对象。因此,function MyObject是一个对象实例。在第二行中,我们已经将myProperty属性设置到这个函数对象上。
回顾上面的创建步骤,你会发现它并没有包括将函数对象的属性应用于新实例对象。它只应用来自函数对象原型的属性,然后使用this运行函数体,这里的this被设置为新实例。
function MyObject() {
    this.myProperty = 'MyProperty';
}

在这里,属性myProperty被设置在个体实例上。
function MyObject() {}
MyObject.prototype.myProperty = 'MyProperty';

在这个例子中,每个新的MyObject实例将被赋予自己的属性myProperty,并且该值设置为'MyProperty'。从那里开始,每个实例都可以更改自己的myProperty为任何它需要的值,而不会影响其他实例。

function MyObject() {
    console.log('myProperty', this.myProperty); //Will output 'Outside constructor.'
    this.myProperty = 'Inside constructor.';
    console.log('myProperty', this.myProperty); //Will output 'Inside constructor.
}

MyObject.prototype.myProperty = 'Outside constructor.';

上面的例子展示了如何首先从原型中应用myProperty,然后被运行的函数中应用的值覆盖。
让我们看一个包含你提到的所有形式的例子:

var output1 = document.getElementById('output1'),
    output2 = document.getElementById('output2'),
    output3 = document.getElementById('output3');

function MyObject(myProperty) {
  this.myProperty = myProperty;
}

MyObject.myProperty = 'Function property.';

MyObject.prototype.myProperty = 'Prototype property.';


var obj = new MyObject('Constructor property');

output1.innerHTML = obj.myProperty;
output2.innerHTML = MyObject.myProperty;
output3.innerHTML = MyObject.prototype.myProperty;
<div id="output1"></div>
<div id="output2"></div>
<div id="output3"></div>

在上面的例子中,您可以看到如何引用每个内容。现在仔细观察一下。当您从两个不同的对象实例设置“函数属性”时,看看会发生什么:

var output1 = document.getElementById('output1'),
    output2 = document.getElementById('output2');


function MyObject() {
  //We are concatenating a string to the end of the property on each function call.
  MyObject.myProperty += ' test ';
}

MyObject.myProperty = 'Function property.';


var obj1 = new MyObject();
var obj2 = new MyObject();

output1.innerHTML = MyObject.myProperty;
output2.innerHTML = MyObject.myProperty;
<div id="output1"></div>
<div id="output2"></div>

上面的代码演示了函数级属性如何有效地共享。这是因为它不是每个实例的一部分,而是函数对象的一部分。
在这里,我将向您展示使用“new”运算符时发生的过程,而不实际使用“new”运算符:

var output = document.getElementById('output');


//Let's have an object that has a prototype property with some properties:
var MyPrototypeObject = {
  prototype: {
    myPrototypeProperty: 'Prototype property'
  }
};

//Let's specify a function that will be used as a constructor:
function MyConstructorFunction() {
  this.myInstanceProperty = 'Instance property';
}


//First, new creates an object
var obj = {};

//Next, it applies all the properties from the prototype. We are using the MyPrototypeObject's prototype property for this example
for (var key in MyPrototypeObject.prototype) {
  var val = MyPrototypeObject.prototype[key];
  
  //Set the same property on the new object.
  obj[key] = val;
}

//Now the prototype has been applied, let's apply the constructor function that was called.
MyConstructorFunction.call(obj); //This calls MyConstructorFunction with this set to obj.

output.innerHTML = 'myPrototypeProperty: ' + obj.myPrototypeProperty + '<br>' + 'myInstanceProperty: ' + obj.myInstanceProperty;
<div id="output"></div>


谢谢,但我仍然不知道someVar存储在哪里以及作为什么?如果它成为函数“object”的一部分,那么当我们实例化一个新对象时,新对象应该具有所有函数的属性,如“someVar”。请阅读我的编辑的最后一部分。 - CS.
@CS。我尝试重写答案以使其更清晰。你能再看一下,看看这是否解决了你的问题? - crush
是的,谢谢,问题已经解决了,因为其他人也帮忙了。我点赞你的好答案,但接受guffa的。 - CS.

1

为什么我的变量在函数中不可用?如果它存储在对象的原型中,应该可以在 f() 中访问。

它在函数中是可访问的,但不是作为 f.myVar,而是作为 this.myVarf.prototype.myVar

为什么我得到了不同的结果?如果'this'指向对象,那么f.myVar不是表示访问myObject中的myVar吗?

函数 f 不同于对象实例。函数是对象的构造函数,使用它和 new 关键字创建一个实例,该实例是与函数不同的单独的对象。

当您使用 f.var 时,那是函数对象的属性 var。当您在函数中使用 this.var 时,那是使用 new 关键字创建的对象实例中的属性 var

如果你使用 f.var,那是构造函数对象的属性,因此即使创建多个实例对象,它也将是相同的变量,并且只能使用 f.var 访问。
如果你使用 f.prototype.var,那也将是一个对于所有实例对象都一样的变量,但是它可以使用 this.var 访问,因为对象继承了原型的成员。
例如:
function f() {
  console.log(f.var); // shows "42"
  console.log(f.prototype.var); // shows "0.01"
  console.log(this.var); shows "0.01";
}

f.var = 42;
f.prototype.var = 0.01;

如果您想要一个仅限于每个对象实例的局部变量,那么您不应该使用上述任何一种方法。您应该将值分配给“this.var”,这将使其成为对象实例中的属性。
例如:
function f(value) {
    this.var = value;
}

f.prototype.getValue = function(){
  return this.var;
};

var instance1 = new f(42);
var instance2 = new f(0.01);

// now you have two instances with separate values:

console.log(instance1.getValue()); // shows "42"
console.log(instance2.getValue()); // shows "0.01"

@CS:strangeObjectf相同,因为构造函数是用于创建对象的函数。使用strangeObject.var与使用f.var相同。 - Guffa
现在我比以前更喜欢C语言了。因为我总是想知道背后发生了什么,而不仅仅是“考虑”成为“某物”。所以,如果我们想用C的方式来表达它,我们可以说函数是指向数据结构的指针,奇怪的对象也是如此,它们指向相同的位置吗? - CS.
@CS:是的,对象(包括函数)的引用与C语言中的指针类似。fstrangeObject都是对类型为“Function”的同一对象的引用。 - Guffa
如果我说: "f =指向新对象的指针,如{var1:val1,var2:val2,.....,function_body {OUR CODE},.....}",并且strangeObject = f(也是指针)..这个考虑正确吗?因此,我可以说函数是具有键值对和特殊键值对的对象,函数体在调用“new f()”时执行,并返回此新对象的地址?这样,函数对象属性和函数实例属性之间的边界就清晰了。 - CS.
是的,我提到了“new f()”返回新对象的地址。 - CS.
显示剩余5条评论

0

关于编辑中的问题,实际上非常简单 :-)

你有一个函数对象function f(){}

你可以像在JavaScript中添加其他对象属性一样向该对象添加属性f.someVar = 1

这与function f(){this.someVar = someValue;}不同,因为JavaScript中的this取决于函数调用的方式,可以引用创建的对象、全局对象或使用callapply函数时的其他内容。

当你使用new运算符创建对象时 - 你将f作为构造函数调用,在这种情况下,函数内部的this指的是创建的对象,并且所有在函数内部添加的属性,如this.something = val都会添加到创建的对象中。

注意:你没有直接使用函数中的任何属性,因此这些属性不会被添加到创建的对象中。

关于原型:prototype:当您创建对象时,只需将创建的对象的prototype属性设置为f.prototype对象即可。因此,在创建对象时,您不会直接使用添加到函数对象的任何属性,而是使用原型中的属性以及在构造函数中手动添加到this的属性。

你说函数就像一个对象,我们可以给它添加属性。但是这个对象的一部分将通过使用 new 实例化。我们能否说函数有一些属性(包括我们添加的,比如 'f.myVar'),还有一个“代码”部分(我们编写的函数体),当使用 'new' 关键字时执行并创建一个新对象?而这个“代码”部分与我们添加的其他属性无关? - CS.
@CS. 为什么不行?在函数体内,您也可以使用类似于 function f(){ this.myFromGlobal = f.global;} 的东西。或者您是指其他的东西吗? - Grundy
我的意思是,函数对象大致如下:{var1: val1,var2: val2,.....,function_body:{OUR CODE},......},只有function_body与“实例化对象”相关。其他属性(我们添加的那些)与函数对象本身有关。 - CS.
1
几乎,结构有点不同,但我认为这通常是正确的,当你实例化对象时,只需调用function_body并将创建的对象作为this传递给你的function_body - Grundy
我希望有官方文件来解释这个结构,但很可能就像我说的那样,如果是真的,那么差异是明显的,也不会有问题为什么f.myVar没有实例化。谢谢我的朋友。 - CS.
@CS,我觉得你可以看到规范 - Grundy

0

似乎你对函数和对象的原型感到困惑。

以下是《JavaScript 高级程序设计》一书中的一句话,解释了它们之间的区别:

需要注意的是,原型是如何与构造函数关联(通过其原型属性)以及对象如何具有原型的区别(可以使用 Object.getPrototypeOf 检索)。构造函数的实际原型是 Function.prototype,因为构造函数本质上是函数。它的原型属性将是通过它创建的实例的原型,而不是构造函数自身的原型。


如果我说:“f = {var1: val1,var2:val2,.....,function_body {OUR CODE},.....}”,您认为这个考虑是正确的吗?因此,我可以说函数是具有键值对和一个特殊的键值对作为函数体的对象,当我们调用“new f()”时执行,并返回该新对象的地址?这样,函数对象属性与函数实例属性之间的边界就清晰了。 - CS.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接