4

前段时间我试图扩展Object.prototype......当后来我在控制台中看到来自 jQuery 文件的错误时,我感到很惊讶。我试图找出问题所在,当然我发现扩展Object.prototype是“邪恶”的信息,“你不应该这样做,因为 JS 是动态语言,你的代码不会很快工作”以及 jQuery 现在将添加hasOwnProperty方法的信息到他们的for in循环。

因为我不想离开 jQuery,所以我放弃了扩展Object.prototype.

直到现在。我的项目越来越大,我真的很生气,因为我必须多次重复代码的某些部分。以下是我在项目中使用的一些结构:

图表.js:

CHARTS = {
    _init: function () {
        this.monthlyChart();
        /*
         * 
         * more propertys goes here
         * 
         */
        return this;
    },
    monthlyChart: function () {
        //create my chart
        return {
            update: function () {
               // update chart
            }
        };
    }()
    /*
     * 
     * more propertys goes here
     * 
     */
}._init;

仪表板.js

NAVBAR = {
    _init: function () {
        /*
         * 
         * more propertys goes here
         * 
         */
        return this;
    },
    doSomething: function(){            
        $(document).ready(function(){
            $('.myButton').on('click', function(){
                var data = [];
                // calling property from charts.js
                CHARTS.monthlyChart.update(data);
            });
        });
    }
}._init

正如我提到的,项目现在真的很大——它有 40 多个 js 文件,其中一些有几千行代码。_init我每次都必须重复部分真的很烦人,还有很多功能我必须重复$(document).ready&& $(window).load

我试图为我的问题找到另一种解决方案。我尝试创建具有init属性的类(您可以在此处找到更多),但我这个解决方案迫使我向每个文件添加另一个“不必要”的代码片段,并且访问其他文件对象属性也使其变得复杂(在任何地方返回适当的对象等)。正如评论中所建议的,我开始阅读 JS 中的 getter 和 setter。

毕竟我创造了这样的东西:

 //Auto initialization
if (typeof $document === 'undefined') {
    var $document = $(document),
            $window = $(window),
            $body = $('body');
}

Object.defineProperty(Object.prototype, '_init', {
    get: function () {
        // if object has no property named `_init`
        if (!this.hasOwnProperty('_init')) {
            for (var key in this) {
                // checking if name of property does starts from '_' and if it is function
                if (this.hasOwnProperty(key) && key[0] === '_' && typeof this[key] === 'function') {                    
                    if (key.indexOf('_ready_') > -1) {
                        //add function to document ready if property name starts from '_ready_'
                        $document.ready(this[key].bind(this));
                    } else if (key.indexOf('_load_') > -1) {
                        //add function to window load if property name starts from '_load_'
                        $window.load(this[key].bind(this));
                    } else {
                        // else execute function now
                        this[key].bind(this)();
                    }
                }
            }
            return this;
        }
    }
});

和我的对象:

var DASHBOARD = {
    _runMe: function(){

    },
    _ready_runMeOnReady: function(){

    },
    _load_runMeOnLoad: function(){

    },
    iAmAString: ''
}._init

这个解决方案似乎适用于 jQuery。但是使用起来安全吗?我没有看到代码可能导致的任何问题,也没有看到它可能导致的任何进一步问题。如果有人告诉我为什么我不应该使用这个解决方案,我会非常高兴。

此外,我正在尝试详细了解它的工作原理Object.prototype从理论上讲,我为by定义了属性defineProperty,但没有为其赋值。不知何故,它不会在 jQueryfore in循环中导致任何错误,为什么?这是否意味着_init在某些时候或根本没有定义属性,因为我只定义了它的 getter?

任何帮助将不胜感激 :)

4

3 回答 3

3

通过在 JavaScript 中不包含描述符,Object.defineProperty(obj, prop, descriptor)所有布尔描述符属性默认为 false。即 writable,enumerableconfigurablefor in您的新属性对迭代器隐藏,因为您的_init属性是enumerable:false.

我不是 JQuery 的粉丝,所以不会评论关于 JQuery 的原因

向 JavaScript 的基本类型添加属性没有绝对规则,这取决于代码运行的环境。添加到基本类型会将其添加到全局命名空间。如果您的应用程序与第 3 方脚本共享命名空间,您可能会发生冲突,从而导致您的代码或第三方代码或两者都失败。

如果您是唯一的代码,那么冲突将不是问题,但添加到 object.prototype 将导致所有使用 object 的代码的额外开销。

我强烈建议您重新检查对全局 _init 的需求。当然,您不会在每次需要新对象时都使用它。我是 JavaScript 数据结构的附加方法的粉丝,并尝试远离正式的 OOP 范式

于 2015-05-19T14:24:29.053 回答
3

您的问题实际上包含两个问题。

看起来这个解决方案适用于 jQuery。但是使用安全吗?我没有看到代码可能导致的任何问题,也没有看到它可能导致的任何进一步问题。如果有人告诉我为什么我不应该使用这个解决方案,我会非常高兴。

首先,避免修改内置原型有三个主要原因。

For-in 循环

没有检查使用for-in循环的代码太多。hasOwnProperty在您的情况下,这是不执行检查的 jQuery 代码。

解决方案

  • 不要在没有检查的 情况下使用for-in循环。.hasOwnProperty
    • 不适用于这种情况,因为它是第三方代码,您不能修改它。
  • for-in循环仅遍历可枚举的键。
    • 您已经使用了该解决方案。Object.defineProperty默认情况下创建不可枚举的属性(ECMAScript 5.1 规范
      • IE8 不支持。

冲突

存在财产名称风险。想象一下,您使用 jQuery 插件来检查._init对象上是否存在属性 - 它可能会导致微妙且难以调试的错误。带有下划线前缀的名称在现代 JavaScript 库中广泛用于指示私有属性。

封装违规(糟糕的设计)

但是你有更严重的问题。定义全局._init属性意味着每个对象都有通用的初始化逻辑。它破坏了封装,因为您的对象无法完全控制它们的状态。

因此,您不能依赖_init方法的存在。你的同事不能用

替代设计

全局初始化器

您可以创建全局函数initialize并将所有需要初始化的对象包装在其中。

解耦视图和逻辑

您的对象不应将逻辑和视图合并到一个对象中(这违反了单一责任原则),您是意大利面条代码的受害者。此外 - 对象初始化不应将其绑定到 DOM,一些控制器对象应该是您的逻辑和显示之间的代理。

检查流行的客户端 MVC 框架是如何解决这个问题的(Angular、Ember、Backbone)是如何解决这个问题的,这可能是个好主意。

于 2015-05-19T14:32:43.570 回答
1
  1. 使用 getter 和 setter 是否安全?

    是的。但是如果你只支持IE9+。

  2. 修改 Object.prototype 是否安全?

    否。创建另一个对象来继承您的所有应用程序对象。


为什么扩展基本的 JavaScript 对象是eval邪恶的?

因为在加载脚本的网页上创建的每个对象都将继承该属性或方法。如果你这样做,会有很多缺点,比如碰撞和性能开销。

有很多方法可以让它变得更好,让我向您展示我使用的一种。

// Here we create the base object:
var someBaseObject = {};
someBaseObject.someMethod = function () {
    // some code here
}
someBaseObject.someProperty = "something";

// And inherit another object from the someBaseObject
someObject = Object.create(someBaseObject);
someObject.someAnotherMethod = function () {
    // some code here
}

这种方法让我们可以不理会 Object 原型,而是构建一个原型链,其中 someObject 继承自 someBaseObject,而 someBaseObject 继承自 Object。

在这篇文章中我唯一想说的是:不要管基础对象,自己构建,这样你就不会那么头疼了。

注意:Object.createIE9+ 支持。这是Douglas Crockford的 IE8 和更低版本的 shim :

if (typeof Object.create !== 'function') {
    Object.create = function (o) {
        function F() {}
        F.prototype = o;
        return new F();
    };
}
于 2015-05-19T14:26:20.123 回答