【问题标题】:Encapsulating Class Definitions in JavaScript for Cleaner Namespace在 JavaScript 中封装类定义以实现更简洁的命名空间
【发布时间】:2013-02-03 11:07:20
【问题描述】:

我正在创建一个测验交互,经过几天的研究,我试图确定声明我的对象的最佳方式。有一个主匿名函数(可以称为测验,但不需要公共方法),其中包含场景的类定义,其中包含问题的类定义:

测验 > 情景 > 问题 > 答案(最终)

我更喜欢立即调用 ('iffy') 模型来强制执行私有/公共,但我也需要多个实例,所以我认为我应该使用原型?我已将类定义设为私有,因为它们仅用于此交互。这是最好的模型吗?

JsFiddle:http://jsfiddle.net/QtCm8/

(function(quizData) {
    var scenarios = [];
    for(var s=0;s<quizData.scenarios.length;s++) scenarios.push(new Scenario(quizData.scenarios[s]));
    function Scenario(scenarioData) {
        console.log("New Scenario: " + scenarioData.title);
        var questions = [];
        for(var q=0;q<scenarioData.questions.length;q++) questions.push(new Question(scenarioData.questions[q]));
        function Question(questionData) {
            console.log("New Question: " + questionData.text);
            // Repeat pattern for future answers object
        }
    }
})({
    scenarios: [
        {
            title: 'Scenario1'
            ,questions: [
                {
                    text: 'What is 1+1?'
                }
                ,{
                    text: 'What is 2+2?'
                }
            ]
        }
    ]
});

【问题讨论】:

    标签: javascript class namespaces


    【解决方案1】:

    首先,Javascript 中没有类,只有对象。它是一种prototype-based 语言(不是基于类的),它的功能是first-class objects。顺便说一句,prototype 是一个对象,您创建的每个函数都将它指向一个新的空白对象。只是使用函数模拟的“类”的概念。

    正如您所说,这种立即模式(又名匿名函数)对于提供更清晰的命名空间非常有用。当有一项工作只需要完成一次时,它也很有用,例如。初始化代码,所以没有理由有一个可重用的命名函数。

    但是,如果您需要可重用的成员(例如方法),是的,它应该转到 prototype。向原型属性添加公共属性和方法,允许这些公共部分在使用相同构造函数创建的所有实例之间共享。

    也许您正在寻找 Module Pattern,它是像

    这样的模式的组合
    • 命名空间,
    • 立即函数,
    • 私人会员等

    【讨论】:

    • 是的,这就是我说“确定声明我的对象的最佳方式”的原因。 ;) 总的来说,我更喜欢模块模式,但它不包括可重用的成员。我正在寻找两者的完美结合。
    【解决方案2】:

    就个人而言,我喜欢使用返回他们希望公开的属性的类定义。以下是您的案例的示例。你可以玩弄这个结构,找到你最喜欢的东西,但它应该给你一些例子:

    // Namespace
    var q = {};
    
    q.Quiz = function Quiz (data) {
    
        // Private code & variables
        var scenarios = [];
        for (var i = 0; i < data.scenarios.length; i++) {
            scenarios.push(new q.Scenario(data.scenarios[i]));
        }
    
        // Return public methods & values
        return {
            scenarios: scenarios
        };
    };
    
    q.Scenario = function Scenario (sData) {
    
        // Private inner class
        var Question = function (qData) {
            console.log("New Question: " + qData.text);
            return {
                text: qData.text
            };
        };
    
        console.log("New Scenario: " + sData.title);
        var questions = [];
        for (var i = 0; i < sData.questions.length; i++) {
            questions.push(new Question(sData.questions[i]));       
        }
    
        return {
            questions: questions
        };
    };
    
    var data = {
        scenarios: [
            {
                title: 'Scenario1'
                ,questions: [
                    {
                        text: 'What is 1+1?'
                    }
                    ,{
                        text: 'What is 2+2?'
                    }
                ]
            }
        ]
    };
    
    console.log(new q.Quiz(data));
    

    这里的工作示例:http://jsfiddle.net/5M8bp/

    或者,您可以摆脱 return 语句并使用“this”关键字定义公共属性和方法。例如:

    q.Quiz = function Quiz (data) {
    
        this.scenarios = [];
    
        for (var i = 0; i < data.scenarios.length; i++) {
            this.scenarios.push(new q.Scenario(data.scenarios[i]));
        }
    };
    
    q.Scenario = function Scenario (sData) {
    
        // Private inner class
        var Question = function (qData) {
            console.log("New Question: " + qData.text);
            this.text = qData.text;
        };
    
        console.log("New Scenario: " + sData.title);
        this.questions = [];
        for (var i = 0; i < sData.questions.length; i++) {
            this.questions.push(new Question(sData.questions[i]));       
        }
    };
    

    【讨论】:

    • 感谢您的回复——为什么您将 Questions 编码为内部类,而将 Scenario 保留为根类?总的来说,这就是我想要弄清楚的——它们应该是根定义还是内部定义。我应该使用原型定义,以便为所有实例重复使用 Scenario 和 Question 类的方法吗?
    • 旁注:最终,我将使用公共方法更新所有类(可能通过返回对象字面量),这样我就不必返回整个数据对象。
    • 没有完美的设计,所以你必须弄清楚什么对你的情况有意义。你想在多个不同的场景中使用相同的问题,还是在多个不同的测验中使用相同的场景?如果是这样,那么它们可能应该是公共课程。如果不是,那么内部类可能是有意义的。我的示例是演示多种不同的范例。
    • 我认为原型方法定义看起来不那么清晰和直观,因为它们没有封装在单个类声明中;我猜个人喜好。他们也无法访问构造函数方法中的私有变量。至于对象创建中的性能提升,我认为这可能是一个过早的优化。但是,如果您对此感到担心,可以尝试分析两种设计选择并查看您的特定情况的差异。
    • 我用原型写了所有内容,并且倾向于同意易读性减弱,而且我还发现了您现在确认的关于访问私有变量的内容。由于过早的优化,我可能会放弃共享成员的要求。一直在努力成长和学习,但现在可能太多了。
    【解决方案3】:

    考虑将您的类打包成对象。以下是我提出的设计模式。由于您有这么多相互依赖的类,我认为最好将它们排列成一个对象。代码如下:

    var quiz = {};
    
    quiz.init = (function(data) {
        this.scenarios = this.questions = this.answers = [];
    
        this.data = {
            Question: data.Question.bind(this),
            Answer: data.Answer.bind(this),
            Scenario: data.Scenario.bind(this)
        };
    
        this.set_data = function(qas) {
            // fill the arrays here
            // Question, Scenario and Answer are now this.data.Question/Answer...
            // you can use a variable to shorten the names here
        };
    });
    
    var Quiz = function(data) {
        return new quiz.init(data);
    };
    
    var data = { // classes are defined here
        Question: function() {},
        Answer: function() {},
        Scenario: function() {}
    };
    
    var q = Quiz( data );
    
    q.data.set_data(/* Big object literal goes here */);
    

    【讨论】:

    • 有趣的方法。当它们设置为全局变量时,为什么要传入“数据”(类定义)?如果您从 '(function(data) {' 中取出参数 'data',那么绑定将简单地引用全局对象而不是本地对象,是吗?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-02-19
    • 2020-08-07
    • 1970-01-01
    • 2010-09-05
    • 2012-02-21
    • 2011-06-20
    • 1970-01-01
    相关资源
    最近更新 更多