Chrome会保留每个对象的构造函数吗?

时间:2014-01-27 14:00:52

标签: javascript google-chrome constructor google-chrome-devtools

在Chrome的JavaScript控制台中:

> function create(proto) {
    function Created() {}
    Created.prototype = proto
    return new Created
  }
undefined

> cc = create()
Created {}

> cc
Created {}

Createdcreate函数的私有函数; create完成后,Created没有(我已知)引用。然而,Chrome可以随时显示该功能的名称,从它创建的对象开始。

Chrome没有按照“天真”的方法实现这一目标:

> cc.constructor
function Object() { [native code] }

> cc.toString()
"object [Object]"

无论如何,我没有在传递给constructor的{​​{1}}参数上设置proto

create

我猜测的一点是,为了> cc.__proto__.hasOwnProperty("constructor") false 机制,JavaScript VM保留在Created。据说instanceof

  

测试一个对象在其原型链中是否具有构造函数的prototype属性。

但是在上面的代码中我输入了instanceof,有效地传递了create()作为原型;因此undefined甚至没有将Created设置为实际prototype。如果我们破解cc.__proto__公开create函数:

,我们就可以验证这一点
Created

现在让我们输入

function create(proto) {
  function Created() {}
  Created.prototype = proto
  GlobalCreated = Created
  return new Created
}

我的问题(所有密切相关):

  1. Chrome的JavaScript引擎究竟保留了什么才能在控制台中使用该对象?它是构造函数,还是只是函数名?

  2. 对于比控制台打印输出更重要的内容,是否需要保留?

  3. 此类保留对内存消耗的影响是什么?例如,如果构造函数(甚至其名称)异常巨大,该怎么办?

  4. 这只是Chrome吗?我已经对Firebug和Safari进行了重新测试,他们的控制台不会以这种方式呈现对象。但是出于其他可能的目的,它们是否仍然保留相同的数据(例如,由于JavaScript VM固有的真正担忧)?

5 个答案:

答案 0 :(得分:12)

晚编辑:

我最近重新审视了这个问题/答案,我想想我已经弄明白为什么chrome似乎"挂在" Created名称。它不是V8独有的东西,但我认为它是V8在幕后工作的结果(我在初步答案中解释的隐藏物体),以及V8需要做什么(符合ECMAScript标准)。

默认情况下,任何函数,构造函数或其他函数共享相同的构造函数和原型链:

function Created(){};
console.log(Created.constructor);//function Function() { [native code] }
console.log(Object.getPrototypeOf(Created));//function Empty() {}
console.log(Created.__proto__);//same as above
console.log(Created.prototype);//Created {}

这告诉我们一些事情:所有函数共享本机Function构造函数,并从用作原型的特定函数实例(function Empty(){})继承。但是,函数的prototype属性必须是一个对象,如果它被称为构造函数,函数将返回(参见ECMAScript standard)。

  

在将Function对象作为新创建的对象的构造函数调用之前,prototype属性的值用于初始化新创建的对象的[[Prototype]]内部属性。此属性具有属性{[[Writable]]:true,[[Enumerable]]:false,[[Configurable]]:false}。

我们可以通过查看Created.prototype.constructor

轻松验证这一点
console.log(Created.prototype.constructor);//function Created() {}

现在,让我们暂时列出V8需要创建的隐藏类,并且可能会创建它以使其符合标准:

function Created(){}

隐藏课程:

  • Object,当然:所有物品的母亲,其中Function是特定的孩子
  • Function:正如我们已经演示的那样,这个原生对象是构造函数
  • function Empty:原型,我们的函数将从中继承
  • Created我们的空函数将继承上述所有

在这个阶段,没有发生任何异常情况,并且当我们返回 Created构造函数的实例时,Created是不言而喻的。由于其原型,功能将被暴露 现在,因为我们正在重新分配prototype属性,你可能会认为这个实例会被丢弃,而且会丢失,但据我所知,这并不是V8将如何处理这种情况。相反,它会创建一个额外的隐藏类,在遇到此语句后,它会简单地覆盖其父级的prototype属性:

Created.prototype = proto;

它的内部结构最终会看起来像这样(这次编号,因为我将在这个继承链中进一步向下引用某些阶段):

  1. Object,当然:所有物品的母亲,其中Function是特定的孩子
  2. Function:正如我们已经演示的那样,这个原生对象是构造函数
  3. function Empty:原型,我们的函数将从中继承
  4. Created我们的空函数将继承上述所有
  5. Created2:扩展上一个班级(Created),并覆盖prototype
  6. 那么为什么Created仍然可见?

    这是一个百万美元的问题,我认为我现在有了答案: 优化

    V8根本无法,也不应该被允许优化Created隐藏类(第4阶段)。为什么?因为覆盖prototype的内容是一个参数。这是无法预测的事情。 V8可能会对优化代码执行的操作是存储隐藏对象4,每当调用create函数时,它都会创建一个新的隐藏类,它会扩展第4阶段,覆盖{{1}具有传递给函数的任何值的属性。

    因此,prototype将始终存在于每个实例的内部表示中。同样重要的是要注意,您可以将Created.prototype属性替换为实际引用prototype实例的属性(具有混乱的原型链,但仍然):

    Created

    对于一个心灵弯曲的人来说怎么样?创作脚本作家,吃掉你的心......

    无论如何,我希望这一滴运动对这里的某些人有所帮助,如果没有,我会对评论作出回应,因此我可能对错误做出更正,或者对此更新的某些部分提出的问题有点不清楚欢迎......


    我会逐个回答问题,但正如你所说,他们都是密切相关的,所以答案重叠到一点。
    在读这篇文章时,请记住我一次性写这篇文章,同时感觉有点发烧。我不是V8专家,而是基于我前段时间在V8内部进行挖掘的回忆。底部的链接是官方文档,当然包含有关该主题的更准确和最新信息。

    发生了什么
    chrome的V8引擎实际所做的是为每个对象创建一个隐藏类,并且该类映射到对象的JS表示。
    或者谷歌的人自己说:

      

    为减少访问JavaScript属性所需的时间,V8不使用动态查找来访问属性。相反,V8会在幕后动态创建隐藏类。

    在你的情况下发生了什么,扩展,从特定实例创建一个新的构造函数并覆盖cc = create(); console.log(Object.getPrototypeOf(cc))//Object {} cc = create(new GlobalCreated); console.log(Object.getPrototypeOf(cc));//Created {} 属性实际上只是你在这个图上看到的内容:

    Google inheritance graph

    隐藏类C0可以被视为标准constructor类。基本上,V8会解释您的代码,构建一组类似C ++的类,并在需要时创建一个实例。每当您更改/添加属性时,您拥有的JS对象都将设置为指向不同的实例。

    Object函数中,这很可能 - 发生了什么:

    create
    1. 右:function create(proto) {//^ creates a new instance of the Function class -> cf 1 in list below function Created(){};//<- new instance of Created hidden class, which extends Function cf 2 function Created.prototype = proto;//<- assigns property to Created instance return new Created;//<- create new instance, cf 3 for details } 是本机构造。 V8的工作方式意味着所有函数都引用了一个Function类。但是,它们间接引用了这个类,因为每个函数都有自己的特性,这些特性在派生的隐藏类中指定。那么,Function应该被视为对create类的引用 或者,如果您愿意,可以使用C ++语法:create extends HiddenFunction
    2. class create : public Hidden::Function{/*specifics here*/}函数引用与Create相同的隐藏函数。但是,在声明它之后,该类接收1个专有属性,称为create,因此创建了另一个隐藏类,指定了此属性。这是构造函数的基础。因为prototype的函数体,所有这些都发生了,这是一个给定的,V8可能会足够聪明,无论如何事先创建这些类:在C ++伪代码中,它看起来类似编码下面的清单1 每个函数调用都会将对上述隐藏类的新实例的引用分配给create名称,该名称是Created范围的本地名称。当然,返回的create实例仍然保留对此实例的引用,但这是JS范围的工作方式,因此这适用于所有引擎...想想闭包,你就是这样。我明白我的意思(我真的很难忍受这种讨厌的发烧......抱歉唠叨这一点)
    3. 在这个阶段create指向这个隐藏类的一个实例,它扩展了一个扩展类的类(我试图在第2点解释)。当然,使用Create关键字会触发new类定义的行为(因为它是JS语言结构)。这会导致创建一个隐藏类,对于所有实例可能都是相同的:它扩展了本机对象,并且它具有Function属性,该属性引用了我们constructor的实例刚刚制作完成Created返回的实例尽管都是一样的。当然,它们的构造函数可能具有不同的create属性,但它们生成的对象看起来都是一样的。我相当确信V8只会为对象prototype返回创建一个隐藏类。我不明白为什么实例应该需要不同的隐藏类:它们的属性名称和count是相同的,但每个实例引用另一个实例,但那是
    4. 的类

      无论如何:第2项的代码清单,隐藏类术语中create可能看起来像的伪代码表示:

      Created

      忽略任何(可能的)语法怪异(自从我编写一行C ++以来已经超过一年),忽略名称空间和古怪的名称,列出的类除了//What a basic Function implementation might look like namespace Hidden {//"native" JS types class Function : public Object { //implement new keyword for constructors, differs from Object public: Function(...);//constructor, function body etc... Object * operator new ( const Function &);//JS references are more like pointers int length;//functions have a magic length property std::string name; } } namespace Script {//here we create classes for current script class H_create : public Hidden::Function {}; class H_Created : public Hidden::Function {};//just a function class H_Created_with_prototype : public H_Created {//after declaring/creating a Created function, we add a property //so V8 will create a hidden class. Optimizations may result in this class // being the only one created, leaving out the H_Created class public: Hidden::Object prototype; } class H_create_returnVal : public Hidden::Object { public: //the constructor receives the instance used as constructor //which may be different for each instance of this value H_create_returnVal(H_Created_with_prototype &use_proto); } } 之外有效为了运行代码而需要创建的所有隐藏类。然后,您的所有代码都会分配对这些类的实例的引用。课程本身并没有在记忆中占用太多空间。任何其他引擎都会创建尽可能多的对象,因为它们也需要符合ECMAScript规范 所以我想,就像这样看待它,这类问题回答了你所有的问题:不是所有的引擎都不是这样的,但是这种方法不会导致大量的内存被使用,是的,这确实意味着很多保留所有对象的信息/数据/参考,但这只是一种不可避免的,在某些情况下这种方法的副作用很快。
      更新 :我做了一些挖掘,并找到了一个如何使用模板将JS函数添加到V8的示例,它说明了V8如何将JS对象/函数转换为C ++课程,see the example here

      这只是我的推测,但我不会惊讶于了解V8的工作方式,而且这种保留业务一般用于垃圾收集和内存管理(EG:删除属性)改变隐藏的类等)
      例如:

      Hidden::Function

      最后一点只是我的猜测,但可能可以让GC执行此操作。

      的保留期是什么? 正如我所说,在V8中,JS对象实际上是指向C ++类的一种指针。访问属性(这也包括数组的神奇属性!)很快。真的,真的很快。理论上,访问属性是 O(1)操作 这就是IE上的原因:

      var foo = {};//foo points to hidden class Object instance (call id C0)
      foo.bar = 123;//foo points to child of Object, which has a property bar (C1)
      foo.zar = 'new';//foo points to child of C1, with property zar (C2)
      delete foo.zar;//C2 level is no longer required, foo points to C1 again
      

      快于:

      var i,j;
      for(i=0,j=arr.length;i<j;++i) arr[i] += j;
      

      在Chrome上,for (i=0;i<arr.length;++i) arr[i] += arr.length; 更快as shown her。我也回答了这个问题,它也包含了一些你可能要检查的V8的细节。可能是因为我的答案不再(完全)适用,因为浏览器及其引擎变化很快......

      内存怎么样
      不是一个大问题。是的,有时候Chrome可能会占用一些资源,但JS并不总是应该受到责备。编写干净的代码,大多数浏览器的内存占用量都不会太大 如果你创建了一个巨大的构造函数,那么V8将创建一个更大的隐藏类,但是如果该类已经指定了很多属性,那么它们需要额外隐藏类的可能性会更小。
      当然,每个函数都是arr.length类的一个实例。这是函数式语言中的本机(和非常重要)类型,无论如何都很可能是一个高度优化的类。
      无论如何:就内存使用而言:V8在管理内存方面做得非常好。例如,远远超过IE的旧版本。以至于V8引擎用于服务器端JS(如在node.js中),如果内存确实存在问题,那么你就不会梦想在必须启动和运行的服务器上运行V8。现在,你好吗?

      这只是Chrome
      是的,在某种程度上。 V8确实特别关注它如何使用和运行JS。它不是将代码编译为字节码并运行它,而是将AST直接编译成机器代码。再次,就像隐藏类的诡计一样,这是为了提高性能 我知道我在CR的答案中包含了这个图表,但仅仅是为了完整性#39;清酒:这是一张显示chrome(底部)和其他JS引擎(顶部)之间差异的图表 Bytecode vs machine-code

      请注意,在字节码指令和CPU下面,有一个(橙色)解释器层。由于JS被直接翻译成机器代码,因此V8不需要这样做。
      缺点是这使得某些优化更难做,特别是关于在代码中使用DOM数据和用户输入的那些(例如:Function)并且代码的初始处理更难CPU。
      好处是:一旦将代码编译成机器代码,它就是您将获得的最快速度,并且运行代码可能会减少开销。大多数时候,CPU上的字节码解释器比较重,这就是为什么FF和IE上的繁忙循环会导致浏览器提醒用户&#34;正在运行的脚本&#34; 问他们是否要阻止它。

      more on V8 internals here

答案 1 :(得分:4)

我对Chrome的内部结构知之甚少,所以这只是猜测,但在我看来,Chrome正在对创建该功能的代码执行某种静态分析,并将其存储以用于调试目的。 / p>

看一下这个例子:

> function create(proto) {
    object = {}
    object.x = {}
    x = object.x
    x.func = function() {}
    x.func.prototype = proto
    return new object.x.func
}
undefined
> create()
x.func {}

x.func? JavaScript没有任何内置方式可供您访问函数最初分配给的变量的名称。 Chrome 必须这样做是出于其自身原因。

现在看看这个例子:

> function newFunc() {
  return function() {}
}

> function create(proto) {
    object = {}
    object.x = {}
    x = object.x
    x.func = newFunc()
    x.func.prototype = proto
    return new object.x.func
}
undefined
> create()
Object {}

在这个例子中,由于我们在将函数分配给变量之前在单独的闭包中创建了函数,因此Chrome不知道函数的“名称”,所以它只是说“对象”。


这些例子让我猜测你问题的以下答案:

  

Chrome的JavaScript引擎究竟保留什么才能在控制台中进行对象演示?它是构造函数,还是只是函数名?

它执行代码的静态分析,并在某处存储包含函数“name”的字符串。

  

对于比控制台打印输出更重要的东西,是否需要保留?

可能不是。

  

此类保留对内存消耗的影响是什么?例如,如果构造函数(甚至它的名称)异常巨大会怎么样?

我不确定,但我猜它不太可能成为一个问题。由于函数的名称是使用静态分析确定的,因此函数名称的潜在大小受创建它的脚本中变量名称的大小限制(除非您使用eval,在这种情况下我我不确定。

  

只是Chrome吗?我已经对Firebug和Safari进行了重新测试,他们的控制台不会以这种方式呈现对象。但是,出于其他可能的目的,它们是否仍然保留相同的数据(例如,由于JavaScript VM固有的真正关注)?

我对此表示怀疑,这似乎是Chrome用于使调试更容易一些的特定内容。据我所知,这样的功能没有其他原因可以存在。

答案 2 :(得分:1)

免责声明:我不是Google Chrome专家,但我认为这些不是浏览器特定的,可以通过基本的Javascript规则来解释。

  

Chrome的JavaScript引擎究竟保留了什么呢?   控制台中的对象呈现工作?是构造函数吗?   功能,还是只是功能名称?

Javascript中的每个对象或函数都有自己的继承链,一直到基本原型。

你不能通过将prototype属性设置为undefined来避免这种情况,尽管它可能看起来像是来自控制台输出。

所以它是由于继承而保留的整个构造函数,尽管不能通过全局范围访问。

  

对于比控制台更重要的东西,是否需要保留   打印输出?

是的,原型继承系统需要它才能工作。

  

此类保留对内存消耗的影响是什么?如果,   例如,构造函数(甚至其名称)异常   巨大?

是的,如果使用不当,可能会导致内存泄漏。

这就是为什么你应该总是删除和清理未使用的变量,这样它们及其原型就可以被垃圾收集器收集。

  

只是Chrome吗?我已经重新测试了Firebug和Safari,他们的   控制台不会以这种方式呈现对象。但他们仍然保留   相同的数据,用于其他可能的目的(例如由于真实的原因)   关注JavaScript VM固有的问题)?

这应该在所有浏览器中以相同的方式工作,因为原型继承的工作原理相同。然而,我没有专门测试它。请注意,控制台输出int浏览器可能会有所不同,这并不意味着什么,因为每个浏览器都可以以自己的方式实现其控制台。

答案 3 :(得分:0)

(jpg|png|bmp)

答案 4 :(得分:-6)

将新实例从create返回到名为Created的对象。

create()()
> TypeError: object is not a function

如果你要删除'new'关键字,那么你会将Created函数暴露给调用者的范围。