================================================================================
Qomolangma OpenProject v1.0
类别 :Rich Web Client
关键词 :JS OOP,JS Framwork, Rich Web Client,RIA,Web Component,
DOM,DTHML,CSS,JavaScript,JScript
项目发起:aimingoo (aim@263.net)
项目团队:..\..\Qomo_team.txt
有贡献者:JingYu(zjy@cnpack.org)
================================================================================
一、概述:Qomolangma中的框架库(v0.1)
~~~~~~~~~~~~~~~~~~
在UI层方面,Qomo一直没有足够的进展,因此Qomo在beta 1之前公布的代码看起来就象是一个语言实验
工程,而不象是一个面向应用的项目。
其实Qomo的前身(WEUI)本身就是围绕UserInterface Library来提出的,因此WEUI的确有自己的UI层。此
外,它也有完整的DB和Graphics层(及一个VML的实现)。但是Qomo对UI层提出的目标与WEUI并不一致,因
此这直接导致了“Qomo需要一个新的UI库”的结果。
Qomo在beta 2中包含部分UI、DB层的代码,但是并不推荐将它归为Qomo的一个组成部分并应用。——尽管
这些的确可以在Qomo下运行得很好,开发人员可以从中得到很多的思想与技术实现。——事实上Qomo也从
WEUI的UI和Graphics框架的基础框架上借鉴了一些东西。
但这些都不是这一组文章要讨论的内容。
因为在Qomo的beta 2之后,除了一些底层语言体系的修补之外,Qomo团队将开始有关框架库(并不是UI库)
的开发工作。这些工作包括:
- 公共框架类库: Framework\Classes.js, Framework\Common\*
- 日志、调试、分析和单元测试框架:Framework\Debug\*
- DOM、CSS兼容层框架:Components\Compat\*
二、Qomo的基础库与基础类库
~~~~~~~~~~~~~~~~~~
Qomo的基础库是一些工具函数,或者原生(native)的JavaScript类。它的地位与RTL中的JSEnhance.js
是相同的,但基础库并不只是针对JavaScript进行增强。
Qomo的基础类库建立自Qomo的OOP框架。也就是说,至少是继承自TObjecct的类。基础类库表现为一些工
具类、容器类、全局(单例)类和一些其它抽象层次较低的类。
Qomo的基础库与基础类库位于:
Framework\Common\*
它通过一个包文件载入:
Framework\Classes.js
三、基础库中的工具函数
~~~~~~~~~~~~~~~~~~
基础库中的工具函数(目前)包括在:
- 系统实用工具: Framework\Common\SysUtils.js
- 对象实用工具: Framework\Common\ObjUtils.js
- 类型或数据结构定义及转换工具: Framework\Common\ConvUtils.js
三个文件中。
其中,ObjUtils.js和ConvUtils.js虽然包括在Qomo Beta2中,但事实上没有正式发布。——很多代码
已经移除;部分代码未经测试也没有相关的说明。因此本文先不讲述它们。
SysUtils.js中的目前只发布了4个工具函数。事实上他们在我以前的工程中大量使用过。下面做一些
简单地介绍。
1. createUniqueID()
----------
该函数产生一个唯一的标识符。一般来说,他在当前的Web页(包括多帧)中都是不重复的。它基于一
个随机数产生的算法规则:即使是在同一时刻调用两次随机数,也会因种子的变化而产生不同的值。因
此拼接随机数和日期值通常会得到一个唯一的标识。——如果对随机数的算法的设定出了问题,则另论。
2. createUniqueVar()
----------
UniqueID可以作为标识,但无法作为全局变量使用。而本函数则用声明一个全局变量,并且返回该变
量名。这个声明可以被delete删除以回收内存。例如:
------
function myFunc() {
// 创建并得到变量名
var name = createUniqueVar();
// 使用该变量
eval('name').value = 'abcdefgh';
// 删除该变量
eval('delete ' + name);
}
------
3. isVariant(varName)
----------
传入一个变量名,该函数会返回该变量名是否是一个变量。既可以是全局变量,也可以是局部变量。
例如:
------
function myFunc() {
alert(isVariant('myFunc'));
}
------
4. defined(aVariant)
----------
查看一个变量或值是否被声明过。例如:
------
var all = [1,,3,4];
function myFunc() {
for (var i=0; i<all.length; i++) {
if (defined(all[i])) {
alert(all[i]);
}
}
}
------
四、基础库中的异常与断言
~~~~~~~~~~~~~~~~~~
1. JavaScript中的异常基础
----------
在JavaScript语言中,创建异常对象的方法是:
e = new Error([number[, description]]);
你可以在创建时或者创建后抛出异常:
------
// 创建时抛出
throw new Error(100, 'this is exception - 100.');
// 创建后抛出
var e = new Error(101, 'this is exceptio - 101.');
throw e;
------
按照JavaScript的语言规范,你可以在try中捕获到异常后再次抛出。例如:
------
try {
// ...
}
catch (e) {
throw e; // 再次抛出
}
------
2. Qomo基础库中的异常
----------
我们发现标准JS中的异常很难管理。例如异常的编号,或者显示信息时大多使用直接声明
的字符串。因此,Qomo约定一个异常可以用两个成员的数组表示。例如:
EAccessInvaildClass = [8109, 'Class invaild: lost typeinfo!'];
在第二个成员(字符串)中允许使用%s通配符。例如:
EAttributeCantRead = [8112, 'The "%s" attribute can\'t read for %s.'];
Qomo认为这是一个标准的异常记录/对象的结构。这种定义是非常取巧的:
- 如果不使用Qomo的异常框架,那么标准JS中将会把数组转换成字符串,这时得到的信息
是可以阅读的。
- 如果使用Qomo异常框架,那么由于Qomo替换了Error()类,因此将生成更友好的信息。
- 在JSEnhance.js中,Qomo再次替换了Error()类,这使得%s可以被处理,从而使动态的
组织异常信息成为非常便利的事。
在RTL/Error.js中,Qomo重写了Error()类。这使得Error()有以下构造形式:
e = new Error();
e = new Error(number);
e = new Error(number, description);
e = new Error(number, description, instanceObj);
e = new Error(a_qomo_exp);
e = new Error(a_qomo_exp, instanceObj);
其中,instanceObj 表明一个关联到该异常的对象实例。但Qomo并不处理instanceObj, 只是
通过异常对象来传递它。这样可以使try .. catch捕获到的异常对象有一个instanceObj属性,
指向触发异常时送过来的一个“参考实例”。
而a_qomo_exp表是一个按qomo的规则声明的异常数组(参阅前面的内容)。这样我们就可以用下
面的代码来简单的触发一个异常:
throw new Error(EAccessInvaildClass);
如果系统未装载过Error.js,那么显示的信息将是:
---------------------------
错误: 8109,Class invaild: lost typeinfo!
---------------------------
如果系统已经装载过Error.js,那么显示的信息将是:
---------------------------
错误: Class invaild: lost typeinfo!
---------------------------
或者我们也可以这样使用带通配符的异常:
throw new Error(EAttributeCantRead.concat('get', 'Enumerator'));
如果我们装载过JSEnhance.js,那么显示的信息将是:
---------------------------
错误: The "get" attribute can't read for Enumerator.
---------------------------
这样,无论如何,我们都能给用户“相对友好”的错误信息。
3. Qomo基础库中的断言
----------
Qomo中的断言实现得非常简单。它其实就是一个Qomo的异常。如下:
---------------------------
var
EAssertFail = [8001, 'assert is failed.\n\n%s'];
$assert = function (isTrue, info) {
if (!isTrue) throw new Error(EAssertFail.concat([info]));
}
---------------------------
由于Qomo有自己的异常实现,因此断言的显示将非常友好。
五、基础库中的性能分析工具(Profiler)
~~~~~~~~~~~~~~~~~~
Qomo在Debug.js单元中载入了一个Profiler工具,是分析代码性能的利器。它的一个
使用示例是:
Framework/Debug/TestCase/T_profiler.html
Qomo中的profiler使用起来非常方便,也可以使用多组的profiler。系统中单独初始
化了一个全局的$profilers,以方便使用。
1. 核心结构
----------
如果不考虑输出的效果,那么直接调用Qomo的profiler就可以得到它的核心结构和
使用流程了:
---------------------------
<!-- 载入Profiler类 -->
<script src='Framework/Debug/Profilers.js'></script>
<script>
// 在用户代码中插入分析语句, 然后执行
function myFunc() {
$profilers('myFunc').begin()
// your code ...
$profilers('myFunc').end()
}
myFunc();
// 输出分析结果
document.writeln($profilers);
</script>
---------------------------
但是这样输入的东西根本没有办法看。因此,Qomo提供一组工具来辅助使用profiler。
当然,你也可以定制它。——这个后面再讲。
2. 基础Dbg.Utils的简单使用
----------
使用Dbg.Utils.js是非常便捷的做法:
---------------------------
<!-- 载入Profiler类和调试用工具单元 -->
<script src='Framework/Debug/Profilers.js'></script>
<script src='Framework/Debug/Dbg.Utils.js'></script>
<script>
// 在用户代码中插入分析语句, 然后执行
// (同上, 略... )
// 重写$debug()函数
$debug.resetTo(function() {
arguments.join = Array.prototype.join;
document.writeln(arguments.join(''));
});
// 显示profiler信息
showProfiler($profilers);
</script>
---------------------------
注意这里有两处关键的细节。一是要求载入Dbg.Utils.js,至于它位于Profilers.js之前或者
之后并没有关系。
3. 为分析对象指定精确的标签
----------
在开始的示例里,我们用一对
---------------------------
$profilers('myFunc').begin()
$profilers('myFunc').end()
---------------------------
来开始和结束分析。这里的myFunc可以是任意字符,也可以是任意多的参数。这样,你可以
指定:
---------------------------
$profilers('myFunc', 'build').begin();
$profilers('myFunc', 'build').end();
$profilers('myFunc', 'execute').begin();
$profilers('myFunc', 'execute').end();
---------------------------
这样对一个函数的多组分析。也可以指定:
---------------------------
$profilers('myFunc', '1').begin();
$profilers('myFunc', '1').end();
$profilers('myFunc', '2').begin();
$profilers('myFunc', '2').end();
---------------------------
这样来表明步骤。
4. 处理递归
----------
接下来,对于递归的函数,你可以采取两种方法来处理它。其一是加标签,例如:
---------------------------
function calc(n) {
$profilers('calc', n).begin();
var v = n;
if (v > 0) {
v = n + calc(n - 1);
}
$profilers('calc', n).end();
return v;
}
calc(10);
---------------------------
第二种方法,则是借用profiler返回标志,例如:
---------------------------
function calc(n) {
var tag = $profilers('calc').begin();
var v = n;
if (v > 0) {
v = n + calc(n - 1);
}
$profilers('calc').end(tag);
return v;
}
calc(10);
---------------------------
这两种方法中,第一种分另产生不同的profiler记录项,因此采用标准方法处理即可;第二种
则产生同一记录项的多个记录值,对于这种情况,在Dbg.Utils.js中的showProfiler()展示了
如何处理。
5. 显示结果: $debug()的重写
----------
$debug()最初被声明在system.js中,用于直接向document输出调试信息。但是这样必然会
破坏网页的显示效果。——尤其profiler的信息量非常大。
因此,在Dbg.Utils.js中,我们重写了它。使它的输出被放到缓存中:
---------------------------
$debug = function() {
// ...
arguments.callee['$cached$'] += arguments.join('');
}
---------------------------
其中,arguments.callee直接向$debug()函数本身。使用callee而不是$debug自身,是避免
$debug()被再次重写。
我们还为$debug添加了一个方法resetTo()。这个方法传入一个函数,新函数要能够输出这些
被缓存的信息。——它在被resetTo()时将被自动调用一次。然后该新函数将替代原$debug()
在系统中的作用。
例如我们在面第二节中讲到的:
---------------------------
// 重写$debug()函数
$debug.resetTo(function() {
arguments.join = Array.prototype.join;
document.writeln(arguments.join(''));
});
showProfiler($profilers);
---------------------------
这个重写就是用于向document输出,它也被用在下面这个示例里:
Framework/Debug/TestCase/T_profiler.html
另外一处使用,则是在:
Components/QomoHierarchyPoster.html
它的写法是将信息显示到一个HTML元素中:
---------------------------
$debug.resetTo(function() {
arguments.join = Array.prototype.join;
document.getElementById('Qo_DBGINFO').insertAdjacentHTML(
'beforeEnd', arguments.join(''));
});
---------------------------
6. 显示结果: showProfiler()及其定制
----------
事实上,Dbg.Utils.js作为一个实用工具单元,可以完全不用载入到当前页面。这种情况下,
Profilers的功能依然是可用的。包括使用:
---------------------------
$profilers('your_tag').begin();
$profilers('your_tag').end();
---------------------------
这样的方法来记录profiler数据。因为$profilers这个全局对象是被创建在Profiles.js中的。
如果你不打算使用Dbg.Utils.js中的showProfiers()来显示结果。那么你可以自己写一个,例
如打开一个新窗口来显示。这时你只需要参考一下showProfiers()的代码即可:
---------------------------
function showProfiers(prof) {
var data = prof.toData();
// ...
}
---------------------------
这样从prof中得到的数据是如下的一种结构:
---------------------------
a_data_instance = {
'your_tag_1': [beginTime1, endTime1, beginTime2, endTime2, ...],
'your_tag_2': [...],
'your_tag_3': [...]
// ...
}
---------------------------
你只需要写代码去循环处理即可。——注意beginTime/endTime的值是Number类型的。如果你
要转换为Date()对象,那么可以“new Date(beginTime)”这样即可。
7. 在系统中处理多个Profilers()对象
----------
只要你愿意,你可以创建多个Profilers()对象,他们之间是没有干扰的。——当然,你也
可以只创建一个,并用不同的标签来分组显示它们。这一切只取决于你的选择。
如果你打算创建多个Profilers,那么大概的代码如下:
---------------------------
var prof1 = new Profilers();
var prof2 = new Profilers();
// prof1('tag').begin() ...
// ...
showProfiler(prof1);
showProfiler(prof2);
---------------------------
8. 清理profilers数据
----------
如果你要开始新一批的profilers分析。那么你应该清除一下原有的数据。但到目前为止,
Profilers()对象并没有提供clear()方法。——我认为不必须。
因此如果你需要清理数据,最好的方法就是重新创建一个。例如:
---------------------------
// 重新创建全局的分析器
$profilers = new Profilers();
---------------------------
六、Profilers与AOP的结合使用
~~~~~~~~~~~~~~~~~~
大概要为每一个函数去写.begin()和.end()会是一件让人痛苦的事,而且频繁地改动原先的
函数,也不是是一件什么好事。因此事实上在Profilers的使用示例中,我采用的都是AOP来
实现。在
Components/QomoHierarchyPoster.html
Framework/Debug/TestCase/T_profiler.html
这两个文件中,都可以看到AOP的用法。
其中,T_profiler.html载入了一个AOP_MyProf.js文件,这里的示例最为简单:
---------------------------
// 加入profiler相关的代码($profilers是全局对象)
var asp_import = new FunctionAspect($import, '$import', 'Function');
asp_import.OnBefore.add(function(o, n, p, a) {
with ($profilers(n, FN(a[0]))) {
set('url', a[0]);
a['$tag$'] = begin();
}
});
asp_import.OnAfter.add(function(o, n, p, a, v) {
$profilers(n, FN(a[0])).end(a['$tag$']);
});
---------------------------
我们看到asp_import就是一个为"$import()"创建的一个切面,这个切面的名字,就是"$import"。
所以我们在OnBefore和OnAfter中看到的参数n,值就是"$import"。
接下来,我们在OnBefore的事件处理函数中,添加了对每一次$import()调用的profiler分析。这
里传入的参数a,就是调用$import()时的arguments。所以a[0]就是$import()的文件名。——因为,
我们总是用“$import('a_js_url')”来导入文件的。
调用$profilers()时传入了两个标签。其中n总是"$import",而FN(a[0])则是取到URL未尾的文件名。
因此,相当于创建了一个名为"$import/a_js_filename"形式的标识。这在后面用showProfilers()
时就可以看到了。
接下来,由于我们还需要在showProfilers()时能显示一点数据,例如载入的实例的URL的完整径,
因此我们调用了set()方法。这段代码实际相当于:
---------------------------
asp_import.OnBefore.add(function(o, n, p, a) {
var prof = $profilers(n, FN(a[0]));
prof.set('url', a[0]);
a['$tag$'] = prof.begin();
});
---------------------------
prof.set('url', ...)这行代码可以为一个prof添加任意多的、任何名称的定制数据。这些数据
可以提供给showProfiler()来使用。——当然,你自己也可以写一个showProfiler()来处理这些
数据。
最后一行是为了处理在Qomo中将同一个.js文件导入多次。——"$import/a_js_filename"会重复,
从而看起来象是处理同一段代码(像对递归过程profiler)。——因此这里把prof.begin()返回的
标识放在a['$tag$']里。
这里用了一个取巧的方法:AOP事件中的参数a是调用被关注点时的参数arguments,因此对同一个
关注对象来说,OnBefore与OnAfter所使用的arguments也是同一个。所以当切面到达OnAfter时,
我们只需要处理成
---------------------------
$profilers(n, FN(a[0])).end(a['$tag$']);
---------------------------
就可以了。——a['$tag']就是我们需要使用的begin()返回值。而且,我们也不需要去delete这
个属性。因为当函数运行结束后,arguments将自动被javascript引擎销毁。
同样的技术也被用在
Components/QomoHierarchyPoster.html
这个文件中。不过QomoHierarchyPoster.html在profilers中值得言讲的,却在于它对combine()
的活用。
在QomoHierarchyPoster.html中,我们处理了五个切面,并试图对它们做性能分析:
---------------------------
var asp_getTopoString = new FunctionAspect(getTopoString, 'getTopoString', 'Function');
var asp_LinesString = new FunctionAspect(getLinesString, 'getLinesString', 'Function');
var asp_drawTopo = new FunctionAspect(drawTopo, 'drawTopo', 'Function');
var asp_active = new FunctionAspect(activeTopoInCanvas, 'activeTopoInCanvas', 'Function');
var asp_cache = new FunctionAspect(cacheTargetByNodes, 'cacheTargetByNodes', 'Function');
---------------------------
而这时,我们只为一个切面的OnBefore()和OnAfter()添加了事件处理句柄:
---------------------------
asp_getTopoString.OnBefore.add(function(o, n, p, a) {
a["$tag$"] = $profilers('$prof', p, n).begin();
});
asp_getTopoString.OnAfter.add(function(o, n, p, a, v) {
$profilers('$prof', p, n).end(a["$tag$"]);
});
---------------------------
为了让其它几个切面也得到相同的处理能力,我们使用了Qomo AOP中的“联合(combine)”,这
非常简单,一行代码就可以了:
---------------------------
asp_getTopoString.combine(asp_LinesString, asp_drawTopo, asp_active, asp_cache);
---------------------------
于是,上面的五个切面所处理的被观察者(函数)执行时,都会触发asp_getTopoString的OnBefore
和OnAfter事件了。
我相信Qomo AOP的价值远非如此。接下来的应用,看大家的吧。^.^
七、其它
~~~~~~~~~~~~~~~~~~
1. 可以独立于Qomo框架的内核模块
----------
在Qomo内核中,Profilers.js、Dbg.Utils.js与JSEnhance.js一样,都不需要加载Qomo的
框架,因此可以直接在其它的、第三方的代码框架中使用。这样的内核模块还包括:
------------
// 包括Profilers, Dbg.Utils和RepImport等
$import('Debug/Debug.js');
// 替换错误处理
$import('RTL/Error.js');
// 接口
$import('RTL/Interface.js');
// 协议(目前只包括URL分析)
$import('RTL/Protocol.js');
// 兼容层
$import('Compat/CompatLayer.js');
// 命名空间(依赖于system.js与Protocol.js)
$import('Names/NamedSystem.js');
// 脚本功能增强
$import('RTL/JSEnhance.js');
------------
由于Profilers被设计成可以针对整个Qomo做性能分析(包括最初的$import(),以及后续加载
的各个组件与类),因此它必须在最先载入。同样的原因,整个的Debug.js是第一个由system.js
载入的单元。
2. 框架库之其它内容
----------
在Qomo beta2的基础库中,还包括三个重要的成员:
- 队列/池,以及处理器类
- 时间序列、时间线与数据发生器
- ajax原型:HttpMachine()
这些内容在随后我将专门撰文讲解。
分享到:
相关推荐
这篇PPT课件是针对英语教学,特别是针对人教版八年级下册Unit A的内容,主要探讨了形容词比较级和最高级的用法,以及关于珠穆朗玛峰(Qomolangma)的一些地理和历史知识。以下是相关知识点的详细说明: 1. 形容词...
【教学计划概述】 这篇文档是关于初中英语教学方案的一个详细示例,主要针对《What’s the highest mountain in the world?》这一课题,适用于初中教师资格认定考试中的教育教学能力测试。教学对象为八年级学生,由...
同时,教师应利用数据分析工具,跟踪和评估学生的学习进度,以便调整教学策略,确保每个学生都能在深度阅读中受益。 总结来说,深度学习为初中英语阅读教学提供了新的视角和方法,它强调理解、探索和迁移,旨在培养...
在JavaScript框架和库方面,例如提到的Qomolangma OpenProject,它们通常提供了一套工具和API,简化了JavaScript开发,提高了代码的可维护性和复用性。这些框架常包括组件系统、事件处理、动画等功能,帮助开发者...
【知识点】 1. 英语词汇: - Asia:亚洲,日本位于亚洲东部。 - include:包含,小说包括五个部分。...通过这样的练习,学生可以提升语言运用能力,了解世界地理与历史,同时培养解决问题和分析文本的能力。
这份文档是针对外研社小学英语四年级下册的一个知识点总结复习资料,涵盖了多个模块的主要短语,旨在帮助学生巩固和复习所学的词汇和表达。以下是对这些模块中的主要知识点的详细解释: **Module 1** - `my friends...
- `get along with`:与...相处,进展。 - `gossip`:闲话,流言蜚语。 - `fall in love`:相爱,坠入爱河。 - `exactly`:恰好,正是。 - `disagree`:不同意,有分歧。 - `grateful`:感激的,感恩的。 - `...
【Lesson 70 教学设计方案二】是一个针对英语课程的教学教案,主要目的是教授与“Lesson 70”相关的对话内容、新词汇以及复习过去完成时态。此教案适合使用计算机、投影仪和PPT文档等教学工具进行。 **教学目标:**...
综上所述,这个单元的教学内容不仅涵盖了地理知识,也强调了与地理相关的英语表达,旨在提升学生的英语交流能力和对地理知识的理解。同时,它也提醒教师在教授时需注重语言的实际应用,如提供旅游指南或进行比较时的...
5. 交通工具:在表示乘坐交通工具时,如"by bus"(乘公交车)和 "by bike"(骑自行车),通常不使用冠词。 6. 物质名词和抽象名词:没有特指的物质名词和抽象名词如"wood"(木头)和"time"(时间),也不使用冠词。...
为了避免兼容性问题,一些库如Qomolangma会通过void操作符来设置undefined的值,void 0的表达式结果就是undefined。 JavaScript的面向对象特性主要体现在以下几个方面: 1. **构造函数与原型**:函数在JavaScript...
2. **the Changjiang River/Yellow River/Mount Qomolangma/Mount Tai/the Great Wall/the Summer Palace**: 中国的自然与人文景观,分别是长江、黄河、珠穆朗玛峰、泰山、长城和颐和园。 3. **Chinese music**: ...
例如Betsy的故事,她从小爱读书,后来成为了一名作家,与丈夫Giulio合作创作儿童书籍。这类文章旨在帮助学生理解人物性格、动机和情感变化。 2. **环境保护**:环境保护是当今世界的重要议题,文中提到珠穆朗玛峰...
3. **比较级与最高级**:掌握"any other"的用法,它用于比较级中,表示“比其他任何一座山都高”,例如“Qomolangma is higher than any other mountain in the world.”,等同于最高级。 4. **人口表达**:学习...
- 学习并理解新词汇和短语,如文中提到的mountain Qomolangma,及其相关登山术语和困难描述。 - 提高通过阅读获取新知识的意识,了解世界。 4. **独学准备**: - 首次阅读,标出生词。 - 查阅工具书理解生词...
例如,听对话回答问题,如山脉名称(Qomolangma)、动物习性(熊猫最喜欢的食物是竹子)以及人与动物的关系(大象因森林砍伐而处于危险之中)等。 2. **语法与词汇**:在单项选择题中,涉及到冠词的用法(如"a" 和 ...
【火线100天广西专版2016中考英语总复习第一部分第十三课时八下Units7_8试题】 本课时的主题聚焦在自然环境和相关词汇上,涵盖了自然、地理、生物和人类活动等多个方面。高频单词和词组如下: 1. **square** (n.) ...
- `B: Qomolangma.` 5. **学习过程**: - 自学单词和预习课文,填写首字母填空,如 `square kilometers`,`deepest`,`population` 和 `protect China`。 - 合作校对答案,分组学习新单词和短语,进行听力练习和...
5. 文章排序:在最后一段,我们看到一个关于大峡谷(Grand Canyon)的短文,需要根据逻辑顺序重新排列句子。正确的顺序是D(到达时看不到任何东西)→ E(在到达一些岩石之前,我问了一个陌生人路)→ C(我意识到我...