我最近一直在尝试使用模块化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();
})();
但我看到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
};
}());
我有两个问题:
>使用一个对象和它内部设置函数,变量,等等有什么区别:
var webApp = {…};
和一个具有相同特征的函数变量(只有一个
语法写得不同).就像我粘贴的链接中的示例一样.
var webApp = function (){ ... };
>在一个对象/函数中编写所有代码(如图,表,进度条等有些分离元素)的代码是否正确?这应该更好地分离到不同的对象?如果有更好的方法来编写此类代码,请提及我应该研究的内容.
解决方法:
互联网教程的一个问题是它们徘徊在相关点之外,很少有作者让它们保持最新. JS领域的事情发展得非常快,5年前的行业标准(例如jQuery)现在看起来很奇怪,当你仍然偶然发现它时.
所以,要实践我为了省略而唠叨别人的好习惯:
JavaScript模块状态,2018年中期,快速变化,#deprecated
一团糟.
首先你有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是一个工具,它将遍历您的依赖图并将需求表达式转换为浏览器可以处理的内容(基本上,通过创建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设置的.变形金刚被编写为将这两种样式转换为遗留代码和易读性问题(这是一个相当的样板).
但是人们想要更多:他们希望能够在代码中表达他们所有的webapp依赖关系(包括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资产)是不幸的,这意味着一个或另一个将不得不改变,因为我试图编写本机模块 – 最近基于应用程序,并且很难使用库(几乎没有一个提供本机模块的味道).
使用什么是一种自以为是的问题,但如果您使用框架,请使用该框架的其他用户常用的任何内容. Common.js和webpack很常见,有很多工具可以消耗它们,这可能是你最好的选择.另一件要注意的是动态导入,已经在多个浏览器中登陆.
对不起,这一切都让人感到困惑,你恰好在过渡期间输入了JavaScript.