【问题标题】:Combining prototype and "class" inheritance in JavaScript在 JavaScript 中结合原型和“类”继承
【发布时间】:2013-06-18 21:56:15
【问题描述】:

在我的 JavaScript 应用程序中,我有几种对象,包括 QueryDocumentPosting。文档包含一堆关于文档的元数据(标题、摘要等);一个 Posting 表示某个 Query 在某个级别检索到的某个 Document。同一个 Document 可能被多个 Query 检索,因此可能有多个 Posting 与之关联。

该应用程序有两个相关视图:一个显示给定查询的所有过帐,另一个显示所有文档。由于用户可以以几乎相同的方式与这两个视图交互,我决定通过将 Document 实例作为一个或多个 Posting 实例的原型来使用原型继承。这样,Posting 继承了 Document 的所有元数据,并且只添加了自己的 Query 引用和排名值。像魅力一样工作。

除了数据(查询、排名)之外,帖子还带有一些行为。我想排除这种行为,这样我就不会创建数千个相同的方法。在 Document 的情况下,我只是将我所有的函数移到了Document.prototype。但是在 Postings 的情况下,我不能这样做,因为每个 Postings 都有不同的原型(其各自的文档)。虽然我可以将 Posting 使用的一些方法放入 Document.prototype 并以这种方式获取它们,但其中一些方法是多态的,需要针对 Document 和 Posting 以不同的方式实现。

我的问题是这样的:如果我在 Posting 中使用原型继承来携带 数据,我是否还能以某种方式排除 行为,以便我重用相同的方法函数实例对于我所有的 Posting 实例,而不是每次创建新的 Posting 时都创建一批新的方法?

function Document(id, title) {
    this.id = id;
    this.title = title;
}

// Here prototype is used to factor out behavior
Document.prototype.document = function() { return this; }
Document.prototype.toString = function() { return 'Document [' + this.id + ']: "' + this.title + '"'; }


function Posting(query, rank) {
    this.query = query;
    this.rank = rank;

    // Can I move these two definitions out of the Posting instance to avoid
    // creating multiple copies of these functions as I construct 1000s of Postings?
    this.document = function() { return Object.getPrototypeOf(this); }
    this.toString = function() { return this.document().toString() + ' at rank ' + this.rank; }
}

function createPosting(query, document, rank) {
    // here prototype is used to share data
    Posting.prototype = document;
    var posting = new Posting(query, rank);
    return posting;
}

更新

我在上面的代码示例中犯了一个错误:进行继承的正确方法(如下面的 cmets 所指出的)是首先设置原型:Posting.prototype = document; 我认为我的其余问题仍然有效 :-)

【问题讨论】:

  • posting.prototype = Document; 这有什么意义? posting 是一个对象,prototype 属性没有任何特定含义。您不是以这种方式设置继承。只有 functionsprototype 属性是特殊的。一般来说,我不得不说,让 Posting 实例从特定的 Document 实例继承听起来不像是对继承的正确使用。也许你更在寻找作文? IE。 Posting 有一个 Document,而不是 Posting is a Document。
  • 如果这是在浏览器中,我会选择不同的名称,因为Document 已经有意义。
  • @CrazyTrain 你的意思是document(小写D)还是一般的意思?像那样“文档”的概念在浏览器中有意义吗?我不确定是否有一些我不知道的神奇Document 对象:)
  • @Ian:Document 是浏览器中的构造函数。 document instanceof Document; // true
  • @Gene:是的,我明白了,但正如我所说,只有 函数 具有特殊的 prototype 属性。如果将prototype 属性分配给一个对象,它只是一个名为“原型”的任意属性。没有建立继承。

标签: javascript inheritance prototype


【解决方案1】:

很难在浏览器中测试 Javascript 的内存使用情况...正如 Gene 评论中指出的那样,.bind() 会复制该函数,因此 Parthik Gosar 的解决方案不会节省任何内存。此外,它仍然引用了在大多数浏览器中并未真正作为引用处理的函数。所以即使没有 bind() 函数,这也会消耗大量内存。

这种方法应该会节省很多内存...

var staticFcts = {
    'document': {
        'document': function() { return this; },
        'posting': function() { return Object.getPrototypeOf(this); }
    },
    'toString': {
        'document': function() { return 'Document [' + this.id + ']: "' + this.title + '"'; },
        'posting': function() { return this.document().toString() + ' at rank ' + this.rank; }
    }   
};

var myOwnProxy = function(method) {  
    var c = staticFcts[method][this.typeOf].bind(this);
    return c();
}



function Document(id, title) {
    this.id = id;
    this.title = title;
}

Document.prototype.document = function() { var p = myOwnProxy.bind(this,'document');     return p.call(); };
Document.prototype.toString = function() { var p =     myOwnProxy.bind(this,'toString');return p.call(); }; 
Document.prototype.typeOf = 'document'; // default type is 'document', this is necessary
// because there is no way in Javascript to determine the class name.
// Unfortunantely "Proxy" (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy)  is not widely supported either.

function Posting(query, rank) {
    this.query = query;
    this.rank = rank;
}

function createPosting(query, document, rank) {
    Posting.prototype = document;
    var posting = new Posting(query, rank);
    posting.typeOf = 'posting'; //overwrite type

    return posting;
}


var docs = [];
var postings= [];

var now = new Date().getTime();

// memory and performance test
for($i=0;$i<100000;$i++) {
 var d = new Document(1,'document1');
 var p = createPosting('query1', d, 1);
    docs.push(d);
    postings.push(p);
}
console.log('needed time', ((new Date().getTime())-now)/1000 + ' sec');

function roughSizeOfObject( object ) {

    var objectList = [];
    var stack = [ object ];
    var bytes = 0;

    while ( stack.length ) {
        var value = stack.pop();

        if ( typeof value === 'boolean' ) {
            bytes += 4;
        }
        else if ( typeof value === 'string' ) {
            bytes += value.length * 2;
        }
        else if ( typeof value === 'number' ) {
            bytes += 8;
        }
        else if
        (
                typeof value === 'object'
            && objectList.indexOf( value ) === -1
        )
        {
            objectList.push( value );

            for( i in value ) {
                stack.push( value[ i ] );
        }
        }
    }
    return bytes;
}   

console.log('memory usage: ', roughSizeOfObject(docs) + roughSizeOfObject(postings));

西蒙

【讨论】:

  • roughSizeOfObject() 是如何计算成员函数的?
  • 这只是对真实大小的猜测。在javascript中,一切都是对象,成员函数也被视为对象。但如前所述,实际上不可能测量正确的内存使用情况。您可以做的是使用 toString 方法获取该方法的 javascript 代码并将其添加为内存大小。例如:arguments.callee.toString().length 或通过 .prototype.toString()..但是在这种情况下 toString 方法被覆盖,所以这不会按预期工作!
【解决方案2】:

为什么不把函数保存在 Posting 的构造函数之外。

function Document(id, title) {
    this.id = id;
    this.title = title;
}

// Here prototype is used to factor out behavior
Document.prototype.document = function() { return this; }
Document.prototype.toString = function() { return 'Document [' + this.id + ']: "' + this.title + '"'; }

function PostingToString()
{
  return this.document().toString() + ' at rank ' + this.rank;
}
function PostingDocument ()
{
    return Object.getPrototypeOf(this);
}
function Posting(query, rank) {
    this.query = query;
    this.rank = rank;

    this.document = PostingDocument; 
    this.toString = PostingToString;
}


function createPosting(query, document, rank) {
    var posting = new Posting(query, rank);
    // here prototype is used to share data
    posting.prototype = document;
    return posting;
}

【讨论】:

  • 是的,我在考虑这个折衷方案。一个问题:PostingDocumentPostingToString 函数是否会绑定到正确的对象(这样this 指的是具体实例)?
  • 这种方法有一个限制:为了保证PostingDocumentPostingToString被正确的this值调用,bind()需要被调用。但是bind() 复制了调用它的函数,从而避免了必须指定一次代码所带来的节省。当这些函数被 knockoutjs 事件调用时,就会出现此问题。
猜你喜欢
  • 1970-01-01
  • 2013-07-17
  • 1970-01-01
  • 2018-05-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-16
相关资源
最近更新 更多