В чем разница между 'var webApp = {..}' и 'var webApp = function () {..}' - PullRequest
0 голосов
/ 06 июня 2018

В последнее время я много экспериментировал с модульным 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();
})();

но я вижу другие примеры , которые пишут функциональность под функцией, а не объектом:

webApp = function (){ ... };

как в примере:

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
  };
}());

У меня есть 2вопросы по этому поводу:

  1. В чем разница между использованием объекта и внутри него функций настройки, vars, ect ':

    var webApp = {...};

и переменная функции с одинаковыми характеристиками (только с синтаксисом, написанным по-разному).как пример из ссылки, которую я вставил.

var webApp = function (){ ... };
Правильно ли писать код, подобный тому, где все (вроде отдельные элементы, такие как график, таблица, индикатор выполнения) в одном объекте / функции?Должно ли это быть лучше разделено на разные объекты?Если есть более новый и лучший способ написания такого кода, пожалуйста, укажите, что я должен исследовать.

Ответы [ 3 ]

0 голосов
/ 06 июня 2018

Если вы знакомы с объектно-ориентированным программированием, его можно понять по аналогии с частными и открытыми свойствами и функциями класса.Другой подход: модульная структура, см. Узловые модули.Речь идет об упаковке.

Посмотрите на этот код:

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".

Отвечая на ваш второй вопрос, да, вы должны разделить вещи на отдельные объекты по интересам.Смотрите твердые принципы.:)

0 голосов
/ 06 июня 2018

Одна из проблем с интернет-уроками заключается в том, что они остаются за рамками актуальности, и очень немногие авторы держат их в курсе.На земле JS дела идут очень быстро, и отраслевой стандарт 5 лет назад (например, jQuery) теперь кажется странным, когда вы все еще наталкиваетесь на него.

Итак, чтобы применить на практике хорошую привычку, которой я придерживаюсьдругие за пропуск:

Состояние модулей JavaScript, середина 2018 года, быстрое изменение, # устарело

Это беспорядок.

Сначала у вас есть модули ES 6.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 в то, что браузер мог бы обрабатывать (в основном, требуя функции).Модули Node в некотором роде не подходят для браузера, но конвергенция по одному стандарту была лучше, чем восемьдесят миллионов конкурирующих adhoc-реализаций.Люди посмотрели на эти три конкурирующие модели / системы модулей (один в вашем вопросе, 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 ($) {

Тогда вы видели UMD.Обратите внимание, как он проверяет, настроена ли среда, в которой он находится, для AMD или common.js.Трансформаторы были написаны для преобразования обоих стилей в это для унаследованного кода и проблем с разборчивостью (это довольно большой пример).

Но люди хотели большего: они хотели иметь возможность выразить все иззависимости их веб-приложений (включая CSS и изображения) в коде с помощью инструмента для его разбиения и выборочной загрузки.К тому же к этому времени спецификация нативного модуля находилась в стадии разработки, и люди хотели использовать этот синтаксис.И, таким образом,

Модули Webpack

Webpack в настоящее время является используемой сегодня системой defacto (хотя многие люди все еще используют browserify).Синтаксис модуля Webpack выглядит примерно так:

import Foo from 'foo'; // imports default export from foo.js somewhere on PATH

Это выглядит знакомо?Очень похоже на (но тонко отличается от родных модулей).Webpack также может выполнять следующие действия:

import 'Something.css'; // commonly seen in react apps
import 'logo.svg'; // ditto

Это удобно, когда люди переходят к компонентным системам, приятно иметь возможность выражать все зависимости компонентов в файле точек входа для этого компонента.К сожалению, импорт HTML, который позволил бы вам сделать это изначально без шага сборки, умер страшной ужасной смертью.

Несовместимость с нативной системой модулей, тонкими (пути и расширения файлов) и брутто (импорт ресурсов, отличных от js), вызывает сожаление, это означает, что один или другой придется изменить, потому что я пытался написатьродные основанные на модулях приложения в последнее время, и очень трудно использовать библиотеки (почти ни одна из которых не предлагает родной вариант модуля).

Что использовать - это своего рода вопрос, но есливы используете фреймворк, используйте то, что является общим для других пользователей этой фреймворк.Common.js и webpack достаточно распространены, так что существует множество инструментов для их потребления, и, вероятно, они являются лучшим выбором на данный момент.Другая вещь, на которую стоит обратить внимание, - это динамический импорт, уже загруженный в несколько браузеров.

Извините, что все это так запутанно, что вы просто вводите JavaScript в очень переходный период.

0 голосов
/ 06 июня 2018

Шаблон IIFE допускает частные переменные - DOM, cacheDom, loadImage - которые недоступны извне IIFE.В шаблоне объекта все доступно публично как свойства объекта.Иногда вы вообще не можете создать нужный объект, не объявив сначала некоторые переменные, поэтому полезно хранить их изолированными внутри IIFE.

...