多个构造函数的 JavaScript 模式

Posted

技术标签:

【中文标题】多个构造函数的 JavaScript 模式【英文标题】:JavaScript pattern for multiple constructors 【发布时间】:2011-03-14 08:08:05 【问题描述】:

我的实例需要不同的构造函数。常见的模式是什么?

【问题讨论】:

请更具体一点。你想要具有不同参数集的构造函数吗? javascript 中可以有多个构造函数吗? 是也不是@DougHauf。是的,因为 bobince 提供的答案提供了一种提供等效行为的方法。不,因为如果你想要多个不同的构造函数(每个都共享同一个原型对象)原型对象的构造函数属性如何设置(因为构造函数属性只能指向一个构造函数)。 所有这些答案都是旧的/不理想的。我懒得输入答案,但是您可以将对象传递给函数和构造函数,然后像使用参数一样使用键,例如:function ( oneThing = 7, otherThing = defaultValue = ) 。我在其中添加的额外= 是我最近学到的另一个技巧,以防您希望用户根本不传递任何对象并使用所有默认值。 后续:这里有一些解决这个问题的好方法:***.com/a/32626901/1599699***.com/a/41051984/1599699***.com/a/48287734/1599699我特别喜欢最后一个,它是真正的多构造函数支持,使用静态工厂用作构造函数(return new this();return new this.otherStaticFactoryFunction(); 等)! 【参考方案1】:

JavaScript 没有函数重载,包括方法或构造函数。

如果您希望函数根据传递给它的参数的数量和类型而表现不同,则必须手动嗅探它们。 JavaScript 将愉快地调用一个函数,其参数数量多于或少于声明的数量。

function foo(a, b) 
    if (b===undefined) // parameter was omitted in call
        b= 'some default value';

    if (typeof(a)==='string')
        this._constructInSomeWay(a, b);
    else if (a instanceof MyType)
        this._constructInSomeOtherWay(a, b);

您还可以像数组一样访问arguments,以获取传入的任何其他参数。

如果您需要更复杂的参数,最好将部分或全部参数放在对象查找中:

function bar(argmap) 
    if ('optionalparam' in argmap)
        this._constructInSomeWay(argmap.param, argmap.optionalparam);
    ...


bar(param: 1, optionalparam: 2)

Python 演示了如何使用默认参数和命名参数以比函数重载更实用、更优雅的方式覆盖大多数用例。 JavaScript,没那么多。

【讨论】:

谢谢,这真的很好。我想说第二个选项不仅在您有复杂的论点时很有用,而且在简单但难以区分的论点也很有用,例如支持MyObj(foo: "foo") 加上MyObj(bar: "bar")。 MyObj 有两个构造函数 - 但都接受一个参数,它是一个字符串 :-) 您能否在这个特定示例的示例代码中添加更多内容。 嗨 @DougHauf,Crockford 的书 'JavaScript: The Good Parts' 中有一个名为 'Object Specifiers' 的部分,网上有很多例子可以参考。 “JavaScript 会愉快地调用一个函数,其参数数量多于或少于声明的数量”这需要哈哈表情符号,哈哈脚本 :D :D,我认为应该在答案和问题中添加表情符号反应。 【参考方案2】:

您可以使用带有返回该类实例的静态方法的类

    class MyClass 
        constructor(a,b,c,d)
            this.a = a
            this.b = b
            this.c = c
            this.d = d
        
        static BAndCInstance(b,c)
            return new MyClass(null,b,c)
        
        static BAndDInstance(b,d)
            return new MyClass(null,b, null,d)
        
    

    //new Instance just with a and other is nul this can
    //use for other params that are first in constructor
    const myclass=new MyClass(a)

    //an Instance that has b and c params
    const instanceWithBAndC = MyClass.BAndCInstance(b,c)

    //another example for b and d
    const instanceWithBAndD = MyClass.BAndDInstance(b,d)

使用这种模式,您可以创建多个构造函数

【讨论】:

这是最好的答案。其他人则求助于解析数组并做一堆不必要的工作。【参考方案3】:

你是怎么找到这个的?

function Foobar(foobar) 
    this.foobar = foobar;


Foobar.prototype = 
    foobar: null
;

Foobar.fromComponents = function(foo, bar) 
    var foobar = foo + bar;
    return new Foobar(foobar);
;

//usage: the following two lines give the same result
var x = Foobar.fromComponents('Abc', 'Cde');
var y = new Foobar('AbcDef')

【讨论】:

return new this(foobar);不起作用。我改变了 return new Foobar(foobar);一切正常。 我不明白。您可以在实际使用它的地方添加代码吗?您每次都必须调用 fromComponents 吗?因为那不是真正的构造函数,而是辅助函数。 @bobince 的回答似乎更准确。 @hofnarwillie 虽然不是一个精确的构造函数,但通过使用静态方法,它的执行非常相似,即 var foobarObj = Foobar.fromComponents(foo,bar);是您使用替代参数创建新对象所需的全部内容。 如果Foobaz从Foobar扩展,Foobaz.fromComponets不会创建Foobar实例吗?【参考方案4】:

不喜欢像 bobince 的回答那样手工完成,所以我完全撕掉了 jQuery 的插件选项模式。

这是构造函数:

//default constructor for Preset 'class'
function Preset(params) 
    var properties = $.extend(
        //these are the defaults
        id: null,
        name: null,
        inItems: [],
        outItems: [],
    , params);

    console.log('Preset instantiated');
    this.id = properties.id;
    this.name = properties.name;
    this.inItems = properties.inItems;
    this.outItems = properties.outItems;

这里有不同的实例化方式:

presetNoParams = new Preset(); 
presetEmptyParams = new Preset();
presetSomeParams = new Preset(id: 666, inItems:['item_1', 'item_2']);
presetAllParams = new Preset(id: 666, name: 'SOpreset', inItems: ['item_1', 'item_2'], outItems: ['item_3', 'item_4']);

这就是它的原因:

presetNoParams
Preset id: null, name: null, inItems: Array[0], outItems: Array[0]

presetEmptyParams
Preset id: null, name: null, inItems: Array[0], outItems: Array[0]

presetSomeParams
Preset id: 666, name: null, inItems: Array[2], outItems: Array[0]

presetAllParams
Preset id: 666, name: "SOpreset", inItems: Array[2], outItems: Array[2]

【讨论】:

我现在也在 node.js 中推出了相同的模式:npmjs.com/package/extend【参考方案5】:

进一步了解 eruciform 的答案,您可以将您的 new 调用链接到您的 init 方法中。

function Foo () 
    this.bar = 'baz';


Foo.prototype.init_1 = function (bar) 
    this.bar = bar;
    return this;
;

Foo.prototype.init_2 = function (baz) 
    this.bar = 'something to do with '+baz;
    return this;
;

var a = new Foo().init_1('constructor 1');
var b = new Foo().init_2('constructor 2');

【讨论】:

所以基本上你在这里所做的是获取对象 Foo,然后使用原型函数调用 init_1 和 init_2 参数。您的 init_1 和 init_2 是否应该带有单词 function。 第一个 Foo() 中的 后是否必须有分号。 谢谢 Doug,我做出了改变。 你确定这行得通吗?我无法将new Foo() 和对init 的调用链接在一起,因为我无法访问对象的属性。我不得不跑var a = new Foo(); a.init_1('constructor 1'); @MillieSmith 我承认我已经有一段时间没有编写 JS 了……但我只是将这段代码粘贴到 Chrome JS 控制台中,并且从 new 到 init 的链起作用了。【参考方案6】:

回答是因为这个问题首先返回 谷歌,但答案现在已经过时了。

您可以使用Destructuring objects as constructor parameters in ES6

这是模式:

你不能有多个构造函数,但你可以使用解构和默认值来做你想做的事情。

export class myClass 

  constructor( myArray = [1, 2, 3], myString = 'Hello World' ) 

    // ..
  

如果你想支持“无参数”构造函数,你可以这样做。

export class myClass 

      constructor(myArray = [1, 2, 3], myString = 'Hello World' = ) 

        // ..
      

【讨论】:

【参考方案7】:
export default class Order 

    static fromCart(cart) 
        var newOrder = new Order();
        newOrder.items = cart.items;
        newOrder.sum = cart.sum;

        return newOrder;
    

    static fromOrder(id, order) 
        var newOrder = new Order();
        newOrder.id = id;
        newOrder.items = order.items;
        newOrder.sum = order.sum;

        return newOrder;
    

用途:

  var newOrder = Order.fromCart(cart)
  var newOrder = Order.fromOrder(id, oldOrder)

【讨论】:

【参考方案8】:

有时,参数的默认值对于多个构造函数就足够了。如果这还不够,我会尝试将大部分构造函数功能包装到之后调用的 init(other-params) 函数中。还可以考虑使用工厂概念来制作一个可以有效地创建您想要的其他对象的对象。

http://en.wikipedia.org/w/index.php?title=Factory_method_pattern&oldid=363482142#Javascript

【讨论】:

Factory 方法感觉像是一个很好的解决方案 - 请确保不要将其与使用单独的工厂类混淆,这在此用例中可能完全不相关。 该链接无处可去。该页面上没有 Javascript 锚点。【参考方案9】:

一般你可以传递更多的参数,当你实例化对象时你也可能会错过一些值,它们的默认值将是未定义的,如果你不想管理未定义,构建多构造函数的简单方法应该是这样:

class Car 
  constructor(brand, year = '', owner = '')  // assign default value
    this.carname = brand;
    this.year = year;
    this.owner = owner;
  
  presentCarName() 
    return 'I have a ' + this.carname;
  
  presentCarNameAndYear() 
    return 'I have a ' + this.carname + ' year: ' + this.year;
  


let myCar = new Car("Ford");
console.log(myCar.presentCarName());
myCar = new Car("Ford", 1996);
console.log(myCar.presentCarNameAndYear());

【讨论】:

@word@word 谢谢你的帮助,我很感激!【参考方案10】:

我相信有两个答案。一种使用带有 IIFE 函数的“纯”Javascript 来隐藏其辅助构造函数。而另一个使用NodeJS模块也隐藏了它的辅助构造函数。

我将只展示带有 NodeJS 模块的示例。

Vector2d.js 类:



/*

    Implement a class of type Vetor2d with three types of constructors.

*/

// If a constructor function is successfully executed,
// must have its value changed to 'true'.let global_wasExecuted = false;  
global_wasExecuted = false;   

//Tests whether number_value is a numeric type
function isNumber(number_value) 
    
    let hasError = !(typeof number_value === 'number') || !isFinite(number_value);

    if (hasError === false)
        hasError = isNaN(number_value);
    

    return !hasError;


// Object with 'x' and 'y' properties associated with its values.
function vector(x,y)
    return 'x': x, 'y': y;


//constructor in case x and y are 'undefined'
function new_vector_zero(x, y)

    if (x === undefined && y === undefined)
        global_wasExecuted = true;
        return new vector(0,0);
    


//constructor in case x and y are numbers
function new_vector_numbers(x, y)

    let x_isNumber = isNumber(x);
    let y_isNumber = isNumber(y);

    if (x_isNumber && y_isNumber)
        global_wasExecuted = true;
        return new vector(x,y);
    


//constructor in case x is an object and y is any
//thing (he is ignored!)
function new_vector_object(x, y)

    let x_ehObject = typeof x === 'object';
    //ignore y type

    if (x_ehObject)

        //assigns the object only for clarity of code
        let x_object = x;

        //tests whether x_object has the properties 'x' and 'y'
        if ('x' in x_object && 'y' in x_object)

            global_wasExecuted = true;

            /*
            we only know that x_object has the properties 'x' and 'y',
            now we will test if the property values ​​are valid,
            calling the class constructor again.            
            */
            return new Vector2d(x_object.x, x_object.y);
        

    



//Function that returns an array of constructor functions
function constructors()
    let c = [];
    c.push(new_vector_zero);
    c.push(new_vector_numbers);
    c.push(new_vector_object);

    /*
        Your imagination is the limit!
        Create as many construction functions as you want.    
    */

    return c;


class Vector2d 

    constructor(x, y)

        //returns an array of constructor functions
        let my_constructors = constructors(); 

        global_wasExecuted = false;

        //variable for the return of the 'vector' function
        let new_vector;     

        //traverses the array executing its corresponding constructor function
        for (let index = 0; index < my_constructors.length; index++) 

            //execute a function added by the 'constructors' function
            new_vector = my_constructors[index](x,y);
            
            if (global_wasExecuted) 
            
                this.x = new_vector.x;
                this.y = new_vector.y;

                break; 
            ;
        ;
    

    toString()
        return `(x: $this.x, y: $this.y)`;
    



//Only the 'Vector2d' class will be visible externally
module.exports = Vector2d;  

useVector2d.js 文件使用 Vector2d.js 模块:

const Vector = require('./Vector2d');

let v1 = new Vector(x: 2, y: 3);
console.log(`v1 = $v1.toString()`);

let v2 = new Vector(1, 5.2);
console.log(`v2 = $v2.toString()`);

let v3 = new Vector();
console.log(`v3 = $v3.toString()`);

终端输出:

v1 = (x: 2, y: 3)
v2 = (x: 1, y: 5.2)
v3 = (x: 0, y: 0)

这样我们可以避免脏代码(许多 if 和 switch 遍布整个代码),难以维护和测试。每个建筑功能都知道要测试哪些条件。增加和/或减少您的建筑功能现在很简单。

【讨论】:

【参考方案11】:

这是Programming in html5 with JavaScript and CSS3 - Exam Ref中为多个构造函数给出的示例。

function Book() 
    //just creates an empty book.



function Book(title, length, author) 
    this.title = title;
    this.Length = length;
    this.author = author;


Book.prototype = 
    ISBN: "",
    Length: -1,
    genre: "",
    covering: "",
    author: "",
    currentPage: 0,
    title: "",

    flipTo: function FlipToAPage(pNum) 
        this.currentPage = pNum;
    ,

    turnPageForward: function turnForward() 
        this.flipTo(this.currentPage++);
    ,

    turnPageBackward: function turnBackward() 
        this.flipTo(this.currentPage--);
    
;

var books = new Array(new Book(), new Book("First Edition", 350, "Random"));

【讨论】:

和不变性?使用原型将属性公开,对吗? 这是不正确的。您的第二个构造函数定义覆盖了第一个,因此当您稍后调用 new Book() 时,您将调用第二个构造函数并将所有参数的值设置为 undefined。

以上是关于多个构造函数的 JavaScript 模式的主要内容,如果未能解决你的问题,请参考以下文章

Javascript-基础-Object创建对象

JavaScript设计模式之构造函数模式

JavaScript 创建对象 (工厂模式构造函数模式原型模式组合使用构造函数模式与原型模式)

深入理解JavaScript系列(26):设计模式之构造函数模式

JavaScript高级 对象创建模式 object 对象字面量 工厂模式 自定义构造函数 构造函数+原型

JavaScript创建对象的默认方式:组合使用构造函数模式和原型模式