十八、前端开发

在本章中,我们将涵盖以下主题:

  • 使用引导
  • 编写干净的 JavaScript
  • RequireJS
  • 打印稿
  • 用 JavaScript 编写和执行单元测试
  • 在浏览器中调试 JavaScript 代码

All examples in this chapter can be found at the following Github repo: https://github.com/polatengin/B05277/tree/master/Chapter18.

使用引导

Bootstrap 是一个成熟的前端框架,可以帮助开发者构建响应性强的(对移动设备友好的)网站。

What is Responsive Web Design?

According to W3Schools (World Wide Web Schools: https://www.w3schools.com/bootstrap/bootstrap_get_started.asp), responsive web design is about creating websites that automatically adjust themselves to look good on all devices, from small phones to large desktops.

准备

Bootstrap 可以从https://www.w3schools.com/bootstrap/bootstrap_get_started.asp下载,也可以从 Bootstrap CDN 中添加到网页中。

一些 Bootstrap CSS 类需要 JavaScript 代码在网站上工作。

在撰写本文时,Bootstrap 的最新版本是 Beta.2。

The example project can be found at https://github.com/polatengin/B05277/tree/master/Chapter18/0-GettingStartedBootstrap.

为了使用 Bootstrap,你应该将这些行添加到网页的头部部分:

<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script> 
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.3/umd/popper.min.js" integrity="sha384-vFJXuSJphROIrBnz7yo7oB41mKfc8JzQZiCq4NCceLEaO4IHwicKwpJf9c9IpFgh" crossorigin="anonymous"></script> 

<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/css/bootstrap.min.css" integrity="sha384-PsH8R72JQ3SOdhVi3uxftmaW6Vc51MKb0q5P2rRUpPvrszuE4W1povHYgTpBfshb" crossorigin="anonymous"> 
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/js/bootstrap.min.js" integrity="sha384-alpBpkh1PFOepccYVYDB4do5UnbKysX5WZXm3XxPqe5iKTfUKjNkCk9SaVuEZflJ" crossorigin="anonymous"></script> 

使用 CDN 的优点

CDN 服务器只提供静态文件,并在客户端缓存它们很长一段时间,比如一年。

通过这样做,如果任何用户访问该网页,他们下载所需的静态文件从 CDN 和缓存他们。

在访问之后,相同的用户可以访问其他网站和网页,并且由于先前缓存了所需的文件,网页使用缓存的文件版本。

仅仅处理文件要比下载文件快得多。

而且,大多数 cdn 在世界各地都有许多静态文件服务器,并从离用户最近的服务器提供所需的静态文件。

怎么做……

我们将开发一个网页,具有响应能力,并适应屏幕的实际:

  1. 让我们创建一个新的网页来实现以下图像:

注意,我们将有一个响应式页面,在不同的屏幕尺寸下更好地显示其内容。

  1. 我们应该通过创建一个新的 HTML 文件并命名为index.html来创建一个基本的 HTML5 文件,如下所示:
<!DOCTYPE html> 
<html lang="en"> 
<head> 
    <meta charset="UTF-8"> 
    <meta name="viewport" content="width=device-width, initial-scale=1.0"> 
    <title>HTML5 Bootstrap</title> 
</head> 
<body> 
</body> 
</html> 
  1. 在这之后,我们需要从 bootstrap CDN 中添加 bootstrap 文件,如下所示:
<!DOCTYPE html> 
<html lang="en"> 
<head> 
    <meta charset="UTF-8"> 
    <meta name="viewport" content="width=device-width, initial-scale=1.0"> 
    <title>HTML5 Bootstrap</title> 

    <script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script> 
    <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.3/umd/popper.min.js" integrity="sha384-vFJXuSJphROIrBnz7yo7oB41mKfc8JzQZiCq4NCceLEaO4IHwicKwpJf9c9IpFgh" crossorigin="anonymous"></script> 

    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/js/bootstrap.min.js" integrity="sha384-alpBpkh1PFOepccYVYDB4do5UnbKysX5WZXm3XxPqe5iKTfUKjNkCk9SaVuEZflJ" crossorigin="anonymous"></script> 
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/css/bootstrap.min.css" integrity="sha384-PsH8R72JQ3SOdhVi3uxftmaW6Vc51MKb0q5P2rRUpPvrszuE4W1povHYgTpBfshb" crossorigin="anonymous"> 
</head> 
<body> 
</body> 
</html> 

我们已经准备好使用 Bootstrap CSS 类来开发我们的第一个网页。

  1. 让我们创建一个div元素并向其添加一个container类。
<div class="container"> 
</div> 

When using Bootstrap, we generally start from container elements because some of the Bootstrap classes require a parent container element. There are two container classes we can choose: the .container class provides a responsive fixed width container, and the .container-fluid class provides a full width container, spanning the entire width of the viewport.

  1. 现在我们可以在页面中添加div.row类来创建一个网格系统,如下所示:
<div class="row"> 
</div> 

Bootstrap has a 12-column grid system. The row class creates a horizontal space and we can place 12 columns in it. But, there are some rules about using the row class in Bootstrap, such as, rows must be placed in container classes.

  1. 我们可以在一行中放置多达 12 列。 在本例中,我们希望在其中放入三列。 但这里有一个要求:我们希望在大屏幕上并排放置三列,但如果查看器的屏幕较小,三列必须垂直放置。
  2. Bootstrap 有一组列类,例如 col、col-2、col-3、col-4、col-5、col-6 等等。 我们可以使用它们的总和为 12,并将它们放入行类中。

如果我们想把一行分成两半,如果我们想让前半部分的大小是后半部分的两倍,我们可以编写以下代码:

<div class="row"> 
  <div class="col-8"></div> 
  <div class="col-4"></div> 
</div> 

或者,我们可以将一行分成四个等宽的列,如下所示:

<div class="row"> 
  <div class="col-3"></div> 
  <div class="col-3"></div> 
  <div class="col-3"></div> 
  <div class="col-3"></div> 
</div> 

我们可以混合和匹配列的总和为 12,并实现所需的布局。

在本例中,我们希望有三个列,但要求在大屏幕上,列必须是水平的,而在小屏幕上,列必须是垂直的。

Bootstrap 也涵盖了这个场景。 Bootstrap 网格系统有一些媒体查询和一些列类,如下所示:

| | 特小 | | 中位 | | 超大 | | < 576 px | ≥576px | ≥768px | ≥992 px | ≥1200px | | 集装箱最大宽度 | 没有(汽车) | 540 px | 720 px | 960 px | 1140 px | | 类前缀 | .col- | .col-sm- | .col-md- | .col-lg- | .col-xl- | | #列 | 12 | | 排水沟宽度 | 30px(每列各 15px) |

  1. 因此,我们可以在具有.row类的div元素中添加以下代码,从而达到预期的要求:
<div class="col-sm-4"> 
  <h3>Column 1</h3> 
  <p>Title SubHeading 1</p> 
  <p>Lorem ipsum dolor sit amet...</p> 
</div> 
<div class="col-sm-4"> 
  <h3>Column 2</h3> 
  <p>Title SubHeading 2</p> 
  <p>Nunc et euismod nisl...</p> 
</div> 
<div class="col-sm-4"> 
  <h3>Column 3</h3> 
  <p>Title SubHeading 3</p> 
  <p>Phasellus et consectetur arcu...</p> 
</div> 

现在,在大屏幕上,柱子将水平放置,在小屏幕上,柱子将垂直放置。

它是如何工作的…

随着时间的推移,Bootstrap 也随着 web 的发展而发展,现在已经是一个成熟的框架了。

通过从 CDN 中使用它,我们可以减轻在服务器上托管它的负担。 有成千上万的其他网站通过 CDN 使用它,所以我们可以相信,用户可能在使用我们之前访问了一些其他网站。 我们的网站可以使用预缓存的磁盘引导文件,所以我们的网站可以更快地吸引到用户屏幕上。

Bootstrap 具有内置的响应性,所以每个开箱即用的类都可以帮助我们开发适合屏幕的页面,无论大小。

编写干净的 JavaScript

JavaScript 是一种强大的语言。 我们可以使用 JavaScript 创建游戏、电子表格应用、邮件客户端等等。 然而,如果不小心的话,使用 JavaScript 开发应用会变得很混乱。

我们使用 JavaScript 框架/库来清理冗余和混乱的代码,这是一件好事。

Lodash 是最常用的 JavaScript 库之一,它可以简化 JavaScript 代码的开发,例如遍历数组、处理对象及其属性等等。

According to https://lodash.com/, Lodash makes JavaScript easier by taking the hassle out of working with arrays, numbers, objects, strings, and so on. Lodash's modular methods are great for:

Iterating arrays, objects, and strings   Manipulating and testing values   Creating composite functions

准备

首先,我们需要将lodash.js添加到我们的网页中。 我们可以从https://lodash.com/下载lodash.js或者从 CDN 中添加lodash.js(就像我们在使用 Bootstrap 时所做的那样)。

It is considered a best practice to use minified versions of CSS and JS files. Almost every framework provides both un-minified and minified versions of CSS and JS files. The un-minified versions are suitable for development environments, and minified versions are suitable for production environments.

让我们创建一个 HTML5 文件,并从 CDN 中添加lodash.js,如下所示:

<!DOCTYPE html> 
<html> 
<head lang="en"> 
    <meta charset="UTF-8"> 
    <title>Lodash demo</title> 
    <script src="https://cdn.jsdelivr.net/npm/lodash@4.17.4/lodash.min.js"></script> 
</head> 
<body> 
</body> 
</html> 

The example project can be found at: https://github.com/polatengin/B05277/tree/master/Chapter18/1-GettingStartedLodash.

怎么做……

现在我们可以在 body 元素中添加一个 script 标签,并使用 lodash 函数:

<script> 
var teams = ["Barcelona", "Real Madrid", "Manchester United", "Borissia Dortmund", "Chelsea",  "Arsenal", "Bayern Munich", "Milan", "Juventus", "Galatasaray"]; 
        console.log("First element in teams: ", _.first(teams)); 
        console.log("Last element in teams: ", _.last(teams)); 

var books = [ 
            { title: "Matrix", price: 40 }, 
            { title: "Lord of the Rings", price: 30 }, 
            { title: "Harry Potter", price: 30 }, 
            { title: "White Fang", price: 5 }, 
            { title: "Godfather", price: 25 } 
        ]; 

console.log("Name of the cheapest book is ", _.chain(books).sortBy('price').first().value().title) 

console.log("Available chars:", _.times(26, function(x) { return String.fromCharCode(65+x); })); 
</script> 

在本例中,我们将使用first()last()chain()sortBy()value()times()函数:

  • 顾名思义,first()last()方法选择给定数组中的第一个和最后一个元素
  • 方法根据给定的属性对数组进行排序
  • 方法按次数执行函数
  • chain()方法获取一个对象并将其转换为lodash对象,从而允许其他lodash方法调用该对象
  • value()方法返回一个由 lodash 方法包装的底层对象

这段代码的输出如下:

一些最常用的lodash方法是:

  • 方法cloneDeep(),它创建给定对象的深度克隆版本
  • random()方法,在给定的参数之间生成随机数
  • sample()方法,它从给定数组中选取给定数量的随机项
  • forEach()方法,它对数组中的每个项执行给定的函数
  • flattenDeep()方法,它从具有更多级别的给定数组生成一个深度为一层的数组
  • filter()方法,该方法在将给定的选择器应用于给定的数组后返回一个数组
  • 方法,它返回给定数组的打乱版本

More documentation can be found at https://lodash.com/docs/.

它是如何工作的…

大多数的lodash方法可以在下划线(_)之后使用,例如:

_.times() 
_.first() 
_.last() 

一些lodash方法可以在变量之后使用,例如:

var chain = _.chain(arr); 
var first = arr.sortBy('prop').first(); 

通过使用 lodash,我们可以编写更干净、更少冗余的 JavaScript 代码。

RequireJS

RequireJS 是一个 JavaScript 文件和模块加载器。 我们通常在浏览器应用中使用它,但它也可以在 Node.js 应用中使用。

RequireJS 的好处是它可以异步加载文件和模块。 异步加载使网页显示在屏幕上更快。 它基本上把加载文件的时间推迟到需要的时候。

通过异步加载,网页从更小的文件和更少的网络调用开始,导致更快的启动。

The example project can be found at https://github.com/polatengin/B05277/tree/master/Chapter18/2-GettingStartedRequireJs.

如果我们在开发一个大型应用它有几十个 JavaScript 文件,就很难维持依赖关系并将正确的文件加载到正确的位置,例如,在电子商务应用中; shoppingCart.jsproduct.jscustomer.js文件。

main.js文件中,如果我们调用purchase()方法从shoppingCart.js文件,它需要一个方法调用从product.js文件,和它需要调用函数customer.js文件,我们最终将脚本文件添加到 web 页面如下:

<script src="customer.js"></script> 
<script src="product.js"></script> 
<script src="shoppingCart.js"></script> 
<script src="main.js"></script> 

然而,从文件中改变依赖很容易,我们应该为网页添加依赖到 JavaScript 文件中。

RequireJS 通过加载所需的模块并调用require()方法来解决这个问题。

如果在define()方法的 JavaScript 文件中定义了所需的模块,那么require()方法将加载该模块。

准备

RequireJS 必须通过 CDN 添加到网页,或者从http://requirejs.org/docs/download.html下载require.js文件。

在这个例子中,我们将开发一个简单的待办事项列表应用,如下所示:

让我们创建一个简单的 HTML5 文件,并添加 Bootstrap 和 RequireJS 从 CDN 如下:

<!DOCTYPE html> 
<html> 
<head lang="en"> 
    <meta charset="UTF-8"> 
    <title>Simple ToDo app using RequireJs</title> 
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/css/bootstrap.min.css" integrity="sha384-PsH8R72JQ3SOdhVi3uxftmaW6Vc51MKb0q5P2rRUpPvrszuE4W1povHYgTpBfshb" crossorigin="anonymous"> 
    <script data-main="scripts/main" src="http://requirejs.org/docs/release/2.3.5/minified/require.js"></script> 
</head> 
<body> 
</body> 
</html> 

然后,创建一个 scripts 文件夹,并添加以下 JavaScript 文件:

  • main.js
  • todo.js

现在我们可以在 body 标签中添加以下 HTML 元素:

<div class="container"> 
    <div class="jumbotron p-4"> 
        <h1>ToDo App</h1> 
    </div> 
    <div class="form-group"> 
        <label>What to do?</label> 
        <input type="text" class="form-control" placeholder="Enter what to do..." autofocus> 
    </div> 
    <button type="submit" class="btn btn-primary">Add</button> 
    <h1>ToDo List</h1> 
    <ul class="list-group"> 
    </ul> 
</div> 

怎么做……

就像前面的例子一样,RequireJS 可以通过一个简单的脚本标签添加到网页中。 关于这个脚本标记的重要一点是,它必须有一个data-main属性来指向 JavaScript 的起始点。

在本例中,我们确定起点为 scripts/main,require()方法添加缺失的.js扩展名,并加载scripts/main.js文件。

  1. 首先,我们将按照如下方式开发todo.js文件:
define(function () { 
    var list = []; 
    return { 
        add: function (item) { 
            list.push(item); 
        }, 
        renderList: function() { 
            var html = ''; 

            for (var iLoop = 0, len = list.length; iLoop < len; iLoop++){ 
                html += '<li class="list-group-item">' + list[iLoop] + '</li>'; 
            } 

            return html; 
        } 
    }; 
}); 

todo.js文件定义了一个模块,该模块有一个名为list的变量,并返回两个方法。 第一个方法只是向列表中添加一个项,第二个方法遍历列表并返回所需的 HTML。

  1. 现在,在main.js文件中,我们将设置一个基本配置并从 CDN 加载 jQuery,如下所示:
require.config({ 
    "baseUrl": "scripts", 
    "paths": { 
      "jquery": "https://code.jquery.com/jquery-3.2.1.min" 
    } 
}); 
  1. require.config({ ... })方法之后,我们将开发主模块如下:
require(['jquery', 'todo'], function($, todo) { 
    $(document).ready(function() { 
        $('button').click(function() { 
            todo.add($('input').val()); 
            $('input').val(''); 
            $('input').focus(); 
            $('ul').html(todo.renderList()); 
        }); 
    }); 
}); 

require()方法的第一个参数是依赖项,require()方法的第二个参数是一个callback函数,它将在 RequireJS 加载依赖项后执行。

我们在require()方法的第二个参数中以相同的顺序命名这些依赖项。

require()方法的callback函数中,我们基本上调用了todo.js文件中todo模块对应的方法。

它是如何工作的…

Require.Js加载一个脚本标记时,它首先执行data-main属性中定义的模块。 如果那个模块有一个config()方法,RequireJS 就会通过网络获取所需的文件。

每个define()方法都定义了一个与其文件名同名的新模块。 每个require()方法通过网络异步加载所需的模块。

打印稿

在我写这篇文章的时候,JavaScript 已经有 22 年的历史了。 但是,它仍然缺乏一些现代软件开发原则,例如 OOP、强类型等等。

OOP usually refers to the ability of encapsulation, abstraction, polymorphism, and inheritance. JavaScript has some techniques that mimic these concepts, but not in a native way, such as interfaces, classes, access modifiers, and so on.

Typescript 是一种开发 JavaScript 应用的现代方式。 Typescript 是 JavaScript 的超集,这意味着你可以在 JavaScript 中做任何事,你也可以在 Typescript 中做任何事。

Typescript 提供了静态类型、类、接口、访问修饰符、异步执行等等,而 JavaScript 没有这些功能。 你可以使用 Typescript 开发前端应用,Typescript 编译器会编译你的代码,把它转换成等效的 JavaScript 代码。 毕竟,浏览器只理解 JavaScript,所以所有的 Typescript 代码在发布到服务器之前都必须转换成 JavaScript。

你可以从https://www.typescriptlang.org/下载 Typescript 安装程序。

在安装 Typescript 编译器之后,你可以创建一个扩展名为.ts的 Typescript 文件,而不是一个扩展名为.js的 JavaScript 文件。

Typescripttranspiler(tsc)可以检查你的代码,执行一些规则,将你的代码转换成等效的 JavaScript,并以.js文件扩展名保存。

The example project can be found at https://github.com/polatengin/B05277/tree/master/Chapter18/3-GettingStartedTypescript.

准备

让我们创建一个扩展名为.ts的新文件,并将其命名为main-0,并在其中编写以下代码:

interface Customer { 
    name: string; 
    email: string; 
    phone: string; 
} 

let customers: Array<Customer> = []; 

customers.push({ 
    name: 'Engin Polat', 
    email: 'engin@enginpolat.com', 
    phone: '0123456789' 
}); 

如你所见,Typescript 允许我们在代码中定义接口,并在其中添加属性。 每个属性都可以有一个类型,并且对该属性的所有赋值都必须是该类型。 否则,Typescripttsc将抛出一个异常,并且不会将 Typescript 代码编译成 JavaScript 代码。

让我们也写下面的代码并检查它:

class Product { 
    public title: string; 
    public price: number; 
    private updatedOn: Date; 
} 

let ledTV = new Product(); 
ledTV.title = 'Awesome LED TV with huge screen'; 
ledTV.price = 1000; 

Typescript 也有一个class关键字,它可以包含带有访问修饰符的属性。

Like many OOP programming languages, a public keyword means the member can be accessed outside the class, and a private keyword means a member can be accessed only inside the class.

如果你对这个main-0.ts文件执行tsc命令,Typescript 编译器会把它转换成等效的 JavaScript 代码,如下所示:

var customers = []; 
customers.push({ 
    name: 'Engin Polat', 
    email: 'engin@enginpolat.com', 
    phone: '0123456789' 
}); 
var Product = /** @class */ (function () { 
    function Product() { 
    } 
    return Product; 
}()); 
var ledTV = new Product(); 
ledTV.title = 'Awesome LED TV with huge screen'; 
ledTV.price = 1000; 

正如你所看到的,接口定义已经被 Typescript 编译器省略了,但是所有的赋值在转换成 JavaScript 代码之前仍然会被验证。

现在,我们将创建一个新文件,命名为main-1.ts,并在其中编写以下代码:

function logProductNameUsingPromises() { 
    return fetch('./data/products.json') 
           .then((response: any) => { 
              return response.json(); 
           }) 
           .then((product: any) => { 
             console.log(`${product.title} ${product.price}`); 
           }) 
} 

如果我们在这个代码文件上执行 Typescript 编译器,它会变成以下 JavaScript 代码:

function logProductNameUsingPromises() { 
    return fetch('./data/products.json') 
        .then(function (response) { 
        return response.json(); 
    }) 
        .then(function (product) { 
        console.log(product.title + " " + product.price); 
    }); 
} 

正如您可能已经注意到的,编译后的代码几乎是相同的。 让我们在maint-1.ts文件中添加以下代码:

async function logProductNameUsingAsyncAwait() { 
    let response = await fetch('./data/products.json'); 
    let product = await response.json(); 
    console.log(`${product.title} ${product.price}`); 
} 

Typescript 让使用 async-await 关键字对开发异步代码变得更加容易。

如果我们对这些代码执行 Typescript 编译器,编译后的 JavaScript 代码将如下所示:

var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 
    return new (P || (P = Promise))(function (resolve, reject) { 
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 
        function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } 
        step((generator = generator.apply(thisArg, _arguments || [])).next()); 
    }); 
}; 
var __generator = (this && this.__generator) || function (thisArg, body) { 
    var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; 
    return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; 
    function verb(n) { return function (v) { return step([n, v]); }; } 
    function step(op) { 
        if (f) throw new TypeError("Generator is already executing."); 
        while (_) try { 
            if (f = 1, y && (t = y[op[0] & 2 ? "return" : op[0] ? "throw" : "next"]) && !(t = t.call(y, op[1])).done) return t; 
            if (y = 0, t) op = [0, t.value]; 
            switch (op[0]) { 
                case 0: case 1: t = op; break; 
                case 4: _.label++; return { value: op[1], done: false }; 
                case 5: _.label++; y = op[1]; op = [0]; continue; 
                case 7: op = _.ops.pop(); _.trys.pop(); continue; 
                default: 
                    if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } 
                    if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } 
                    if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } 
                    if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } 
                    if (t[2]) _.ops.pop(); 
                    _.trys.pop(); continue; 
            } 
            op = body.call(thisArg, _); 
        } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } 
        if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; 
    } 
}; 
function logProductNameUsingAsyncAwait() { 
    return __awaiter(this, void 0, void 0, function () { 
        var response, product; 
        return __generator(this, function (_a) { 
            switch (_a.label) { 
                case 0: return [4 /*yield*/, fetch('./data/products.json')]; 
                case 1: 
                    response = _a.sent(); 
                    return [4 /*yield*/, response.json()]; 
                case 2: 
                    product = _a.sent(); 
                    console.log(product.title + " " + product.price); 
                    return [2 /*return*/]; 
            } 
        }); 
    }); 
} 

现在几乎认不出来了。 显然,更多的代码意味着更多的执行时间,但是更清晰的代码意味着更易于维护的代码。

怎么做……

在本例中,我们注意到可以使用枚举、构造函数、实例化类等等。 让我们创建一个新文件,命名为chessboard-sample.ts,并编写以下代码:

enum PieceColor { 
    White = 1, 
    Black = 2 
} 

enum PieceType { 
    Pawn = 1, 
    Bishop = 2, 
    Knight = 3, 
    Rook = 4, 
    Queen = 5, 
    King = 6 
} 

interface Piece { 
    type: PieceType; 
    color: PieceColor; 
} 

class Block { 
    isEven: boolean; 
    row: number; 
    col: number; 
    piece: Piece; 
    constructor(row: number, col: number) { 
        this.row = row; 
        this.col = col; 
        this.isEven = row % 2 ? !(col % 2) : !!(col % 2); 
    } 
} 

class Board { 
    blocks: Array<Block> = []; 

    placePieceOnBoard(piece: Piece, row: number, col: number) { 
        this.blocks[(row * 8) + (col * 8)].piece = piece; 
    } 
} 

let game = new Board(); 
for (let iLoop = 0; iLoop < 8; iLoop++) { 
    for (let yLoop = 0; yLoop < 8; yLoop++) { 
        game.blocks.push(new Block(iLoop, yLoop)); 
    } 
} 
game.placePieceOnBoard({ color: PieceColor.White, type: PieceType.Pawn }, 1, 0); 
game.placePieceOnBoard({ color: PieceColor.Black, type: PieceType.Pawn }, 7, 0); 
// more code... removed for brevity 

这不是一个完整的象棋游戏应用,但它是一个很好的起点。

它是如何工作的…

Typescript 有两个主要部分; 一个是 Typescript 语言及其 JavaScript 的超集。 它为 JavaScript 带来了 OOP 和现代应用开发范式的支持。

Typescript 的另一部分是 Typescript 编译器。 编译器分析 Typescript 代码并将其转换为 JavaScript 代码。

毕竟,浏览器只能执行 JavaScript 代码,所以 Typescript 代码必须转换成 JavaScript。

用 JavaScript 编写和执行单元测试

随着时间的推移而开发应用,并且在以后不要破坏功能,这是很重要的。 应用是用 c#、Java、Python 还是 JavaScript 开发的并不重要。

有几个客户端单元测试框架,最突出的是:

  • 茉莉花
  • AVA
  • 摩卡
  • Jest
  • QUnit

每个测试框架都有它的起伏。 我们将在本章中使用 QUnit。 QUnit 是由 jQuery 开发人员开发的,用于测试 jQuery 方法。 jQuery 依赖于 QUnit,当开发人员添加新函数并破坏框架中完全无关的部分时,QUnit 不会随着时间的推移而中断。

The example project can be found at https://github.com/polatengin/B05277/tree/master/Chapter18/4-GettingStartedQUnit.

准备

用 QUnit 创建一个基本的测试设置非常容易。 让我们创建一个 HTML5 文件并添加几个元素,如下所示:

<!DOCTYPE html> 
<html> 
<head> 
    <meta charset="utf-8"> 
    <meta name="viewport" content="width=device-width" /> 
    <title>QUnit Unit Testing Example</title> 
    <link rel="stylesheet" href="https://code.jquery.com/qunit/qunit-2.4.1.css" /> 
</head> 
<body> 
    <div id="qunit"></div> 
    <div id="qunit-fixture"></div> 
    <script src="https://code.jquery.com/qunit/qunit-2.4.1.js"></script> 
    <script src="app.js"></script> 
    <script src="test-fixture.js"></script> 
</body> 
</html> 

这是我们的测试设置; 它包括一个 CSS 文件,以更好地在屏幕上显示测试结果。 它还包括test-fixture.jsapp.js文件。 app.js文件是我们要在其上运行测试的应用代码。 test-fixture.js文件是我们将要编写的测试文件。

当然,app.js文件将被我们的生产代码文件所取代,但是在本例中,它将尽可能地简单。

怎么做……

我们将创建一个基本的 JavaScript 应用,然后创建一些测试,以确保我们的应用在对其进行一些更改后能够正常运行。

  1. 让我们创建一个app.js文件,并添加以下代码:
var App = { 
    current: 0, 
    increment: function(num) { 
        this.current += num || 1; 
    }, 
    decrement: function(num) { 
        this.current -= num || 1; 
    }, 
    isEven: function() { 
        return this.current % 2 === 0; 
    }, 
    isOdd: function() { 
        return this.current % 2 !== 0; 
    } 
}; 

它只是保存一个数字,并使用几个函数来操作和查询这些数字的值。

  1. 现在我们可以创建一个新文件,命名为test-fixture.js,并在其中编写以下代码:
QUnit.test('This is most basic test', function (assert) { 
    assert.expect(1); 
    assert.ok(true); 
}); 

QUnit.test('This is another basic test', function (assert) { 
    assert.ok(true, 'It works!'); 
}); 

QUnit.test('current number is zero', function (assert) { 
    assert.equal(App.current, 0); 
}); 

QUnit.test('current number is one after increment', function (assert) { 
    App.increment(); 
    assert.equal(App.current, 1); 
}); 

QUnit.test('current number is zero again after decrement', function (assert) { 
    App.decrement(); 
    assert.equal(App.current, 0); 
}); 

QUnit.test('current number is zero AND even', function (assert) { 
    assert.equal(App.current, 0); 
    assert.equal(App.isEven(), true); 
}); 

QUnit.test('current number is odd after increment by 5', function (assert) { 
    App.increment(5); 
    assert.equal(App.isOdd(), true); 
}); 

方法是我们的测试实现。 每个test()方法都有一个要测试的东西。 test()方法有两个参数:

测试实现通过参数获取一个assert对象,并使用它通过一些方法测试具有所需值的变量。

最常用的断言方法是:

如果我们在浏览器中导航到测试装置,我们应该会看到以下屏幕,所有测试都通过了:

它是如何工作的…

QUnit 的优点在于它没有依赖关系,可以在所有浏览器中工作,执行QUnit.test()方法,并在屏幕上显示结果。

在浏览器中调试 JavaScript 代码

JavaScript 代码在浏览器中执行。 我们(作为开发人员)使用智能 ide、智能测试框架等来轻松地构建应用。 但有时,当浏览器执行我们的智能构建的代码时,我们没有预料到的事情发生了,我们的代码中断了。

bug 可以是语法错误。 这些类型的 bug 很容易确定。 大多数 ide 都有一些内置的工具来确定语法错误,甚至提供智能修复。

有些错误是逻辑错误。 这些类型的错误不容易确定,但是大多数 ide 都不支持确定逻辑错误。

因此,我们需要在执行环境(即浏览器)中调试代码的工具。

The example project can be found at https://github.com/polatengin/B05277/tree/master/Chapter18/5-GettingStartedDebugging.

准备

所有浏览器都有一个理解和执行 JavaScript 代码的内部机制。 这种机制称为 JavaScript 引擎。

JavaScript 引擎的主要工作是将我们开发的 JavaScript 代码转换为快速的、优化的、可以由浏览器解释的代码。

一些最流行的 JavaScript 引擎是:

  • 谷歌 V8
  • Mozilla Spidermonkey
  • 微软脉轮

怎么做……

让我们研究几个调试代码的方法。 第一个是 JavaScript 的console.log()方法:

  1. 创建一个新文件,命名为index.html,并编写以下代码:
<!DOCTYPE html> 
<html> 
<head> 
    <meta charset="utf-8"> 
    <title>Debugging</title> 
</head> 
<body> 
    <script> 
        var num1 = 5; 
        var num2 = 8; 

        var sum = num1 + num2; 

        console.log(sum); 
    </script> 
</body> 
</html> 
  1. 进入该网页后,按键盘上的F12键,单击“控制台”页签。 你应该看到 13 如下(截图是从谷歌 Chrome 浏览器,但它几乎相同在其他浏览器):

如果想要查看变量的值,可以添加console.log()方法并将变量的值打印到 Console 选项卡中。

Usually, the F12 key opens the Developer Panel, which includes several debugging tools such as JavaScript Debugger, Console, DOM Elements, and so on.

而且,所有现代浏览器都有一个内置的 JavaScript 调试器。 在调试器窗口中,您可以在 JavaScript 代码中设置断点。

我们可以在每一行 JavaScript 中添加断点; JavaScript 会在这一行停止执行,让你检查变量的值。

在检查值之后,您可以恢复代码的执行(通常使用一个播放按钮),并且您还可以从代码中的某个点恢复执行。

正如你在下面的截图中看到的,你可以在一些代码行上设置断点,并且 Watch 面板让你看到和改变变量的值:

当浏览器到达断点行时,将停止执行 JavaScript 代码,并显示一个通知:Paused in debugger。 我们可以通过点击播放按钮来恢复执行:

如果我们想根据某个变量的值或代码的执行路径设置断点,我们应该使用调试器; 关键字如下:

if(sum > 10) { 
  debugger; 
} 

它是如何工作的…

浏览器首先获取 HTML,然后解释它并确定所需的外部文件(图像、CSS、JavaScript 文件)。

浏览器获取所需的外部文件并分别处理下载的每个文件。

HTML 文件中的 JavaScript 文件和 JavaScript 代码将由浏览器中的一个特殊单元处理,即 JavaScript Engine。

JavaScript 引擎将 JavaScript 代码或 JavaScript 文件作为输入,对其执行一些分析和验证,然后将其编译为快速和优化的机器码,这些机器码通过浏览器在 CPU 上执行。