我试图了解Javascript幕后的景象,并且卡在理解内置对象(尤其是对象和函数)及其之间的关系的创建方面。
它很复杂,很容易造成误解,并且许多初学者的Javascript书籍都弄错了,所以不要相信您阅读的所有内容。
我是1990年代Microsoft JS引擎的实现者之一,并且是标准化委员会的成员,在将这个答案汇总在一起时,我犯了很多错误。(尽管自从我从事这项工作已有15年以上,我也许可以原谅。)这是一件棘手的事情。但是,一旦您了解了原型继承,一切都将变得有意义。
当我读取到所有内置对象(如Array,String等)都是从Object扩展(继承)时,我假定Object是第一个被创建的内置对象,其余对象都继承自该对象。
首先,丢弃所有有关基于类的继承的知识。JS使用基于原型的继承。
接下来,请确保您对“继承”的含义有一个非常清晰的定义。习惯于C#或Java或C ++等OO语言的人们认为继承意味着子类型化,但是继承并不意味着子类型化。 继承意味着一件事的成员也是另一事物的成员。这并不一定意味着这些事物之间存在子类型关系!类型理论中的许多误解是人们没有意识到存在差异的结果。
但是,当您知道对象只能由函数创建时,然后函数也不过是Function的对象而已,这没有任何意义。
这是完全错误的。有些对象不是通过调用new F
某些函数来创建的F
。JS运行时创建的某些对象一无所有。有些鸡蛋不是任何鸡都产的。它们只是在运行时启动时创建的。
让我们说说规则是什么,也许会有所帮助。
- 每个对象实例都有一个原型对象。
- 在某些情况下,原型可以是
null
。
- 如果访问对象实例上的成员,并且该对象没有该成员,则该对象将遵循其原型,如果原型为null,则停止。
prototype
对象的成员通常不是对象的原型。
- 相反,
prototype
功能对象F 的成员是将成为所创建对象原型的对象new F()
。
- 在某些实现中,实例会获得
__proto__
确实提供其原型的成员。(现在不建议使用。不要依赖它。)
- 函数对象
prototype
在创建时会分配一个全新的默认对象。
- 当然,功能对象的原型是
Function.prototype
。
让我们总结一下。
- 的原型
Object
是Function.prototype
Object.prototype
是对象原型对象。
- 的原型
Object.prototype
是null
- 的原型
Function
是Function.prototype
-这是罕见的情况,Function.prototype
实际上是的原型Function
!
Function.prototype
是函数原型对象。
- 的原型
Function.prototype
是Object.prototype
假设我们创建一个函数Foo。
- 的原型
Foo
是Function.prototype
。
Foo.prototype
是Foo原型对象。
- 的原型
Foo.prototype
是Object.prototype
。
假设我们说 new Foo()
确保有道理。让我们画一下。椭圆是对象实例。边缘的__proto__
含义是“的原型”或prototype
“ prototype
属性”。
运行时所要做的就是创建所有这些对象并相应地分配它们的各种属性。我相信您可以看到将如何完成。
现在,让我们看一个测试您的知识的示例。
function Car(){ }
var honda = new Car();
print(honda instanceof Car);
print(honda.constructor == Car);
该打印什么?
好吧,这instanceof
是什么意思? honda instanceof Car
表示“ Car.prototype
等于honda
原型链上的任何对象吗?”
是的。 honda
的原型是Car.prototype
,所以我们完成了。这打印真实。
那第二个呢?
honda.constructor
不存在,因此我们参考了原型Car.prototype
。当Car.prototype
对象被创建它被自动赋予属性constructor
等于Car
,所以这是真的。
现在呢?
var Animal = new Object();
function Reptile(){ }
Reptile.prototype = Animal;
var lizard = new Reptile();
print(lizard instanceof Reptile);
print(lizard.constructor == Reptile);
该程序打印什么?
同样,lizard instanceof Reptile
意味着“ Reptile.prototype
等于lizard
原型链上的任何对象吗?”
是的。 lizard
的原型是Reptile.prototype
,所以我们完成了。这打印真实。
现在呢
print(lizard.constructor == Reptile);
您可能会认为这也是正确的,因为它lizard
是用构造的,new Reptile
但是您会错的。推理出来。
- 是否
lizard
有一个constructor
属性?否。因此,我们看一下原型。
- 的原型
lizard
是Reptile.prototype
,这是Animal
。
- 是否
Animal
有一个constructor
属性?否。所以我们看它的原型。
- 的原型
Animal
是Object.prototype
,并且Object.prototype.constructor
由运行时创建的并等于Object
。
- 因此,此打印错误。
我们应该Reptile.prototype.constructor = Reptile;
在那儿说些什么,但我们不记得要说!
确保一切对您有意义。如果仍然令人困惑,请绘制一些方框和箭头。
另一个非常令人困惑的事情是,如果我console.log(Function.prototype)
打印了一个函数,但是当我打印时console.log(Object.prototype)
它打印了一个对象。为什么Function.prototype
当它打算成为一个对象时是一个函数?
函数原型定义为一个函数,该函数在调用时返回undefined
。我们已经知道那Function.prototype
是Function
原型,这很奇怪。因此Function.prototype()
是合法的,并且当您这样做时,您会undefined
回来。所以这是一个功能。
该Object
原型没有这个属性; 它是不可调用的。这只是一个对象。
当您console.log(Function.prototype.constructor)
再次使用该功能时。
Function.prototype.constructor
只是Function
,很明显。并且Function
是一个功能。
现在,您如何使用自己创建的东西(思维=吹动)。
您对此考虑过多。所需要做的就是运行时在启动时创建一堆对象。对象只是将字符串与对象相关联的查找表。当运行时启动时,所有它做的是创造了几十个空对象,然后开始分配prototype
,__proto__
,constructor
,等每一个对象的属性,直到他们做他们需要做的图表。
如果您采用我上面提供的示意图并添加constructor
边缘,将会很有帮助。您将很快看到这是一个非常简单的对象图,并且运行时创建它不会有任何问题。
一个好的练习是自己做。在这里,我先开始。我们将使用my__proto__
“的原型对象”和myprototype
“的原型属性”来表示。
var myobjectprototype = new Object();
var myfunctionprototype = new Object();
myfunctionprototype.my__proto__ = myobjectprototype;
var myobject = new Object();
myobject.myprototype = myobjectprototype;
等等。您是否可以填写该程序的其余部分,以构建具有与“真实” Javascript内置对象相同的拓扑的一组对象?如果这样做,您会发现它非常简单。
JavaScript中的对象只是将字符串与其他对象相关联的查找表。而已!这里没有魔术。您之所以陷入困境,是因为您在想象实际上并不存在的约束,例如每个对象都必须由构造函数创建。
函数只是具有附加功能的对象:被调用。因此,通过您的小型仿真程序,.mycallable
向每个对象添加一个属性,以指示该对象是否可调用。就这么简单。
Function.prototype
可以是函数并具有内部字段。因此,不,在遍历其结构时不要执行原型函数。最后要记住,有一个引擎在解释Javascript,因此对象和函数可能是在引擎内创建的,而不是像Javascript和特殊引用那样创建的Function.prototype
,Object.prototype
可能只是由引擎以特殊方式解释。