最近,我一直在对模块化JS进行大量试验,但我仍然想知道是否以“正确的方式”编写它。
例如,如果我有一个页面,其中包含输入和提交按钮,这些页面应在提交后显示数据(例如表格和图形),所以我在IFFE下编写代码,因此没有人可以访问它,但是使用这样的对象变量:
var webApp = { ... }
在其中,我缓存了DOM中的元素,添加了绑定(bind)事件和其他有用的功能。
这是我使用的真正代码,用于在加载数据时显示图形,表格和进度条的表单,并且所有这些都在一个对象
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();
})();
但是我看到other examples在函数而不是对象下编写了功能:
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个问题:
var webApp = {...};
和具有相同特征的函数变量(
语法写得不同)。就像我粘贴的链接中的示例一样。
var webApp = function (){ ... };
最佳答案
互联网教程的问题之一是它们徘徊在相关性之上,并且很少有作者将它们保持最新。在JS Realm 中,事情进展得非常快,而5年前的行业标准(例如jQuery)现在在您仍然迷迷糊糊时似乎很奇怪。
因此,要实践我为别人遗漏而good的好习惯:
JavaScript模块状态,2018年中,变化迅速,已弃用
一团糟。
首先,您拥有ES 6模块。 ES 6被重命名为ES 2015,并且取出了模块部分,并使其成为一个单独的规范,这意味着浏览器可以符合ES 2015且仍不具有本机模块。但是,三年后,每个具有相关全局市场份额的浏览器(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的模块有点不适合浏览器,但是在一个标准上的融合要比八千万个竞争性的即席实现更好。人们查看了这三种竞争的模块模式/系统(在您的问题中,一个是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设置。编写变形金刚是为了将两种样式都转换为这种样式,以解决遗留代码和易读性问题(这是一个很简单的示例)。
但是人们想要更多:他们希望能够使用分片和有选择地加载工具的方式,在代码中表达其Web应用程序的所有依赖关系(包括CSS和图像)。加上此时,本机模块规范已经起草,人们希望使用该语法。因此
Webpack模块
Webpack当前是当今使用的事实上的系统(尽管很多人仍在使用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 Assets )不兼容,这意味着一个或另一个将不得不更改,因为我尝试编写本机模块,基于应用程序的应用程序,并且使用库非常困难(几乎没有库提供本地模块样式)。
使用什么是一种自以为是的问题,但是如果您使用的是框架,请使用该框架的其他用户所共有的东西。 Common.js和webpack很常见,因此有很多工具可以使用它们,并且这时可能是最好的选择。要注意的另一件事是动态导入,它已经在几种浏览器中实现了。
抱歉,这是如此令人困惑,您恰好在非常过渡的时间内输入了JavaScript。
关于javascript - 'var webApp = { .. }'和 'var webApp = function (){ .. }'有什么区别,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50718685/