在浏览器中使用javascript __proto__作为实现算法的一部分

Using javascript __proto__ in browser, naturally, as part of implemented algorithms

本文关键字:实现 算法 一部分 proto javascript 浏览器      更新时间:2023-09-26

考虑下面的代码片段,这里使用javascript原型继承来从DOM元素收集CSS属性。elem数组包含DOM节点对应的对象,每个对象有一个style字段,每个elem[x].style也是一个对象。elem[x].style对象重复DOM树层次结构,通过__proto__连接。rootStyle变量作为所有elem[x].style原型链的根:

var rootStyle = {},
    elem = [{}, {}, {}];
rootStyle.__proto__ = null;
rootStyle.fontSize = '14px';
elem[0].style = {};
elem[0].style.__proto__ = rootStyle;
elem[0].style.fontWeight = 'bold';
elem[0].className = 'my-elem-123';
elem[1].style = {};
elem[1].style.__proto__ = rootStyle;
elem[1].style.fontStyle = 'italic';
elem[1].className = 'my-elem-456';
elem[2].style = {};
elem[2].style.__proto__ = elem[1].style;
elem[2].style.textDecoration = 'underline';
elem[2].className = 'my-elem-789';
...
// later in the code
var cssCode = [],
    i, len = 3;
for(i = 0; i < len; i++) {
    cssCode.push('.' + elem[i].className + '{' + getCssRules(elem[i]) + '}';
}
function getCssRules(elem) {
    var result = [],
        cssProperty, cssValue;
    for(cssProperty in elem.style) {
        // No hasOwnProperty check!
        // Here (and in for..in loop) happens the lookup magic that I need
        cssValue = elem.style[cssProperty];
        result.push(cssValue + ':' + cssProperty + ';';
    }
    return result;
}

正如这里所说:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/proto,我不应该因为性能影响而改变对象的原型。

对象键值查找在内部变成了许多操作,但这是可以接受的,它是一种托管语言,这里的任何操作都有开销。如果你不制作深度疯狂的原型链,那么原型链查找的速度应该与任何"单步"操作的速度相当,我的意思是,接近0(1)的复杂度。如果我的算法自然需要这样的数据结构和这样的行为,这是痛苦的屁股-实现我自己的链查找,或者一些完全不同的解决方案,只是因为"设置__proto__是坏的"。

那么,__proto__有哪些好的/坏的用法呢?

如果我像这样在elem数组中存储对真实DOM节点的引用会发生什么:

elem[0].domNode = document.body.childNodes[0];
elem[1].domNode = document.body.childNodes[1];
elem[2].domNode = elem[1].domNode.childNodes[0];

用自定义原型链连接对象,用通用原型链连接对象,是好是坏,还是无所谓,引擎的优化在这里失败了吗?

添加:好了,现在我都明白了!设置__proto__的所有令人困惑的东西都是关于更改__proto__引用的。它会引发不好的事情(见第一条评论中的链接)。我们真的应该坚持使用公认答案中的语法。首先,我认为,糟糕的事情发生是因为空原型本身,缺乏一些成员,需要javascript引擎进行优化。

那么,__proto__的好/坏用法是什么呢?

不考虑设置 __proto__是否有好的用例,1您不需要在代码中这样做。而不是:

创建没有原型的根目录:

rootStyle = Object.create(null);

使用rootStyle作为原型创建对象:

elem[0].style = Object.create(rootStyle);

将其应用到代码的开头,而不做任何其他更改:

var rootStyle = Object.create(null),          // **
    elem = [{}, {}, {}];
rootStyle.fontSize = '14px';
elem[0].style = Object.create(rootStyle);     // **
elem[0].style.fontWeight = 'bold';
elem[0].className = 'my-elem-123';
elem[1].style = Object.create(rootStyle);     // **
elem[1].style.fontStyle = 'italic';
elem[1].className = 'my-elem-456';
elem[2].style = Object.create(elem[1].style); // **
elem[2].style.textDecoration = 'underline';
elem[2].className = 'my-elem-789';

如果我像这样在元素数组中存储对真实DOM节点的引用会发生什么:

elem[0].domNode = document.body.childNodes[0];
elem[1].domNode = document.body.childNodes[1];
elem[2].domNode = elem[1].domNode.childNodes[0];

elem实例上的原型链的性质与如果您执行上述操作会发生什么问题完全无关。

如果你这样做了,"会发生什么"的答案是:没什么不寻常的(除非你处理的是一个过时的IE版本)。这是对元素的另一个引用,所以如果你在这样做之后删除(比如说)document.body的第一个子元素,而不是被清理,它会一直存在,直到你在elem[0].domNode中清除对它的引用,但这与你维护对任何其他对象的引用没有什么不同(再次,如果我们不谈论过时的IE版本)。


1三个音符:

  1. 如果您认为需要更改现有对象的原型,请退一步查看您的整体设计。这样做是非常不寻常的;从设计的角度来看,您最好创建一个正确类型的新对象,并复制任何相关信息。(在十多年的JavaScript编程生涯中,我从来没有这样做过。如果你用经典的OOP术语[c++, Java]来描述它,它就像在创建对象之后改变对象的类型一样;例如,您创建了一个Cat,但现在您想将其更改为Dog。只是很少有真正需要这样做。

  2. 正如您在MDN上看到的,更改对象的原型并不是JavaScript引擎优化的对象,因此作为一个例外情况,它往往会破坏对象的优化,减慢属性访问。当然,这种放缓是否以某种方式显着将因用例而异;

  3. 在现代JavaScript引擎上,如果你确实有一个真正的用例来改变原型,通过Object.setPrototypeOf而不是分配给__proto__