【问题标题】:dynamic object construction in javascript?javascript中的动态对象构造?
【发布时间】:2011-04-21 18:23:56
【问题描述】:

当我想使用从其他地方提供的参数调用 javascript 中的函数时,我可以使用函数的 apply 方法,例如:

array = ["arg1", 5, "arg3"] 
...
someFunc.apply(null, array);

但是如果我需要以类似的方式调用构造函数怎么办?这似乎不起作用:

array = ["arg1", 5, "arg3"] 
...
someConstructor.apply({}, array);

至少不像我尝试的那样:

template = ['string1', string2, 'etc'];
var resultTpl = Ext.XTemplate.apply({}, template);

这不起作用:

Ext.XTemplate.prototype.constructor.apply({}, template);

有什么方法可以使这个工作? (在这种特殊情况下,我发现new Ext.XTemplate(template) 可以工作,但我对一般情况感兴趣)

类似的问题,但特定于内置类型并且没有我可以使用的答案: Instantiating a JavaScript object by calling prototype.constructor.apply

谢谢。

编辑:

时间已经过去,ES6 和转译器现在已经成为一件事。 在 ES6 中,做我想做的事是微不足道的:new someConstructor(...array)Babel 会将其转换为 ES5 new (Function.prototype.bind.apply(someConstructor, [null].concat(array)))();,在 How to construct JavaScript object (using 'apply')? 中进行了解释。

【问题讨论】:

    标签: javascript dynamic-languages


    【解决方案1】:

    没有简单直接的方法可以使用构造函数来做到这一点。这是因为当您使用 new 关键字调用构造函数时会发生特殊情况,因此如果您不打算这样做,则必须模拟所有这些特殊情况。它们是:

    1. 创建一个新的对象实例(您正在这样做)。
    2. 将该对象的内部原型设置为构造函数的prototype 属性。
    3. 设置该对象的constructor 属性。
    4. 使用该对象实例作为 this 值调用构造函数(您正在这样做)。
    5. 处理构造函数的特殊返回值。

    认为就是这样,但值得仔细检查the spec

    因此,如果您可以避免它并直接使用构造函数,我会这样做。 :-) 但是,如果您不能,您仍然可以这样做,这很尴尬并且涉及变通方法。 (另请参阅 StackOverflow 上的 this related answer,尽管我在这里 [以及一些] 涵盖了所有内容。)

    您最大的问题是上面的#2:设置对象的内部原型。长期以来,没有标准的方法来做到这一点。一些浏览器支持 __proto__ 属性,所以你可以使用它,如果它在那里。好消息是 ECMAScript 5 引入了一种明确执行此操作的方法:Object.create。因此,像 Chrome 这样的尖端浏览器将拥有它。但是,如果您使用的浏览器既没有Object.create 也没有__proto__,那就有点难看:

    1) 定义自定义构造函数。

    2) 将其prototype属性设置为真实构造函数的prototype属性

    3) 用它来创建一个空白对象实例。

    它会为您处理原型。然后你继续:

    4) 将该实例上的 constructor 属性替换为真正的构造函数。

    5) 通过apply调用真正的构造函数。

    6) 如果真正的构造函数的返回值是一个对象,使用它而不是你创建的那个;否则,请使用您创建的那个。

    类似这样的东西 (live example):

    function applyConstruct(ctor, params) {
        var obj, newobj;
    
        // Use a fake constructor function with the target constructor's
        // `prototype` property to create the object with the right prototype
        function fakeCtor() {
        }
        fakeCtor.prototype = ctor.prototype;
        obj = new fakeCtor();
    
        // Set the object's `constructor`
        obj.constructor = ctor;
    
        // Call the constructor function
        newobj = ctor.apply(obj, params);
    
        // Use the returned object if there is one.
        // Note that we handle the funky edge case of the `Function` constructor,
        // thanks to Mike's comment below. Double-checked the spec, that should be
        // the lot.
        if (newobj !== null
            && (typeof newobj === "object" || typeof newobj === "function")
           ) {
            obj = newobj;
        }
    
        // Done
        return obj;
    }
    

    您可以更进一步,仅在必要时使用假构造函数,查看是否首先支持 Object.create__proto__,如下所示 (live example):

    function applyConstruct(ctor, params) {
        var obj, newobj;
    
        // Create the object with the desired prototype
        if (typeof Object.create === "function") {
            // ECMAScript 5 
            obj = Object.create(ctor.prototype);
        }
        else if ({}.__proto__) {
            // Non-standard __proto__, supported by some browsers
            obj = {};
            obj.__proto__ = ctor.prototype;
            if (obj.__proto__ !== ctor.prototype) {
                // Setting it didn't work
                obj = makeObjectWithFakeCtor();
            }
        }
        else {
            // Fallback
            obj = makeObjectWithFakeCtor();
        }
    
        // Set the object's constructor
        obj.constructor = ctor;
    
        // Apply the constructor function
        newobj = ctor.apply(obj, params);
    
        // If a constructor function returns an object, that
        // becomes the return value of `new`, so we handle
        // that here.
        if (typeof newobj === "object") {
            obj = newobj;
        }
    
        // Done!
        return obj;
    
        // Subroutine for building objects with specific prototypes
        function makeObjectWithFakeCtor() {
            function fakeCtor() {
            }
            fakeCtor.prototype = ctor.prototype;
            return new fakeCtor();
        }
    }
    

    在 Chrome 6 上,以上使用Object.create;在 Firefox 3.6 和 Opera 上,它使用 __proto__。在 IE8 上,它使用了假构造函数。

    以上内容是相当即兴的,但它主要处理我在这方面所知道的问题。

    【讨论】:

    • 这肯定比我预期的要复杂一些。不过,谢谢您的精彩解释!
    • @ormuriauga: :-) 很高兴有帮助。解释使它看起来比实际复杂一些(因为解释往往会这样做)。从第一个代码sn-p可以看出,其实可以是一段相当简短的代码。
    • 甚至单行代码也经常涉及;) javascript 对象模型的更精细的细节仍然让我回避,但我现在购买了“好的部分”,很快就会阅读。当较短且不太复杂的代码有效时,是否有任何充分的理由使用您的后一个代码 sn-p?通过使用更新的功能,我会获得任何速度或兼容性或其他方面吗?
    • @ormuriauga:有趣,我也在考虑这个问题。我不确定你是否这样做,坦率地说,当有一种通用且直接的方法可以代替时,我不会立即看到检测和使用Object.create__proto__ 的好处。我预计它们会稍微快一点,但如果你经常很多,你只需要担心速度。关于 JavaScript 的对象模型,令人震惊的是它非常简单。我们有对象,一个对象可以由一个原型对象支持,而原型对象又可以由另一个原型对象支持,无限。 (续)
    • (继续)没有包或类(尽管我们经常调用构造函数类)。没有方法,只有函数(尽管当它们与对象的属性挂钩时,我们经常它们为方法)。该语言将几乎所有东西视为一个对象(包括调用函数时创建的上下文,这就是闭包的工作方式)。等等。如果你有兴趣,这些年来我写了一些关于 JavaScript 的杂乱无章的想法(包括关于类、方法、this 以及它与其他语言的区别、闭包):blog.niftysnippets.org
    【解决方案2】:

    我认为实现此目的的另一种方法是扩展 Ext Template 类,以便新对象的构造函数获取您的数组并执行您想做的事情。这样,所有的构建工作都将为您完成,您只需使用参数调用超类的构造函数。

    【讨论】:

    • 事实上,确实如此——在他的问题中,他说 "(在这种特殊情况下,我发现 new Ext.XTemplate(template) 会起作用,但我对一般案例)”
    • 我认为他的一般意思是像我描述的那样。对不起,如果我误解了这个问题。
    【解决方案3】:

    来自developer.mozilla: 绑定函数自动适合与 new 运算符一起使用,以构造由目标函数创建的新实例。当绑定函数用于构造值时,提供的 this 将被忽略。但是,提供的参数仍会添加到构造函数调用之前。

    也就是说,我们仍然需要使用 apply 将参数从数组中取出并放入绑定调用。此外,我们还需要将 bind 的函数重置为 apply 函数的 this 参数。这为我们提供了一个非常简洁的单行代码,可以完全按照需要完成。

    function constructorApply(ctor, args){
        return new (ctor.bind.apply(ctor, [null].concat(args)))();
    };
    

    【讨论】:

    • 也来自链接源中的相关部分:“警告:本部分演示 JavaScript 功能并记录了 bind() 方法的一些边缘情况。下面显示的方法不是最好的方法并且可能不应该在任何生产环境中使用。”
    • 如果你想让这个 args 参数在实际的参数数组或 Arguments 对象之间不可知,你可以这样做...... // ... function constructorApply(ctor, args){ return new (ctor.bind.apply(ctor, [null].concat([].slice.call(args))))(); };
    【解决方案4】:

    由于最初的问题已经很老了,所以在几乎所有现代浏览器中都有一个更简单的方法(截至写作,2018 年,我正在计算 Chrome、FF、IE11、Edge ......)。

    var template = ['string1', string2, 'etc'];
    var resultTpl = Object.create(Ext.XTemplate.prototype);
    Ext.XTemplate.apply(resultTpl, template);
    

    这两行也解释了new 运算符的基本工作原理。

    【讨论】:

    • 在现代浏览器中你可以做new Ext.XTemplate(...template)
    • 仅当您幸运且不必支持最新的 IE 迭代时。
    • 我想这取决于你在“现代”这个词中的含义:)
    • 确实如此。 :)
    猜你喜欢
    • 2013-03-30
    • 1970-01-01
    • 2021-06-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多