javascript - Chrome是否保留每个对象的构造函数?

标签 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]"

无论如何,我没有在传递给constructorproto参数上设置create:
> cc.__proto__.hasOwnProperty("constructor")
false

我的一个猜测是,为了实现Created机制,JavaScript VM保留了instanceof。据说 instanceof

tests whether an object has in its prototype chain the prototype property of a constructor.



但是在上面的代码中,我键入了create(),有效地将undefined传递为原型(prototype);因此Created甚至没有将prototype设置为实际的cc.__proto__。如果我们破解create以公开Created函数,我们可以验证这一点:
function create(proto) {
  function Created() {}
  Created.prototype = proto
  GlobalCreated = Created
  return new Created
}

现在让我们输入
> cc = create()
Created {}

> GlobalCreated
function Created() {}

> GlobalCreated.prototype
undefined

> cc instanceof GlobalCreated
TypeError: Function has non-object prototype 'undefined' in instanceof check

我的问题(所有密切相关):
  • Chrome的JavaScript引擎究竟要保留什么才能使该对象在控制台中呈现?是构造函数,还是函数名?
  • 除了控制台打印输出以外,是否还需要其他功能来保留?
  • 这样的保留对内存消耗有什么影响?例如,如果构造函数(甚至是其名称)异常大怎么办?
  • 只是Chrome吗?我已经对Firebug和Safari进行了重新测试,它们的控制台没有那样显示对象。但是它们是否仍出于其他可能的目的(例如,由于JavaScript VM固有的真正关注)保留相同的数据?
  • 最佳答案

    后期编辑:
    我最近重新审视了这个问题/答案,我想我已经弄清楚了为什么chrome似乎“卡在” Created名称上。这并不是V8专有的,但我认为这是V8在幕后工作的方式(我在最初回答中解释的隐藏对象)以及V8要做的事情(符合ECMAScript标准)的结果。 。
    默认情况下,任何函数,构造函数或其他函数都共享相同的构造函数和原型(prototype)链:

    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构造函数,并从用作其原型(prototype)的特定函数实例(function Empty(){})继承。但是,函数的prototype属性必须是一个对象,如果将该函数作为构造函数调用,则该函数将返回(请参阅ECMAScript standard)。

    The value of the prototype property is used to initialise the [[Prototype]] internal property of a newly created object before the Function object is invoked as a constructor for that newly created object. This property has the attribute { [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: false }.


    我们可以通过查看Created.prototype.constructor来轻松地验证这一点:
    console.log(Created.prototype.constructor);//function Created() {}
    
    现在,让我们暂时列出V8为使其符合标准而需要并且可能会创建的隐藏类:
    function Created(){}
    
    隐藏类(class):
  • Object当然是:所有对象的母亲,Function是特定的子对象
  • Function:如我们所展示的,此本地对象是构造函数
  • function Empty:原型(prototype),我们的函数将从该原型(prototype)继承
  • Created我们将从上面所有
  • 继承的空函数

    在这一阶段,没有任何异常发生,并且不言而喻的是,当我们返回此Created构造函数的实例时,Created函数将由于其原型(prototype)而暴露。
    现在,由于我们正在重新分配prototype属性,因此您可能会争辩说该实例将被丢弃并丢失,但是据我所知,这不是V8如何处理这种情况。相反,它将创建一个附加的隐藏类,在遇到此语句后,该类将简单地覆盖其父级的prototype属性:
    Created.prototype = proto;
    其内部结构最终将看起来像这样(这次编号,因为我将在后面进一步提到该继承链中的某些阶段):
  • Object当然是:所有对象的母亲,Function是特定的子对象
  • Function:如我们所展示的,此本地对象是构造函数
  • function Empty:原型(prototype),我们的函数将从该原型(prototype)继承
  • Created我们将从上面所有
  • 继承的空函数
  • Created2:扩展上一个类(Created),并覆盖prototype

  • 那么,为什么Created仍然可见?
    那是百万美元的问题,我想我现在已经回答了:优化
    V8根本不能(也不应该允许)优化Created隐藏类(阶段4)。为什么?因为将覆盖prototype的是一个参数。这是无法预料的。 V8可能最优化代码的方法是存储一个隐藏的对象4,每当调用create函数时,它将创建一个扩展阶段4的新的隐藏类,以传递给函数的任何值覆盖prototype属性。 。
    因此,Created.prototype将始终存在于每个实例的内部表示中的某个位置。同样重要的是要注意,您可以将prototype属性替换为实际上引用了Created实例的实例(但该原型(prototype)链已被破坏):
    cc = create();
    console.log(Object.getPrototypeOf(cc))//Object {}
    cc = create(new GlobalCreated);
    console.log(Object.getPrototypeOf(cc));//Created {}
    
    对于一个弯弯曲曲的人来说怎么样?盗版剧本编剧,全力以赴...
    无论如何,我希望所有这些运球对这里的某个人都有意义,如果没有,我的确会回应评论,所以欢迎更正我可能犯的错误,或对此更新的某些部分不清楚的问题。 ..

    我将尝试逐个问题地回答问题,但是正如您所说,它们都是紧密相关的,因此答案在一定程度上是重叠的。
    在阅读本文时,请记住我一口气写了这篇文章,同时感到有点发烧。我不是V8专家,并且基于我前段时间在V8内部进行一些挖掘的回忆。底部的链接是官方文档,当然会包含有关该主题的更准确和最新的信息。
    怎么回事
    chrome的V8引擎实际上所做的是为每个对象创建一个隐藏的类,并将该类映射到该对象的JS表示形式。
    或就像Google员工自己说的那样:

    To reduce the time required to access JavaScript properties, V8 does not use dynamic lookup to access properties. Instead, V8 dynamically creates hidden classes behind the scenes.


    在您的情况下,进行扩展,从特定实例创建新的构造函数并覆盖constructor属性实际上只不过是您在该图上看到的:

    隐藏类C0可以视为标准Object类。基本上,V8会解释您的代码,构建一组类似于类的C++,并在需要时创建一个实例。更改/添加属性时,您拥有的JS对象将设置为指向不同的实例。
    create函数中,这很可能发生了什么:
    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
    }
    
  • 对:Function是本机结构。 V8的工作方式意味着所有功能都引用了一个Function类。但是,它们间接引用该类,因为每个函数都有自己的规范,这些规范在派生的隐藏类中指定。因此,应将create视为对create extends HiddenFunction类的引用。
    或者,如果您愿意,可以使用C++语法:class create : public Hidden::Function{/*specifics here*/}
  • Create函数引用与create相同的隐藏函数。但是,在声明它之后,该类将获得1个适当的属性,称为prototype,因此将创建另一个隐藏类,并指定此属性。这是构造函数的基础。因为create的函数体(所有这些都发生在这里)是给定的,所以V8可能足够聪明以预先创建这些类,无论如何:在C++伪代码中,它看起来类似于下面的代码 list 1。
    每个函数调用都会将对上述隐藏类的新实例的引用分配给Created名称,该名称在create范围内是本地的。当然,返回的create实例仍然保留对该实例的引用,但这就是JS作用域的工作方式,因此这适用于所有引擎...考虑闭包,您将明白我的意思(我真的挣扎着这令人讨厌的发烧...对此感到抱歉)
  • 在此阶段,Create指向该隐藏类的实例,该实例扩展了扩展类的类(正如我在第2点中所做的解释)。当然,使用new关键字会触发Function类定义的行为(因为它是一种JS语言构造)。这将导致创建一个隐藏类,该隐藏类对于所有实例都可能是相同的:它扩展了本机对象,并且具有constructor属性,该属性引用了我们刚创建的Created实例。尽管create返回的实例都是相同的。当然,他们的构造函数可能具有不同的prototype属性,但是他们推出的所有对象看起来都一样。我非常有信心V8只会为create返回的对象创建1个隐藏类。我看不到为什么实例需要不同的隐藏类:它们的属性名称和计数相同,但是每个实例都引用了另一个实例,但这就是
  • 的类

    无论如何:第2项的代码 list ,用隐藏类术语表示的Created的伪代码表示形式:
    //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);
        }
    }
    
    忽略任何(可能)语法上的古怪(自编写C++以来已经一年多了),并且忽略了 namespace 和古怪的名称,除了Hidden::Function之外,列出的类实际上是所有需要创建的隐藏类运行您的代码。然后,您所做的所有代码就是将引用分配给这些类的实例。类本身不会占用太多内存。任何其他引擎都将创建同样多的对象,因为它们也需要遵守ECMAScript规范。
    因此,我想这样看待您所有的问题:并非并非所有引擎都这样工作,但是这种方法不会导致使用大量内存,是的,这确实意味着很多信息保留了对所有对象的/ data / references,但这只是不可避免的,在某些情况下,这种方法还会带来一些副作用。
    更新:我做了更多的挖掘工作,并找到了一个示例,该示例说明如何使用模板将JS函数添加到V8中,它说明了V8如何将JS对象/函数转换为C++类see the example here
    这只是我的推测,但我完全不会惊讶地了解到V8的工作方式,而且这种保留业务通常在垃圾回收和内存管理中大量使用(例如:删除更改隐藏类和喜欢)
    例如:
    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
    
    最后一点只是我的猜测,但是GC可能会这样做。
    这个保留用于
    就像我说过的那样,在V8中,JS对象实际上是指向C++类的一种指针。快速访问属性(而且还包括数组的魔术属性!)。真的,真的很快。从理论上讲,访问属性是O(1)操作。
    这就是为什么在IE上:
    var i,j;
    for(i=0,j=arr.length;i<j;++i) arr[i] += j;
    
    快于:
    for (i=0;i<arr.length;++i) arr[i] += arr.length;
    
    在chrome上,arr.length更快as shown her。我也回答了这个问题,它也包含您可能要检查的有关V8的一些详细信息。可能是我的答案不再(完全)适用,因为浏览器及其引擎的更改速度很快...
    关于内存
    没什么大问题。是的,Chrome有时可能会占用大量资源,但是JS并非总是应归咎于此。编写干净的代码,在大多数浏览器中内存占用不会有太大差异。
    如果创建了一个巨大的构造函数,则V8将创建一个更大的隐藏类,但是如果该类已经指定了许多属性,则它们需要其他隐藏类的机会就较小。
    当然,每个函数都是Function类的实例。无论如何,这是功能语言中的本机(并且非常重要)类型,很可能是高度优化的类。
    无论如何:就内存使用而言:V8在管理内存方面做得很好。例如,远比旧的IE好得多。如此之多以至于V8引擎用于服务器端JS(就像在node.js中一样),如果确实存在内存问题,那么您就不会梦想在必须启动并运行尽可能多的服务器上运行V8可能,现在可以吗?
    这只是Chrome
    是的,在某种程度上。 V8在使用和运行JS方面确实有特殊之处。而不是JIT将代码编译为字节码并运行,而是将AST直接编译为机器代码。再次,就像隐藏类的欺骗一样,这是为了提高性能。
    我知道我在关于CR的答案中包含了这张图,但是出于完整性考虑:这是一张图,显示了chrome(底部)和其他JS引擎(顶部)之间的差异

    注意,在字节码指令和CPU之下,有一个(橙色)解释器层。这是V8不需要的,这是因为JS直接转换为机器代码。
    不利的一面是,这使得某些优化工作更加困难,特别是在代码中使用DOM数据和用户输入的优化方面(例如:someObject[document.getElementById('inputField').value]),并且代码的初始处理在CPU上更加困难。
    好处是:一旦将代码编译为机器代码,它就会成为最快的代码,运行代码可能会减少开销。字节码解释器通常在CPU上较重,因此FF和IE上的繁忙循环会导致浏览器向用户发出“正在运行的脚本”警报,询问他们是否要停止运行。
    more on V8 internals here

    关于javascript - Chrome是否保留每个对象的构造函数?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21382811/

    相关文章:

    javascript - 为什么在这个代码片段中箭头函数的外部范围是 `showList` 而不是 `forEach` ?

    javascript - 使用javascript重置元标记以防止其刷新页面

    javascript - 下载后获取文件内容

    c++ - 在C++中的父类构造函数中构造成员对象

    java - 在 java 中使用默认构造函数,即使存在参数化构造函数

    javascript - DOM1 JavaScript 不工作

    javascript - 即使使用 HTTPS,WebRTC 屏幕捕获仍然不安全

    asp.net - 如何查看 Web 服务器 http 响应大小(以 KB 为单位)?

    android - Chrome Android - 滚动时处理 CSS 和 JS

    c++ - Cython 和类的构造函数