vlambda博客
学习文章列表

读书笔记《digital-java-ee-7-web-application-development》渐进式JavaScript框架和模块

Chapter 7. Progressive JavaScript Frameworks and Modules

 

“如果你是一名跑步者并且你在比赛中跑步,你可能会输。如果你不跑步,你肯定会输。”跨度>

 
  --Reverend Jesse Jackson

在构建网站的当代方式中,JavaScript 语言无法逃脱,因为它是现代 Web 浏览器的事实标准。 JavaScript 对开发人员来说要么是一种乐趣,要么是极大的不便。如果您为客户编写或打算构建数字 Web 应用程序,几乎无法逃避 HTML5、CSS 和 JavaScript 的知识。幸运的是,您无需成为 JavaScript 专家即可提高工作效率,因为有许多框架可以帮助您,并且您可以利用这些想法。从本质上讲,就 JavaScript 而言,您需要了解并赶上现代数字最佳实践。

虽然 JavaScript 是与数字网站非常相关的主题,但本章无法教会您需要了解的所有内容。相反,我将努力为您指出正确的方向并为您提供监督,您绝对应该通过更多资源扩展您的知识。

我们将从基本的 JavaScript 编程和语言的概念开始。然后,我们将直接使用 JavaScript 对象进行编程。之后,我们将研究 JavaScript 世界中的一些主要框架。

JavaScript essentials


JavaScript 本身就是一种受人尊敬的编程语言。它有一个名为 ECMAScript (http://www.ecmascript.org/) 并被 W3C 接受为已批准的标准。该语言是三个基本标准 Web 技术的一部分:HTML5、CSS 和 JavaScript。也就是说,什么是 JavaScript?它是一种具有对象类型和封闭范围功能块的原型动态类型脚本语言。在 JavaScript 中,每种类型都是严格意义上的对象。 JavaScript 支持函数作为一等公民和规则,并支持分配给关联词法范围变量、属性或实体的函数的声明。 JavaScript 支持字符串、整数、浮点数和原型。 JavaScript 本质上是一种属性和原型语言。它通过范围和闭包对基于对象的编程提供了词法支持。该语言的广泛使用没有明确地保留关键字,并且它的结构支持面向对象的继承。通过巧妙的 编程和原型,开发人员可以复制对象类继承。

Tip

我应该学习的基础级标准 JavaScript 是什么?

本章着眼于 JavaScript 1.5,即 ECMA Script Edition 3。该语言版本适用于所有主要的 Web 浏览器:Firefox、Chrome、Safari 和 Internet Explorer。即将推出的 JavaScript ECMA 6 将支持面向对象的编程(http://es6-features.org/)。

JavaScript 是普通 Java Web 应用程序客户端的一种流行语言。您应该知道,JavaScript 也可以通过 Node.js 或 Nashorn 等实现在服务器端运行。但是,这些主题超出了本章的范围。

Creating objects

让我们深入了解客户端的 JavaScript。使用您作为数字开发人员可以编写的对象的 JavaScript 编程是什么?这是一个答案 - 一个带有嵌入式脚本的 HTML5 网页,该脚本可以创建联系人详细信息,如下所示:

<!DOCTYPE html>
<html lang="en">
  <body>
    <script> var contact = new Object(); contact.gender = 'female'; contact.firstName = 'Anne'; contact.lastName = 'Jackson'; contact.age = 28; contact.occupation = 'Software Developer' contact.getFullName = function() { return contact.firstName + " " + contact.lastName; } console.log(contact); </script>
  </body>
</html>

这个简单的程序创建了一个带有属性的联系人详细信息。 JavaScript 属性可以是整数、数字:数字、布尔或字符串类型。 JavaScript 对象也可以像 getFullName() 一样定义方法。对于普通的蓝领 Classic Java 开发人员来说,这种从函数定义属性的语法看起来很奇怪。然而,函数是许多语言的一等公民。定义对象的 JavaScript 函数称为方法。

在现代 JavaScript 编写实践中,您将学会识别以这种方式编写的类似函数,这与 Java 表示法相反。这是 JavaScript 数学中的三阶多项式函数:

var polynomial = function(x1,x2,x3) {
   return (2 * x1 * x1 * x1) - ( 3 * x2 * x2 )
       + 4 * x3 + 5;
}

console.log("The answer is: " + polynomial(1.5,2.0,3.75) );

该变量定义了一个名为 polynomial() 的 JavaScript 函数,它接受三个数字类型参数。它还返回一个数字类型。 JavaScript 是一种动态类型语言,因此没有静态类型。

The console log

控制台日志 是一个标准对象,它是现代网络浏览器的一部分:Firefox、Chrome、Opera、Safari 和 Internet Explorer。它通常可以从为调试保留的菜单中获得。以前,浏览器不完全支持控制台对象。

值得庆幸的是,我们不会在 2016 年编写以下条件代码:

if ( window.console && window.console.log ) {
  // console is available
}

让我们转到对象构造函数。请允许我提供关于编写控制台日志的最后一点建议:仅在开发代码中使用它。开发人员忘记从生产代码中删除控制台日志输出,最终导致某个 Web 浏览器崩溃,破坏了数字客户的旅程。利用 jQuery、RequireJS 或 Dojo 等 JavaScript 框架,将控制台日志抽象为库函数。

如果您最终没有这样做,我强烈建议您下载适用于 Google Chrome 或 Firefox Web 浏览器的 Chrome 开发人员和 Web 开发人员工具。

Writing JavaScript object constructors

相比之下,JavaScript 语言 由有限的各种基本类型 组成。粗略地说,这些原语是字符串、数字、布尔值、数组和对象。这些可以使用原生 JavaScript 对象构造函数创建:String()Number() Boolean()Array()Object()

下面是如何使用这些本机构造函数的说明:

var message = new String('Digital');
var statusFlag = new Boolean(true);
var itemPrice= new Number(1199.95);
var names = new Array();
names[0] = 'Emkala';
names[1] = 'Sharon';
names[2] = 'Timothy';
console.log(message);  // Digital
console.log(statusFlag); // true
console.log(itemPrice); // 1199.95
console.log(names); // Object (Surprise ;-)

显然,很少从本地构造函数中分配 String、Boolean 和 Number 类型。但是,请注意使用 Array 本机构造函数。在 JavaScript 中,数组被视为对象。像大多数计算机语言一样,它们是从索引零开始枚举的。要查找数组的大小,请调用隐式长度属性 (name.length)。

为了建立 JavaScript 的基本要素,我们可以利用前面的例子,利用函数引入自己的作用域的能力,如下所示:

var ContactDetail = function(
  gender, firstName, lastName, age, occupation ) 
{
  this.gender = gender;
  this.firstName = firstName;
  this.lastName = lastName;
  this.age = age;
  this.occupation = occupation
  this.getFullName = function() { 
    return contact.firstName + " " + contact.lastName;
  }
   return this;
}

var anne = new ContactDetail(
  'female', 'Anne', 'Jackson', 28, 'Software Developer');

console.log(anne.female); 
console.log(anne.firstName);
console.log(anne.lastName);

在第二个示例中, 发生了一些事情。首先,我们将为ContactDetail变量分配一个函数类型。这个函数实际上是一个由变量名命名的新对象类型的构造函数。在此构造函数中,有一个特殊的 this 引用变量,它与此级别的函数范围相关联。当引用返回时,它成为对象实例。在函数中,我们可以定义与对象相关联的其他函数,例如getFullName()。这就是基于对象的编程在现代 JavaScript 中的工作方式。

我们将使用这个新的对象类型构造函数在名为 anne 的变量中声明联系人详细信息。对于 Java 普通程序员来说,这种语法一开始可能看起来很奇怪,但这种 JavaScript 与 Java 完全不同,它本身就被认真地接受为一种编程语言。作用域在定义对象模块方面具有实际用途,我在本书的第一章中对此进行了说明。

The JavaScript property notations

有两种基本的 方法可以访问类型中的 JavaScript 属性。 第一种方式对所有 Java 程序员来说都非常熟悉。它是点符号。第二种方式称为方括号表示法,它看起来像 Java 以外的语言中的映射或字典关联。括号表示法等同于点表示法并且有其用途。

检查以下代码,它演示了创建 JavaScript 对象的另一种方法。请记住,JavaScript 是一种动态类型语言。

var product = { 
  'title': 'Java EE Developer Handbook', 
  'price': 38.75,
  'author': 'Peter A. Pilgrim'
};

console.log( product.name );
console.log( product['name']);

product.price = 34.50;
product['price'] = 34.50;

product.subject = 'Enterprise Java';
console.log(product['price']);

product.pi = function() { return 3.14159; };
console.log(product.pi());

您是否发现为对象引入了一个名为 subject 的新属性以及一个方法函数?当然,我在这里宣传我的第一本技术书的书名,但这与 点无关。 JavaScript 允许程序员 在对象内部和属性方面非常灵活。对象产品的声明应该敲响一些警钟,因为这个声明与事实上的 JavaScript 对象表示法 (JSON) 标准。开放大括号表示法是一种使用属性键和值定义对象的方法。

Dealing with a null and undefined reference pointer

查尔斯·安东尼·理查德·霍尔 (Tony Hoare) 教授开发了经典的计算机科学算法,称为 QuickSort< /span>,但当他说这也是发明了十亿美元的错误时,他也感到遗憾:可怕的空引用指针。就个人而言,我原以为其他人会偶然发现如此明显的解决方法,并且可以快速解决一般问题。

JavaScript 将空引用作为标记处理,并且还具有未定义的特性。以下 JavaScript 提取尝试在 test 对象中打印空引用:

var testObject = { item: null };
console.log(testObject.item);   // prints out 'null'.

null 值告诉您对象类型事物已定义但尚不可用。 undefined 值通知开发人员缺少某些内容。请记住,JavaScript 是一种动态语言,因此,完全有可能在对象图中导航,而找不到您的团队认为放置在那里的对象类型。

在 JavaScript 中,如果需要测试 null 值,则必须使用三等号运算符(===),如下所示:

var testbject = null;
console.log(testObject == null);   // Wrong!
console.log(testObject === null);  // Correct.

写等价,我们怎么知道两个对象在 JavaScript 中什么时候是等价的?

The JavaScript truth

在 JavaScript 中,条件 表达式为假,如果它匹配 空值:false0-0null、空字符串 ('')、NaN未定义。当且仅当值不匹配空值集中的任何元素时,值才会在条件表达式中计算为 JavaScript 真值。与任何元素匹配的每个其他值都是一个空值集,其评估结果为 JavaScript true。

以下 JavaScript 都评估为 false:

console.log(Boolean(0)); 
console.log(Boolean(-0)); 
console.log(Boolean('')); 
console.log(Boolean(null)); 
console.log(Boolean(false)); 
console.log(Boolean(undefined)); 
console.log(Boolean(Number.NAN));

在这里,我们使用带有 new 关键字的 Boolean 构造函数来直接实例化一个类型。这些陈述评估为真:

console.log(Boolean(1)); 
console.log(Boolean(-1)); 
console.log(Boolean('runner')); 
console.log(Boolean(true)); 
console.log(Boolean(1234567); 
console.log(Boolean(new Array()); 

Runtime type information

为了找出JavaScript值的运行时信息,您可以申请typeof 运算符。这允许程序员编写专门的代码来检查函数的参数。以下是 typeof 询问的示例:

console.log(typeof true); // Prints 'boolean'.
console.log(typeof 'magic'); // Prints 'string'. 
console.log(typeof 3.141596527); // Prints 'number'. 

如果您使用本机构造函数,JavaScript 还有一些其他怪癖:

console.log(typeof new Boolean(true)); // Prints 'object'.
console.log(typeof new String('MAGIC')); // Prints 'object'. 
console.log(typeof new Number(3.141596527)); // Prints 'object'. 

这很令人惊讶!在这里,您可以看到为什么数字 Web 开发人员会因为这些标准的语言、标准和实现的不一致而被逼疯的证据。

The JavaScript functions

在现代 JavaScript 中,与 Classic 相比,您会看到大量的准函数式编程。 a> Java(版本 8 之前的 Java 和 Lambda 表达式)。职能是一等公民。您可以将函数作为参数提供给函数。您还可以从函数返回函数类型。将函数作为参数传递给方法有什么帮助?在 JavaScript 中,您可以编写没有名称的匿名函数。您可以利用将代码块传递给库函数的优势。这种风格是函数式编程的基础。我们可以编写简洁的代码和内联代码,而不是命令式编码,这几乎是声明性的。

这是一个匿名函数示例:

var outside = function (yourIn) {
  yourIn(); // Invokes the supplied function.
}

outside( function () { 
  console.log('inside'); 
});

outside() 函数接受匿名 yourIn() 函数作为单个参数。现在在 outside() 函数内部,它立即调用参数 yourIn,这是提供的匿名定义函数。这是一项强大的技术。

JavaScript 还有另外一个技巧,它适用于模块的声明,尤其是当它与功能对象作用域结合时。可以定义一个函数并直接内联调用它。考虑这个例子:

var initializeGui = function() { 
  console.log("start up the client side GUI...");
} ();

在前面的代码中,我们定义了一个名为 initializeGui 的变量,并为其分配了一个匿名函数。定义的关键是方法语句末尾的最后一个圆括号。 JavaScript 立即在解析定义的精确位置调用该函数。在这里,我们假装通过写入控制台来初始化客户端 GUI。

您还可以将参数传递给内联函数,如下所示:

var initializeGui2 = function(msg,value) { 
  console.log("start up GUI..."+msg+","+value);
} ( 'Spirits in the Sky', 1973 );

前面的代码演示了参数是从外部全局范围传递到调用的函数的。

其实我们可以去掉initializeGui2这个变量,创建一个自调用匿名函数:

(function(msg,value) { 
  console.log("start up GUI..."+msg+","+value);
})( 'Spirits in the Sky', 1973 );

这类代码相当典型,常见于流行的 JavaScript 框架和应用程序中。

在函数定义中,我们将利用 JavaScript 作用域。请参阅 第 1 章Digital Java EE 7关于模块命名空间技术的早期解释。

我想我会在这里停下来。与现代 JavaScript 编程相比,种类繁多且知识渊博我可以在这里写一下。我建议您购买其他介绍性编程书籍,例如 Douglas Crockford 的优秀 JavaScript: The Good Parts 以及 Packt Publishing 的 面向对象的 JavaScript 作者 Stoyan Stefanov库马尔·切坦·夏尔马

Tip

Steve Kwan 在 JavaScript 中编写了一个出色的模块模式示例;您可能想在 blob/master/javascript/best-practices.md" target="_blank">https://github.com/stevekwan/best-practices/blob/master/javascript/best-practices.md。

我们来看一个非常重要的 JavaScript 编程框架,jQuery。

Introducing the jQuery framework


jQuery (http://learn.jquery.com/) 是 用于客户端 Web 应用程序开发的跨平台 JavaScript 框架。它是 2004 年和 2005 年原始 AJAX 热潮的幸存者,它曾经并且仍然在与 Prototype (http://prototypejs.org/) 和 Scriptaculous (<类="ulink" href="http://script.aculo.us/" target="_blank">http://script.aculo.us/)。 jQuery 被称为相当于 Java Collections 框架为 Java 编程语言所做的。根据 Wikipedia,jQuery 占全球 10,000 个访问量最大的网站的 70%。换句话说,它是第一个真正引起开发人员注意并促使他们重新思考底层语言的最远功能的 JavaScript 框架。 jQuery 是免费的,在 MIT 开源许可下提供。

jQuery 的构建是为了对 文档对象模型(< span class="strong">DOM) 更容易并将 CSS 应用于 HTML 元素。在 jQuery 中,有一种叫做 Sizzle 的秘方,它是一个遍历 DOM 的选择器引擎。该引擎结合了选择的灵活性、对函数式编程的尊重和回调,允许工程师轻松编写利用网页中底层 HTML 和 CSS 元素的 JavaScript。

Including jQuery in a JSF application

您可以在页面视图中包含 jQuery JavaScript 库。在 JSF 中, 文件夹将位于 src/main/webapp/resources/ 文件夹下:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"... >
  ...
  <script src="#{request.contextPath} /resources/javascripts/jquery-2.1.0.min.js"></script>
  <script src="#{request.contextPath} /resources/app/main.js"></script>
  ...
</html>

我们将使用表达式语言 #{request.contextPath} 来提供位置独立性。优秀的数字开发人员将使用缩小的 JavaScript 来提高性能并提高他们的业务 SEO 机会!

jQuery ready function callbacks

jQuery 框架$ 符号重新用于全局头部作用域 仅供我们使用。 (美元符号是 jQuery 对象实例的别名,它显然很短。)通过巧妙的编程,这超出了本书的范围,jQuery 接受了一个代表 HTML DOM 对象的参数。 jQuery 入口点是 ready() 方法,它接受函数对象类型参数。

该参数可以是匿名函数或命名函数,我们将在此处演示以初始化虚构网站:

$( document ).ready(function() {
    // Write your code here.
    console.log("this page view is starting up!");
   MyNamespace.MyModule1.init(); 
   MyNamespace.MyModule2.init(); 
});

当 jQuery 调用匿名函数时,框架可以做出一定的保证:浏览器已经初始化,图片已经全部下载,事件栈设置为 go,以及某些 web 客户端的其他专有功能已经完成。在前面的示例中,我们将初始化模块模式中的其他 JavaScript 库并登录到控制台:

console.log( $ === jQuery );  // true

$( document ) 表达式可以概括为:

$( <expression-selector> )

表达式选择器节可以是 CSS 选择器表达式或 HTML DOM 元素。以下是一些示例选择器:

var divs1 = $('div');
var divs2 = jQuery('div');
var win1  = $( window );

var redBtn  = $( "#redButton" );
var offerSubmitBtn = $( "#offerSubmitBtn" );

var navigationControl  = $( ".navControls" );
var footerArea = $( ".footerArea  div" );

以哈希字符 (#) 开头的元素选择器等价于 DOM HTML API 调用 getElementById(),它意味着他们可能会返回元素或不返回元素。 #offerSubmitBtn 选择器检索使用 ID 属性指定的元素:

<h:commandButton value="Apply Now!" id="offerSubmitButton" action="#{longTermProvider.applyForCredit}"/>

jQuery 提供了非常 强大的类选择器,它检索HTML 元素的集合$('div') 选择器检索文档和页面视图中的所有 HTML div 元素。同样,$('div') 类选择器检索所有 HTML 锚元素。有人可能会说,知识就是力量!我们可以结合 CSS 类选择器来拒绝和微调我们想要操作的元素。 $( ".footerArea div" ) 选择器限制页脚区域中的 HTML div 元素。

Acting on the jQuery selectors

jQuery 允许数字 开发人员访问网页上的 HTML DOM 元素。那么你如何操作这些强大的选择器呢? API 具有许多接受函数类型参数的回调方法。让我们看一下这样一个名为 click() 的方法,它会在特定的 HTML 元素被按下和按下时触发一个事件。

这是我们之前看到的红色按钮的代码:

$( document ).ready(function() { 
  $( "#redButton" ).click(function( event ) {
      alert( "You pressed the red button!" );
  });
});

当用户点击红色按钮时,jQuery 处理 DOM 事件并调用与 jQuery 匹配选择器关联的匿名函数。用户会看到警报对话框。它不止于此。这是一些使红色按钮淡出视图的代码:

$( document ).ready(function() { 
  $( "#redButton" ).click(function( event ) {
    $( "#redButton").animate(
      {
        'opacity':'0.0'
      }, 250);
  });
}

这是 jQuery 动画功能的示例。 animate() 方法接受两个参数:动画属性的键和值以及持续时间。在这里,我们将指定按钮的不透明度,但我们可以设置其他属性,例如元素的宽度或高度,甚至目标 Web 客户端的 3D 转换也将仅针对 CSS3 确认的 Web 浏览器提供服务。持续时间以毫秒为单位。

如果这是一个设计为可重用的代码,并且我希望为一组接口开发人员提供一个干净的模块化代码,那么我将如何整理代码并避免尴尬:

var DigitalJavaEE7 = DigitalJavaEE7 || {};

DigitalJavaEE7.RedModule = function($) {
  var init = function() {
    $( document ).ready(function() { 
      var redButton = $( "#redButton" )
      redButton.click(function( event ) {
        redButton.animate({'opacity':'0.0'}, 250);
      });
    }
  };

  var performOtherOWork = function() {
    console.log("other animation stuff.")
    /* ... */
  };

  var oPublic = {
      init: init,
      performOtherWork: performOtherWork
  };

  return oPublic;
}(jQuery);

使用流行的模块 模式,我将 jQuery 的初始化代码推送到带有 RedModule 模块中class="literal">DigitalJavaEE7 命名空间。在该模块的 init() 函数中,我在一次调用 redButton 变量时优化了 CSS 选择器。事实证明,jQuery 努力将 CSS 选择器解释为一组潜在的 HTML DOM 元素。因此,我们将避免要求框架对 DOM 进行两次搜索。代码本质上是相同的,但更简洁且易于理解。

Manipulating the DOM elements

在上一节中,您 学习了如何使用 jQuery 选择 DOM 元素。可以使用 jQuery 选择器检索组元素。使用 API 的操作部分,我们将为元素添加和删除类样式,在组件之前和之后插入元素,并替换元素的内容。有很多电话要学习;我们将在这里回顾一小部分。

为了演示如何操作 DOM 元素,让我们定义两个 HTML 按钮元素。我们将应用一种红色的样式,另一种是蓝色的。这是代码:

$( document ).ready(function() { 
  var redButton = $( "#redButton" )
  var blueButton = $( "#blueButton" )
  var textArea = $( "#messageArea" )
  redButton.click(function( event ) {
    textArea.addClass('.text-danger');
    textArea.html('Danger Will, watch out!');
  });

  blueButton.click(function( event ) {
    textArea.removeClass('.text-danger');
    textArea.html('Everthing is fine now.');
  });
}

我们将设置匿名函数以将 Bootstrap 类 text-danger 添加到 messageArea,您可以正确地假设它是一个保留的 div< /code> 元素用于文本输出。 addClass() 方法将样式类附加到匹配的元素。我们将使用 redButton 元素的回调将样式添加到文本区域。 blueButton 的第二个匿名函数从元素中删除类。这两个功能都会改变显示区域中的信息。

html() 方法是 双重目的。它在 jQuery 框架中被重载。当使用单个参数调用 html() 时,它会替换元素的内容。我们将使用 html() 方法来更改消息区域中的文本。如果在没有参数的情况下调用该方法,它会返回元素的内容。 jQuery 中有几个 API 方法具有这种双重性,例如 attr()val()。它们描述如下:

  • attr() 方法检索或操作 DOM 元素的属性。

  • val() 方法检索匹配元素集中第一个元素的当前值,或者设置每个匹配元素的值。 val() 方法对于访问一组 HTML 选择选项元素中的名称和值特别有用。

Animation

网站上最复杂的数字应用程序包含智能动画和提示(显然由 UX 负责人批准)来指导用户的数字之旅。通常,就用户如何获得最佳网站体验提供微妙的提示就足够了,这会对整体满意度产生巨大影响。 jQuery 具有基本级别的动画功能,例如上下滑动 div 图层和弹出窗口、缩小和扩展图层,以及可以提供帮助的不透明度技巧。

要查看 动画,我们来看看当用户向下滚动页面一定距离时如何使用 jQuery 为滚动到顶部的箭头设置动画.这是一种常见的用户界面设计模式。出于明显的空间原因,我们不会放入模块模式。

假设我们的 JSF 页面视图上有一个简单的 HTML 内容:

<div id="scrollBackTopArrow">
  <img src="#{request.contextPath}/scroll-back-top-arrow.jpg" >
</div>

首先,我们需要编写一个函数处理程序,在用户向上或向下滚动页面视图时监听滚动事件。在 DOM 中,全局 Window 对象上有一个标准方法 scroll(),它接受一个函数对象作为回调。

有了入口点,我们将编写一个处理函数,如下所示:

$(window).scroll( function(){
  var epsilon    = 0.25;
  var scrollingCount = 0;
     var minOpacity = 0.0, maxOpacity = 0.85;
  var scrollArrow=$('#scrollBackTopArrow');
  scrollArrow.each( function(i) {
    var windowHeight = $(window).height();
    var windowScroll = $(window).scrollTop();
    var opacity = scrollArrow.css("opacity")

    var upper = windowHeight * 0.525;
    var lower = windowHeight * 0.315;

    if( windowScroll > upper ){
      if ( opacity <= (maxOpacity - epsilon )) {
        if ( scrollingCount == 0 ) {
            scrollArrow.animate({'opacity':'0.75'}, 100);
            scrollingCount = 15;
        }
      }
    }

    if( windowScroll < lower ){
      if ( opacity >= (minOpacity + epsilon)) {
        if ( scrollingCount == 0 ) {
            scrollArrow.animate({'opacity':'0.0'}, 100);
            scrollingCount = 15;
        }
      }
    }

    if ( scrollingCount > 0 ) {
      scrollingCount = scrollingCount - 1;
    }
  }); 
}); // end of the scroll function

不要害怕 这个 JavaScript 函数的长度,因为现在一切都会揭晓。事实证明,将回调附加为滚动侦听器意味着 Web 浏览器可能每秒调用回调 10 次或数十次,具体取决于用户的设备。因此,我们引入了一个阻尼因子 scrollingCount 作为倒计时变量,它可以防止动画被过度触发。 epsilon 变量还控制动画被激活时的敏感度。我们可以使用设置的最小和最大不透明度值来绑定动画激活。

由于 jQuery 选择器 API $('#scrollBackTopArrow') 可能会检索零个或多个 DOM 元素,我们将调用 each()< /code> 方法来有效地遍历元素。我们使用匿名函数来做到这一点,它接受元素的单个参数。在这种情况下,我们知道选择器只会返回一个 DOM 元素,如果它存在的话。

我们将在函数中的变量中捕获当前窗口高度 $(window).height()。使用 windowHeight 变量,我们将推导出箭头应该淡入和淡出的一些垂直限制:下限和上限。原点坐标 (0,0) 位于设备窗口的左上角。函数调用 ${window).scrollTop() 检索表示页面当前滚动位置的整数位置。

现在我们将解释棘手的部分。这两个条件语句检查页面视图滚动位置是否高于最低或最高界限。如果滚动位置超过上限,那么我们将从视图中淡入箭头。如果滚动位置小于下限,那么我们将从视图中淡出箭头。我们将设置一个倒数计时器以防止重新触发动画。请注意,JavaScript 支持访问在函数定义之外声明的变量的词法范围,也称为闭包。 minOpacitymaxOpacityepsilonscrollingCount 变量是闭包变量。

这是另一个使用 CSS3 三维变换来实现扩展按钮或图标的 jQuery 示例。这种效果是从旧的 Mac OS X 风格的用户界面中借用的,其中应用程序图标在 Dock 应用程序栏中展开和收缩,如以下代码所示:

var selector = $("expando-btn");

selector.mouseenter( function() {
  $(this).each( function(i) {
    css({
        'transition': 'all 0.5s',
        '-webkit-transform': 'scale(1.667)',
        '-moz-transform': 'scale(1.667)',
        '-o-transform': 'scale(1.667)',
        'transform': 'scale(1.667)',
    });
  });
});

selector.mouseexit( function() {
  $(this).each( function(i) {
    css({
        'transition': 'all 0.5s',
        '-webkit-transform': 'scale(1.0)',
        '-moz-transform': 'scale(1.0)',
        '-o-transform': 'scale(1.0)',
        'transform': 'scale(1.0)',
    });
  });
});

我们将使用 mouseenter()mouseexit() 方法来构建效果。这些方法分别捕获桌面鼠标进入和离开按钮,如果显示出来,可以看到。匿名函数使 CSS 处于运动状态。 CSS 3 已经有了动画类样式。过渡类声明了整个动画的长度,即 0.5 毫秒,我们还声明了一个 2D 变换,可以放大或缩小元素。为了扩展按钮元素,我们将缩放因子设置为默认按钮大小的 1.667。为了收缩按钮元素,我们将缩放因子重置为默认渲染大小 1.0。请注意,我们仍然必须为 Apple 的 Safari 和以前版本的 Google Chrome 等 WebKit 浏览器声明专有的浏览器类,例如 webkit-transform。归根结底,这个例子对触摸屏设备没有帮助,因为没有可用的设备(目前)可以检测到手指非常靠近屏幕悬停! (见本章末尾的练习。)

使用 HTML、JavaScript 和 CSS 可能非常复杂,界面开发人员的工作是找出需求并构建前端。但是,Java 开发人员也应该欣赏这项工作。我希望你能看到一些结果。

The RequireJS framework


如果您对 大量 JavaScript 文件和组件的组织很认真,那么您会很高兴来自依赖注入框架(如 CDI 和 Spring)的想法也在世界上成功了。一些专业组织已经依赖一个名为 RequireJS (http://requirejs.org/)。 RequireJS 框架是一个 JavaScript 文件和模块加载器。该框架有一个内置的模块脚本加载器,它将提高代码的速度和质量。

RequireJS 实现了 异步模块定义 (AMD) 规范 JavaScript (https://github.com/amdjs/amdjs-api/wiki/AMD)。该规范定义了一种机制,该机制又定义了模块和模块之间的依赖关系以及如何异步加载它们。

AMD 规范解决了您有许多 JavaScript 模块并定义多个 HTML 脚本元素以便加载它们的关键问题,但随后您发现每个模块都有一个依赖顺序。

假设我们有一个 JavaScript 模块 A 依赖于模块 B,然后模块 B 依赖于模块 C 和 D。您可能忘记包含模块 D 的依赖项。更糟糕的是,您可能会得到以下顺序依赖错误:

<script src="js/module-b.js" ></script>
<!-- Code failure: we forgot to load the module C -->
<script src="js/module-d.js" ></script>
<!-- Oops: module B has a dependency on module D -->
<script src="js/module-a.js" ></script>

RequireJS 帮助处理这些临时依赖。首先,我们必须了解 RequireJS 是如何加载 JavaScript 文件的。该框架具有最佳实践文件夹布局。

对于一个Java web应用来说,我们在一个项目中定义一些文件,如下:

src/main/webapp/

src/main/webapp/index.xhtml

src/main/webapp/resources/js/

src/main/webapp/resources/js/app.js

src/main/webapp/resources/js/app/

src/main/webapp/resources/js/app/easel.js

src/main/webapp/resources/js/app/nested/sub.js

src/main/webapp/resources/js/lib/

src/main/webapp/resources/js/lib/jquery-2.1.1.js

src/main/webapp/resources/js/lib/bootstrap-3.2.0.js

src/main/webapp/resources/js/require.js

src/main/webapp/resources/js/require-setup.js

在 JSF 应用程序中,我们将 JavaScript 模块放在 resources 文件夹中。由于 JSF 所需的间接性,这与标准 JavaScript 描述的读取方式不同。应用程序文件通常保存在 /js/app 文件夹中。 JavaScript 库存储在 /js/lib 文件夹中。 /js/require.js 文件是 RequireJS 框架模块的 JavaScript 文件。

对于 HTML5 应用程序,您将首先包含对 RequireJS 文件的引用:

<!DOCTYPE html>
<h:html>
  <h:head>
    <meta charset="UTF-8">
    <title>Digital Java EE 7 :: Require JS </title>
    <link href="styles/bootstrap.css" rel="stylesheet" />
    <script src="#{request.contextPath}/js/lib/require-setup.js" > </script>
    <script src="#{request.contextPath}/js/lib/require.js" data-main="#{request.contextPath}js/app/app"></script>
    <script src="#{request.contextPath}/js/app/main.js" ></script>
  </h:head>

  <h:body>
    <header>RequireJS Application</header>
    <!-- ... -->
  </h:body>
</h:html>

前面的代码是 RequireJS 的实际用途,因为我们在应用程序中使用了 Bootstrap 和 jQuery。最重要的 HTML 脚本元素是第二个,因为它加载了 RequireJS (require.js)。第一个脚本标签很重要,因为它配置了 RequireJS 框架。我们马上就会看到这一点。第三个脚本标签加载一个应用程序 JavaScript 模块。

Note

许多商业网站将标签放在页面内容的底部,以实现最佳实践惯例使用并简化性能。但是,由于 RequireJS 是为 AMD 设计的,因此这种做法可能会破坏在页面继续加载时异步加载和执行脚本的目的。换句话说,您的里程可能会有所不同,您需要在开发工作中对此进行测试。

A RequireJS configuration

让我们反过来看加载的JavaScript文件的顺序,所以这是/js/app/app.js。这是包含 RequireJS 库的 <script> 标记元素中的引用 data-main 属性的目标:

requirejs.config({
  baseUrl: 'js/lib',

  paths: {
      app: '../app'
  }
});

此文件配置 RequireJS 如何搜索和加载 JavaScript 文件作为模块。 requirejs 是库在全局头作用域中定义的 JavaScript 对象类型变量。引用对象有一个名为 config() 的方法,它接受一个 JavaScript 属性对象。 baseUrl 属性定义了加载文件的默认位置。 path 属性是一个嵌套属性,它列出了默认加载规则的例外路径的集合。

默认情况下,之前的 RequireJS 配置会按 ID 从文件夹 js/lib 加载任何模块。但是,如果模块 ID 以前缀 app 开头,则从路径指定的 js/app 目录加载钥匙。

path 属性配置是相对于 baseUrl 并且从不包含 .js 后缀扩展,因为 paths 属性可以代表目录文件夹.

由于我们将为此示例加载 jQuery 和 Bootstrap,因此我们需要将一个方形钉插入一个圆孔中。在 JavaScript 编程世界中,为了避免与许多流行的库发生冲突,作者采用了 shims 的想法。

Tip

什么是垫片?

shim 是 JavaScript 用语中的一种口语表达概念,用于强制不同的框架协同工作。它也是对 JavaScript 上下文进行猴子修补以包含所有 EmcaScript 5 方法的术语。

在 RequireJS 中,我们必须在第一个加载的文件(require-setup.js)中进行设置:

// require-setup.js
var require = {
  shim: {
    "bootstrap" : { "deps" :['jquery'] },
   "jquery": { exports: '$' }
  },
  paths: {
    "jquery" : "jquery-2.1.3",
    "bootstrap" : "bootstrap-3.2.0"  
  }
};

重新检查 JavaScript 文件的文件夹布局很有帮助。 require-setup 文件只是在全局头范围内使用对象定义设置一个名为 require 的特殊命名变量。属性名称 shim 引用的嵌套对象定义了两个更改。首先,名为 bootstrap 的模块依赖于名为 jquery 的模块。其次,jquery 模块导出符号($)。

配置中称为路径的第二个属性键定义了模块名称的关联对象。每个模块名称都映射到其真实名称。因此,jquery 模块实际上与一个名为 jquery-2.1.3 的文件相关联。间接有一个额外的功能,因为现在我们有一种简单的方法来升级库版本。换了一行!

An application module

RequireJS的配置完成后,我们现在可以编写默认应用模块了我们的应用,如下:

requirejs(['jquery', 'bootstrap', 'easel', 'nested/sub'],
  function ($, bootstrap, easel, sub) {
    // jquery, easel and the nested/sub module are all
    // loaded and can be used here now.
    console.log("start it up now!");

    var e = new easel();
    console.log("module 1 name = "+e.getName() );

    var s = new sub();
    console.log("module 2 name = "+s.getName() );
    console.log("other name = "+s.getCanvasName() );

    // DOM ready
    $(function(){
      // Programmatically add class to toggles
      $('.btn.danger').button('toggle').addClass('fat');

      console.log("set it off!");

      alert("set it off!");
    });
  }
);

前面的 /js/app/main.js 脚本是我们简单的客户端应用程序的通用文件。全局 requirejs() 函数是通往库的依赖注入特性的途径。下面是这个函数的格式:

requirejs( <MODULE-ARRAY-LIST>, <CALLBACK> )

在这里,< MODULE-ARRAY-LIST> 是模块名称依赖项的列表集合,<CALLBACK> 是单个函数参数。

因此,代码示例要求 RequireJS 初始化模块:jquerybootstrapeasel< /code> 和 嵌套/子。特别注意最后一个模块,因为 sub.js 位于 app 文件夹的子目录中;因此,名称使用路径分隔符。请记住,使用 RequireJS,您不需要添加后缀 (.js)。

当 RequireJS 调用回调函数时,模块就被加载了。因此,我们将写入控制台日志,如果我们使用 jQuery,我们将创建另一个匿名函数声明,以便对切换按钮进行一些花哨的选择器操作。关于为什么我们将在前面的填充程序配置中显式导出美元符号应该开始有意义了。另请注意,我们可以通过函数参数访问引用依赖项。

那么,我们将如何使用 RequireJS 定义模块模式?阅读下一节。

Defining modules

为了使用 RequireJS 定义我们自己的 自定义模块,我们将 使用框架中的另一个全局范围方法。按照 AMD 规范,该框架提供了一个名为 define() 的方法。该方法的格式如下:

define( <MODULE-ARRAY-LIST>, <FUNCTION-OBJECT> )

这与 requirejs() 调用几乎相同。 define() 方法接受模块名称列表作为依赖项。 <FUNCTION-OBJECT> 第二个参数意味着函数必须显式返回一个 JavaScript 对象。换句话说,它不能返回 void 或 nothing 结果。

让我们看一下canvas模块的定义:

// js/app/easel.js
define([], function () {
  console.log("initializing the `easel' module");
  var returnedModule = function () {
    var _name = 'easel';
    this.getName = function () {
      return _name;
    }
  };

  return returnedModule; 
});

模块列表可以是一个空数组,这意味着该模块没有必需的依赖项。文件路径为 /js/app/easel.js。在匿名函数中,我们将使用方法和属性实例化我们的 JavaScript 构造函数对象,然后将其返回给 RequireJS。该模块只定义了一个名为 getName() 的方法,该方法返回一个私有可访问变量的值。遵循 JavaScript 中的模块模式,可以在示例中声明私有作用域变量和函数,例如 _name,它们在函数定义之外无法访问。

以下是文件路径为 /js/app/nested/sub.js 的另一个模块的列表,它依赖于画架模块:

// js/app/nested/sub.js
define(['easel'], function (easel) {
  var easel = new easel();
  console.log("initializing the `nested' module");
  var returnedModule = function () {
    this.getCanvasName = function () {
      return easel.getName();
    }
    this.getName = function () {
      return "sub";
    }
  };

  return returnedModule;
});

nested/sub module 定义了一个函数 object 包含两个 方法:getName()getCanvasName()。我们将创建一个名为 easel 的对象变量。 RequireJS 在函数调用期间提供模块引用作为参数。 getCanvasName() 方法使用这个私有引用来调用依赖模块上的 getName() 方法,

这是加载模块的 RequireJS 的屏幕截图。 Chrome 开发者工具有一个网络视图,允许我们检查通过网络加载的 JavaScript 文件:

读书笔记《digital-java-ee-7-web-application-development》渐进式JavaScript框架和模块

RequireJS 示例应用程序的屏幕截图

如果您一开始觉得这个 很简洁,请记住它需要一个一会儿来了解函数和对象范围。对于专业的界面开发人员来说,克服 JavaScript 原始设计的严重缺陷的优势是显而易见的。我们涵盖了足够多的 RequireJS,以使数字开发能够继续广泛传播。我们将转向另一个框架。

UnderscoreJS


我将向您介绍一个 更多的有助于开发的 JavaScript 框架。 UnderscoreJS (http://underscorejs.org/) 是 一个为语言带来函数式编程结构和技术的框架。该库包含 100 多种为 JavaScript 添加功能支持的方法。

UnderscoreJS 是与 jQuery 和 RequireJS 一样下载的单个 JavaScript 文件。如果您将必需的版本化 underscore.js 文件添加到 /js/lib 文件夹,那么您已经有了注入的方法将其添加到您的应用程序中。这是文件中的附加配置,require-setup.js

var require = {
  shim: {
    "bootstrap" : { "deps" :['jquery'] },
    "jquery": { exports: '$' },
    "underscore": { exports: '_'}
  },
  paths: {
    "jquery" : "jquery-2.1.3",
    "bootstrap" : "bootstrap-3.2.0",  
    "underscore" : "underscore-1.8.2"
  }
};

UnderscoreJS 将其库的符号下划线(_)导出给开发人员,并且可以通过以下方式访问其函数方法符号。我们将回顾这些方法的一小部分。

函数式程序员往往对以下五个主要问题感兴趣:

  • 如何在内部迭代一组元素?

  • 如何过滤集合中的元素?

  • 如何将集合中的元素从一种类型映射到另一种类型?

  • 如何将元素集合展平为一个集合?

  • 最后,如何将集合中的元素收集或减少为单个元素或值?

您可能会将这些问题视为 JVM 中的替代编程语言(例如 Scala、Clojure 甚至带有 Lambda 的 Java 8)中的标准思想。

The for-each operations

在 UnderscoreJS 中,我们可以 获取一个 数组对象并简单地对其进行迭代。

each() 函数允许您遍历列表集合,如下所示:

// /js/app/underscore-demo.js
requirejs(['underscore'],
  function (_) {
    console.log("inside underscore-demo module");

    _.each( [1, 2, 3], function(n) { console.log(n); });
  }
);

在这里,我们在名为 underscore-demo.js 的模块中使用 RequireJS 作为 AMD 加载器。 each() 函数迭代数组对象的元素并调用提供的函数,称为 iteratee 将元素作为单个参数。 each() 函数替换了典型的 foreachfor-do 复合命令式 编程语言中的语句。

The filter operations

过滤可以通过几种方式来实现。让我们以 过滤列表为例:

var r1 = _.filter(['Anne','Mike','Pauline','Steve'],
  function(name){ return name.startsWith('P'); });
console.log(r1);    // ['Pauline']

此代码搜索列表中的每个值,返回一个包含所有通过真值测试(谓词)的值的数组。 filter() 的第二个参数称为谓词,它是一个函数回调,如果提供的元素满足条件测试,则返回一个布尔值。在这里,如果列表中的名称以字母 P 开头,我们将过滤它们。

UnderscoreJS 还提供了更复杂的过滤方法。 where() 方法搜索列表并返回包含属性中列出的所有键值对的所有值的数组:

var contacts = [ 
  new ContactDetail( 'F', 'Anne', 'Jackson', 28, 'Developer' ),
  new ContactDetail( 'M', 'William', 'Benson', 29, 'Developer' ),
  new ContactDetail( 'M', 'Micheal', 'Philips', 33, 'Tester' ),
  new ContactDetail( 'M', 'Ian', 'Charles', 45, 'Sales' ),
  new ContactDetail( 'F', 'Sarah', 'Hart', 55, 'CEO' ),
];

var r2 = _.where(contacts, {occupation: 'Developer', age: 28 });
console.log(r2);

前面的代码使用了我们在本章前面定义的 ContactDetail JavaScript 对象。我们将使用联系人列表调用 where() 方法,并提供带有我们要过滤的属性的键值对象。结果是 ContactDetail 与 Anne Jackson 匹配,因为她的职业(软件开发人员)和年龄(28 岁)匹配。

The map operations

map() 函数通过映射列表中的每个元素生成一个 新数组对象,其中 用户提供的函数:

console.log( 
  _.map( [1, 2, 3], 
   function(n){ return n * 3; } )  
);
// [3, 6, 9]

console.log(
  _.map( [ 1, 2, 3, 4], function(x){ return x * x; } )
);
// [1, 4. 9, 16]

console.log(
  _.map( [ 1, 2, 3, 4], 
    function(x){ return "A" + x; } )
);
// [ 'A1', 'A2', 'A3', 'A4']

用户提供的函数接受当前元素参数并负责返回新的元素类型。在这些示例中,我们将创建一个包含数字元素三元组的新数组列表,接下来,我们将创建一个包含数字元素平方的新数组列表。最后,我们将创建一个字符串元素的数组列表。

The flatten operations

现在我们知道如何 使用 UnderscoreJS 迭代、过滤和映射集合,我们 a>还应该学习如何展平元素的集合。有一种称为 flatten() 的方法,它接受一个元素集合,如果其中一个或多个元素本身就是一个集合,则将其展平。

让我们检查以下两个示例:

var Sector = function( name, value ) {
  this.name = name;
  this.value = value;
};

var salesSectorData = [ 
  [
    [ 
      new Sector("New York", 3724.23),
      new Sector("Boston", 8091.79)
    ],
    [
      new Sector("Houston", 9631.54)
    ],
  ],
  [
    new Sector("London", 2745.23),
    new Sector("Glasgow", 4286.36)
  ]
];

var f3 = _.flatten(salesSectorData);
console.log("f3 = "+f3);
// [Sector, Sector, Sector, Sector, Sector ]
var f4 = _.flatten(salesSectorData, true );
console.log("f4 = "+f4);
// [ [Sector, Sector], [Sector], Sector, Sector ]

在这里,我们定义了一个名为 Sector 的对象,它表示销售和营销数据。我们创建了一个嵌套集合 salesSectorData,它实际上是一个包含两个元素的数组,但每个元素都是一个进一步的集合。简而言之,salesSectorData 是一个二级有序数据结构。

第一个 flatten() 调用 将数组列表中的数据结构完全展平。所以我们最终会得到一个包含五个项目的数组。我们将第二个参数传递给第二个 flatten() 调用,它是一个布尔参数,指定展平操作是否也应该对集合的元素进行操作。 f4 的结果是一个包含四个项目的数组。第一个元素是一个包含两项的数组列表,第二个元素是一个包含一项的数组列表,然后是其余元素。

应该很清楚为什么 JavaScript 界面开发人员对 UnderscoreJS 赞不绝口。我们将在这次火力审查中继续进行最后的操作。

The reduction operations

这些函数式操作如果我们不能将它们简化为一个 单个操作又有什么用呢?标量值还是对象?值得庆幸的是,RequireJS 为我们提供了多种方法,例如 reduce()reduceRight()min()max()

我们现在只看 reduce() 操作。如果我们想发现所有先前部门对象的总销售价值,我们将如何做?这是答案:

var totalValue=  _.reduce(
  f3, 
  function(acc, sector) {
    return acc + sector.value;
  }, 
  0 );
console.log("totalValue = " + totalValue)

reduce() 操作接受三个参数:集合、迭代函数和初始值。为了将集合减少为单个标量值,reduce() 操作在每个 元素。 iteratee 函数接受标量 值参数和元素。匿名函数将销售部门值添加到累加器。

reduce() 运算符相对于集合是左关联的,而 reduceRight() 是右关联的。这完成了我们在 UnderscoreJS 中的旅程。更感兴趣的读者可以在线和通过其他信息资源深入研究这个框架。

GruntJS


在结束这一章关于渐进式 JavaScript 编程之前,我们将快速了解一个用于启动操作的工具。这个工具是 GruntJS (http://gruntjs.com/),和 其背后的人将其描述为 JavaScript 任务运行器。 GruntJS 是一个 Node.js 工具,适用于这个生态系统。因此,开发人员必须先安装 Node.js,然后才能使用 GruntJS。

该工具目前是数字社区中许多人的最爱。以下是 GruntJS 被视为奖励的部分原因:

  • 配置在一个地方,可以在您的数字团队中的其他开发人员、测试人员和操作员之间共享。

  • GruntJS 是使用插件系统构建的。

  • 该工具会压缩您的 CSS 并最小化您的 JavaScript 文件,以提高性能并将其交付给产品站点。

  • GruntJS 允许专门的界面开发团队在网站的客户端部分单独或一起工作。然后,这些工具将它们的 JavaScript 和 CSS 组件连接在一起以进行生产交付。

  • 它可以优化您的图像以减小整体文件大小,同时仍保持质量,非常适合提供大量 hero 风格的视网膜显示图形和还可以创建适合移动设备的图像。

  • 开发人员可以利用 Sass 和 Less 进行 CSS 创作。

有适用于 Less、Sass、RequireJS、CoffeeScript 等的 GruntJS 插件。

Node.js 是一个 JavaScript 运行平台,因此,它绝对不同于Java 平台。如果你碰巧使用 Gradle,有两个开源插件可以帮助弥合它们之间的差距。它们被称为 Gradle-GruntJS Plugin (https://github.com/srs/gradle-grunt-plugin) 和 Gradle-Node Plugin ( https://github.com/srs/gradle-node-plugin)。 Node.js 也有自己的包管理器,名为 npm,它处理库的安装、更新和删除。 Npm 允许与社区共享 Node.js 和 JavaScript 开源库。

每个 GruntJS 项目都需要根文件夹中的以下两个文件:

  • package.json:这个 文件指定 npm 项目的元数据并包含 JavaScript 工具和库依赖项您的项目需要,包括 GruntJS。

  • gruntfile.js:这个 文件配置和定义项目的构建任务。您还可以在该文件中添加 GruntJS 插件的依赖项。

对于 JSF 项目,您将把 package.json 放在项目根文件夹中。这是该文件的代码:

{
  "name": "my-digital-project",
  "version": "1.0-SNAPSHOT",
  "devDependencies": {
    "grunt": "~0.4.5"
  }
}

您会注意到该文件看起来几乎像一个 JSON 文件。关键的 devDependencies 属性声明了一组 npm 工具和框架。我们肯定希望从 0.4.5 或更高版本加载 GruntJS。

现在,我们将直接研究 GruntJS 的真实案例。在数字项目中,我们希望优化性能并确保我们在搜索引擎中的 SEO 排名。我们需要将第三方 JavaScript 库合并在一起并最小化 JavaScript 文件。界面开发人员更喜欢继续使用 Sass 工具来灵活管理 CSS 文件。我们与管理层达成协议,暂时保持开发的 JavaScript 文件不变。

这是实现这一目标的 gruntfile.js 文件:

module.exports = function(grunt) {              
  grunt.initConfig({
    pkg: grunt.file.readJSON('package.json'),

    concat: {  /* ... */ }

    uglify: { /* ... */ }

    sass: { /* ... */ } 
  });

  grunt.loadNpmTasks('grunt-contrib-concat');
  grunt.loadNpmTasks('grunt-contrib-uglify');
  grunt.loadNpmTasks('grunt-contrib-sass');
  grunt.registerTask('default', ['concat', 'uglify', 'sass']);
};

前面的 gruntfile.js 文件定义了 Node.js 系统中的单个模块。在另一个模块中有一个模块系统听起来很混乱;然而,我们只需要知道模块是一种封装形式,它允许通过重用进行共享。 module.exports 定义允许 grunt 参与 Node.js 系统。因此,可以与其他节点模块共享这个 grunt 模块。

grunt.initConfig() 节是初始化 GruntJS 工具所必需的。最重要的部分指定了元数据文件的名称,package.json。之后,我们为插件配置保留了一个区域。每个 GruntJS 插件都有单独的属性配置。共有三个插件:grunt-contrib-concatgrunt-contrib-uglify grunt-contrib-sass。每个插件声明一个配置属性名称:concatuglifysass .

要配置连接插件,我们有以下节:

concat: {   
  dist: {
    src: [
      'src/main/webapp/resources/js/libs/*.js', 
      'src/main/webapp/resources/js/global.js'  
    ],
    dest: 'build/webapp/js/build/thirdparty.js',
  }
}

grunt-contrib-concat 插件需要文件源和目标文件。它获取那里的任何 JavaScript 库文件,然后生成一个名为 thirdparty.js 的文件。在我们的 Gradle(或 Maven 项目)中,假设我们有一个 WAR 任务,它最终将捆绑最终的目标文件。

我们必须了解 Web 项目事实上的 Gradle 和 Maven 目录布局配置。因此,我们将 src/main/webapp 添加到文件路径中。

要配置最小化插件,我们将执行以下代码:

uglify: {
  build: {
    src: 'src/main/webapp/thirdparty.js',
    dest: 'src/main/webapp/thirdparty.min.js'
  }
}

这个配置很容易理解;我们只需将 grunt-uglify-contrib 插件指向源并定位文件路径。

最后,我们将配置 Sass CSS 构建插件如下:

sass: {
  dist: {
    options: {
      style: 'compressed'
    },
    files: {
      'src/main/webapp/resources/js/styles/build/mysite.css': 
      'build/webapp/resources/styles/mysite.scss'
    }
  }
}

我们需要更多关于 grunt-contrib-sass 插件的说明。这个插件需要文件的键值属性。目标文件是关键属性,而 SASS 源文件是值。

Tip

安装 SASS 还需要安装有效的 Ruby 安装和相应的 RubyGem。

GruntJS 是一个令人兴奋且为客户端数字开发人员提供的强大工具。毫无疑问。插件系统还很不成熟,我建议您查看文档以了解配置更改。

Summary


This chapter was a whirlwind tour of the modern digital JavaScript programming. If you have not worked with the JavaScript language at all or left it far behind for several years, then I do hope that you have been invigorated to learn this essential skill. We looked at the programming of the JavaScript objects. We saw how to construct the objects. We learned about the property notations and dealt with the JavaScript truth.

我们对 jQuery 进行了一次值得尊敬的访问——JavaScript 编程的祖父或祖父。如果你什么都没学到,那么你现在可以理解 jQuery。我们看到了选择器如何在 HTML DOM 中搜索元素,然后可以对其进行操作以获得效果。我们短暂涉足动画领域,这为为客户和企业主创造更复杂的体验打开了大门。

我们冒险使用 RequireJS 对模块进行依赖管理。我们了解了该框架如何帮助数字开发人员组织模块并将检索它们的顺序留给框架。

在本章的最后,我们跨过了一个叫做 Node.js 的非 Java 平台,特别是学习了 GruntJS 的基本细节。我们研究了一个 GruntJS 定义示例,该示例将 JavaScript 库文件捆绑在一起,从 Sass 生成 CSS,并优化 JavaScript 文件的大小。

在接下来的章节中,我们将了解 AngularJS 和新兴的 Java EE MVC 框架。

Exercises


以下是本章的习题和问题:

  1. 以下 JavaScript 代码定义了什么?还有什么和它相似的?

    var hospital = {
      name: "St Stephen's Hospital",
      location: "Kensington & Chelsea",
      patients: 1250,
      doctors: ['Patel', 'Radeksky', 'Brown', 'Shockley']
    };
  2. 从前面的代码中,解释 hospital.patientshospital['patients'] 属性访问器的主要区别是什么?

  3. 编写一个 JavaScript 对象,该对象构造一个 Java 用户组成员的参与者。假设您将对象称为 JUGParticipant。您将需要获取他们的名字和姓氏、他们的电话联系号码、他们的电子邮件地址(可选)以及他们的特定兴趣(可选)。

  4. 修改 JUGParticipant 以接受兴趣作为另一个对象。编写一个 JavaScript Tag 对象,以便您构建一系列技能,例如 Java EE、Android 或 HTML。证明您可以构建 JUGParticipants 和 Tag 对象的对象图。

  5. 通过创建 JUGEvent 会议对象来调整您的 JavaScript 对象图。您的对象将需要保存诸如事件标题、描述、位置、演讲者(可选)和愿意 JUGParticipants 等属性。

  6. 找出 JavaScript 1.5(或 ECMAScript 版本 3)中的九个原生对象构造函数。首先,这里有四个:Number()String()Boolean ()Object()

  7. 使用以下 HTML,使用 jQuery 更改 div 元素 messageArea 的背景颜色。代码中有三个按钮来表示交通灯的约定。 (您可以使内容漂亮!)

    <div class="traffic-light">
      <div id="messageArea" class="message-area">
        <p class="lead">
          This is it!
        </p>
      </div>
      <div>
        <a id="#redBtn" href="#" class="button" >Red</a>
        <a id="#yellowBtn" href="#" class="button" >Yellow</a>
        <a id="#greenBtn" href="#" class="button" >Green</a>
      </div>
    </div>
  8. 解释以下两个 jQuery 语句之间的区别:

    $('hero-image').attr('alt');
    $('hero-image').attr('alt', 'Voted best by Carworld 2015!');
  9. 下面的 HTML5 代码显示了一个 Bootstrap 警告框,我们希望用户在单击关闭图标时使警告消失。编写 JavaScript 和 jQuery 来为关键通知设置动画,将其淡出,然后从 HTML 中删除内容。

    <div>
      <p class="lead">
        Here is something I desperately want you
        to know right here, right now.
      </p <div class="critical-notice alert-danger">
        <button type="button" class="close pull-right" aria-label="Close">
          <span aria-hidden="true">&times;</span>
        </button>
    
        <span class="glyphicon glyphicon-warning-sign" aria-hidden="true" id="closeNotch" ></span>
        <strong>Danger!</strong> I'm very annoyed.
      </div>
    </div>
  10. 查看本书的源代码并开始使用 RequireJS。研究代码。你的任务是修改代码并添加你的模块,你可以调用 /js/app/oxygen.js。您将需要使用 AMD 规范的 define() 方法调用。

  11. 现在您已经编写了新的 RequireJS 模块 oxygen.js,您如何知道它按预期工作?你写了一个测试网页吗?您可能需要编写一个 /js/app/oxygendemo.js JavaScript 文件。

  12. 定义两个名为 /js/app/hydrogen.js/js/app/water.js 的模块。设置水模块,使其依赖于其他两个模块:氢气和氧气。

  13. 使用 UnderscoreJS 并假设您已设置依赖项,请练习以下 JavaScript 示例:

    var arr = [1, 2, 3, 4, 5];
    var newArr = _.map(arr, function (x) {
      return (x + 3) * (x - 2) * (x + 1);
    });
  14. 看看下面的 JavaScript 代码,它定义了一个百分比和一个产品价格数组。使用 UnderscoreJS,计算总价以及每个价格增加百分比时的总价:

    var Product = function(name, price, type ) {
      this.name = name;
      this.price = price;
      this.type = type;
    }
    
    var percentage = 3.25,
    var productPrices = [
      new Product("Secutars", 27.99, "Garden"), 
      new Product("Chair", 99.99, "Home" ), 
      new Product("Luxury Red Wines", 79.99, "Food" ),
      new Product("Microwave", 49.99, "Home" ),
      new Product("Mower", 169.99, "Garden" )
    ];
  15. 请注意,我们有不同类型的产品。使用 UnderscoreJS,计算出原始价格的总计,然后计算出增加了 5.35% 的总计。