扩展原语而无需对其进行原型设计 [英] Extend primitives without prototyping them

查看:99
本文介绍了扩展原语而无需对其进行原型设计的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在开发一个非常丑陋的库,让你做一些奇怪的事情。
有了图表,您可以以链式样式映射一组集合,并在您更改整个系统中要更改的
值时。

I am working on a pretty ugly library that lets you do some strange things. Having a graph you can map a set of collections in a chain-like style and when you alter a value to be altered in the whole system.

当结束类型是JS原语时出现了问题。

The problem came when the end type is a JS primitive.

在使用值和对象制作图表之后,我可以做类似这样的事情:

In my case after making the graph with the values and objects I can do something like this:

CHAIN.components[0].value = 20; 

components 是图表上的过滤函数使用setter和getter的节点。
如果在组件中只有一个节点被过滤,则用户设置的默认值可以不用这样做:
CHAIN.components.value = 20;
而是这个:
CHAIN.components = 20;

components is a filter function over the graph's nodes using setters and getters. If there is only one node filtered in components the default value set by the user would be available without doing this: CHAIN.components.value = 20; But rather this: CHAIN.components = 20;

现在的问题是节点除了默认值之外还可以有其他方法或属性(在我的情况下设置在

Now the problem is that the node could have other methods or properties besides a default ( which in my case is set on value.

如何在没有黑客攻击的情况下使用Number对象上的setter和getter在Number.prototype中,因为 CHAIN.components 现在是一个数字(如果它不是原始我让它以不引人注目的方式工作),但是当我想要调用 CHAIN.components.func()有一个问题,因为我必须将 func 每次我制作一套或获得组件然后将其删除。

How can i use the setters and getters on Number object without hacking in the Number.prototype, because CHAIN.components is now a Number ( if it's not a primitive i've made it work in an unobtrusive way ), but when i want to call the CHAIN.components.func() there is a problem because i would have to append to the Number.prototype the func every time i make a set or get on the components and then delete it.

你还有另一个吗?完成这种行为的想法?

Do you have another idea for accomplishing this kind of behavior?

你想要代码所以这里是:

You wanted code so here it is:

/*jslint nomen: true, sloppy: true*/
GRID.modules.OHM || Object.extend(GRID.modules, ( function() {
    var Node, Nodes, Ohm, num_proto = Number.prototype.__clone(), str_proto = String.prototype.__clone();
    Node = function(uid) {
        var UID = uid;
        this.getUID = function() {
            return UID;
        };
    };
    Nodes = function() {
        var stack = [];
        this.add = function(id, val) {
            var n = new Node(stack.length);
            val.id = id;
            Object.extend(n, val);
            stack.push(n);
            return n.getUID();
        };
        this.getById = function(id) {
            return stack.filter(function(v) {
                var a = id || v.id;
                return (v.id === a);
            });
        };
        this.getByUID = function(UID) {
            return stack[UID];
        };
        this.get = function(callback) {
            !Object.isString(callback) || ( callback = [callback]);
            var f = Object.isFunction(callback) ? callback : (Object.isArray(callback) ? function(k) {
                return (callback.indexOf(k.id) >= 0);
            } : function(k) {
                return true;
            });
            return stack.filter(f);
        };
    };
    Ohm = function(n) {
        var graph = n || (new Nodes()), filters = {}, __nodes = {}, addGS = function(obj, name, conf, binder) {
            var alfa = {};
            Object.extend(alfa, conf);
            if (!alfa.get) {
                alfa.get = function() {
                    var a = this.g.getById(this.p);
                    return a.length === 1 ? a[0] : a;
                }.bind(binder);
            } else {
                alfa.get = alfa.get.bind(binder);
            }
            if (!alfa.set) {
                alfa.set = function(value) {
                    this.g.getById(this.p).forEach(function(k) {
                        Object.extend(k, value);
                        return true;
                    });
                }.bind(binder);
            } else {
                alfa.set = alfa.set.bind(binder);
            }
            Object.defineProperty(obj, name, alfa);
        }, add = function(id, node) {
            if (__nodes.hasOwnProperty(id)) {
                addGS(__nodes, id, {
                    enumerable : true
                }, {
                    t : this,
                    p : id,
                    g : graph
                });
            }
            return graph.add(id, node || {});
        };
        Object.extend(this, {
            add : function() {
                add.apply(this, arguments);
            },
            map : function(name, f, that) {
                var n = name, filterer = ['add', 'map', '__all'];
                n = Object.isFunction(n) ? name.apply(that, arguments.slice(3)) : n;
                if (filterer.indexOf(n.toLowerCase()) >= 0) {
                    console.log("You can't map over a basic property of object !!! Please read the freakin' manual.");
                    return null;
                }
                if (!filters.hasOwnProperty(n)) {
                    filters[n] = new Ohm(graph);
                    addGS(this, n, {
                        get : function() {
                            this.g.get(this.f).forEach(function(v, key, arr) {
                                var temp, binder;
                                if (arr.length !== 1) {
                                    if (!this.filt.hasOwnProperty(v.id)) {
                                        addGS(this.filt, v.id, {
                                            set : function(value) {
                                                this.t.g.getById(this.p).filter(this.t.f).forEach(function(k) {
                                                    Object.extend(k, value);
                                                });
                                            },
                                            get : function() {
                                                var a = this.t.g.getById(this.p).filter(this.t.f);
                                                return a.length === 1 ? a[0] : a;
                                            }
                                        }, {
                                            t : this,
                                            p : v.id
                                        });
                                        (key !== arr.length - 1) || Object.extend(this.filt, this.g.get(this.f));
                                    }
                                } else {
                                    if (Object.isFunction(v.__new__)) {
                                        v.__default = function() {
                                            return Object.extend((new this.__new__(arguments)), this);
                                        };
                                    }
                                    if (!Object.isUndefined(v.__default)) {
                                        temp = this.filt;
                                        this.filt = Object.isFunction(v.__default) ? v.__default.bind(v) : v.__default;
                                        if (Object.isNumber(this.filt) || Object.isString(this.filt)) {
                                            var prot = Object.isNumber(this.filt) ? Number : String;
                                            for (var i in temp) {
                                                if (temp.hasOwnProperty(i) && !prot.prototype.hasOwnProperty(i)) {
                                                    var bin = {
                                                        t : temp,
                                                        m : i,
                                                        p : prot,

                                                    };
                                                    Object.defineProperty(prot.prototype, i, {
                                                        set : function(value) {
                                                            Object.defineProperty(this.p.prototype, this.m, {
                                                                configurable : true, // defaults to false
                                                                writable : false,
                                                                value : 1
                                                            });
                                                            delete this.p.prototype[this.m];
                                                            this.t[this.m] = value;
                                                        }.bind(bin),
                                                        get : function() {
                                                            Object.defineProperty(this.p.prototype, this.m, {
                                                                configurable : true, // defaults to false
                                                                writable : false,
                                                                value : 1
                                                            });
                                                            delete this.p.prototype[this.m];
                                                            return this.t[this.m];
                                                        }.bind(bin),
                                                        enumerable : true,
                                                        configurable : true
                                                    });
                                                }
                                            }
                                        } else {
                                            Object.extend(this.filt, temp);
                                        }
                                    }
                                    if (Object.isNumber(this.filt) || Object.isString(this.filt)) {
                                        var prot = Object.isNumber(this.filt) ? Number : String;
                                        for (var i in v) {
                                            if (v.hasOwnProperty(i) && !prot.prototype.hasOwnProperty(i)) {
                                                var bin = {
                                                    t : v,
                                                    m : i,
                                                    p : prot,

                                                };
                                                Object.defineProperty(prot.prototype, i, {
                                                    set : function(value) {
                                                        Object.defineProperty(this.p.prototype, this.m, {
                                                            configurable : true, // defaults to false
                                                            writable : false,
                                                            value : 1
                                                        });
                                                        delete this.p.prototype[this.m];
                                                        this.t[this.m] = value;
                                                    }.bind(bin),
                                                    get : function() {
                                                        Object.defineProperty(this.p.prototype, this.m, {
                                                            configurable : true, // defaults to false
                                                            writable : false,
                                                            value : 1
                                                        });
                                                        delete this.p.prototype[this.m];
                                                        return this.t[this.m];
                                                    }.bind(bin),
                                                    enumerable : true,
                                                    configurable : true
                                                });
                                            }
                                        }
                                    } else {
                                        Object.extend(this.filt, v);
                                    }
                                }
                            }, this);
                            return this.filt;
                        },
                        set : function(value) {
                            this.g.get(this.f).forEach(function(k) {
                                Object.extend(k, value);
                            });
                        }
                    }, {
                        t : this,
                        f : f,
                        g : graph,
                        filt : filters[n]
                    });
                }
            }
        }, true, true);
        addGS(this, '__all', {
            get : function() {
                var a = this.g.getById();
                Object.extend(__nodes, a.length === 1 ? a[0] : a);
                return __nodes;
            },
            enumerable : true
        }, {
            t : this,
            p : null,
            g : graph
        });
    };
    window['Ω'] = Ohm;
    return {
        OHM : Ohm,
    };
}()));

现在演示:

var c = new Ω();
c.add('ann', {
__default : 58,
blah : 98,
ceva : function()
{
    console.log('asd');
}
});
c.add('ann2',{
    __default: function(){
       console.log('hello');
    },
    abc: 78,
    dce: function(){
       console.log(' world');
    }
};
c.add('b2', {
__new__ : function() {
    this.init = function() {
        this.id = 86;
    };
    this.mer = function() {
        console.log(this);
    };
},
els : 'asadar'
});
c.map('b2', function(k) {
return k.id === 'b2';
});
c.map('ann', function(k) {
return k.id === 'ann';
});
c.map('ann2', function(k) {
return k.id === 'ann2';
});
console.log(c.ann); // returns 58 ( the __default value )
console.log(c.ann.blah); // returns 98
console.log(c.ann.blah.blah); // undefined
console.log(c.ann2);  // function()
c.ann2(); // prints out 'hello'
c.ann2.cde(); // prints out 'world'
c.ann2 = 60;
console.log(c.ann2); // 60
console.log(c.ann2.cde()); // prints out 'world'

此代码有效,但我必须使用数字或字符串原型的部分困扰我。你有另外一种方法吗?

This code works, but the part where i have to use the Number or String prototype bothers me. Do you have another way of doing this?

原因是有人说它可以用PHP而不是JS完成,这家伙最近一直在与我在WebGL着色器上并且讨厌他必须编写700行代码才能使用多种效果结合FBO而不是100,这将使他使用类似的工具,就像用PHP编写的那样。所以是的,我知道原始原型上的访问器是一个黑客,但如果链终端对象是原始的话,如何在不使用valueOf的情况下使它变得不同?

The reason is to do something that someone said it could be done in PHP but not JS, this guy had recently been working with me on WebGL shaders and hated that he had to write 700 lines of code to use multiple effect combined with FBO instead of 100 which would took him with a similar tool like this one which was written in PHP. So yes i KNOW the accessors on primitive prototypes is a hack but how can i make it different without having to use valueOf if the chain end object is a primitive?

推荐答案

好的,现在我已经阅读了附加的代码(但不能完全遵循它)。缺乏一些评论,但我不会喋喋不休,我自己的代码并不是更好,因为我不希望任何人阅读或理解它们: - )

OK, now I've read the attached code (but could not follow all of it). Lacks some comments, but I won't bitch about that, my own codes are not better as I don't expect anybody to read or understand them :-)

你是对的,你扩展原生原型的部分是可怕的。根据我的理解,在返回数字/字符串之前,在Number或String原型上定义一个访问器属性。在获取和设置时,访问者属性被数据属性(???)覆盖,然后在存储/返回值之前删除整个属性。这似乎是允许原始值的自定义属性的聪明黑客,但是:

You are right, the part where you extend the native prototypes is scary. From what I understand, you define an accessor property on the Number or String prototype before returning a number / string. Both on getting and setting, the accessor property is overwritten by a data property (???), then the whole property is deleted, before you store / return the value. This seems to be a clever hack to allow custom properties on primitive values, yet:


  • 存在高冲突风险。可以通过使用 this 值作为查找表中的键来降低它(区分(5).x 来自(3).x ),但仍然无法完全避免。

  • 删除自己的属性访问/设置非常不直观。避免这种情况。

  • 在原语原型上任意改变访问器属性是昂贵的。这是4个表现 - 对比组合。没有引擎能够优化这一点。而你似乎经常使用它们。

  • There is a high risk of collisions. It might be possible to lower that by using the this value as a key in a lookup table (to differentiate (5).x from (3).x), but it still can't be fully avoided.
  • Properties that remove themselves on accessing / setting are extremely unintuitive. Avoid that.
  • arbitrarily changing accessor properties on the prototype of primitives are costly. That's 4 performance-contras combined. No engine will be able to optimize this. And you seem to use them quite often.

如果你真的需要这个(我仍然没有得到你的理由),我' d使用带有查找表的变体。它应该减少冲突(不确定你的代码如何处理它们),不会改变它们定义的访问者属性,因此更持久(尽管它可能会泄漏):

If you really needed this (I still didn't get your reason), I'd use a variant with a lookup-table. It should reduce collisions (not sure how your code handled them), does not change the accessor properties ones they're defined and is therefore more persistent (though it might leak then):

// Let's call this
// PRIMITIVE PROXIES
// as they proxy real objects behind primitive values
var proxy = _.map( { // some map that works on Objects
    string: String.prototype,
    number: Number.prototype
}, function closure(type, proto) {
    var table = {};
    function setupProperty(prop) {
        if (prop in proto) return; // ah, we already proxied this kind of object
        Object.defineProperty(proto, prop, {
            configurable:true, // for deleting
            get: function getter() {
                // "this" is the primitive value
                if (!this in table)
                    return undefined;
                return table[this][prop]; // get prop from obj
            },
            set: function setter(val) {
                if (this in table)
                    table[this][prop] = val; // pass val to obj
            }
        });
    }
    return {
        create: function createProxy(prim, obj) {
            if (prim in table) // we already did create a proxy on this primitive
                return; // let's abort. You might continue to overwrite
            table[prim] = obj;
            Object.getOwnPropertyNames(obj).forEach(setupProperty);
            return prim; // the new "proxy"
        },
        move: function moveName(from, to) {
            if (to in table) return false;
            table[to] = table[from];
            delete table[from];
            return true;
        }
    };
});
proxy.create = function(prim, obj) {
    return proxy[typeof prim].create(prim, obj);
};
proxy.move = function(from, to) {
    return proxy[typeof from].create(from, to);
};
// USAGE:
// proxy.create works just like Object.extend
> var c = {ann: 58},
>     o = {blah: 98};
> proxy.create(c.ann, o);
> 58..blah
98
> c.ann.blah
98
> (58).blah = 60;
> o
{blah: 60}
> var num = c.ann; // 58
> c.ann.blah = function(){return "Hello"};
> num.blah()
"Hello"
> proxy.move(c.ann, c.ann = 78);
> c.ann
78
> (58).blah
undefined
> c.ann.blah()
"Hello"
> // getters/setters for properties are global:
> c.ann.blub = "something"; // does not work, there is no getter
> c.ann.blub
undefined
> proxy.create(58, {blub: "foo"})
> c.ann.blub // still returns
undefined
> c.ann.blub = "bar"; // but can be set now
> (58).blub + (78).blub
"foobar"
> // infinite lookup loops are possible:
> proxy.create("loop", {x:"loop"});
> "loop" === "loop".x
true
> "loop".x.x.x.….x
"loop"

然而,有一件事你永远无法解决:

However, there is one thing you will never be able to work around:


与对象不同,原始值不是唯一的;他们没有身份。

Unlike objects, primitive values are not unique; they have no identity.

你永远无法区分 c.ann 来自 58 ,或循环来自循环.x ,所以两者都有财产。这不是构建API的好前提。

You will never be able to distinguish c.ann from 58, or "loop" from "loop".x, and so both will have a property or not. This is not a good premise to build an API upon.

所以,我仍然建议使用 Number String 对象。你不需要对它们进行子类化(如我之前的答案中所示),因为你似乎没有(m)它们的任何方法,所以你可以轻松地构建它们:

So, I still recommend to use Number and String objects. You don't need to subclass them (as shown in my previous answer), as you don't seem to have (m)any methods on them, so you can easily build them:

c.ann = new Number(58);
c.ann.blah = 98;
return c;

对于 typeof 运营商。您是否可以添加一些使用 __ default 值的示例?

There should be hardly a difference, expect for the typeof operator. Could you maybe add some more examples that use the __default value?


但是如何如果链末端对象是原始的话,我可以使它变得不同而不必使用valueOf吗?

but how can I make it different without having to use valueOf if the chain end object is a primitive?

回答这个简单的问题:那家伙是的,如果不破解原生原型,就无法在JavaScript中完成。你是对的,黑客很难看: - )

To answer this simple question: That guy was right, it cannot be done in JavaScript without hacking native prototypes. And you are right, the hack is quite ugly :-)

这篇关于扩展原语而无需对其进行原型设计的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆