Javascript 什么';这是';var webApp={..}';和';var webApp=function(){..}';

Javascript 什么';这是';var webApp={..}';和';var webApp=function(){..}';,javascript,javascript-objects,modularity,Javascript,Javascript Objects,Modularity,我最近一直在尝试使用模块化JS,我仍然怀疑我是否用“正确的方式”编写它 例如,如果我有一个页面,其中包含输入和提交按钮,提交后应显示数据(例如,表格和图形),那么我在IFFE下编写代码,以便任何人都无法访问它,但使用如下对象变量: var webApp = { ... } 在它里面,我缓存来自DOM的元素,添加绑定事件和其他有用的函数 这是我用于表单的真实代码,在加载数据时应显示图形、表格和进度条,并且所有内容都在一个对象中管理qwData: (function() { const

我最近一直在尝试使用模块化JS,我仍然怀疑我是否用“正确的方式”编写它

例如,如果我有一个页面,其中包含输入和提交按钮,提交后应显示数据(例如,表格和图形),那么我在IFFE下编写代码,以便任何人都无法访问它,但使用如下对象变量:

var webApp = { ... } 
在它里面,我缓存来自DOM的元素,添加绑定事件和其他有用的函数

这是我用于表单的真实代码,在加载数据时应显示图形、表格和进度条,并且所有内容都在一个对象中管理
qwData

(function() {

    const qwData = {

        // Initialize functions
        init: function() {
            this.cacheDom();
            this.bindEvents();
        },
        // Cache vars 
        cacheDom: function() {
            this.dataDisplayed      = false;
            this.countUsers         = <?php echo $_SESSION['all_users_count_real']; ?>;
            this.customerMachines   = <?php echo $_SESSION['customer_statistics']['total_machines']; ?>;
            this.$form              = $('#frm');
            this.$alistToolbar      = this.$form.find('.alist-toolbar');
            this.start_date         = this.$form[0][9].value;
            this.end_date           = this.$form[0][10].value;
            this.dateCount          = this.countDays(this.start_date, this.end_date);
            this.show               = document.querySelector('#btn-show');
            this.downloadBtn        = document.querySelector('#download_summary_button');
            this.$dataContainer     = $('#qw-data-container');
            this.$qwTable           = $('#qwtable');
            this.$qwTbody           = this.$qwTable.find('tbody');
            this.$qwTd              = this.$qwTbody.find('td');
            this.qwChart            = echarts.init(document.getElementById('main-chart'));
            this.progressBar        = document.querySelector('.progress-bar');
            Object.defineProperty(this, "progress", {
                get: () => {
                   return this.progressPrecent || 0;
                },
                set: (value) => {

                    if( value != this.progressPrecent ) {
                      this.progressPrecent = value;
                      // this.setQwChartProgress(value);
                      this.setProgressBarValue(value);
                      this.setProgressButton(value);
                    }
                }, 
                  configurable: true
            });
            this.qwChartProgress    = this.progress;
        },
        // Bind click events (or any events..)
        bindEvents: function() {

            var that = this;

            // On click "Show" BTN
            this.show.onclick = this.sendData.bind(this);

            // On Change inputs
            this.$form.change(function(){
                that.updateDatesInputs(this);
            });

            // downloadQw
            this.downloadBtn.onclick = this.downloadQw.bind(this);
        },
        downloadQw: function(e){
            e.preventDefault();

            var customer = "<?php echo $_SESSION['company_name']; ?>";
            var filename = customer + "qws_"+ this.start_date + "-" + this.end_date + ".zip";

            $.ajax({
                url: "/aaa/api/download_csv.php",
                method: "GET",
                dataType : "json",
                data: { 
                    customer: customer,
                    filename: filename
                },
                success:function(result){
                if(result.status){
                    window.location.href="/aaa/api/download_csv.php?customer="+customer+"&filename="+filename+"&download=1";
                }
            },
                error:function(){
                }
            })
        },
        setQwChartProgress: function(value){
            if (value != 0) {
                // Show Chart Loading 
                this.qwChart.showLoading({
                    color: (value == 99) ? '#00b0f0' : '#fff',
                    text: value + '%' 
                });
            }
        },
        setProgressButton: function(value){

            if ( value >= 100 || value == 0 ){
                this.show.value     = 'Show';
            }
            else {
                this.show.value     = value +'%';
                // this.show.disabled   = true;
                this.disableShow();
            }
        },
        resetShowButton: function(){
            this.show.value = 'Show';
            this.disableShow();
        },
        disableShow: function(){
            // this.show.style.color = "grey";
            // this.show.disabled   = true;
            this.show.classList.add("isDisabled");
        }, 
        enableShow: function(){
            // this.show.style.color = "#66aa66";
            // this.show.disabled   = false;
            this.show.classList.remove("isDisabled");
        },
        updateDatesInputs: function(){
            this.start_date     = this.$form[0][9].value;
            this.end_date       = this.$form[0][11].value;
            this.dateCount      = this.countDays(this.start_date,this.end_date);
            // this.show.disabled   = false;
            this.enableShow();
            this.removeError();
        },
        removeError: function(){
            if (this.errors) {
                this.errors.remove();
                delete this.errors;
            }
        },
        countDays: function(date1, date2){

            // First we split the values to arrays date1[0] is the year, [1] the month and [2] the day
            var date1 = date1.split('-');
            var date2 = date2.split('-');

            // Now we convert the array to a Date object, which has several helpful methods
            date1 = new Date(date1[0], date1[1], date1[2]);
            date2 = new Date(date2[0], date2[1], date2[2]);

            // We use the getTime() method and get the unixtime (in milliseconds, but we want seconds, therefore we divide it through 1000)
            var date1_unixtime = parseInt(date1.getTime() / 1000);
            var date2_unixtime = parseInt(date2.getTime() / 1000);

            // This is the calculated difference in seconds
            var timeDifference = date2_unixtime - date1_unixtime;

            // in Hours
            var timeDifferenceInHours = timeDifference / 60 / 60;

            // and finaly, in days :)
            var timeDifferenceInDays = timeDifferenceInHours  / 24;

            if (timeDifferenceInDays > 0){
                return timeDifferenceInDays;
            } else {
                // alert('Error: The date are invalid.');
            }
        },
        // Get data, prevent submit defaults and submit. 
        sendData: function(e) {
            e.preventDefault();

            if (this.show.classList.contains('isDisabled')) {

                this.showErrorDiv("Please select a new date range before submitting.");
            } else {

                let that                = this;
                let estimatedTotalTime  = ( (this.countUsers*this.customerMachines)/777 ) * 0.098; 
                let estimatedTime       = estimatedTotalTime/99;
                let estimatedTimeMs     = estimatedTime*1000;
                let timer               = setInterval( function(){that.incrementProgress(timer);}, estimatedTime); 

                console.log('Total Time: ' + estimatedTotalTime + 's');
                console.log('Estimated Time for 1%: ' + estimatedTime + 's');

                $.ajax({
                    type: 'POST',
                    url: "/manageit/ajax.php?module=qw_module",
                    dataType: 'json',
                    data: {
                            start_ts: that.start_date,
                            stop_ts: that.end_date, 
                            submitted: true, 
                            company_name: "<?php echo $_SESSION['company_name']; ?>"
                    },
                    beforeSend: function() {

                        // Show Chart Loading 
                        that.qwChart.showLoading({ 
                            color: '#00b0f0', 
                            // text: that.qwChartProgress
                            text: ''
                        });

                        // If data div isn't displayed
                        if (!that.dataDisplayed) {
                            // Show divs loading
                            that.showMainDiv();
                        } else {
                            that.$qwTbody.slideUp('fast');
                            that.$qwTbody.html('');
                        }
                    },
                    complete: function(){},
                    success: function(result){

                        // Reset show btn
                        that.resetShowButton();

                        // Clear timer
                        clearInterval(timer);

                        // Set progressbar to 100%
                        that.setProgressBarTo100();

                        // Show Download Button
                        that.downloadBtn.style.display = 'inline-block';

                        // Insert Chart Data
                        that.insertChartData(result);

                        // Insert Table Data
                        that.insertTableData(result);
                    }
                });

                that.dataDisplayed = true;
            }
        },
        showErrorDiv: function(errorTxt){

            if (!this.errors){
                this.errors             = document.createElement("div");
                this.errors.className   = "qw_errors_div";
                this.errors.textContent = errorTxt;
                this.$alistToolbar.append(this.errors);
            } 
        },
        // Insert Data to Table
        insertTableData: function(json){

            let str = '';
            let isOdd = ' rowspan="2" ';

            for ( let i=1; i<9; i++ ) {

                str += '<tr>';

                for (let j = 0; j < 8; j++) {

                    if ((i%2 === 0) && (j==0)){
                        // do nada
                    } else {
                        str += '<td '; 
                        str += ((i % 2 !== 0)&&(j==0)) ? isOdd : '';
                        str += '> '; 
                        str += json[i][j]; 
                        str += '</td>';
                    }
                }
                str += '</tr>'; 
            }

            this.$qwTbody.html(str);

            this.$qwTbody.slideDown('fast', function(){
                if ($(this).is(':visible'))
                    $(this).css('display','table-row-group');
            });

            // Apply colors on table.
            this.tableHover();
        },
        tableHover: function(){

            this.$qwTd              = this.$qwTbody.find('td');
            var that =  this;

            this.$qwTd.eq(0).hover( function(){
                that.$qwTd.eq(0).css('background-color', '#f5f5f5');
                that.$qwTd.eq(0).parent().css('background-color', '#f5f5f5');
                that.$qwTd.eq(0).parent().next().css('background-color', '#f5f5f5');    
            }, function(){
                that.$qwTd.eq(0).css('background-color', '');
                that.$qwTd.eq(0).parent().css('background-color', '');
                that.$qwTd.eq(0).parent().next().css('background-color', '');   
            });

            this.$qwTd.eq(15).hover( function(){
                that.$qwTd.eq(15).css('background-color', '#f5f5f5');
                that.$qwTd.eq(15).parent().css('background-color', '#f5f5f5');
                that.$qwTd.eq(15).parent().next().css('background-color', '#f5f5f5');   
            }, function(){
                that.$qwTd.eq(15).css('background-color', '');
                that.$qwTd.eq(15).parent().css('background-color', '');
                that.$qwTd.eq(15).parent().next().css('background-color', '');  
            });

            this.$qwTd.eq(30).hover( function(){
                that.$qwTd.eq(30).css('background-color', '#f5f5f5');
                that.$qwTd.eq(30).parent().css('background-color', '#f5f5f5');
                that.$qwTd.eq(30).parent().next().css('background-color', '#f5f5f5');   
            }, function(){
                that.$qwTd.eq(30).css('background-color', '');
                that.$qwTd.eq(30).parent().css('background-color', '');
                that.$qwTd.eq(30).parent().next().css('background-color', '');  
            });

            this.$qwTd.eq(45).hover( function(){
                that.$qwTd.eq(45).css('background-color', '#f5f5f5');
                that.$qwTd.eq(45).parent().css('background-color', '#f5f5f5');
                that.$qwTd.eq(45).parent().next().css('background-color', '#f5f5f5');   
            }, function(){
                that.$qwTd.eq(45).css('background-color', '');
                that.$qwTd.eq(45).parent().css('background-color', '');
                that.$qwTd.eq(45).parent().next().css('background-color', '');  
            });
        },
        incrementProgress: function(timer){

            if (this.progress == 99)
                clearInterval(timer);
            else 
                this.progress++;
        },
        // Insert Data to Chart
        insertChartData: function(json){

            var posList = [
                'left', 'right', 'top', 'bottom',
                'inside',
                'insideTop', 'insideLeft', 'insideRight', 'insideBottom',
                'insideTopLeft', 'insideTopRight', 'insideBottomLeft', 'insideBottomRight'
            ];

            this.qwChart.configParameters = {
                rotate: {
                    min: -90,
                    max: 90
                },
                align: {
                    options: {
                        left: 'left',
                        center: 'center',
                        right: 'right'
                    }
                },
                verticalAlign: {
                    options: {
                        top: 'top',
                        middle: 'middle',
                        bottom: 'bottom'
                    }
                },
                position: {
                    options: echarts.util.reduce(posList, function (map, pos) {
                        map[pos] = pos;
                        return map;
                    }, {})
                },
                distance: {
                    min: 0,
                    max: 100
                }
            };

            this.qwChart.config = {
                rotate: 90,
                align: 'left',
                verticalAlign: 'middle',
                position: 'insideBottom',
                distance: 15,
                onChange: function () {
                    var labelOption = {
                        normal: {
                            rotate: this.qwChart.config.rotate,
                            align: this.qwChart.config.align,
                            verticalAlign: this.qwChart.config.verticalAlign,
                            position: this.qwChart.config.position,
                            distance: this.qwChart.config.distance
                        }
                    };
                    this.qwChart.setOption({
                        series: [{
                            label: labelOption
                        }, {
                            label: labelOption
                        }, {
                            label: labelOption
                        }]
                    });
                }
            };

            var labelOption = {
                normal: {
                    show: true,
                    position: this.qwChart.config.position,
                    distance: this.qwChart.config.distance,
                    align: this.qwChart.config.align,
                    verticalAlign: this.qwChart.config.verticalAlign,
                    rotate: this.qwChart.config.rotate,
                    // formatter: '{c}  {name|{a}}',
                    formatter: '{name|{a}}',
                    fontSize: 16,
                    rich: {
                        name: {
                            // textBorderColor: '#fff', 
                            // color: '#333',
                            // color: '#717171',
                            color: '#525252',
                            shadowColor: 'transparent', 
                            shadowBlur: 0, 
                            textBorderColor: 'transparent',
                            textBorderWidth: 0, 
                            textShadowColor: 'transparent', 
                            textShadowBlur: 0
                        }
                    }
                }
            };

            option = {
                color: ['#007bff', '#00b0f0', 'red', '#e5323e'],
                tooltip: {
                    trigger: 'axis',
                    axisPointer: {
                        type: 'shadow'
                    }
                },
                legend: {
                    data: ['Inactives / Viewers', 'Inactives / Viewers / Less than 1min per day', 'Light no Macro'] 
                },
                toolbox: {
                    show: true,
                    orient: 'vertical',
                    left: 'right',
                    top: 'center',
                    feature: {
                        mark: {show: true},
                        // dataView: {show: true, readOnly: false},
                        // magicType: {show: true, type: ['line', 'bar', 'stack', 'tiled']},
                        restore: {show: true},
                        saveAsImage: {show: true}
                    }
                },
                calculable: true,
                xAxis: [
                    {
                        type: 'category',
                        axisTick: {show: false},
                        data: ['Excel', 'Word', 'PowerPoint', 'All 3 Apps']
                    }
                ],
                yAxis: [
                    {
                        type: 'value', 
                        name: 'Score'
                    }
                ],
                series: [
                    {
                        name: 'Light no Macro',
                        type: 'bar',
                        label: labelOption,
                        color: 'red',
                        data: [ [3, json[7][7]] ]
                    },
                    {
                        name: 'Inactives / Viewers',
                        type: 'bar',
                        barGap: 0,
                        label: labelOption,
                        data: [ json[1][7], json[3][7], json[5][7], json[8][7] ]
                    },
                    {
                        name: 'Inactives / Viewers / Less than 1min per day',
                        type: 'bar',
                        label: labelOption,
                        data: [ json[2][7], json[4][7], json[6][7] ]
                    }
                ]
            };

            // Set charts options
            this.qwChart.setOption(option);
            // Hide Loading
            this.qwChart.hideLoading();
        },
        // Show Main div on submition (only)
        showMainDiv: function(){
            // Show all contatner div
            this.$dataContainer.slideDown('slow');
        },
        // Sets a new value for the progress bar
        setProgressBarValue: function(value){

            this.progressBar.style.width = this.returnNumWithPrecent(value);
        },
        returnNumWithPrecent: function(num){

            return num.toString() + '%';
        },
        setProgressBarTo100: function(){
            var that = this;
            // Show Download Button
            this.progress = 100;
            setTimeout(function () {
                // Show Download Button
                that.progress = 0;
            }, 1000);
        }
    }
    // run object
    qwData.init();
})();
例如:

var Background = (function() {
  'use strict';
  // placeholder for cached DOM elements
  var DOM = {};
  /* =================== private methods ================= */
  // cache DOM elements
  function cacheDom() {
    DOM.$background = $('#background');
  }

  // coordinate async assembly of image element and rendering
  function loadImage() {
    var baseUrl = 'https://source.unsplash.com/category',
        cat     = 'nature',
        size    = '1920x1080';
    buildElement(`${baseUrl}/${cat}/${size}`)
      .then(render);
  }

  // assemble the image element
  function buildElement(source) {
    var deferred = $.Deferred(function (task) {
      var image = new Image();
      image.onload = function () {
        task.resolve(image);
      };
      image.onerror = function () {
        task.reject();
      };
      image.src = source;
    });
    return deferred.promise();
  }

  // render DOM
  function render(image) { 
    DOM.$background
      .append(image)
      .css('opacity', 1);
  }

  /* =================== public methods ================== */
  // main init method
  function init() {
    cacheDom();
    loadImage();
  }

  /* =============== export public methods =============== */
  return {
    init: init
  };
}());
关于这一点,我有两个问题:

  • 使用对象与在对象内部设置函数、变量等有什么区别:

    var webApp={…}

  • 和具有相同特征的函数变量(仅具有 语法不同)。就像我粘贴的链接中的示例

    var webApp = function (){ ... };
    
  • 在一个对象/函数中编写这样的代码(有点像图、表、进度条这样的分离元素)是正确的吗?是否应该更好地将其分离到不同的对象?如果有更新更好的方法来编写这样的代码,请提及我应该研究什么
    IIFE模式允许从IIFE外部无法访问的私有变量--
    DOM
    cacheDom
    loadImage
    。在对象模式中,所有内容都可以作为对象的属性公开访问。有时,如果不先声明一些变量,就无法构造所需的对象,因此将这些变量隔离在IIFE中也很有用。

    如果熟悉面向对象编程,可以通过类比类的私有和公共属性和函数来理解它。 其他方法:模块化模式,参见节点模块。这是关于包装的

    请看以下代码:

    var obj = (function() {
        let a = function() { console.log('function a'); };
        let b = function() { a(); console.log('function b'); };
        return { b: b };
    })();
    
    obj.b();
    // function a
    // function b
    
    obj.a();
    // TypeError: obj.a is not a function
    
    或者更熟悉的方式是:

    function MyStuff() {
        let a = function() { console.log('function a'); };
        let b = function() { a(); console.log('function b'); };
        return { b: b };
    }
    
    var obj = MyStuff();
    
    obj.b();
    // function a
    // function b
    
    obj.a();
    // TypeError: obj.a is not a function
    
    在函数内部,您有一个本地作用域,无法从外部访问。在这个局部范围内,您可以创建变量、函数等

    在return语句中,返回一个将函数和变量导出到外部作用域的对象。这就像决定哪些功能/道具是公共的,哪些应该是私有的

    当您以
    var webApp={…}
    的方式创建对象时,您直接创建了一个对象,它的所有道具和函数都在您定义变量“webApp”的范围内公开


    回答你的第二个问题,是的,你应该通过关注点把东西分成不同的对象。见坚实的原则。:)

    互联网教程的一个问题是,它们的相关性已经过时,很少有作者能及时更新它们。在JS领域,事情发展得非常快,5年前的行业标准(例如jQuery)现在看起来很奇怪,因为你仍然偶然发现它

    所以,要把我一直抱怨别人忽略的好习惯付诸实践:

    JavaScript模块状态,2018年年中,变化迅速,已弃用 一团糟

    首先,您有ES6模块。ES 6更名为ES 2015,模块部分被取出并制成单独的规范,这意味着浏览器可以兼容ES 2015,但仍然没有本机模块。然而,3年后,每个拥有相关全球市场份额的浏览器(chrome、android chrome、firefox、iOS Safari)都至少实现了本机模块系统的基本版本(Edge、Opera等也是如此)。我不清楚,因为我相信规范允许路径更宽容(我们将在一分钟内回到这一点),但以下是语法、相对或绝对文件路径以及所需的扩展:

    import Foo from './foo.js'; // Import default export from foo.js, alias to 'Foo'
    import { Foo } from './foo.js'; // Import named export 'Foo' from foo.js
    
    export default function () {}; // exports a function as default, importer aliases
    const Foo = class Foo {};
    export Foo; // exports named class Foo
    
    与其他任何东西相比,它们有很多优点(首先,您不需要特殊的工具或构建过程),但由于它们是最新的,所以它们在JavaScript生态系统中还没有得到广泛的使用。因为他们的时间很长,而且人们还有工作要做,所以他们实现了各种其他模块模式/工具/系统。最早的一个是你的问题中的一个,但这种模式虽然肯定比没有好,但有足够多的问题让人们开始四处寻找

    AMD模块 另一个早期产品是
    require.js
    的异步模块定义。虽然它有一些引人注目的技术优势,但实际上已经死了

    Common.js模块 node.js凭借其基于common.js模块(基本上已经成为common.js的实际风格)的模块系统在现场大放异彩。人们开始说“嘿,在浏览器中也能做到这一点真是太好了”,于是,browserify。Browserify是一个工具,它可以遍历依赖关系图,并将
    require
    表达式转换为浏览器可以处理的内容(基本上,通过使require成为一个函数)。Node的模块有点不适合浏览器,但集中在一个标准上比8000万个竞争性的临时实现要好。人们看着这三个相互竞争的模块模式/系统(你的问题中有一个,AMD,common.js),说我们可以统一它们。因此

    通用模块定义 如果您在野外看到过这样的代码:

    (function (root, factory) {
        if (typeof define === 'function' && define.amd) {
            // AMD
            define(['jquery'], factory);
        } else if (typeof exports === 'object') {
            // Node, CommonJS-like
            module.exports = factory(require('jquery'));
        } else {
            // Browser globals (root is window)
            root.returnExports = factory(root.jQuery);
        }
    }(this, function ($) {
    
    import Foo from 'foo'; // imports default export from foo.js somewhere on PATH
    
    那你已经看过UMD了。注意它是如何检查它所在的环境是否为AMD或common.js设置的。为了遗留代码和易读性的考虑,Transformers将这两种样式都转换成这种样式(这是一个相当简单的样板)

    但人们想要的更多:他们希望能够用代码表达他们的webapp的所有依赖项(包括css和图像),并使用一个工具对其进行切分和选择性加载。另外,此时本机模块规范已经在草案中,人们希望使用这种语法。因此

    网页包模块 Webpack目前是默认的
    import Foo from 'foo'; // imports default export from foo.js somewhere on PATH
    
    import 'Something.css'; // commonly seen in react apps
    import 'logo.svg'; // ditto