JavaScript 高级编程(四)
原文:
zh.annas-archive.org/md5/C01E768309CC6F31A9A1148399C85D90译者:飞龙
附录
关于
本节旨在帮助学生执行书中的活动。它包括详细的步骤,学生需要执行这些步骤以实现活动的目标。
第一章:介绍 ECMAScript 6
活动 1 - 实现生成器
您被要求构建一个简单的应用程序,根据请求生成斐波那契数列中的数字。该应用程序为每个请求生成序列中的下一个数字,并在给定输入时重置序列。使用生成器生成斐波那契数列。如果将一个值传递给生成器,则重置序列。为了简单起见,您可以从 n=1 开始斐波那契数列。
为了突出生成器如何用于构建迭代数据集,请按照以下步骤进行:
-
查找斐波那契数列并了解下一个值是如何计算的。
-
为斐波那契数列创建一个生成器。
-
在生成器内,使用变量
n2和n1设置current和next(0, 1)的默认值。 -
创建一个无限的
while循环。 -
在
while循环内,使用yield关键字提供序列中的当前值,并将 yield 语句的返回值保存到名为input的变量中。 -
如果输入包含一个值,则将变量
n2和n1重置为它们的起始值。 -
在
while循环内,从current+next计算出新的下一个值,并将其保存到变量 next 中。 -
否则,将
n2更新为包含n1(next值)的值,并将n1设置为我们在while循环顶部计算的next值。
代码:
index.js
function* fibonacci () {
let n2 = 0;x
let n1 = 1;
while ( true ) {
let input = yield n2;
if ( input ) {
n1 = 1;
n2 = 0;
} else {
let next = n1 + n2;
[ n1, n2 ] = [ next, n1 ];
}
}
}
let gen = fibonacci();
片段 1.87:实现生成器
结果:
。
图 1.19:带有生成器的斐波那契数列
您已成功演示了如何使用生成器基于斐波那契数列构建迭代数据集。
第二章:异步 JavaScript
活动 2 - 使用 Async/Await
您被要求构建一个与数据库交互的服务器。您必须编写一些代码来查找数据库中的集合和基本用户对象。导入simple_db.js文件。使用async/await语法编写以下程序,使用get和insert命令:
-
查找名为
john的键,名为sam的键,以及您的名字作为数据库键。 -
如果数据库条目存在,则记录结果对象的
age字段。 -
如果您的名字在数据库中不存在,请插入您的名字并关联一个包含您的名字、姓氏和年龄的对象。查找新的数据关联并记录年龄。
对于任何失败的db.get操作,将键保存到数组中。在程序结束时,打印失败的键。
DB API:
db.get(index):
这需要一个索引并返回一个 promise。如果索引不存在,数据库查找失败,或者未指定键参数,则 promise 将被拒绝并返回错误。
db.insert(index,insertData):
这需要一个索引和一些数据,并返回一个 promise。如果操作完成,promise 将被插入的键满足。如果操作失败,或者没有指定键或插入的数据,则 promise 将被拒绝并返回错误。
要利用 promise 和 async/await 语法构建程序,请按照以下步骤进行:
-
使用
require('./simple_db')导入数据库 API,并将其保存到变量db中。 -
编写一个异步主函数。所有操作都将在这里进行。
-
创建一个数组来跟踪导致
db错误的键。将其保存到变量missingKeys中。 -
创建一个 try-catch 块。
在 try 部分内,使用 async/await 和db.get()函数从数据库中查找键john。
将值保存到变量user中。
记录我们查找的用户的年龄。
在 catch 部分,将键john推送到missingKeys数组中。
- 创建第二个 try-catch 块。
在 try 部分中,使用 async/await 和db.get()函数查找数据库中的键sam
将值保存到变量user中。
记录查找到的用户的年龄。
在 catch 部分,将键sam推送到missingKeys数组中。
- 创建第三个 try-catch 块。
在 try 部分中,使用 async/await 和db.get()函数查找数据库中的您的名字的键。
将值保存到变量user中。
记录查找到的用户的年龄。
在 catch 部分,将键推送到missingKeys数组中。
在 catch 部分,使用 await 和db.insert()将您的user对象插入db中。
在 catch 部分,在catch块内创建一个新的 try-catch 块。在新的 try 部分中,使用 async/await 查找刚刚添加到 db 中的用户。将找到的用户保存到variable user中。记录查找到的用户的年龄。在 catch 部分,将键推送到missingKeys数组中。
-
在所有的 try-catch 块之外,在主函数的末尾,返回
missingKeys数组。 -
调用
main函数,并为返回的承诺附加一个then()和catch()处理程序。 -
then()处理程序应传递一个记录承诺解析值的函数。 -
catch()处理程序应传递一个记录错误消息字段的函数。
代码:
index.js
const db = require( './simple_db' );
async function main() {
const missingKeys = [];
try { const user = await db.get( 'john' ); }
catch ( err ) { missingKeys.push( 'john' ); }
try { const user = await db.get( 'sam' ); }
catch ( err ) { missingKeys.push( 'sam' ); }
try { const user = await db.get( 'zach' ); }
catch ( err ) {
missingKeys.push( 'zach' );
await db.insert('zach', { first: 'zach', last: 'smith', age: 25 });
try { const user = await db.get( 'zach' ); }
catch ( err ) { missingKeys.push( 'zach' ); }
}
return missingKeys;
}
main().then( console.log ).catch( console.log );
片段 2.43:使用 async/await
结果:
图 2.12:显示的姓名和年龄
您已成功实现了文件跟踪命令并浏览了存储库的历史记录。
第三章:DOM 操作和事件处理
活动 3 - 实现 jQuery
您想要制作一个控制家中智能 LED 灯系统的 Web 应用程序。您有三个 LED 灯,可以单独打开或关闭,或者一起切换。您必须构建一个简单的 HTML 和 jQuery 界面,显示灯的开启状态。它还必须有控制灯的按钮。
要利用 jQuery 构建一个功能应用程序,请按照以下步骤进行:
-
为该活动创建一个目录,并在该目录中,在命令提示符中运行
npm run init以设置package.json。 -
运行
npm install jquery -s以安装 jQuery。 -
为该活动创建一个 HTML 文件,并给 HTML 块添加一个 body。
-
添加一个样式块。
-
添加一个 div 来容纳所有的按钮和灯。
-
添加一个带有
jQuery文件源的script标签。
<script src="./node_modules/jquery/dist/jquery.js"></script>
-
添加一个
script标签来保存主 JavaScript 代码。 -
在 CSS 样式表中为
light类添加以下设置:
宽度和高度:100px
背景颜色:白色
-
在 div 中添加一个切换按钮,使用
id=toggle。 -
添加一个 div 来容纳带有 id
lights的灯。 -
在这个 div 内添加三个 div。
注意
每个 div 应该有一个带有light类的 div 和一个带有lightButton类的按钮。
- 在代码脚本中,设置一个函数在 DOM 加载时运行:
$(() => { ... })
- 选择所有
lightButton类按钮,并添加一个点击处理程序,执行以下操作:
停止事件传播并选择元素目标,并通过遍历 DOM 获取light div。
检查lit属性。如果已点亮,则取消lit属性。否则,使用jQuery.attr()设置它
将background-color css样式更改为反映lit属性的jQuery.css()。
- 通过 ID 选择
toggle按钮,并添加一个点击处理程序,执行以下操作:
停止事件传播并通过 CSS 类选择所有的灯按钮,并在它们上触发点击事件:
代码:
activity.html
以下是精简的代码。完整的解决方案可以在activities/activity3/index.html中找到。
$( '.lightButton' ).on( 'click', e => {
e.stopPropagation();
const element = $( e.target ).prev();
if ( element.attr( 'lit' ) !== 'true' ) {
element.attr( 'lit', 'true' );
element.css( 'background-color', 'black' );
} else {
element.attr( 'lit', 'false' );
element.css( 'background-color', 'white' );
}
} );
$( '#toggle' ).on( 'click', e => {
e.stopPropagation();
$( '.lightButton' ).trigger( 'click' );
} );
片段 3.32:jQuery 函数应用
结果:
图 3.14:在每个 div 后添加按钮
图 3.15:添加切换按钮
您已成功利用 jQuery 构建了一个功能应用程序。
第四章:测试 JavaScript
Activity 4 – 利用测试环境
您的任务是将斐波那契数列测试代码升级为使用 Mocha 测试框架。将您为 Activity 1 创建的斐波那契数列代码和测试代码升级为使用 Mocha 测试框架来测试代码。您应该为 n=0 条件编写测试,然后实现 n=0 条件,然后为 n=1 条件编写测试并实现,然后为 n=2 条件编写测试并实现,最后为 n=5、n=7 和 n=9 条件做同样的操作。如果 it() 测试通过,调用 done 回调而不带参数。否则,使用错误或其他真值调用 test done 回调。
要利用 Mocha 测试框架编写和运行测试,请按照以下步骤进行:
-
使用
npm run init设置项目目录。使用npm install mocha -s -g全局安装 mocha。 -
创建
index.js来保存代码,test.js保存测试。 -
将测试脚本添加到
package.json的scripts字段中。测试应该调用mocha模块并传入test.js文件。 -
将递归斐波那契数列代码添加到
index.js。您可以使用Exercise 24中构建的代码。 -
导出函数与
module.exports = { fibonacci }. -
使用以下命令将斐波那契函数导入测试文件:
const { fibonacci } = require( './index.js' ); -
为测试编写一个
describe块。传入字符串fibonacci和一个回调函数 -
通过手工计算每个斐波那契数列中的预期值(您也可以在 Google 上查找该数列,以避免手工计算太多数学)。
-
对于每个测试条件(n=0、n=1、n=2、n=5、n=7 和 n=9),执行以下操作:
使用 it() 函数创建一个 mocha 测试,并将测试描述作为第一个参数传入。
将回调作为第二个参数。回调函数应该接受一个参数 done。
在回调函数中调用斐波那契数列,并使用不等于比较将其结果与预期值进行比较。
调用 done() 函数并传入测试比较结果。
如果测试失败,返回 done( error )。否则,返回 done(null) 或 done(false)。
- 使用
npm run test从命令行运行测试。
代码:
test.js
'use strict';
const { fibonacci } = require( './index.js' );
describe( 'fibonacci', () => {
it( 'n=0 should equal 0', ( done ) => {
done( fibonacci( 0 ) !== 0 );
} );
it( 'n=1 should equal 1', ( done ) => {
done( fibonacci( 1 ) !== 1 );
} );
it( 'n=2 should equal 1', ( done ) => {
done( fibonacci( 2 ) !== 1 );
} );
it( 'n=5 should equal 5', ( done ) => {
done( fibonacci( 5 ) !== 5 );
} );
it( 'n=7 should equal 13, ( done ) => {
done( fibonacci( 7 ) !== 13 );
} );
it( 'n=9 should equal 34, ( done ) => {
done( fibonacci( 9 ) !== 34 );
} );
} );
Snippet 4.9:利用测试环境
查看下面的输出截图:
图 4.8:显示斐波那契数列
结果
您已成功利用 Mocha 测试框架编写和运行测试。
第五章:函数式编程
Activity 1 – 递归不可变性
您正在使用 JavaScript 构建一个应用程序,并且您的团队被告知出于安全原因不能使用任何第三方库。现在,您必须为该应用程序使用函数式编程(FP)原则,并且您需要一个算法来创建不可变的对象和数组。创建一个递归函数,使用 Object.freeze() 强制对象和数组在所有嵌套级别上的不可变性。为简单起见,您可以假设对象中没有嵌套的 null 或类。在 activities/activity5/activity-test.js 中编写您的函数。该文件包含测试您的实现的代码。
为了演示在对象中强制不可变性,请按照以下步骤进行:
-
打开
activities/activity5/activity-test.js中的活动测试文件。 -
创建一个名为
immutable的函数,它接受一个参数data。 -
检查
data是否不是object类型。如果不是,则返回。 -
冻结
data对象。您不需要冻结非对象。 -
使用
object.values和forEach()循环遍历对象值。对每个值递归调用不可变函数。 -
运行测试文件中包含的代码。如果有任何测试失败,修复错误并重新运行测试。
代码:
activity-solution.js
function immutable( data ) {
if ( typeof data !== 'object' ) {
return;
}
Object.freeze( data );
Object.values( data ).forEach( immutable );
}
片段 5.11:递归不可变性
查看下面的输出截图:
图 5.7:通过测试的输出显示
结果:
您已成功地展示了在对象中强制使用不可变性。
第六章:JavaScript 生态系统
活动 6 - 使用 React 构建前端
从练习 35 中负责笔记应用的前端团队意外退出。您必须使用 React 构建此应用程序的前端。您的前端应该有两个视图:一个 Home 视图和一个Edit视图。为每个视图创建一个 React 组件。Home视图应该有一个按钮,可以将视图切换到 Edit 视图。Edit 视图应该有一个按钮,可以切换回 Home 视图,一个包含笔记文本的文本输入,一个调用 API 加载路由的加载按钮,以及一个调用 API 保存路由的保存按钮。已提供一个 Node.js 服务器。在activities/activity6/activity/src/index.js中编写您的 React 代码。当您准备测试您的代码时,在启动服务器之前运行构建脚本(在package.json中定义)。您可以参考练习 35 中的index.html文件,了解如何调用 API 路由的提示。
要构建一个可工作的 React 前端并将其与 Node.js express 服务器集成,按照以下步骤进行:
-
开始在"activity/activity6/activity"中工作。运行
npm install来安装所需的依赖项。 -
在 src/index.js 中,创建名为
Home和Editor的 React 组件。 -
向 App react 组件添加一个构造函数。在构造函数中:
接受props变量。调用super并将props传递给super。
在this作用域中设置state对象。它必须具有一个名为view且值为home的属性。
- 向应用程序添加一个
changeView方法。
changeView方法应该接受一个名为view的参数。
使用setState更新状态,并将state的view属性设置为提供的参数view。
在构造函数中,添加一行代码,将this.changeView设置为this.changeView.bind(this)。
- 在 App 的
render函数中,根据this.state.view的值创建一个条件渲染,执行以下操作:
如果state.view等于home,则显示 Home 视图。否则,显示编辑器视图。
将changeView函数作为参数传递给两个视图,即<Editor changeView={this.changeView}/>和<Home changeView={this.changeView}/>.
-
在
Home组件中,添加一个goEdit()函数,调用通过props传入的changeView函数(this.props.changeView),并将字符串editor传递给changeView函数。 -
在
Home组件中创建render函数:
返回一个 JSX 表达式。
在返回的 JSX 表达式中,添加一个包含应用程序Note Editor App标题的div,并添加一个按钮,将视图更改为Edit视图。
按钮应在单击时调用goEdit函数。确保将this状态正确绑定到goEdit函数。
- 向
Editor组件添加一个构造函数:
接受props参数
调用super并将props变量传递给它。
在this作用域中将state变量设置为{value: ‘’}对象。
- 在
Editor中添加一个名为handleChage的函数:
接受一个名为e的参数,表示事件对象。
使用setState更新状态,将状态属性value设置为事件目标的值:
this.setState( { value: e.target.value } );
- 在
Editor中创建一个名为save的函数,向 API 的 save 路由发出 HTTP 请求。
创建一个新的 XHR 请求,并将其保存到xhttp变量中。
将xhttp属性onreadystatechange设置为一个函数,检查this.readyState是否等于4。如果不是,则返回。还要检查this.status是否等于200。如果不是,则抛出错误。
通过在xhttp上调用open函数打开xhr请求。传入参数POST,/save和true。
通过在xhttp对象上调用setRequestHeader将请求头Content-Type设置为application/json;charset=UTF-8。传入指定的值。
通过xhttp.send发送文本输入的 JSON 数据。
必须发送的值存储在this.state中。在发送之前对值进行字符串化。
- 在
Editor中创建一个load函数,向 API 加载路由发出 HTTP 请求。
创建一个新的 XHR 请求并将其保存到xhttp变量中。
将this范围保存到名为that的变量中,以便在xhr请求内部使用。
将xhttp属性onreadystatechange设置为一个函数,检查this.readyState是否等于 4。如果不是,则返回。然后检查this.status是否等于 200。如果不是,则抛出错误。它在 React 组件的范围上调用setState函数,该函数保存在that变量中。
传入一个对象,其中键value等于请求的解析响应。使用JSON.parse函数从this.response变量中解析 HTTP 响应值。
通过在xhttp变量上调用open函数打开 HTTP 请求。传入参数GET、/load和true。
通过在xhttp对象上调用send()方法发送 XHR 请求。
- 在
Editor中创建一个goHome函数。
调用通过 React 元素属性对象传入的changeView函数(this.props.changeView())。
传入字符串“主页”。
- 在
Editor中创建render函数。
添加一个按钮,点击后调用包含文本“返回主页”的goHome函数。在点击事件上调用goHome函数。确保将this范围正确绑定到函数。
添加一个包含笔记文本的“文本”输入。文本输入从state.value字段加载其值。“文本”字段在更改事件上调用handleChange函数。确保将this范围正确绑定到handleChange函数。
添加一个按钮,从包含文本“加载”的服务器中加载笔记数据。在点击事件上调用load函数。确保将this范围正确绑定到 load 函数调用。
添加一个按钮,将包含文本“保存”的笔记数据保存到服务器。在点击事件上调用save函数。确保将this范围正确绑定到 save 函数调用。
确保将this状态正确绑定到所有监听器。
- 准备好后通过以下方式测试代码:
在根项目文件夹中运行npm run build以将代码从 JSX 转换。
运行npm start以启动服务器。
在服务器启动时加载指定的 URL(127.0.0.1:PORT)。
通过单击“编辑”和“返回主页”按钮测试视图更改。
通过在“文本”输入框中输入文本,保存并检查创建的文件夹中的文本文件,测试“保存”功能。
通过在文本文件中输入文本,加载它,并确保“文本”输入中的值与文本文件中的值匹配,测试“加载”功能。
以下提供了一个简化的片段。有关完整解决方案代码,请参阅activities/activity6/solution/src/index.js。
Index.js
class Editor extends React.Component {
constructor( props ) { ... }
handleChange( e ) { ... }
save() { ... }
load() { ... }
goHome() { ... }
render() {
return (
<div>
<button type="button" onClick={this.goHome.bind( this )}>Back to home</button>
<input type="text" name="Note Text" value={this.state.value} onChange={this.handleChange.bind( this )}/>
<button type="button" onClick={this.load.bind( this )}>Load</button>
<button type="button" onClick={this.save.bind( this )}>Save</button>
</div>
);
}
}
class Home extends React.Component {
goEdit() { ... }
render() {
return (
<div>
<h1>Note Editor App</h1>
<button type="button" onClick={this.goEdit.bind( this )}>Edit Note</button>
</div>
);
}
}
//{…}
片段 6.42:React 组件
结果:
查看这里的输出截图:
图 6.13:编辑视图
图 6.14:服务器视图
图 6.15:运行服务器以测试代码
您已成功构建了一个可工作的 React 前端,并将其与 Node.js express 服务器集成。