【翻译】理念:无冲突的扩展本地DOM原型

菜鸟翻译,望大家多多指正哈

原文:http://lea.verou.me/2015/04/idea-extending-native-dom-prototypes-without-collisions/

理念:无冲突的扩展本地DOM原型

正如我昨天在博文中指出,我不喜欢使用jQuery的原因之一是因为它的包装对象。对于jQuery来说,这是一个明智的决定:早在2006年它被第一次开发出来的时候,IE有一个非常讨厌的内存泄漏bug,当我们给一个元素添加属性时它便很容易被引发出来。哦,那时我们还没有在IE浏览器访问元素的原型,所以我们必须手动在每个元素上添加这些属性。Prototype.js试图走这条路但结果却是一团糟:他们打算改变他们之前在Prototype2.0版和依附包装对象的决定。有人曾写过很长的文章来批判企图扩展本地DOM元素是多么典型的错误想法。

  第一个暴露元素原型的IE浏览器是IE8:我们可以访问Node.prototype,Element.prototype和其他几种原型。有些是多变的,有些则不是。在IE9,我们得到了全部,包括HTMLElement.prototype及其后代节点,比如HTMLParagraphElement。内存泄漏bug在IE8时得到了改善,到IE9时则得到了修复。但我们还是不要扩展原生的DOM元素,理由很充分:有冲突的风险。没有哪个函数库想在元素上添加一堆方法,这种方式很糟糕, 就像被邀请到别人家做客,结果却把人家家里弄的一团乱。

  但是,如果我们可在避免冲突的条件下对元素添加方法呢?(好吧,从技术上讲,可能性很小)。我们只能对元素添加一个属性,然后把我们所有的方法都附着上去。例如:如果我们的函数库为yolo并有两个方法:foo()和bar(),就像这样:

var element = document.querySelector(".someclass");
element.yolo.foo();
element.yolo.bar();
//你甚至可以链式返回他们的元素
element.yolo.foo().yolo.bar();

可以肯定,这比包装对象更别扭,但是我认为使用本地DOM元素所带来的好处要大于它。当然,你可能不这么认为。

  这基本上同我们做全局是完全一样的:我们都知道,添加大量的全局变量是不可取的做法,所以每一个函数库都只创建一个全局变量并把所有方法属性都附着在这个全局上。? 然而,如果我们试图以这种天真的方式来实施,我们会发现 用我们的命名空间函数来引用元素是有些难度的:

Element.prototype.yolo = {
    foo: function () {
        console.log(this);
    },
    bar: function () { /* ... */ }};
someElement.yolo.foo(); // Object {foo: function, bar: function}

这里发生了什么?函数中的this指向的调用他们的对象,而不是对象所附着的那个元素,想要避开这个问题我们需要更聪明点。

  记住:Yolo里的this 指向我们试图挂载方法的元素,但是我们没有运行任何代码,所以我们没有利用它。除非我们能够得到一个引用该对象的上下文。然而,运行一个function (例如element.yolo(). foo())会毁坏我们良好的API。

  稍等一下,我们可以通过ES5获得权限!我们这样做:

Object.defineProperty(Element.prototype, "yolo", {
    get: function () {
        return {
            element: this,
            foo: function() {
                console.log(this.element);
            },
            bar: function() { /* ... */ }
        }
    },
    configurable: true,
    writeable: false});
someElement.yolo.foo(); // It works! (打印出该元素)

这个方法奏效,但是这里有一个相当恼人的问题:我们每次生成该对象和重新定义函数都要调用该属性。这是一个很坏的想法。理想情况下,我们需要生成该对象,然后返回生成的对象。我们也不想让每个元素都有自己完全独立的实例,我们想在原型上定义这些函数,并且用JS完美的继承,因此,我们的库也是动态可扩展的。幸运的是,有一种方法可以做到这一切:

var Yolo = function(element) {
    this.element = element;
};

Yolo.prototype = {
    foo: function() {
        console.log(this.element);
    },

    bar: function() { /* ... */ }
};

Object.defineProperty(Element.prototype, "yolo", {
    get: function () {
        Object.defineProperty(this, "yolo", {
            value: new Yolo(this)
        });

        return this.yolo;
    },
    configurable: true,
    writeable: false
});

someElement.yolo.foo(); // 成功! (打印出元素)

// 它也是可以动态扩展的
Yolo.prototype.baz = function(color) {
    this.element.style.background = color;
};

someElement.yolo.baz("red") // 元素获得了一个红色背景

注意,上面我们所提到的getter只执行一次。之后它用一个静态值:一个yolo对象的实例重写了yolo属性。因为我们用 到Object.define Property()所以也不会遇到破坏枚举的问题(for..in循环),这些属性默认enumerable: false。这里任然有一个不足:这些方法需要用this.element来替代this。我们可以通过封装他们来解决这一问题:

for (let method in Yolo.prototype) {
    Yolo.prototype[method] = function(){
        var callback = Yolo.prototype[method];

        Yolo.prototype[method] = function () {
            var ret = callback.apply(this.element, arguments);

            // 可链式返回元素!
            return ret === undefined? this.element : ret;
        }
    }
}

然而,现在你不能动态的在Yolo.prototype上添加方法并让他们像element.yolo里的本地方法那样自动运行。所以它是有点痛的可扩展性(当然,你仍然可以用this.element来添加方法,也是可行的)。?

  你的想法?

时间: 2024-10-05 05:16:36

【翻译】理念:无冲突的扩展本地DOM原型的相关文章

DOM扩展:DOM API的进一步增强[总结篇-下]

本文承接<DOM扩展:DOM API的进一步增强[总结篇-上]>,继续总结DOM扩展相关的功能和API. 3.6 插入标记 DOM1级中的接口已经提供了向文档中插入内容的接口,但是在给文档插入大量HTML标记的时候操作还是很繁杂的,每次插入一个元素,不仅要调用创建元素和文本节点的接口,还要调用appendChild等向文档中添加元素的接口,而且在添加时还要按照正确的顺序.而如果使用插入标记的方法,直接向文档中插入HTML字符串,由执行环境自动解析HTML字符串并创建相应的节点并添加到文档中,这

命名空间与无冲突处理

JS编程中我们可能会遇到命名冲突的问题.命名冲突分两种情况来处理,对于模块内部,我们通常创建命名空间来解决:对于不同框架类库,我们采用无冲突处理(多库共存)的手段解决. 1.模块内部,注册命名空间 一个大模块可以细分成若干小模块,命名空间其实就是在大模块的作用域里面声明小的作用域,这样不同作用域之间同名标识符互不影响. 1)用立即执行函数表达式(IIFE)作为作用域. var a = 1; (function() { var a = 2; console.log(a);//2 })(); con

哈希——无冲突应用

众所周知,哈希的速度是灰常快的,敢号称时间复杂度为O(1)的,呵呵,它和快排有一拼(目前只有快排敢叫"快排"这个名号).而且,它偏偏还非常好用,这注定了它不平凡的存在.倒是目前网说哈希时,多是介绍各种哈希函数的构造及如何避免冲突,然后动不动就扯到MD5这些东西上去了.照这样说来,貌似哈希不常用啊.其实说白了,管他那么多干嘛,就不冲突着用嘛,自然就省去了那些"拉链门"啊神马的东西了.平时没事来个哈希就用用,速度那么快的说. 在这篇文章中说到的东西呢,用到的基本上全是不

Firefox 浏览器的DOM原型扩展

我不想挑起IE与Firefox之间的争论,我只想说说Firefox浏览器有而IE里没有的一个功能,对DOM里的对象原型的扩展. 在DOM里的window.document.element.event等这些对象在Firefox(或者说Mozilla核心的浏览器)里都有与之对应的原型:Window.HTMLDocument.HTMLElement.Event等,对于这些原型扩展之后,那些window.document等对象就“自动”拥有某些成员属性或者成员方法了.举个简单的例子,比如在IE里都有一个

【设计模式+原型理解】第二章:基于构造函数扩展出来的原型模式

在第一章的时候,说过了单例模式.工厂模式.构造函数模式,你还记得构造模式是怎么样的吗? function CreateJsPerson(name, age) { this.name = name; this.age = age; this.writeJs = function() { console.log("my name is " + this.name + ", i can write js."); } } var p1 = new CreateJsPerso

React系列文章:无状态组件生成真实DOM结点

在上一篇文章中,我们总结并模拟了JSX生成真实DOM结点的过程,今天接着来介绍一下无状态组件的生成过程. 先以下面一段简单的代码举例: const Greeting = function ({name}) { return <div>{`hello ${name}`}</div>; }; const App = <Greeting name="scott"/>; console.log(App); ReactDOM.render(App, docum

在无orcale环境的本地,向远程orcale数据库导入本地的dmp文件

本地服务器没有安装orcale数据库和客户端,也没有安装plsql.所以cmd的imp命令不能用. 本地安装orcale客户端 下载地址,安装,配置path,安装后可以用imp命令导入dmp文件. 安装instantclient_11_2,下载地址,解压ok: 安装plsql,下载地址,解压直接打卡plsqldev.exe,界面是英文,可以按下图改为中文.配置orcale路径和oci路径. 用plsql中的导入表功能导入dmp文件. 至于怎么连接远程数据,百度有很多.

JavaWeb无热部署扩展

太久没有写点东西了,今天分享一下Java web中我们的一个简单动态加载jar包,无需热部署以及更新以前的class即可上线服务应用,Java的反射机制内容这里不做科普(下面基本无代码,仅提供思路,代码党绕行). 环境:java8+tomcat(tomcat中的类加与javase的加载器不是一样的,暂不做考虑): 数据交互:webservice(json) 思路:我们都知道,普遍情况下JavaWeb正常运行都需要编译为class文件才能解释运行,很多时候系统更新都需要重启服务或者服务自动热部署,

关于CSS选择器优先级无冲突样式设置的展示

如果id和class都设置了同一样式,则显示id选择器的;如果id写的样式和class不冲突,则都会显示 .top{ border: 1px solid blueviolet; width: 438px; height: 50px; float: left; } #top{ padding-top: 15px; height: 35px; } <div class="top" id="top">    <a href="#"&g