diff --git a/.gitignore b/.gitignore index c7ee9658..96aaa3d0 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,5 @@ .DS_Store /build /captures -.externalNativeBuild \ No newline at end of file +.externalNativeBuild +.apk \ No newline at end of file diff --git a/app-release-1.17.0312内测.apk b/app-release-1.17.0312内测.apk deleted file mode 100644 index 1f0b454c..00000000 Binary files a/app-release-1.17.0312内测.apk and /dev/null differ diff --git a/app/build.gradle b/app/build.gradle index 745ae2b8..7cefafed 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -7,8 +7,8 @@ android { applicationId "com.stardust.scriptdroid" minSdkVersion 19 targetSdkVersion 23 - versionCode 40 - versionName "1.17.0312内测" + versionCode 41 + versionName "1.17.0314内测" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" multiDexEnabled true @@ -46,7 +46,6 @@ repositories { } dependencies { - compile 'com.android.support.test.uiautomator:uiautomator-v18:2.1.0' compile fileTree(include: ['*.jar', '*.so'], dir: 'libs') testCompile 'junit:junit:4.12' debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5' @@ -62,12 +61,12 @@ dependencies { compile 'com.jraska:console:0.4.3' compile 'com.jraska:console-timber-tree:0.4.3' compile 'com.heinrichreimersoftware:android-issue-reporter:eae4c73cbeb91e98da83fe79a3dea088a8b97448' - compile 'com.bezyapps.floatieslibrary:floaties:1.0.1' compile 'de.codecrafters.tableview:tableview:2.5.0' compile group: "pl.openrnd.android", name: "multi-level-listview", version: "1.0.1" compile 'de.psdev.licensesdialog:licensesdialog:1.8.1' compile 'io.mattcarroll.hover:hover:0.9.7' compile 'org.greenrobot:eventbus:3.0.0' + compile 'com.bignerdranch.android:expandablerecyclerview:3.0.0-RC1' compile(name: 'libtermexec-release', ext: 'aar') compile(name: 'emulatorview-release', ext: 'aar') diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 04be0c07..c7c5b804 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -21,11 +21,11 @@ android:name=".ui.main.MainActivity" android:label="@string/_app_name" android:launchMode="singleTask" - android:theme="@style/AppTheme.NoActionBar"> - - + android:theme="@style/AppTheme.FullScreen"> + + @@ -71,12 +71,13 @@ + + - diff --git a/app/src/main/assets/help/catalogue.json b/app/src/main/assets/help/catalogue.json index e04b0abf..bbe379db 100644 --- a/app/src/main/assets/help/catalogue.json +++ b/app/src/main/assets/help/catalogue.json @@ -1,13 +1,260 @@ { - "main" : [ - {"title":"语法"}, - {"title":"JavaScript教程"}, - {"title":"自动操作函数"}, - {"title":"一般常用函数"}, - {"title":"控制台"}, - {"title":"UI(用户界面)"}, - {"title":"驻留模式与无障碍服务委托"}, - {"title":"shell命令"}, - {"title":"需要Root权限的自动操作函数"} + "main": [ + { + "title": "简介与说明", + "type": "markdown", + "path":"documentation" + }, + { + "title": "EMCAScript教程", + "type": "catalogue" + }, + { + "title": "常见问题", + "type": "markdown", + "path":"documentation" + }, + { + "title": "自动操作函数", + "type": "markdown", + "path":"documentation" + }, + { + "title": "一般常用函数", + "type": "markdown", + "path":"documentation" + }, + { + "title": "控制台与输出", + "type": "markdown", + "path":"documentation" + }, + { + "title": "UI(用户界面)", + "type": "markdown", + "path":"documentation" + }, + { + "title": "选择器", + "type": "markdown", + "path":"documentation" + }, + { + "title": "驻留模式与无障碍服务委托", + "type": "markdown", + "path":"documentation" + }, + { + "title": "shell命令", + "type": "markdown", + "path":"documentation" + }, + { + "title": "需要Root权限的自动操作函数", + "type": "markdown", + "path":"documentation" + } + ], + "EMCAScript教程": [ + { + "title": "基础", + "type": "catalogue" + }, + { + "title": "运算符", + "type": "catalogue" + }, + { + "title": "语句", + "type": "catalogue" + }, + { + "title": "函数", + "type": "catalogue" + }, + { + "title": "对象", + "type": "catalogue" + } + ], + "基础": [ + { + "title": "语法", + "type":"html", + "path":"w3c" + }, + { + "title": "变量", + "type":"html", + "path":"w3c" + }, + { + "title": "关键字", + "type":"html", + "path":"w3c" + }, + { + "title": "保留字", + "type":"html", + "path":"w3c" + }, + { + "title": "值", + "type":"html", + "path":"w3c" + }, + { + "title": "原始类型", + "type":"html", + "path":"w3c" + }, + { + "title": "类型转换", + "type":"html", + "path":"w3c" + }, + { + "title": "引用类型", + "type":"html", + "path":"w3c" + } + ], + "运算符": [ + { + "title": "一元运算符", + "type":"html", + "path":"w3c" + }, + { + "title": "位运算符", + "type":"html", + "path":"w3c" + }, + { + "title": "逻辑运算符", + "type":"html", + "path":"w3c" + }, + { + "title": "乘性运算符", + "type":"html", + "path":"w3c" + }, + { + "title": "加性运算符", + "type":"html", + "path":"w3c" + }, + { + "title": "关系运算符", + "type":"html", + "path":"w3c" + }, + { + "title": "等性运算符", + "type":"html", + "path":"w3c" + }, + { + "title": "条件运算符", + "type":"html", + "path":"w3c" + }, + { + "title": "赋值运算符", + "type":"html", + "path":"w3c" + }, + { + "title": "逗号运算符", + "type":"html", + "path":"w3c" + } + ], + "语句": [ + { + "title": "if 语句", + "type":"html", + "path":"w3c" + }, + { + "title": "迭代语句", + "type":"html", + "path":"w3c" + }, + { + "title": "标签语句", + "type":"html", + "path":"w3c" + }, + { + "title": "break 和 continue 语句", + "type":"html", + "path":"w3c" + }, + { + "title": "with 语句", + "type":"html", + "path":"w3c" + }, + { + "title": "switch 语句", + "type":"html", + "path":"w3c" + } + ], + "函数": [ + { + "title": "函数概述", + "type":"html", + "path":"w3c" + }, + { + "title": "arguments 对象", + "type":"html", + "path":"w3c" + }, + { + "title": "Function 对象(类)", + "type":"html", + "path":"w3c" + }, + { + "title": "闭包(closure)", + "type":"html", + "path":"w3c" + } + ], + "对象": [ + { + "title": "面向对象技术", + "type":"html", + "path":"w3c" + }, + { + "title": "对象应用", + "type":"html", + "path":"w3c" + }, + { + "title": "对象类型", + "type":"html", + "path":"w3c" + }, + { + "title": "对象作用域", + "type":"html", + "path":"w3c" + }, + { + "title": "定义类或对象", + "type":"html", + "path":"w3c" + }, + { + "title": "修改对象", + "type":"html", + "path":"w3c" + } ] } \ No newline at end of file diff --git a/app/src/main/assets/help/documentation/shell命令.md b/app/src/main/assets/help/documentation/shell命令.md index 21d749e2..1e4de15b 100644 --- a/app/src/main/assets/help/documentation/shell命令.md +++ b/app/src/main/assets/help/documentation/shell命令.md @@ -3,15 +3,14 @@ shell命令是通过shell函数运行的命令。等同于"adb shell"。shell函 * cmd \ 要执行的命令 * root \ 是否以root权限运行,默认为false。 返回运行一个执行结果。该返回值一般不会用到,其属性如下: - * code \ 错误码。执行成功时为1,失败时为0。 + * code \ 返回码。执行成功时为1,失败时为非1的数字,一般为0。 * result \ 运行结果。 * error \ 运行的错误信息。例如执行需要root权限的命令但没有授予root权限会返回错误信息"Permission denied"。 -例如``。(强制停止微信) -示例: +示例(强制停止微信) : ``` var result = shell("am force-stop com.tencent.mm", true); -if(result.succeed){ +if(result.code == 1){ toast("执行成功"); }else{ toast("执行失败!请到控制台查看错误信息"); @@ -20,16 +19,16 @@ if(result.succeed){ } ``` -以下关于shell命令的资料来自[AndroidStudio用户指南: 发出shell命令](https://developer.android.com/studio/command-line/adb.html#shellcommands)。 +以下关于shell命令的资料来自[AndroidStudio用户指南:Sehll命令](https://developer.android.com/studio/command-line/adb.html#shellcommands)。 目录: -* [am命令](#am) -* [intent 参数的规范](#IntentSpec) -* [应用包名](#PackageName) -* [pm命令](#pm) -* [其他命令](#others) +* [am命令](#am命令) +* [intent参数的规范](#intent参数的规范) +* [应用包名](#应用包名) +* [pm命令](#pm命令) +* [其他命令](#其他命令) -

am命令

+## am命令 am命令即Activity Manager命令,用于管理应用程序活动、服务等。 @@ -37,7 +36,7 @@ am命令即Activity Manager命令,用于管理应用程序活动、服务等 ### start [options] intent 启动 intent 指定的 Activity(应用程序活动)。 -请参阅 [intent 参数的规范](#IntentSpec)。 +请参阅 [intent 参数的规范](#intent参数的规范)。 选项包括: @@ -52,24 +51,26 @@ am命令即Activity Manager命令,用于管理应用程序活动、服务等 ### startservice [options] intent 启动 intent 指定的 Service(服务)。 -请参阅 [intent 参数的规范](#IntentSpec)。 +请参阅 [intent 参数的规范](#intent参数的规范)。 选项包括: * --user user_id | current:指定要作为哪个用户运行;如果未指定,则作为当前用户运行。 ### force-stop package -强行停止与 package([应用包名](#PackageName))关联的所有应用。 +强行停止与 package([应用包名](#应用包名))关联的所有应用。 ### kill [options] package -终止与 package([应用包名](#PackageName))关联的所有进程。此命令仅终止可安全终止且不会影响用户体验的进程。 +终止与 package([应用包名](#应用包名))关联的所有进程。此命令仅终止可安全终止且不会影响用户体验的进程。 选项包括: * --user user_id | all | current:指定将终止其进程的用户;如果未指定,则终止所有用户的进程。 -* kill-all 终止所有后台进程。 + +### kill-all +终止所有后台进程。 ### broadcast [options] intent 发出广播 intent。 -请参阅 [intent 参数的规范](#IntentSpec)。 +请参阅 [intent 参数的规范](#intent参数的规范)。 选项包括: @@ -125,13 +126,13 @@ shell("am display-density 480", true); ``` ### to-uri intent 将给定的 intent 规范以 URI 的形式输出。 -请参阅 [intent 参数的规范](#IntentSpec)。 +请参阅 [intent 参数的规范](#intent参数的规范)。 ### to-intent-uri intent 将给定的 intent 规范以 intent:URI 的形式输出。 请参阅 intent 参数的规范。 -

intent 参数的规范

+### intent参数的规范 对于采用 intent 参数的 am 命令,您可以使用以下选项指定 intent: * -a action @@ -217,12 +218,12 @@ shell("am display-density 480", true); #### URI component package 如果不受上述某一选项的限制,您可以直接指定 URI、软件包名称和组件名称。当参数不受限制时,如果参数包含一个“:”(冒号),则此工具假定参数是一个 URI;如果参数包含一个“/”(正斜杠),则此工具假定参数是一个组件名称;否则,此工具假定参数是一个软件包名称。 -

应用包名

+## 应用包名 所谓应用包名,是唯一确定应用的标识。例如微信的包名是"com.tencent.mm", QQ的包名是"com.tencent.mobileqq"。 要获取一个应用的包名,可以通过函数`getPackageName(appName)`获取。参见帮助->其他一般函数。 -

pm命令

+## pm命令 pm命令用于管理应用程序,例如卸载应用、冻结应用等。 **以下命令均以"pm "开头,例如"shell(\"pm disable com.tencent.mm\");"(冻结微信)** @@ -335,7 +336,7 @@ pm命令用于管理应用程序,例如卸载应用、冻结应用等。 ### get-max-users 输出设备支持的最大用户数。 -

其他命令

+## 其他命令 ### 进行屏幕截图 screencap 命令是一个用于对设备显示屏进行屏幕截图的 shell 实用程序。在 shell 中,此语法为: diff --git a/app/src/main/assets/help/documentation/一般常用函数.md b/app/src/main/assets/help/documentation/一般常用函数.md new file mode 100644 index 00000000..c8502969 --- /dev/null +++ b/app/src/main/assets/help/documentation/一般常用函数.md @@ -0,0 +1,46 @@ +### sleep(n) +* n \ 毫秒数 + +暂停运行n**毫秒**的时间。1秒等于1000毫秒。 + +### launchPackage(packageName) +* packageName \ 应用包名。参见《Shell命令:应用包名》章节。 + +运行包名为packageName的应用主界面(Launcher)。例如,打开微信为: +``` +launchPackage("com.tencent.mm"); +``` + +### launchApp(appName) +* appName \ 应用名称 + +运行应用名称为appName的应用主界面。当有应用名称相同时只运行其中某一个。 +例如,打开微信为: +``` +launchApp("微信"); +``` + +### getPackageName() +返回最近一次监测到的正在运行的应用的包名,一般可以认为就是当前正在运行的应用的包名。 + +### getActivityName() +返回最近一次监测到的正在运行的Activity的名称,一般可以认为就是当前正在运行的Activity的名称。 + +### setClip(text) +* text \ 文本 + +设置剪贴板内容。此剪贴板即系统剪贴板,在一般应用的输入框中"粘贴"既可使用。 + +### isStopped() +当脚本处于应当停止运行的状态时返回true, 否则返回false。 +由于脚本引擎的关系,有时即使强制停止正在运行的脚本也不会生效,此时会出现isStopped()为true但脚本仍在运行的情况。 + +### notStopped() +当脚本处于应当停止运行的状态时返回false, 否则返回false。 +为了避免脚本处于循环中无法正常结束,在诸如while(true)的循环中建议使用while(notStopped())代替,例如: +``` +while(notStopped()){ + //do something +} +``` + diff --git a/app/src/main/assets/help/documentation/常见问题.md b/app/src/main/assets/help/documentation/常见问题.md new file mode 100644 index 00000000..fcb6d8cf --- /dev/null +++ b/app/src/main/assets/help/documentation/常见问题.md @@ -0,0 +1,28 @@ +目录 +* [没有root权限是否支持游戏](#没有root权限是否支持游戏) +* [如何重复做某些操作n次](#如何重复做某些操作n次) +* [某些有文字的按键点击无效怎么办](#某些有文字的按键点击无效怎么办) +* [如何点击图标按钮](#如何点击图标按钮) + +### 没有root权限是否支持游戏 +100%的游戏在没有root权限的情况下都不支持。 +**游戏需要root权限!!!** +**游戏需要root权限!!!** +**游戏需要root权限!!!** + +### 如何重复做某些操作n次 +使用循环语句实现实现。例如每个1秒执行一个操作,总共执行20次的代码如下: +``` +for(var i = 1; i <= 20; i++){ + //这里写要执行的操作 + sleep(1000); +} +``` + +### 某些有文字的按键点击无效怎么办 +例如QQ下方的"联系人"按钮,用click("联系人")是无效的。这是因为这些按钮上的文字并不是真正的文字,而是一个图标。参见[如何点击图标按钮](#如何点击图标按钮)。 + +### 如何点击图标按钮 +有以下几种方法: +* 录制脚本 +* 通过图标在屏幕上的位置来点击。要获取图标在屏幕上的位置,可以开启悬浮窗,选择布局范围查看,之后点击要点击的图标,在弹出的窗口中的"bounds"一栏即图标在屏幕上的区域。例如: diff --git a/app/src/main/assets/help/documentation/控制台.md b/app/src/main/assets/help/documentation/控制台与输出.md similarity index 56% rename from app/src/main/assets/help/documentation/控制台.md rename to app/src/main/assets/help/documentation/控制台与输出.md index 133f8676..70597e8c 100644 --- a/app/src/main/assets/help/documentation/控制台.md +++ b/app/src/main/assets/help/documentation/控制台与输出.md @@ -1,4 +1,5 @@ -控制台通常用来输出一些调试信息和运算结果。 +控制台通常用来输出一些调试信息和运算结果。 +而Toast是安卓上一种显示信息的机制,即使应用在后台运行也能显示。 ### openConsole() 显示控制台。 @@ -10,7 +11,7 @@ 在控制台中输出日志,例如`log("Hello world");`。当text是一个对象时则会转换String以后再输出。 ### err(error) -* error \ | \ +* error \ | \ 在控制台中输出错误信息,以红色字体显示,例如: ``` @@ -21,4 +22,9 @@ try{ err(e); openConsole(); } -``` \ No newline at end of file +``` + +### toast(message) +* message \ | \ 要显示的信息 + +以灰色的方框显示信息message几秒。(具体时间取决于安卓系统) \ No newline at end of file diff --git a/app/src/main/assets/help/documentation/简介与说明.md b/app/src/main/assets/help/documentation/简介与说明.md new file mode 100644 index 00000000..26e76b3f --- /dev/null +++ b/app/src/main/assets/help/documentation/简介与说明.md @@ -0,0 +1,3 @@ +本软件采用JavaScript1.7为脚本语言([ECMA-357 ECMAScript for XML (E4X)](http://www.ecma-international.org/publications/standards/Ecma-357.htm)), 采用[Rhino1.7](https://developer.mozilla.org/zh-CN/docs/Mozilla/Projects/Rhino/Overview)为脚本引擎。因而本软件的语法与JavaScript相同,并允许在脚本中调用Android和Java的库。 + +本软件中的EMCAScript教程的章节全部提取自[W3Cschool](http://www.w3school.com.cn/js/pro_js_syntax.asp),如有侵权请联系软件作者,将立即从软件中删除。 diff --git a/app/src/main/assets/help/documentation/选择器.md b/app/src/main/assets/help/documentation/选择器.md new file mode 100644 index 00000000..afe4d609 --- /dev/null +++ b/app/src/main/assets/help/documentation/选择器.md @@ -0,0 +1,198 @@ +目录: +* [简介](#简介) +* [选择器](#选择器) +* [控件](#控件) +* [控件集合](#控件集合) + +## 简介 + +选择器提供了更强大的定位界面控件与对齐进行操作的功能,可以用他们完成更多的自动操作。 +选择器通过附加筛选条件,筛选出符合条件的控件或控件集合,之后可以对这些控件这些操作。 +可供选择的筛选条件包括: +* id +* text 文本 +* desc 描述(Content-Description) +* className 类名 +* packageName 包名 +* bounds 在屏幕上的范围 +* drawingOrder 绘制顺序 +* 各种属性,主要包括: + * clickable + * longClickable + * checkable + * checked + * scrollable + * selected + * editable + * visibleToUser + * enabled + +要对选择器所确定的筛选条件,对屏幕上的控件进行筛选,调用方法find(), findOne(), untilFind(), untilFind()。其中find(), untilFind()返回控件的集合,findOne(), untilFindOne()返回一个控件。until***函数会一直寻找知道屏幕上出现满足条件的控件为止。 +一个完整的选择器示例如下: +``` +var emoj = id("name").packageName("com.tencent.mobileqq").clickable() + .className("ImageView").drawingOrder(5).untilFindOne(); +emoj.click(); +``` +上述代码是找到QQ聊天界面下面的表情按钮(第5个按钮)并点击。也可以简化为: +``` +id("name").packageName("com.tencent.mobileqq").clickable() + .className("ImageView").click(); +``` +最后的click()函数相当于untilFind().click()。也就是如果只需要在找到控件以后立即进行操作,只需要调用相应的操作函数,而不需要先调用find等函数等到控件再操作。 +要获取屏幕上的控件的信息(例如id, text, desc, bounds等),可以开启悬浮窗,使用界面层次查看和界面范围查看。 + +以下是具体的文档。 + +## 选择器 + +### id(resId) +* resId \ 控件的id,以"包名:id/"开头,例如"com.tencent.mm:id/send_btn"。也可以不指定包名,这时会以当前正在运行的应用的包名来补全id。例如id("send_btn"),在QQ界面想当于id("com.tencent.mobileqq:id/send_btn")。 + +附加id严格匹配筛选条件。 + +### idContains(str) +* str \ id要包含的字符串 + +附加控件id包含字符串str的筛选条件。 + +### idStartsWith(prefix) +* prefix \ id前缀 + +附加id需要以prefix开头的筛选条件。 + +### idEndsWith(suffix) +* suffix \ id后缀 + +附加id需要以suffix结束的筛选条件。 + +### idMatches(regex) +* regex \ id要满足的正则表达式 + +附加id需要满足正则表达式。有关正则表达式,可以查看[菜鸟教程](http://www.runoob.com/regexp/regexp-example.html)。 + +### text(str) + +### textContains(str) + +### textStartsWith(prefix) + +### textEndsWith(suffix) + +### textMatches(str) + +### desc(desc) + +### descContains(str) + +### descStartsWith(prefix) + +### descEndsWith(suffix) + +### descMatches(str) + +### className(className) + +### classNameContains(str) + +### classNameStartsWith(prefix) + +### classNameEndsWith(suffix) + +### classNameMatches(str) + +### packageName(packageName) + +### packageNameContains(str) + +### packageNameStartsWith(prefix) + +### packageNameEndsWith(suffix) + +### packageNameMatches(str) + +### bounds(l, t, r, b) + +### boundsInside(l, t, r, b) + +### boundsContains(l, t, r, b) + +### drawingOrder(order) + +### checkable(b = true) + +### selected(b = true) + +### clickable(b = true) + +### longClickable(b = true) + +### enabled(b = true) + +### password(b = true) + +### scrollable(b = true) + +### editable(b = true) + +### contentInvalid(b = true) + +### contextClickable(b = true) + +### multiLine(b = true) + +### dismissable(b = true) + +### click() + +### longClick() + +### accessibilityFocus() + +### clearAccessibilityFocus() + +### focus() + +### clearFocus() + +### copy() + +### paste() + +### select() + +### cut() + +### collapse() + +### expand() + +### dismiss() + +### show() + +### scrollForward() + +### scrollBackward() + +### scrollUp() + +### scrollDown() + +### scrollLeft() + +### scrollRight() + +### contextClick() + +### setSelection(start, end) + +### setText(text) + +### setProgress(value) + +### scrollTo(row, column) + +## 控件 + +## 控件集合 \ No newline at end of file diff --git a/app/src/main/assets/help/w3c/Function 对象(类).html b/app/src/main/assets/help/w3c/Function 对象(类).html new file mode 100644 index 00000000..dec0e783 --- /dev/null +++ b/app/src/main/assets/help/w3c/Function 对象(类).html @@ -0,0 +1,148 @@ +
+ +

ECMAScript Function 对象(类)

+ +
+ +
+ + +
+

ECMAScript 的函数实际上是功能完整的对象。

+
+ + +
+

Function 对象(类)

+ +

ECMAScript 最令人感兴趣的可能莫过于函数实际上是功能完整的对象。

+ +

Function 类可以表示开发者定义的任何函数。

+ +

用 Function 类直接创建函数的语法如下:

+ +
var function_name = new function(arg1, arg2, ..., argN, function_body)
+ +

在上面的形式中,每个 arg 都是一个参数,最后一个参数是函数主体(要执行的代码)。这些参数必须是字符串。

+ +

记得下面这个函数吗?

+ +
+function sayHi(sName, sMessage) {
+  alert("Hello " + sName + sMessage);
+}
+
+ +

还可以这样定义它:

+ +
+var sayHi 
+= 
+new Function("sName", "sMessage", "alert(\"Hello \" + sName + sMessage);");
+
+ +

虽然由于字符串的关系,这种形式写起来有些困难,但有助于理解函数只不过是一种引用类型,它们的行为与用 Function 类明确创建的函数行为是相同的。

+ +

请看下面这个例子:

+ +
+function doAdd(iNum) {
+  alert(iNum + 20);
+}
+
+function doAdd(iNum) {
+  alert(iNum + 10);
+}
+
+doAdd(10);	//输出 "20"
+
+ +

如你所知,第二个函数重载了第一个函数,使 doAdd(10) 输出了 "20",而不是 "30"。

+ +

如果以下面的形式重写该代码块,这个概念就清楚了:

+ +
+var doAdd = new Function("iNum", "alert(iNum + 20)");
+var doAdd = new Function("iNum", "alert(iNum + 10)");
+doAdd(10);
+
+ +

请观察这段代码,很显然,doAdd 的值被改成了指向不同对象的指针。函数名只是指向函数对象的引用值,行为就像其他对象一样。甚至可以使两个变量指向同一个函数:

+ +
+var doAdd = new Function("iNum", "alert(iNum + 10)");
+var alsodoAdd = doAdd;
+doAdd(10);	//输出 "20"
+alsodoAdd(10);	//输出 "20"
+
+ +

在这里,变量 doAdd 被定义为函数,然后 alsodoAdd 被声明为指向同一个函数的指针。用这两个变量都可以执行该函数的代码,并输出相同的结果 - "20"。因此,如果函数名只是指向函数的变量,那么可以把函数作为参数传递给另一个函数吗?回答是肯定的!

+ +
+function callAnotherFunc(fnFunction, vArgument) {
+  fnFunction(vArgument);
+}
+
+var doAdd = new Function("iNum", "alert(iNum + 10)");
+
+callAnotherFunc(doAdd, 10);	//输出 "20"
+
+ +

在上面的例子中,callAnotherFunc() 有两个参数 - 要调用的函数和传递给该函数的参数。这段代码把 doAdd() 传递给 callAnotherFunc() 函数,参数是 10,输出 "20"。

+ +

注意:尽管可以使用 Function 构造函数创建函数,但最好不要使用它,因为用它定义函数比用传统方式要慢得多。不过,所有函数都应看作 Function 类的实例。

+
+ + +
+

Function 对象的 length 属性

+ +

如前所述,函数属于引用类型,所以它们也有属性和方法。

+ +

ECMAScript 定义的属性 length 声明了函数期望的参数个数。例如:

+ +
+function doAdd(iNum) {
+  alert(iNum + 10);
+}
+
+function sayHi() {
+  alert("Hi");
+}
+
+alert(doAdd.length);	//输出 "1"
+alert(sayHi.length);	//输出 "0"
+
+ +

函数 doAdd() 定义了一个参数,因此它的 length 是 1;sayHi() 没有定义参数,所以 length 是 0。

+ +

记住,无论定义了几个参数,ECMAScript 可以接受任意多个参数(最多 25 个),这一点在《函数概述》这一章中讲解过。属性 length 只是为查看默认情况下预期的参数个数提供了一种简便方式。

+ +
+ + +
+

Function 对象的方法

+ +

Function 对象也有与所有对象共享的 valueOf() 方法和 toString() 方法。这两个方法返回的都是函数的源代码,在调试时尤其有用。例如:

+ +
+function doAdd(iNum) {
+  alert(iNum + 10);
+}
+
+document.write(doAdd.toString());
+
+ +

上面这段代码输出了 doAdd() 函数的文本。亲自试一试

+
+ + +
+ +
+ + + + +
diff --git a/app/src/main/assets/help/w3c/arguments 对象.html b/app/src/main/assets/help/w3c/arguments 对象.html new file mode 100644 index 00000000..37194a90 --- /dev/null +++ b/app/src/main/assets/help/w3c/arguments 对象.html @@ -0,0 +1,85 @@ +
+ +

ECMAScript arguments 对象

+ +
+ +
+ + +
+

arguments 对象

+ +

在函数代码中,使用特殊对象 arguments,开发者无需明确指出参数名,就能访问它们。

+ +

例如,在函数 sayHi() 中,第一个参数是 message。用 arguments[0] 也可以访问这个值,即第一个参数的值(第一个参数位于位置 0,第二个参数位于位置 1,依此类推)。

+ +

因此,无需明确命名参数,就可以重写函数:

+ +
+function sayHi() {
+  if (arguments[0] == "bye") {
+    return;
+  }
+
+  alert(arguments[0]);
+}
+
+
+ + +
+

检测参数个数

+ +

还可以用 arguments 对象检测函数的参数个数,引用属性 arguments.length 即可。

+ +

下面的代码将输出每次调用函数使用的参数个数:

+ +
+function howManyArgs() {
+  alert(arguments.length);
+}
+
+howManyArgs("string", 45);
+howManyArgs();
+howManyArgs(12);
+
+ +

上面这段代码将依次显示 "2"、"0" 和 "1"。

+ +

注释:与其他程序设计语言不同,ECMAScript 不会验证传递给函数的参数个数是否等于函数定义的参数个数。开发者定义的函数都可以接受任意个数的参数(根据 Netscape 的文档,最多可接受 255 个),而不会引发任何错误。任何遗漏的参数都会以 undefined 传递给函数,多余的函数将忽略。

+
+ + +
+

模拟函数重载

+ +

用 arguments 对象判断传递给函数的参数个数,即可模拟函数重载:

+ +
+function doAdd() {
+  if(arguments.length == 1) {
+    alert(arguments[0] + 5);
+  } else if(arguments.length == 2) {
+    alert(arguments[0] + arguments[1]);
+  }
+}
+
+doAdd(10);	//输出 "15"
+doAdd(40, 20);	//输出 "60"
+
+ +

当只有一个参数时,doAdd() 函数给参数加 5。如果有两个参数,则会把两个参数相加,返回它们的和。所以,doAdd(10) 输出的是 "15",而 doAdd(40, 20) 输出的是 "60"。

+ +

虽然不如重载那么好,不过已足以避开 ECMAScript 的这种限制。

+
+ + +
+ +
+ + + + +
diff --git a/app/src/main/assets/help/w3c/break 和 continue 语句.html b/app/src/main/assets/help/w3c/break 和 continue 语句.html new file mode 100644 index 00000000..ffd80636 --- /dev/null +++ b/app/src/main/assets/help/w3c/break 和 continue 语句.html @@ -0,0 +1,114 @@ +
+ +

ECMAScript break 和 continue 语句

+ +
+ +
+ + +
+

break 和 continue 语句对循环中的代码执行提供了更严格的控制。

+
+ + +
+

break 和 continue 语句的不同之处

+ +

break 语句可以立即退出循环,阻止再次反复执行任何代码。

+ +

而 continue 语句只是退出当前循环,根据控制表达式还允许继续进行下一次循环。

+ +

例如:

+ +
+var iNum = 0;
+
+for (var i=1; i<10; i++) {
+  if (i % 5 == 0) {
+    break;
+  }
+  iNum++;
+}
+alert(iNum);	//输出 "4"
+
+ +

在以上代码中,for 循环从 1 到 10 迭代变量 i。在循环主体中,if 语句将(使用取模运算符)检查 i 的值是否能被 5 整除。如果能被 5 整除,将执行 break 语句。alert 显示 "4",即退出循环前执行循环的次数。

+ +

如果用 continue 语句代替这个例子中的 break 语句,结果将不同:

+ +
+var iNum = 0;
+
+for (var i=1; i<10; i++) {
+  if (i % 5 == 0) {
+    continue;
+  }
+  iNum++;
+}
+alert(iNum);	//输出 "8"
+
+ +

这里,alert 将显示 "8",即执行循环的次数。可能执行的循环总数为 9,不过当 i 的值为 5 时,将执行 continue 语句,会使循环跳过表达式 iNum++,返回循环开头。

+
+ + +
+

与有标签的语句一起使用

+ +

break 语句和 continue 语句都可以与有标签的语句联合使用,返回代码中的特定位置。

+ +

通常,当循环内部还有循环时,会这样做,例如:

+ +
+var iNum = 0;
+
+outermost:
+for (var i=0; i<10; i++) {
+  for (var j=0; j<10; j++) {
+    if (i == 5 && j == 5) {
+    break outermost;
+  }
+  iNum++;
+  }
+}
+
+alert(iNum);	//输出 "55"
+
+ +

在上面的例子中,标签 outermost 表示的是第一个 for 语句。正常情况下,每个 for 语句执行 10 次代码块,这意味着 iNum++ 正常情况下将被执行 100 次,在执行完成时,iNum 应该等于 100。这里的 break 语句有一个参数,即停止循环后要跳转到的语句的标签。这样 break 语句不止能跳出内部 for 语句(即使用变量 j 的语句),还能跳出外部 for 语句(即使用变量 i 的语句)。因此,iNum 最后的值是 55,因为当 i 和 j 的值都等于 5 时,循环将终止。

+ +

可以以相同的方式使用 continue 语句:

+ +
+var iNum = 0;
+
+outermost:
+for (var i=0; i<10; i++) {
+  for (var j=0; j<10; j++) {
+    if (i == 5 && j == 5) {
+    continue outermost;
+  }
+  iNum++;
+  }
+}
+
+alert(iNum);	//输出 "95"
+
+ +

在上例中,continue 语句会迫使循环继续,不止是内部循环,外部循环也如此。当 j 等于 5 时出现这种情况,意味着内部循环将减少 5 次迭代,致使 iNum 的值为 95。

+ +

提示:可以看出,与 break 和 continue 联合使用的有标签语句非常强大,不过过度使用它们会给调试代码带来麻烦。要确保使用的标签具有说明性,同时不要嵌套太多层循环。

+ +

提示:想了解什么是有标签语句,请阅读 ECMAScript 标签语句 这一节。

+
+ + +
+ +
+ + + + +
diff --git a/app/src/main/assets/help/w3c/if 语句.html b/app/src/main/assets/help/w3c/if 语句.html new file mode 100644 index 00000000..e13bc32b --- /dev/null +++ b/app/src/main/assets/help/w3c/if 语句.html @@ -0,0 +1,77 @@ +
+ +

ECMAScript if 语句

+ +
+ +
+ +
+

if 语句是 ECMAScript 中最常用的语句之一。

+
+ +
+

ECMAScript 语句

+ +

ECMA - 262 描述了 ECMAScript 的几种语句(statement)。

+ +

语句主要定义了 ECMAScript 的大部分语句,通常采用一个或多个关键字,完成给定的任务。

+ +

语句可以非常简单,例如通知函数退出,也可以非常复杂,如声明一组要反复执行的命令。

+ +

在《ECMAScript 语句》这一章,我们介绍了所有标准的 ECMAScript 语句。

+
+ + +
+

if 语句

+ +

if 语句是 ECMAScript 中最常用的语句之一,事实上在许多计算机语言中都是如此。

+ +

if 语句的语法:

+ +
if (condition) statement1 else statement2
+ +

其中 condition 可以是任何表达式,计算的结果甚至不必是真正的 boolean 值,ECMAScript 会把它转换成 boolean 值。

+ +

如果条件计算结果为 true,则执行 statement1;如果条件计算结果为 false,则执行 statement2

+ +

每个语句都可以是单行代码,也可以是代码块。

+ +

例如:

+ +
+if (i > 30)
+  {alert("大于 30");}
+else
+  {alert("小于等于 30");}
+
+ +

提示:使用代码块被认为是一种最佳的编程实践,即使要执行的代码只有一行。这样做可以使每个条件要执行什么一目了然。

+ +

还可以串联多个 if 语句。就像这样:

+ +
if (condition1) statement1 else if (condition2) statement2 else statement3
+ +

例如:

+ +
+if (i > 30) {
+  alert("大于 30");
+} else if (i < 0) {
+  alert("小于 0");
+} else {
+  alert("在 0 到 30 之间");
+}
+
+
+ + +
+ +
+ + + + +
diff --git a/app/src/main/assets/help/w3c/switch 语句.html b/app/src/main/assets/help/w3c/switch 语句.html new file mode 100644 index 00000000..21257e89 --- /dev/null +++ b/app/src/main/assets/help/w3c/switch 语句.html @@ -0,0 +1,98 @@ +
+ +

ECMAScript switch 语句

+ +
+ +
+ + +
+

switch 语句

+ +

switch 语句是 if 语句的兄弟语句。

+ +

开发者可以用 switch 语句为表达式提供一系列的情况(case)。

+ +

switch 语句的语法:

+ +
switch (expression)
+  case value: statement;
+    break;
+  case value: statement;
+    break;
+  case value: statement;
+    break;
+  case value: statement;
+    break;
+...
+  case value: statement;
+    break;
+  default: statement;
+
+ +

每个情况(case)都是表示“如果 expression 等于 value,就执行 statement”。

+ +

关键字 break 会使代码跳出 switch 语句。如果没有关键字 break,代码执行就会继续进入下一个 case。

+ +

关键字 default 说明了表达式的结果不等于任何一种情况时的操作(事实上,它相对于 else 从句)。

+ +

switch 语句主要是为避免让开发者编写下面的代码:

+ +
+if (i == 20)
+  alert("20");
+else if (i == 30)
+  alert("30");
+else if (i == 40)
+  alert("40");
+else
+  alert("other");
+
+ +

等价的 switch 语句是这样的:

+ +
+switch (i) {
+  case 20: alert("20");
+    break;
+  case 30: alert("30");
+    break;
+  case 40: alert("40");
+    break;
+  default: alert("other");
+}
+
+
+ +
+

ECMAScript 和 Java 中的 switch 语句

+ +

ECMAScript 和 Java 中的 switch 语句有两点不同。在 ECMAScript 中,switch 语句可以用于字符串,而且能用不是常量的值说明情况:

+ +
+var BLUE = "blue", RED = "red", GREEN  = "green";
+
+switch (sColor) {
+  case BLUE: alert("Blue");
+    break;
+  case RED: alert("Red");
+    break;
+  case GREEN: alert("Green");
+    break;
+  default: alert("Other");
+}
+
+ +

这里,switch 语句用于字符串 sColor,声明 case 使用的是变量 BLUE、RED 和 GREEN,这在 ECMAScript 中是完全有效的。

+
+ + +
+ +
+ + + + +
diff --git a/app/src/main/assets/help/w3c/with 语句.html b/app/src/main/assets/help/w3c/with 语句.html new file mode 100644 index 00000000..82b6206c --- /dev/null +++ b/app/src/main/assets/help/w3c/with 语句.html @@ -0,0 +1,41 @@ +
+ +

ECMAScript with 语句

+ +
+ +
+ + +
+

有标签的语句

+ +

with 语句用于设置代码在特定对象中的作用域。

+ +

它的语法:

+ +
with (expression) statement
+ +

例如:

+ +
+var sMessage = "hello";
+with(sMessage) {
+  alert(toUpperCase());	//输出 "HELLO"
+}
+
+ +

在这个例子中,with 语句用于字符串,所以在调用 toUpperCase() 方法时,解释程序将检查该方法是否是本地函数。如果不是,它将检查伪对象 sMessage,看它是否为该对象的方法。然后,alert 输出 "HELLO",因为解释程序找到了字符串 "hello" 的 toUpperCase() 方法。

+ +

提示:with 语句是运行缓慢的代码块,尤其是在已设置了属性值时。大多数情况下,如果可能,最好避免使用它。

+
+ + +
+ +
+ + + + +
diff --git a/app/src/main/assets/help/w3c/一元运算符.html b/app/src/main/assets/help/w3c/一元运算符.html new file mode 100644 index 00000000..581665ac --- /dev/null +++ b/app/src/main/assets/help/w3c/一元运算符.html @@ -0,0 +1,215 @@ +
+ +

ECMAScript 一元运算符

+ +
+ +
+ +
+

一元运算符只有一个参数,即要操作的对象或值。它们是 ECMAScript 中最简单的运算符。

+
+ +
+

delete

+ +

delete 运算符删除对以前定义的对象属性或方法的引用。例如:

+ +
+var o = new Object;
+o.name = "David";
+alert(o.name);	//输出 "David"
+delete o.name;
+alert(o.name);	//输出 "undefined"
+
+ + +

在这个例子中,删除了 name 属性,意味着强制解除对它的引用,将其设置为 undefined(即创建的未初始化的变量的值)。

+ +

delete 运算符不能删除开发者未定义的属性和方法。例如,下面的代码将引发错误:

+ +
delete o.toString;
+ +

即使 toString 是有效的方法名,这行代码也会引发错误,因为 toString() 方法是原始的 ECMAScript 方法,不是开发者定义的。

+ +
+ + +
+

void

+ +

void 运算符对任何值返回 undefined。该运算符通常用于避免输出不应该输出的值,例如,从 HTML 的 <a> 元素调用 JavaScript 函数时。要正确做到这一点,函数不能返回有效值,否则浏览器将清空页面,只显示函数的结果。例如:

+ +
<a href="javascript:window.open('about:blank')">Click me</a>
+ +

如果把这行代码放入 HTML 页面,点击其中的链接,即可看到屏幕上显示 "[object]"。TIY

+ +

这是因为 window.open() 方法返回了新打开的窗口的引用。然后该对象将被转换成要显示的字符串。

+ +

要避免这种效果,可以用 void 运算符调用 window.open() 函数:

+ +
<a href="javascript:void(window.open('about:blank'))">Click me</a>
+ +

这使 window.open() 调用返回 undefined,它不是有效值,不会显示在浏览器窗口中。

+ +

提示:请记住,没有返回值的函数真正返回的都是 undefined。

+
+ + +
+

前增量/前减量运算符

+ +

直接从 C(和 Java)借用的两个运算符是前增量运算符和前减量运算符。

+ +

所谓前增量运算符,就是数值上加 1,形式是在变量前放两个加号(++):

+ +
+var iNum = 10;
+++iNum;
+
+ +

第二行代码把 iNum 增加到了 11,它实质上等价于:

+ +
+var iNum = 10;
+iNum = iNum + 1;
+
+ +

同样,前减量运算符是从数值上减 1,形式是在变量前放两个减号(--):

+ +
+var iNum = 10;
+--iNum;
+
+ +

在这个例子中,第二行代码把 iNum 的值减到 9。

+ +

在使用前缀式运算符时,注意增量和减量运算符都发生在计算表达式之前。考虑下面的例子:

+ +
+var iNum = 10;
+--iNum;
+alert(iNum);	//输出 "9"
+alert(--iNum);	//输出 "8"
+alert(iNum);	//输出 "8"
+
+ +

第二行代码对 iNum 进行减量运算,第三行代码显示的结果是("9")。第四行代码又对 iNum 进行减量运算,不过这次前减量运算和输出操作出现在同一个语句中,显示的结果是 "8"。为了证明已实现了所有的减量操作,第五行代码又输出一次"8"。

+ +

在算术表达式中,前增量和前减量运算符的优先级是相同的,因此要按照从左到右的顺序计算之。例如:

+ +
+var iNum1 = 2;
+var iNum2 = 20;
+var iNum3 = --iNum1 + ++iNum2;	//等于 "22"
+var iNum4 = iNum1 + iNum2;		//等于 "22"
+
+ +

在前面的代码中,iNum3 等于 22,因为表达式要计算的是 1 + 21。变量 iNum4 也等于 22,也是 1 + 21。

+ +
+ + +
+

后增量/后减量运算符

+ +

还有两个直接从 C(和 Java)借用的运算符,即后增量运算符和后减量运算符。

+ +

后增量运算符也是给数值上加 1,形式是在变量后放两个加号(++):

+ +
+var iNum = 10;
+iNum++;
+
+ +

不出所料,后减量运算符也是从数值上减 1,形式为在变量后加两个减号(--):

+ +
+var iNum = 10;
+iNum--;
+
+ +

第二行代码把 iNum 的 值减到 9。

+ +

与前缀式运算符不同的是,后缀式运算符是在计算过包含它们的表达式后才进行增量或减量运算的。考虑以下的例子:

+ +
+var iNum = 10;
+iNum--;
+alert(iNum);	//输出 "9"
+alert(iNum--);	//输出 "9"
+alert(iNum);	//输出 "8"
+
+ +

与前缀式运算符的例子相似,第二行代码对 iNum 进行减量运算,第三行代码显示结果("9")。第四行代码继续显示 iNum 的值,不过这次是在同一语句中应用减量运算符。由于减量运算发生在计算过表达式之后,所以这条语句显示的数是 "9"。执行了第五行代码后,alert 函数显示的是 "8",因为在执行第四行代码之后和执行第五行代码之前,执行了后减量运算。

+ +

在算术表达式中,后增量和后减量运算符的优先级是相同的,因此要按照从左到右的顺序计算之。例如:

+ +
+var iNum1 = 2;
+var iNum2 = 20;
+var iNum3 = iNum1-- + iNum2++;	//等于 "22"
+var iNum4 = iNum1 + iNum2;		//等于 "22"
+
+ +

在前面的代码中,iNum3 等于 22,因为表达式要计算的是 2 + 20。变量 iNum4 也等于 22,不过它计算的是 1 + 21,因为增量和减量运算都在给 iNum3 赋值后才发生。

+ +
+ + +
+

一元加法和一元减法

+ +

大多数人都熟悉一元加法和一元减法,它们在 ECMAScript 中的用法与您高中数学中学到的用法相同。

+ +

一元加法本质上对数字无任何影响:

+ +
+var iNum = 20;
+iNum = +iNum;
+alert(iNum);	//输出 "20"
+
+ +

这段代码对数字 20 应用了一元加法,返回的还是 20。

+ +

尽管一元加法对数字无作用,但对字符串却有有趣的效果,会把字符串转换成数字。

+ +
+var sNum = "20";
+alert(typeof sNum);	//输出 "string"
+var iNum = +sNum;
+alert(typeof iNum);	//输出 "number"
+
+ +

这段代码把字符串 "20" 转换成真正的数字。当一元加法运算符对字符串进行操作时,它计算字符串的方式与 parseInt() 相似,主要的不同是只有对以 "0x" 开头的字符串(表示十六进制数字),一元运算符才能把它转换成十进制的值。因此,用一元加法转换 "010",得到的总是 10,而 "0xB" 将被转换成 11。

+ +

另一方面,一元减法就是对数值求负(例如把 20 转换成 -20):

+ +
+var iNum = 20;
+iNum = -iNum;
+alert(iNum);	//输出 "-20"
+
+ +

与一元加法运算符相似,一元减法运算符也会把字符串转换成近似的数字,此外还会对该值求负。例如:

+ +
+var sNum = "20";
+alert(typeof sNum);	//输出 "string"
+var iNum = -sNum;
+alert(iNum);		//输出 "-20"
+alert(typeof iNum);	//输出 "number"
+
+ +

在上面的代码中,一元减法运算符将把字符串 "-20" 转换成 -20(一元减法运算符对十六进制和十进制的处理方式与一元加法运算符相似,只是它还会对该值求负)。

+
+ + +
+ +
+ + + + +
diff --git a/app/src/main/assets/help/w3c/乘性运算符.html b/app/src/main/assets/help/w3c/乘性运算符.html new file mode 100644 index 00000000..8e8cb641 --- /dev/null +++ b/app/src/main/assets/help/w3c/乘性运算符.html @@ -0,0 +1,88 @@ +
+ +

ECMAScript 乘性运算符

+ +
+ +
+ +
+

ECMAScript 的乘性运算符与 Java、C、Perl 等于语言中的同类运算符的运算方式相似。

+

需要注意的是,乘性运算符还具有一些自动转换功能。

+
+ +
+

乘法运算符

+ +

乘法运算符由星号(*)表示,用于两数相乘。

+ +

ECMAScript 中的乘法语法与 C 语言中的相同:

+ +
var iResult = 12 * 34
+ +

不过,在处理特殊值时,ECMAScript 中的乘法还有一些特殊行为:

+ +
    +
  • 如果结果太大或太小,那么生成的结果是 Infinity 或 -Infinity。
  • +
  • 如果某个运算数是 NaN,结果为 NaN。
  • +
  • Infinity 乘以 0,结果为 NaN。
  • +
  • Infinity 乘以 0 以外的任何数字,结果为 Infinity 或 -Infinity。
  • +
  • Infinity 乘以 Infinity,结果为 Infinity。
  • +
+ +

注释:如果运算数是数字,那么执行常规的乘法运算,即两个正数或两个负数为正数,两个运算数符号不同,结果为负数。

+
+ + +
+

除法运算符

+ +

除法运算符由斜杠(/)表示,用第二个运算数除第一个运算数:

+ +
+var iResult = 88 /11;
+
+ +

与乘法运算符相似,在处理特殊值时,除法运算符也有一些特殊行为:

+ +
    +
  • 如果结果太大或太小,那么生成的结果是 Infinity 或 -Infinity。
  • +
  • 如果某个运算数是 NaN,结果为 NaN。
  • +
  • Infinity 被 Infinity 除,结果为 NaN。
  • +
  • Infinity 被任何数字除,结果为 Infinity。
  • +
  • 0 除一个任何非无穷大的数字,结果为 NaN。
  • +
  • Infinity 被 0 以外的任何数字除,结果为 Infinity 或 -Infinity。
  • +
+ +

注释:如果运算数是数字,那么执行常规的除法运算,即两个正数或两个负数为正数,两个运算数符号不同,结果为负数。

+
+ + +
+

取模运算符

+ +

除法(余数)运算符由百分号(%)表示,使用方法如下:

+ +
var iResult = 26%5; //等于 1
+ +

与其他乘性运算符相似,对于特殊值,取模运算符也有特殊的行为:

+ +
    +
  • 如果被除数是 Infinity,或除数是 0,结果为 NaN。
  • +
  • Infinity 被 Infinity 除,结果为 NaN。
  • +
  • 如果除数是无穷大的数,结果为被除数。
  • +
  • 如果被除数为 0,结果为 0。
  • +
+ +

注释:如果运算数是数字,那么执行常规的算术除法运算,返回除法运算得到的余数。

+
+ + +
+ +
+ + + + +
diff --git a/app/src/main/assets/help/w3c/位运算符.html b/app/src/main/assets/help/w3c/位运算符.html new file mode 100644 index 00000000..e5fc7094 --- /dev/null +++ b/app/src/main/assets/help/w3c/位运算符.html @@ -0,0 +1,357 @@ +
+ +

ECMAScript 位运算符

+ +
+ +
+ + +
+

位运算符是在数字底层(即表示数字的 32 个数位)进行操作的。

+
+ + +
+

重温整数

+ +

ECMAScript 整数有两种类型,即有符号整数(允许用正数和负数)和无符号整数(只允许用正数)。在 ECMAScript 中,所有整数字面量默认都是有符号整数,这意味着什么呢?

+ +

有符号整数使用 31 位表示整数的数值,用第 32 位表示整数的符号,0 表示正数,1 表示负数。数值范围从 -2147483648 到 2147483647。

+ +

可以以两种不同的方式存储二进制形式的有符号整数,一种用于存储正数,一种用于存储负数。正数是以真二进制形式存储的,前 31 位中的每一位都表示 2 的幂,从第 1 位(位 0)开始,表示 20,第 2 位(位 1)表示 21。没用到的位用 0 填充,即忽略不计。例如,下图展示的是数 18 的表示法。

+ +32 位二进制表示的有符号整数 + + +

18 的二进制版本只用了前 5 位,它们是这个数字的有效位。把数字转换成二进制字符串,就能看到有效位:

+ +
+var iNum = 18;
+alert(iNum.toString(2));	//输出 "10010"
+
+ +

这段代码只输出 "10010",而不是 18 的 32 位表示。其他的数位并不重要,因为仅使用前 5 位即可确定这个十进制数值。如下图所示:

+ +5 位二进制表示的整数 18 + +

负数也存储为二进制代码,不过采用的形式是二进制补码。计算数字二进制补码的步骤有三步:

+ +
    +
  1. 确定该数字的非负版本的二进制表示(例如,要计算 -18的二进制补码,首先要确定 18 的二进制表示)
  2. +
  3. 求得二进制反码,即要把 0 替换为 1,把 1 替换为 0
  4. +
  5. 在二进制反码上加 1
  6. +
+ +

要确定 -18 的二进制表示,首先必须得到 18 的二进制表示,如下所示:

+ +
0000 0000 0000 0000 0000 0000 0001 0010
+ +

接下来,计算二进制反码,如下所示:

+ +
1111 1111 1111 1111 1111 1111 1110 1101
+ +

最后,在二进制反码上加 1,如下所示:

+ +
+1111 1111 1111 1111 1111 1111 1110 1101
+                                      1
+---------------------------------------
+1111 1111 1111 1111 1111 1111 1110 1110
+
+ +

因此,-18 的二进制表示即 1111 1111 1111 1111 1111 1111 1110 1110。记住,在处理有符号整数时,开发者不能访问 31 位。

+ +

有趣的是,把负整数转换成二进制字符串后,ECMAScript 并不以二进制补码的形式显示,而是用数字绝对值的标准二进制代码前面加负号的形式输出。例如:

+ +
+var iNum = -18;
+alert(iNum.toString(2));	//输出 "-10010"
+
+ +

这段代码输出的是 "-10010",而非二进制补码,这是为避免访问位 31。为了简便,ECMAScript 用一种简单的方式处理整数,使得开发者不必关心它们的用法。

+ +

另一方面,无符号整数把最后一位作为另一个数位处理。在这种模式中,第 32 位不表示数字的符号,而是值 231。由于这个额外的位,无符号整数的数值范围为 0 到 4294967295。对于小于 2147483647 的整数来说,无符号整数看来与有符号整数一样,而大于 2147483647 的整数则要使用位 31(在有符号整数中,这一位总是 0)。

+ +

把无符号整数转换成字符串后,只返回它们的有效位。

+ +

注意:所有整数字面量都默认存储为有符号整数。只有 ECMAScript 的位运算符才能创建无符号整数。

+
+ + +
+

位运算 NOT

+ +

位运算 NOT 由否定号(~)表示,它是 ECMAScript 中为数不多的与二进制算术有关的运算符之一。

+ +

位运算 NOT 是三步的处理过程:

+ +
    +
  1. 把运算数转换成 32 位数字
  2. +
  3. 把二进制数转换成它的二进制反码
  4. +
  5. 把二进制数转换成浮点数
  6. +
+ +

例如:

+ +
+var iNum1 = 25;		//25 等于 00000000000000000000000000011001
+var iNum2 = ~iNum1;	//转换为 11111111111111111111111111100110
+alert(iNum2);		//输出 "-26"
+
+ +

位运算 NOT 实质上是对数字求负,然后减 1,因此 25 变 -26。用下面的方法也可以得到同样的方法:

+ +
+var iNum1 = 25;
+var iNum2 = -iNum1 -1;
+alert(iNum2);	//输出 -26
+
+
+ + +
+

位运算 AND

+ +

位运算 AND 由和号(&)表示,直接对数字的二进制形式进行运算。它把每个数字中的数位对齐,然后用下面的规则对同一位置上的两个数位进行 AND 运算:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
第一个数字中的数位第二个数字中的数位结果
111
100
010
000
+ +

例如,要对数字 25 和 3 进行 AND 运算,代码如下所示:

+ +
+var iResult = 25 & 3;
+alert(iResult);	//输出 "1"
+
+ +

25 和 3 进行 AND 运算的结果是 1。为什么?分析如下:

+ +
+ 25 = 0000 0000 0000 0000 0000 0000 0001 1001
+  3 = 0000 0000 0000 0000 0000 0000 0000 0011
+---------------------------------------------
+AND = 0000 0000 0000 0000 0000 0000 0000 0001
+
+ +

可以看出,在 25 和 3 中,只有一个数位(位 0)存放的都是 1,因此,其他数位生成的都是 0,所以结果为 1。

+
+ + +
+

位运算 OR

+ +

位运算 OR 由符号(|)表示,也是直接对数字的二进制形式进行运算。在计算每位时,OR 运算符采用下列规则:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
第一个数字中的数位第二个数字中的数位结果
111
101
011
000
+ +

仍然使用 AND 运算符所用的例子,对 25 和 3 进行 OR 运算,代码如下:

+ +
+var iResult = 25 | 3;
+alert(iResult);	//输出 "27"
+
+ +

25 和 3 进行 OR 运算的结果是 27:

+ +
+25 = 0000 0000 0000 0000 0000 0000 0001 1001
+ 3 = 0000 0000 0000 0000 0000 0000 0000 0011
+--------------------------------------------
+OR = 0000 0000 0000 0000 0000 0000 0001 1011
+
+ +

可以看出,在两个数字中,共有 4 个数位存放的是 1,这些数位被传递给结果。二进制代码 11011 等于 27。

+
+ + +
+

位运算 XOR

+ +

位运算 XOR 由符号(^)表示,当然,也是直接对二进制形式进行运算。XOR 不同于 OR,当只有一个数位存放的是 1 时,它才返回 1。真值表如下:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
第一个数字中的数位第二个数字中的数位结果
110
101
011
000
+ +

对 25 和 3 进行 XOR 运算,代码如下:

+ +
+var iResult = 25 ^ 3;
+alert(iResult);	//输出 "26"
+
+ +

25 和 3 进行 XOR 运算的结果是 26:

+ +
+ 25 = 0000 0000 0000 0000 0000 0000 0001 1001
+  3 = 0000 0000 0000 0000 0000 0000 0000 0011
+---------------------------------------------
+XOR = 0000 0000 0000 0000 0000 0000 0001 1010
+
+ +

可以看出,在两个数字中,共有 4 个数位存放的是 1,这些数位被传递给结果。二进制代码 11010 等于 26。

+
+ + +
+

左移运算

+ +

左移运算由两个小于号表示(<<)。它把数字中的所有数位向左移动指定的数量。例如,把数字 2(等于二进制中的 10)左移 5 位,结果为 64(等于二进制中的 1000000):

+ +
+var iOld = 2;		//等于二进制 10
+var iNew = iOld << 5;	//等于二进制 1000000 十进制 64
+
+ +

注意:在左移数位时,数字右边多出 5 个空位。左移运算用 0 填充这些空位,使结果成为完整的 32 位数字。

+ +数字 2 进行左移运算 + +

注意:左移运算保留数字的符号位。例如,如果把 -2 左移 5 位,得到的是 -64,而不是 64。“符号仍然存储在第 32 位中吗?”是的,不过这在 ECMAScript 后台进行,开发者不能直接访问第 32 个数位。即使输出二进制字符串形式的负数,显示的也是负号形式(例如,-2 将显示 -10。)

+
+ + +
+

有符号右移运算

+ +

有符号右移运算符由两个大于号表示(>>)。它把 32 位数字中的所有数位整体右移,同时保留该数的符号(正号或负号)。有符号右移运算符恰好与左移运算相反。例如,把 64 右移 5 位,将变为 2:

+ +
+var iOld = 64;		//等于二进制 1000000
+var iNew = iOld >> 5;	//等于二进制 10 十进制 2
+
+ +

同样,移动数位后会造成空位。这次,空位位于数字的左侧,但位于符号位之后。ECMAScript 用符号位的值填充这些空位,创建完整的数字,如下图所示:

+ +数字 64 进行有符号右移运算 +
+ + +
+

无符号右移运算

+ +

无符号右移运算符由三个大于号(>>>)表示,它将无符号 32 位数的所有数位整体右移。对于正数,无符号右移运算的结果与有符号右移运算一样。

+ +

用有符号右移运算中的例子,把 64 右移 5 位,将变为 2:

+ +
+var iOld = 64;		//等于二进制 1000000
+var iNew = iOld >>> 5;	//等于二进制 10 十进制 2
+
+ +

对于负数,情况就不同了。

+ +

无符号右移运算用 0 填充所有空位。对于正数,这与有符号右移运算的操作一样,而负数则被作为正数来处理。

+ +

由于无符号右移运算的结果是一个 32 位的正数,所以负数的无符号右移运算得到的总是一个非常大的数字。例如,如果把 -64 右移 5 位,将得到 134217726。如何得到这种结果的呢?

+ +

要实现这一点,需要把这个数字转换成无符号的等价形式(尽管该数字本身还是有符号的),可以通过以下代码获得这种形式:

+ +
var iUnsigned64 = -64 >>> 0;
+ +

然后,用 Number 类型的 toString() 获取它的真正的位表示,采用的基为 2:

+ +
alert(iUnsigned64.toString(2));
+ +

这将生成 11111111111111111111111111000000,即有符号整数 -64 的二进制补码表示,不过它等于无符号整数 4294967232。

+ +

出于这种原因,使用无符号右移运算符要小心。

+
+ + +
+ +
+ + + + +
diff --git a/app/src/main/assets/help/w3c/保留字.html b/app/src/main/assets/help/w3c/保留字.html new file mode 100644 index 00000000..bd7297f8 --- /dev/null +++ b/app/src/main/assets/help/w3c/保留字.html @@ -0,0 +1,70 @@ +
+ +

ECMAScript 保留字

+ +
+ +
+ + +
+

本节提供完整的 ECMAScript 保留字列表。

+
+ + +
+

ECMAScript 保留字

+ +

ECMA-262 定义了 ECMAScript 支持的一套保留字(reserved word)

+ +

保留字在某种意思上是为将来的关键字而保留的单词。因此保留字不能被用作变量名或函数名。

+ +

ECMA-262 第三版中保留字的完整列表如下:

+ +
+abstract
+boolean
+byte
+char
+class
+const
+debugger
+double
+enum
+export
+extends
+final
+float
+goto
+implements
+import
+int
+interface
+long
+native
+package
+private
+protected
+public
+short
+static
+super
+synchronized
+throws
+transient
+volatile
+
+ +

注意:如果将保留字用作变量名或函数名,那么除非将来的浏览器实现了该保留字,否则很可能收不到任何错误消息。当浏览器将其实现后,该单词将被看做关键字,如此将出现关键字错误。

+ +
+ + +
+ +
+ + + + +
diff --git a/app/src/main/assets/help/w3c/修改对象.html b/app/src/main/assets/help/w3c/修改对象.html new file mode 100644 index 00000000..85b92d89 --- /dev/null +++ b/app/src/main/assets/help/w3c/修改对象.html @@ -0,0 +1,179 @@ +
+ +

ECMAScript 修改对象

+ +
+ +
+ + +
+

通过使用 ECMAScript,不仅可以创建对象,还可以修改已有对象的行为。

+

prototype 属性不仅可以定义构造函数的属性和方法,还可以为本地对象添加属性和方法。

+
+ + +
+

创建新方法

+ +

通过已有的方法创建新方法

+ +

可以用 prototype 属性为任何已有的类定义新方法,就像处理自己的类一样。例如,还记得 Number 类的 toString() 方法吗?如果给它传递参数 16,它将输出十六进制的字符串。如果这个方法的参数是 2,那么它将输出二进制的字符串。我们可以创建一个方法,可以把数字对象直接转换为十六进制字符串。创建这个方法非常简单:

+ +
+Number.prototype.toHexString = function() {
+  return this.toString(16);
+};
+
+ +

在此环境中,关键字 this 指向 Number 的实例,因此可完全访问 Number 的所有方法。有了这段代码,可实现下面的操作:

+ +
+var iNum = 15;
+alert(iNum.toHexString());		//输出 "F"
+
+ +

TIY

+ +

由于数字 15 等于十六进制中的 F,因此警告将显示 "F"。

+ + +

重命名已有方法

+ +

我们还可以为已有的方法命名更易懂的名称。例如,可以给 Array 类添加两个方法 enqueue() 和 dequeue(),只让它们反复调用已有的 push() 和 shift() 方法即可:

+ +
+Array.prototype.enqueue = function(vItem) {
+  this.push(vItem);
+};
+
+Array.prototype.dequeue = function() {
+  return this.shift();
+};
+
+ +

TIY

+ + +

添加与已有方法无关的方法

+ +

当然,还可以添加与已有方法无关的方法。例如,假设要判断某个项在数组中的位置,没有本地方法可以做这种事情。我们可以轻松地创建下面的方法:

+ +
+Array.prototype.indexOf = function (vItem) {
+  for (var i=0; i<this.length; i++) {
+    if (vItem == this[i]) {
+	  return i;
+	}
+  }
+
+  return -1;
+}
+
+ +

该方法 indexOf() 与 String 类的同名方法保持一致,在数组中检索每个项,直到发现与传进来的项相同的项目为止。如果找到相同的项,则返回该项的位置,否则,返回 -1。有了这种定义,我们可以编写下面的代码:

+ +
+var aColors = new Array("red","green","blue");
+alert(aColors.indexOf("green"));	//输出 "1"
+
+ +

TIY

+ + +

为本地对象添加新方法

+ +

最后,如果想给 ECMAScript 中每个本地对象添加新方法,必须在 Object 对象的 prototype 属性上定义它。前面的章节我们讲过,所有本地对象都继承了 Object 对象,所以对 Object 对象做任何改变,都会反应在所有本地对象上。例如,如果想添加一个用警告输出对象的当前值的方法,可以采用下面的代码:

+ +
+Object.prototype.showValue = function () {
+  alert(this.valueOf());
+};
+
+var str = "hello";
+var iNum = 25;
+str.showValue();		//输出 "hello"
+iNum.showValue();		//输出 "25"
+
+ +

TIY

+ +

这里,String 和 Number 对象都从 Object 对象继承了 showValue() 方法,分别在它们的对象上调用该方法,将显示 "hello" 和 "25"。

+
+ + +
+

重定义已有方法

+ +

就像能给已有的类定义新方法一样,也可重定义已有的方法。如前面的章节所述,函数名只是指向函数的指针,因此可以轻松地指向其他函数。如果修改了本地方法,如 toString(),会出现什么情况呢?

+ +
+Function.prototype.toString = function() {
+  return "Function code hidden";
+}
+
+ +

前面的代码完全合法,运行结果完全符合预期:

+ +
+function sayHi() {
+  alert("hi");
+}
+
+alert(sayHi.toString());	//输出 "Function code hidden"
+
+ +

TIY

+ +

也许你还记得,Function 对象这一章中介绍过 Function 的 toString() 方法通常输出的是函数的源代码。覆盖该方法,可以返回另一个字符串(在这个例子中,可以返回 "Function code hidden")。不过,toString() 指向的原始函数怎么了呢?它将被无用存储单元回收程序回收,因为它被完全废弃了。没有能够恢复原始函数的方法,所以在覆盖原始方法前,比较安全的做法是存储它的指针,以便以后的使用。有时你甚至可能在新方法中调用原始方法:

+ +
+Function.prototype.originalToString = Function.prototype.toString;
+
+Function.prototype.toString = function() {
+  if (this.originalToString().length > 100) {
+    return "Function too long to display.";
+  } else {
+    return this.originalToString();
+  }
+};
+
+ +

TIY

+ +

在这段代码中,第一行代码把对当前 toString() 方法的引用保存在属性 originalToString 中。然后用定制的方法覆盖了 toString() 方法。新方法将检查该函数源代码的长度是否大于 100。如果是,就返回错误信息,说明该函数代码太长,否则调用 originalToString() 方法,返回函数的源代码。

+ +
+ + +
+

极晚绑定(Very Late Binding)

+ +

从技术上讲,根本不存在极晚绑定。本书采用该术语描述 ECMAScript 中的一种现象,即能够在对象实例化后再定义它的方法。例如:

+ +
+var o = new Object();
+
+Object.prototype.sayHi = function () {
+  alert("hi");
+};
+
+o.sayHi();
+
+ +

TIY

+ +

在大多数程序设计语言中,必须在实例化对象之前定义对象的方法。这里,方法 sayHi() 是在创建 Object 类的一个实例之后来添加进来的。在传统语言中不仅没听说过这种操作,也没听说过该方法还会自动赋予 Object 对象的实例并能立即使用(接下来的一行)。

+ +

注意:不建议使用极晚绑定方法,因为很难对其跟踪和记录。不过,还是应该了解这种可能。

+
+ + +
+ +
+ + + + +
diff --git a/app/src/main/assets/help/w3c/值.html b/app/src/main/assets/help/w3c/值.html new file mode 100644 index 00000000..25b3512e --- /dev/null +++ b/app/src/main/assets/help/w3c/值.html @@ -0,0 +1,56 @@ +
+ +

ECMAScript 原始值和引用值

+ +
+ +
+ + +
+

在 ECMAScript 中,变量可以存在两种类型的值,即原始值和引用值。

+
+ + +
+

原始值和引用值

+ +

在 ECMAScript 中,变量可以存在两种类型的值,即原始值和引用值。

+ +
+
原始值
+
存储在栈(stack)中的简单数据段,也就是说,它们的值直接存储在变量访问的位置。
+
引用值
+
存储在堆(heap)中的对象,也就是说,存储在变量处的值是一个指针(point),指向存储对象的内存处。
+
+ +

为变量赋值时,ECMAScript 的解释程序必须判断该值是原始类型,还是引用类型。要实现这一点,解释程序则需尝试判断该值是否为 ECMAScript 的原始类型之一,即 Undefined、Null、Boolean、Number 和 String 型。由于这些原始类型占据的空间是固定的,所以可将他们存储在较小的内存区域 - 栈中。这样存储便于迅速查寻变量的值。

+ +

在许多语言中,字符串都被看作引用类型,而非原始类型,因为字符串的长度是可变的。ECMAScript 打破了这一传统。

+ +

如果一个值是引用类型的,那么它的存储空间将从堆中分配。由于引用值的大小会改变,所以不能把它放在栈中,否则会降低变量查寻的速度。相反,放在变量的栈空间中的值是该对象存储在堆中的地址。地址的大小是固定的,所以把它存储在栈中对变量性能无任何负面影响。如下图所示:

+ +存储在堆和栈中的原始值和引用值 + +
+ + +
+

原始类型

+ +

如前所述,ECMAScript 有 5 种原始类型(primitive type),即 Undefined、Null、Boolean、Number 和 String。ECMA-262 把术语类型(type)定义为值的一个集合,每种原始类型定义了它包含的值的范围及其字面量表示形式。

+ +

ECMAScript 提供了 typeof 运算符来判断一个值是否在某种类型的范围内。可以用这种运算符判断一个值是否表示一种原始类型:如果它是原始类型,还可以判断它表示哪种原始类型。

+ +

在稍后的章节,我们将为您深入讲解 ECMAScript 的原始类型和引用类型。

+
+ + +
+ +
+ + + + +
diff --git a/app/src/main/assets/help/w3c/关系运算符.html b/app/src/main/assets/help/w3c/关系运算符.html new file mode 100644 index 00000000..a6373b72 --- /dev/null +++ b/app/src/main/assets/help/w3c/关系运算符.html @@ -0,0 +1,91 @@ +
+ +

ECMAScript 关系运算符

+ +
+ +
+ +
+

关系运算符执行的是比较运算。每个关系运算符都返回一个布尔值。

+
+ +
+

常规比较方式

+ +

关系运算符小于、大于、小于等于和大于等于执行的是两个数的比较运算,比较方式与算术比较运算相同。

+ +

每个关系运算符都返回一个布尔值:

+ +
+var bResult1 = 2 > 1	//true
+var bResult2 = 2 < 1	//false
+
+ +

不过,对两个字符串应用关系运算符,它们的行为则不同。许多人认为小于表示“在字母顺序上靠前”,大于表示“在字母顺序上靠后”,但事实并非如此。对于字符串,第一个字符串中每个字符的代码都与会第二个字符串中对应位置的字符的代码进行数值比较。完成这种比较操作后,返回一个 Boolean 值。问题在于大写字母的代码都小于小写字母的代码,这意味这着可能会遇到下列情况:

+ +
+var bResult = "Blue" < "alpha";
+alert(bResult);	//输出 true
+
+ +

在上面的例子中,字符串 "Blue" 小于 "alpha",因为字母 B 的字符代码是 66,字母 a 的字符代码是 97。要强制性得到按照真正的字母顺序比较的结果,必须把两个数转换成相同的大小写形式(全大写或全小写的),然后再进行比较:

+ +
+var bResult = "Blue".toLowerCase() < "alpha".toLowerCase();
+alert(bResult);	//输出 false
+
+ +

把两个运算数都转换成小写,确保了正确识别出 "alpha" 在字母顺序上位于 "Blue" 之前。

+
+ + +
+

比较数字和字符串

+ +

另一种棘手的状况发生在比较两个字符串形式的数字时,比如:

+ +
+var bResult = "25" < "3";
+alert(bResult);	//输出 "true"
+
+ +

上面这段代码比较的是字符串 "25" 和 "3"。两个运算数都是字符串,所以比较的是它们的字符代码("2" 的字符代码是 50,"3" 的字符代码是 51)。

+ +

不过,如果把某个运算数该为数字,那么结果就有趣了:

+ +
+var bResult = "25" < 3;
+alert(bResult);	//输出 "false"
+
+ +

这里,字符串 "25" 将被转换成数字 25,然后与数字 3 进行比较,结果不出所料。

+ +

无论何时比较一个数字和一个字符串,ECMAScript 都会把字符串转换成数字,然后按照数字顺序比较它们。

+ +

不过,如果字符串不能转换成数字又该如何呢?考虑下面的例子:

+ +
+var bResult = "a" < 3;
+alert(bResult);
+
+ +

你能预料到这段代码输出什么吗?字母 "a" 不能转换成有意义的数字。不过,如果对它调用 parseInt() 方法,返回的是 NaN。根据规则,任何包含 NaN 的关系运算符都要返回 false,因此这段代码也输出 false:

+ +
+var bResult = "a" >= 3;
+alert(bResult);
+
+ +

通常,如果小于运算的两个值返回 false,那么大于等于运算必须返回 true,不过如果某个数字是 NaN,情况则非如此。

+
+ + +
+ +
+ + + + +
diff --git a/app/src/main/assets/help/w3c/关键字.html b/app/src/main/assets/help/w3c/关键字.html new file mode 100644 index 00000000..63cbb581 --- /dev/null +++ b/app/src/main/assets/help/w3c/关键字.html @@ -0,0 +1,60 @@ +
+ +

ECMAScript 关键字

+ +
+ +
+ +
+

本节提供完整的 ECMAScript 关键字列表。

+
+ +
+

ECMAScript 关键字

+ +

ECMA-262 定义了 ECMAScript 支持的一套关键字(keyword)

+ +

这些关键字标识了 ECMAScript 语句的开头和/或结尾。根据规定,关键字是保留的,不能用作变量名或函数名。

+ +

下面是 ECMAScript 关键字的完整列表:

+ +
+break
+case
+catch
+continue
+default
+delete
+do
+else
+finally
+for
+function
+if
+in
+instanceof
+new
+return
+switch
+this
+throw
+try
+typeof
+var
+void
+while
+with
+
+ +

注意:如果把关键字用作变量名或函数名,可能得到诸如 "Identifier Expected"(应该有标识符、期望标识符)这样的错误消息。

+ +
+ + +
+ +
+ + +
diff --git a/app/src/main/assets/help/w3c/函数概述.html b/app/src/main/assets/help/w3c/函数概述.html new file mode 100644 index 00000000..6e257f4f --- /dev/null +++ b/app/src/main/assets/help/w3c/函数概述.html @@ -0,0 +1,121 @@ +
+ +

ECMAScript 函数概述

+ +
+ +
+ + +
+

什么是函数?

+ +

函数是一组可以随时随地运行的语句。

+ +

函数是 ECMAScript 的核心。

+ +

函数是由这样的方式进行声明的:关键字 function、函数名、一组参数,以及置于括号中的待执行代码。

+ +

函数的基本语法是这样的:

+ +
function functionName(arg0, arg1, ... argN) {
+  statements
+}
+
+ +

例如:

+ +
+function sayHi(sName, sMessage) {
+  alert("Hello " + sName + sMessage);
+}
+
+
+ + +
+

如何调用函数?

+ +

函数可以通过其名字加上括号中的参数进行调用,如果有多个参数。

+ +

如果您想调用上例中的那个函数,可以使用如下的代码:

+ +
sayHi("David", " Nice to meet you!")
+ +

调用上面的函数 sayHi() 会生成一个警告窗口。您可以亲自试一试这个例子

+
+ + +
+

函数如何返回值?

+ +

函数 sayHi() 未返回值,不过不必专门声明它(像在 Java 中使用 void 那样)。

+ +

即使函数确实有值,也不必明确地声明它。该函数只需要使用 return 运算符后跟要返回的值即可。

+ +
+function sum(iNum1, iNum2) {
+  return iNum1 + iNum2;
+}
+
+ +

下面的代码把 sum 函数返回的值赋予一个变量:

+ +
+var iResult = sum(1,1);
+alert(iResult);	//输出 "2"
+
+ +

另一个重要概念是,与在 Java 中一样,函数在执行过 return 语句后立即停止代码。因此,return 语句后的代码都不会被执行。

+ +

例如,在下面的代码中,alert 窗口就不会显示出来:

+ +
+function sum(iNum1, iNum2) {
+  return iNum1 + iNum2;
+  alert(iNum1 + iNum2);
+}
+
+ +

一个函数中可以有多个 return 语句,如下所示:

+ +
+function diff(iNum1, iNum2) {
+  if (iNum1 > iNum2) {
+    return iNum1 - iNum2;
+  } else {
+    return iNum2 - iNum1;
+  }
+}
+
+ +

上面的函数用于返回两个数的差。要实现这一点,必须用较大的数减去较小的数,因此用 if 语句决定执行哪个 return 语句。

+ +

如果函数无返回值,那么可以调用没有参数的 return 运算符,随时退出函数。

+ +

例如:

+ +
+function sayHi(sMessage) {
+  if (sMessage == "bye") {
+    return;
+  }
+
+  alert(sMessage);
+}
+
+ +

这段代码中,如果 sMessage 等于 "bye",就永远不显示警告框。

+ +

注释:如果函数无明确的返回值,或调用了没有参数的 return 语句,那么它真正返回的值是 undefined。

+
+ + +
+ +
+ + + + +
diff --git a/app/src/main/assets/help/w3c/加性运算符.html b/app/src/main/assets/help/w3c/加性运算符.html new file mode 100644 index 00000000..91664917 --- /dev/null +++ b/app/src/main/assets/help/w3c/加性运算符.html @@ -0,0 +1,88 @@ +
+ +

ECMAScript 加性运算符

+ +
+ +
+ + +
+

在多数程序设计语言中,加性运算符(即加号或减号)通常是最简单的数学运算符。

+

在 ECMAScript 中,加性运算符有大量的特殊行为。

+
+ + +
+

加法运算符

+ +

法运算符由加号(+)表示:

+ +
var iResult = 1 + 2
+ +

与乘性运算符一样,在处理特殊值时,ECMAScript 中的加法也有一些特殊行为:

+ +
    +
  • 某个运算数是 NaN,那么结果为 NaN。
  • +
  • -Infinity 加 -Infinity,结果为 -Infinity。
  • +
  • Infinity 加 -Infinity,结果为 NaN。
  • +
  • +0 加 +0,结果为 +0。
  • +
  • -0 加 +0,结果为 +0。
  • +
  • -0 加 -0,结果为 -0。
  • +
+ +

不过,如果某个运算数是字符串,那么采用下列规则:

+ +
    +
  • 如果两个运算数都是字符串,把第二个字符串连接到第一个上。
  • +
  • 如果只有一个运算数是字符串,把另一个运算数转换成字符串,结果是两个字符串连接成的字符串。
  • +
+ +

例如:

+ +
+var result = 5 + 5;	//两个数字
+alert(result);		//输出 "10"
+var result2 = 5 + "5";	//一个数字和一个字符串
+alert(result);		//输出 "55"
+
+ +

这段代码说明了加法运算符的两种模式之间的差别。正常情况下,5+5 等于 10(原始数值),如上述代码中前两行所示。不过,如果把一个运算数改为字符串 "5",那么结果将变为 "55"(原始的字符串值),因为另一个运算数也会被转换为字符串。

+ +

注意:为了避免 JavaScript 中的一种常见错误,在使用加法运算符时,一定要仔细检查运算数的数据类型。

+
+ + +
+

减法运算符

+ +

减法运算符(-),也是一个常用的运算符:

+ +
var iResult = 2 - 1;
+ +

与加法运算符一样,在处理特殊值时,减法运算符也有一些特殊行为:

+ +
    +
  • 某个运算数是 NaN,那么结果为 NaN。
  • +
  • Infinity 减 Infinity,结果为 NaN。
  • +
  • -Infinity 减 -Infinity,结果为 NaN。
  • +
  • Infinity 减 -Infinity,结果为 Infinity。
  • +
  • -Infinity 减 Infinity,结果为 -Infinity。
  • +
  • +0 减 +0,结果为 +0。
  • +
  • -0 减 -0,结果为 -0。
  • +
  • +0 减 -0,结果为 +0。
  • +
  • 某个运算符不是数字,那么结果为 NaN。
  • +
+ +

注释:如果运算数都是数字,那么执行常规的减法运算,并返回结果。

+
+ + +
+ +
+ + + + +
diff --git a/app/src/main/assets/help/w3c/原始类型.html b/app/src/main/assets/help/w3c/原始类型.html new file mode 100644 index 00000000..85696bc3 --- /dev/null +++ b/app/src/main/assets/help/w3c/原始类型.html @@ -0,0 +1,286 @@ +
+ +

ECMAScript 原始类型

+ +
+ +
+ + +
+

ECMAScript 有 5 种原始类型(primitive type),即 Undefined、Null、Boolean、Number 和 String。

+
+ + +
+

typeof 运算符

+ +

typeof 运算符有一个参数,即要检查的变量或值。例如:

+ +
+var sTemp = "test string";
+alert (typeof sTemp);    //输出 "string"
+alert (typeof 86);    //输出 "number"
+
+ +

对变量或值调用 typeof 运算符将返回下列值之一:

+ +
    +
  • undefined - 如果变量是 Undefined 类型的
  • +
  • boolean - 如果变量是 Boolean 类型的
  • +
  • number - 如果变量是 Number 类型的
  • +
  • string - 如果变量是 String 类型的
  • +
  • object - 如果变量是一种引用类型或 Null 类型的
  • +
+ +

注释:您也许会问,为什么 typeof 运算符对于 null 值会返回 "Object"。这实际上是 JavaScript 最初实现中的一个错误,然后被 ECMAScript 沿用了。现在,null 被认为是对象的占位符,从而解释了这一矛盾,但从技术上来说,它仍然是原始值。

+ +
+ + +
+

Undefined 类型

+ +

如前所述,Undefined 类型只有一个值,即 undefined。当声明的变量未初始化时,该变量的默认值是 undefined。

+ +
var oTemp;
+ +

前面一行代码声明变量 oTemp,没有初始值。该变量将被赋予值 undefined,即 undefined 类型的字面量。可以用下面的代码段测试该变量的值是否等于 undefined:

+ +
+var oTemp;
+alert(oTemp == undefined);
+
+ +

这段代码将显示 "true",说明这两个值确实相等。还可以用 typeof 运算符显示该变量的值是 undefined:

+ +
+var oTemp;
+alert(typeof oTemp); //输出 "undefined"
+
+ +

提示:值 undefined 并不同于未定义的值。但是,typeof 运算符并不真正区分这两种值。考虑下面的代码:

+ +
+var oTemp;
+
+alert(typeof oTemp);  //输出 "undefined"
+alert(typeof oTemp2);  //输出 "undefined"
+
+ +

前面的代码对两个变量输出的都是 "undefined",即使只有变量 oTemp2 从未被声明过。如果对 oTemp2 使用除 typeof 之外的其他运算符的话,会引起错误,因为其他运算符只能用于已声明的变量上。

+ +

例如,下面的代码将引发错误:

+ +
+var oTemp;
+alert(oTemp2 == undefined);
+
+ +

当函数无明确返回值时,返回的也是值 "undefined",如下所示:

+ +
+function testFunc() {
+}
+
+alert(testFunc() == undefined);  //输出 "true"
+
+
+ + +
+

Null 类型

+ +

另一种只有一个值的类型是 Null,它只有一个专用值 null,即它的字面量。值 undefined 实际上是从值 null 派生来的,因此 ECMAScript 把它们定义为相等的。

+ +
+alert(null == undefined);  //输出 "true"
+
+ +

尽管这两个值相等,但它们的含义不同。undefined 是声明了变量但未对其初始化时赋予该变量的值,null 则用于表示尚未存在的对象(在讨论 typeof 运算符时,简单地介绍过这一点)。如果函数或方法要返回的是对象,那么找不到该对象时,返回的通常是 null。

+
+ + +
+

Boolean 类型

+ +

Boolean 类型是 ECMAScript 中最常用的类型之一。它有两个值 true 和 false (即两个 Boolean 字面量)。

+ +

即使 false 不等于 0,0 也可以在必要时被转换成 false,这样在 Boolean 语句中使用两者都是安全的。

+ +
+var bFound = true;
+var bLost = false;
+
+
+ + +
+

Number 类型

+ +

ECMA-262 中定义的最特殊的类型是 Number 类型。这种类型既可以表示 32 位的整数,还可以表示 64 位的浮点数。

+ +

直接输入的(而不是从另一个变量访问的)任何数字都被看做 Number 类型的字面量。例如,下面的代码声明了存放整数值的变量,它的值由字面量 86 定义:

+ +
var iNum = 86;
+ +

八进制数和十六进制数

+ +

整数也可以被表示为八进制(以 8 为底)或十六进制(以 16 为底)的字面量。八进制字面量的首数字必须是 0,其后的数字可以是任何八进制数字(0-7),如下面的代码所示:

+
var iNum = 070;  //070 等于十进制的 56
+ +

要创建十六进制的字面量,首位数字必须为 0,后面接字母 x,然后是任意的十六进制数字(0 到 9 和 A 到 F)。这些字母可以是大写的,也可以是小写的。例如:

+
+var iNum = 0x1f;  //0x1f 等于十进制的 31
+var iNum = 0xAB;  //0xAB 等于十进制的 171
+
+ +

提示:尽管所有整数都可以表示为八进制或十六进制的字面量,但所有数学运算返回的都是十进制结果。

+ +

浮点数

+ +

要定义浮点值,必须包括小数点和小数点后的一位数字(例如,用 1.0 而不是 1)。这被看作浮点数字面量。例如:

+
var fNum = 5.0;
+ +

对于浮点字面量的有趣之处在于,用它进行计算前,真正存储的是字符串。

+ +

科学计数法

+

对于非常大或非常小的数,可以用科学计数法表示浮点数,可以把一个数表示为数字(包括十进制数字)加 e(或 E),后面加乘以 10 的倍数。例如:

+ +
var fNum = 5.618e7
+ +

该符号表示的是数 56180000。把科学计数法转化成计算式就可以得到该值:5.618 x 107

+ +

也可以用科学计数法表示非常小的数,例如 0.00000000000000008 可以表示为 8-e17(这里,10 被升到 -17 次冥,意味着需要被 10 除 17 次)。ECMAScript 默认把具有 6 个或 6 个以上前导 0 的浮点数转换成科学计数法。

+ +

提示:也可用 64 位 IEEE 754 形式存储浮点值,这意味着十进制值最多可以有 17 个十进制位。17 位之后的值将被裁去,从而造成一些小的数学误差。

+ + +

特殊的 Number 值

+ +

几个特殊值也被定义为 Number 类型。前两个是 Number.MAX_VALUE 和 Number.MIN_VALUE,它们定义了 Number 值集合的外边界。所有 ECMAScript 数都必须在这两个值之间。不过计算生成的数值结果可以不落在这两个值之间。

+ +

当计算生成的数大于 Number.MAX_VALUE 时,它将被赋予值 Number.POSITIVE_INFINITY,意味着不再有数字值。同样,生成的数值小于 Number.MIN_VALUE 的计算也会被赋予值 Number.NEGATIVE_INFINITY,也意味着不再有数字值。如果计算返回的是无穷大值,那么生成的结果不能再用于其他计算。

+ +

事实上,有专门的值表示无穷大,(如你猜到的)即 Infinity。Number.POSITIVE_INFINITY 的值为 Infinity。Number.NEGATIVE_INFINITY 的值为 -Infinity。

+ +

由于无穷大数可以是正数也可以是负数,所以可用一个方法判断一个数是否是有穷的(而不是单独测试每个无穷数)。可以对任何数调用 isFinite() 方法,以确保该数不是无穷大。例如:

+ +
+var iResult = iNum * some_really_large_number;
+
+if (isFinite(iResult)) {
+    alert("finite");
+}
+
+else {
+    alert("infinite");
+}
+
+ +

最后一个特殊值是 NaN,表示非数(Not a Number)。NaN 是个奇怪的特殊值。一般说来,这种情况发生在类型(String、Boolean 等)转换失败时。例如,要把单词 blue 转换成数值就会失败,因为没有与之等价的数值。与无穷大一样,NaN 也不能用于算术计算。NaN 的另一个奇特之处在于,它与自身不相等,这意味着下面的代码将返回 false:

+ +
alert(NaN == NaN);  //输出 "false"
+ +

出于这个原因,不推荐使用 NaN 值本身。函数 isNaN() 会做得相当好:

+ +
+alert(isNaN("blue"));  //输出 "true"
+alert(isNaN("666"));  //输出 "false"
+
+ +
+ + +
+

String 类型

+ +

String 类型的独特之处在于,它是唯一没有固定大小的原始类型。可以用字符串存储 0 或更多的 Unicode 字符,有 16 位整数表示(Unicode 是一种国际字符集,本教程后面将讨论它)。

+ +

字符串中每个字符都有特定的位置,首字符从位置 0 开始,第二个字符在位置 1,依此类推。这意味着字符串中的最后一个字符的位置一定是字符串的长度减 1:

+ +字符串的长度和字符的位置 + +

字符串字面量是由双引号(")或单引号(')声明的。而 Java 则是用双引号声明字符串,用单引号声明字符。但是由于 ECMAScript 没有字符类型,所以可使用这两种表示法中的任何一种。例如,下面的两行代码都有效:

+ +
+var sColor1 = "red";
+var sColor2 = 'red';
+
+ +

String 类型还包括几种字符字面量,Java、C 和 Perl 的开发者应该对此非常熟悉。

+ +

下面列出了 ECMAScript 的字符字面量:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
字面量含义
\n换行
\t制表符
\b空格
\r回车
\f换页符
\\反斜杠
\'单引号
\"双引号
\0nnn八进制代码 nnn 表示的字符(n 是 0 到 7 中的一个八进制数字)
\xnn十六进制代码 nn 表示的字符(n 是 0 到 F 中的一个十六进制数字)
\unnnn十六进制代码 nnnn 表示的 Unicode 字符(n 是 0 到 F 中的一个十六进制数字)
+
+ + +
+ +
+ + + + +
diff --git a/app/src/main/assets/help/w3c/变量.html b/app/src/main/assets/help/w3c/变量.html new file mode 100644 index 00000000..db3c3eaf --- /dev/null +++ b/app/src/main/assets/help/w3c/变量.html @@ -0,0 +1,191 @@ +
+ +

ECMAScript 变量

+ +
+ +
+ +
+

请使用 var 运算符声明变量。

+

变量名需要遵守一些简单的规则。

+
+ + +
+

声明变量

+ +

在上一节中我们讲解过,ECMAScript 中的变量是用 var 运算符(variable 的缩写)加变量名定义的。例如:

+ +
var test = "hi";
+ +

在这个例子中,声明了变量 test,并把它的值初始化为 "hi"(字符串)。由于 ECMAScript 是弱类型的,所以解释程序会为 test 自动创建一个字符串值,无需明确的类型声明

+ +

还可以用一个 var 语句定义两个或多个变量:

+ +
var test1 = "hi", test2 = "hello";
+ +

前面的代码定义了变量 test1,初始值为 "hi",还定义了变量 test2,初始值为 "hello"。

+ +

不过用同一个 var 语句定义的变量不必具有相同的类型,如下所示:

+ +
var test = "hi", age = 25;
+ +

这个例子除了(再次)定义 test 外,还定义了 age,并把它初始化为 25。即使 test 和 age 属于两种不同的数据类型,在 ECMAScript 中这样定义也是完全合法的。

+ +

与 Java 不同,ECMAScript 中的变量并不一定要初始化(它们是在幕后初始化的,将在后面讨论这一点)。因此,下面这一行代码也是有效的:

+ +
var test;
+ +

此外,与 Java 不同的还有变量可以存放不同类型的值。这是弱类型变量的优势。例如,可以把变量初始化为字符串类型的值,之后把它设置为数字值,如下所示:

+ +
+var test = "hi";
+alert(test);
+test = 55;
+alert(test);
+
+ +

这段代码将毫无问题地输出字符串值和数字值。但是,如前所述,使用变量时,好的编码习惯是始终存放相同类型的值。

+ +
+ + +
+

命名变量

+ +

变量名需要遵守两条简单的规则:

+ +
    +
  • 第一个字符必须是字母、下划线(_)或美元符号($)
  • +
  • 余下的字符可以是下划线、美元符号或任何字母或数字字符
  • +
+ +

下面的变量都是合法的:

+ +
+var test;
+var $test;
+var $1;
+var _$te$t2;
+
+
+ + +
+

著名的变量命名规则

+ +

只是因为变量名的语法正确,并不意味着就该使用它们。变量还应遵守以下某条著名的命名规则:

+ +

Camel 标记法

+

首字母是小写的,接下来的字母都以大写字符开头。例如:

+
+var myTestValue = 0, mySecondValue = "hi";
+
+ +

Pascal 标记法

+

首字母是大写的,接下来的字母都以大写字符开头。例如:

+
+var MyTestValue = 0, MySecondValue = "hi";
+
+ +

匈牙利类型标记法

+

在以 Pascal 标记法命名的变量前附加一个小写字母(或小写字母序列),说明该变量的类型。例如,i 表示整数,s 表示字符串,如下所示“

+
+var iMyTestValue = 0, sMySecondValue = "hi";
+
+ +

本教程采用了这些前缀,以使示例代码更易阅读:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
类型前缀示例
数组aaValues
布尔型bbFound
浮点型(数字)ffValue
函数fnfnMethod
整型(数字)iiValue
对象ooType
正则表达式rerePattern
字符串ssValue
变型(可以是任何类型)vvValue
+
+ + +
+

变量声明不是必须的

+ +

ECMAScript 另一个有趣的方面(也是与大多数程序设计语言的主要区别),是在使用变量之前不必声明。例如:

+ +
+var sTest = "hello ";
+sTest2 = sTest + "world";
+alert(sTest2);
+
+ +

在上面的代码中,首先,sTest 被声明为字符串类型的值 "hello"。接下来的一行,用变量 sTest2 把 sTest 与字符串 "world" 连在一起。变量 sTest2 并没有用 var 运算符定义,这里只是插入了它,就像已经声明过它一样。

+ +

ECMAScript 的解释程序遇到未声明过的标识符时,用该变量名创建一个全局变量,并将其初始化为指定的值。

+ +

这是该语言的便利之处,不过如果不能紧密跟踪变量,这样做也很危险。最好的习惯是像使用其他程序设计语言一样,总是声明所有变量。

+ +
+ + +
+ +
+ + + + +
diff --git a/app/src/main/assets/help/w3c/定义类或对象.html b/app/src/main/assets/help/w3c/定义类或对象.html new file mode 100644 index 00000000..f7bf8c4f --- /dev/null +++ b/app/src/main/assets/help/w3c/定义类或对象.html @@ -0,0 +1,455 @@ +
+ +

ECMAScript 定义类或对象

+ +
+ +
+ + +
+

使用预定义对象只是面向对象语言的能力的一部分,它真正强大之处在于能够创建自己专用的类和对象。

+

ECMAScript 拥有很多创建对象或类的方法。

+
+ + +
+

工厂方式

+ +

原始的方式

+ +

因为对象的属性可以在对象创建后动态定义,所有许多开发者都在 JavaScript 最初引入时编写类似下面的代码:

+ +
+var oCar = new Object;
+oCar.color = "blue";
+oCar.doors = 4;
+oCar.mpg = 25;
+oCar.showColor = function() {
+  alert(this.color);
+};
+
+ +

TIY

+ +

在上面的代码中,创建对象 car。然后给它设置几个属性:它的颜色是蓝色,有四个门,每加仑油可以跑 25 英里。最后一个属性实际上是指向函数的指针,意味着该属性是个方法。执行这段代码后,就可以使用对象 car。

+ +

不过这里有一个问题,就是可能需要创建多个 car 的实例。

+ + +

解决方案:工厂方式

+ +

要解决该问题,开发者创造了能创建并返回特定类型的对象的工厂函数(factory function)。

+ +

例如,函数 createCar() 可用于封装前面列出的创建 car 对象的操作:

+ +
+function createCar() {
+  var oTempCar = new Object;
+  oTempCar.color = "blue";
+  oTempCar.doors = 4;
+  oTempCar.mpg = 25;
+  oTempCar.showColor = function() {
+    alert(this.color);
+  };
+  return oTempCar;
+}
+
+var oCar1 = createCar();
+var oCar2 = createCar();
+
+ +

TIY

+ +

在这里,第一个例子中的所有代码都包含在 createCar() 函数中。此外,还有一行额外的代码,返回 car 对象(oTempCar)作为函数值。调用此函数,将创建新对象,并赋予它所有必要的属性,复制出一个我们在前面说明过的 car 对象。因此,通过这种方法,我们可以很容易地创建 car 对象的两个版本(oCar1 和 oCar2),它们的属性完全一样。

+ + +

为函数传递参数

+ +

我们还可以修改 createCar() 函数,给它传递各个属性的默认值,而不是简单地赋予属性默认值:

+ +
+function createCar(sColor,iDoors,iMpg) {
+  var oTempCar = new Object;
+  oTempCar.color = sColor;
+  oTempCar.doors = iDoors;
+  oTempCar.mpg = iMpg;
+  oTempCar.showColor = function() {
+    alert(this.color);
+  };
+  return oTempCar;
+}
+
+var oCar1 = createCar("red",4,23);
+var oCar2 = createCar("blue",3,25);
+
+oCar1.showColor();		//输出 "red"
+oCar2.showColor();		//输出 "blue"
+
+ +

TIY

+ +

给 createCar() 函数加上参数,即可为要创建的 car 对象的 color、doors 和 mpg 属性赋值。这使两个对象具有相同的属性,却有不同的属性值。

+ + +

在工厂函数外定义对象的方法

+ +

虽然 ECMAScript 越来越正式化,但创建对象的方法却被置之不理,且其规范化至今还遭人反对。一部分是语义上的原因(它看起来不像使用带有构造函数 new 运算符那么正规),一部分是功能上的原因。功能原因在于用这种方式必须创建对象的方法。前面的例子中,每次调用函数 createCar(),都要创建新函数 showColor(),意味着每个对象都有自己的 showColor() 版本。而事实上,每个对象都共享同一个函数。

+ +

有些开发者在工厂函数外定义对象的方法,然后通过属性指向该方法,从而避免这个问题:

+ +
+function showColor() {
+  alert(this.color);
+}
+
+function createCar(sColor,iDoors,iMpg) {
+  var oTempCar = new Object;
+  oTempCar.color = sColor;
+  oTempCar.doors = iDoors;
+  oTempCar.mpg = iMpg;
+  oTempCar.showColor = showColor;
+  return oTempCar;
+}
+
+var oCar1 = createCar("red",4,23);
+var oCar2 = createCar("blue",3,25);
+
+oCar1.showColor();		//输出 "red"
+oCar2.showColor();		//输出 "blue"
+
+ +

TIY

+ +

在上面这段重写的代码中,在函数 createCar() 之前定义了函数 showColor()。在 createCar() 内部,赋予对象一个指向已经存在的 showColor() 函数的指针。从功能上讲,这样解决了重复创建函数对象的问题;但是从语义上讲,该函数不太像是对象的方法。

+ +

所有这些问题都引发了开发者定义的构造函数的出现。

+
+ + +
+

构造函数方式

+ +

创建构造函数就像创建工厂函数一样容易。第一步选择类名,即构造函数的名字。根据惯例,这个名字的首字母大写,以使它与首字母通常是小写的变量名分开。除了这点不同,构造函数看起来很像工厂函数。请考虑下面的例子:

+ +
+function Car(sColor,iDoors,iMpg) {
+  this.color = sColor;
+  this.doors = iDoors;
+  this.mpg = iMpg;
+  this.showColor = function() {
+    alert(this.color);
+  };
+}
+
+var oCar1 = new Car("red",4,23);
+var oCar2 = new Car("blue",3,25);
+
+ +

TIY

+ +

下面为您解释上面的代码与工厂方式的差别。首先在构造函数内没有创建对象,而是使用 this 关键字。使用 new 运算符构造函数时,在执行第一行代码前先创建一个对象,只有用 this 才能访问该对象。然后可以直接赋予 this 属性,默认情况下是构造函数的返回值(不必明确使用 return 运算符)。

+ +

现在,用 new 运算符和类名 Car 创建对象,就更像 ECMAScript 中一般对象的创建方式了。

+ +

你也许会问,这种方式在管理函数方面是否存在于前一种方式相同的问题呢?是的。

+ +

就像工厂函数,构造函数会重复生成函数,为每个对象都创建独立的函数版本。不过,与工厂函数相似,也可以用外部函数重写构造函数,同样地,这么做语义上无任何意义。这正是下面要讲的原型方式的优势所在。

+
+ + +
+

原型方式

+ +

该方式利用了对象的 prototype 属性,可以把它看成创建新对象所依赖的原型。

+ +

这里,首先用空构造函数来设置类名。然后所有的属性和方法都被直接赋予 prototype 属性。我们重写了前面的例子,代码如下:

+ +
+function Car() {
+}
+
+Car.prototype.color = "blue";
+Car.prototype.doors = 4;
+Car.prototype.mpg = 25;
+Car.prototype.showColor = function() {
+  alert(this.color);
+};
+
+var oCar1 = new Car();
+var oCar2 = new Car();
+
+ +

TIY

+ +

在这段代码中,首先定义构造函数(Car),其中无任何代码。接下来的几行代码,通过给 Car 的 prototype 属性添加属性去定义 Car 对象的属性。调用 new Car() 时,原型的所有属性都被立即赋予要创建的对象,意味着所有 Car 实例存放的都是指向 showColor() 函数的指针。从语义上讲,所有属性看起来都属于一个对象,因此解决了前面两种方式存在的问题。

+ +

此外,使用这种方式,还能用 instanceof 运算符检查给定变量指向的对象的类型。因此,下面的代码将输出 TRUE:

+ +
alert(oCar1 instanceof Car);	//输出 "true"
+ + +

原型方式的问题

+ +

原型方式看起来是个不错的解决方案。遗憾的是,它并不尽如人意。

+ +

首先,这个构造函数没有参数。使用原型方式,不能通过给构造函数传递参数来初始化属性的值,因为 Car1 和 Car2 的 color 属性都等于 "blue",doors 属性都等于 4,mpg 属性都等于 25。这意味着必须在对象创建后才能改变属性的默认值,这点很令人讨厌,但还没完。真正的问题出现在属性指向的是对象,而不是函数时。函数共享不会造成问题,但对象却很少被多个实例共享。请思考下面的例子:

+ +
+function Car() {
+}
+
+Car.prototype.color = "blue";
+Car.prototype.doors = 4;
+Car.prototype.mpg = 25;
+Car.prototype.drivers = new Array("Mike","John");
+Car.prototype.showColor = function() {
+  alert(this.color);
+};
+
+var oCar1 = new Car();
+var oCar2 = new Car();
+
+oCar1.drivers.push("Bill");
+
+alert(oCar1.drivers);	//输出 "Mike,John,Bill"
+alert(oCar2.drivers);	//输出 "Mike,John,Bill"
+
+ +

TIY

+ +

上面的代码中,属性 drivers 是指向 Array 对象的指针,该数组中包含两个名字 "Mike" 和 "John"。由于 drivers 是引用值,Car 的两个实例都指向同一个数组。这意味着给 oCar1.drivers 添加值 "Bill",在 oCar2.drivers 中也能看到。输出这两个指针中的任何一个,结果都是显示字符串 "Mike,John,Bill"。

+ +

由于创建对象时有这么多问题,你一定会想,是否有种合理的创建对象的方法呢?答案是有,需要联合使用构造函数和原型方式。

+
+ + +
+

混合的构造函数/原型方式

+ +

联合使用构造函数和原型方式,就可像用其他程序设计语言一样创建对象。这种概念非常简单,即用构造函数定义对象的所有非函数属性,用原型方式定义对象的函数属性(方法)。结果是,所有函数都只创建一次,而每个对象都具有自己的对象属性实例。

+ +

我们重写了前面的例子,代码如下:

+ +
+function Car(sColor,iDoors,iMpg) {
+  this.color = sColor;
+  this.doors = iDoors;
+  this.mpg = iMpg;
+  this.drivers = new Array("Mike","John");
+}
+
+Car.prototype.showColor = function() {
+  alert(this.color);
+};
+
+var oCar1 = new Car("red",4,23);
+var oCar2 = new Car("blue",3,25);
+
+oCar1.drivers.push("Bill");
+
+alert(oCar1.drivers);	//输出 "Mike,John,Bill"
+alert(oCar2.drivers);	//输出 "Mike,John"
+
+ +

TIY

+ +

现在就更像创建一般对象了。所有的非函数属性都在构造函数中创建,意味着又能够用构造函数的参数赋予属性默认值了。因为只创建 showColor() 函数的一个实例,所以没有内存浪费。此外,给 oCar1 的 drivers 数组添加 "Bill" 值,不会影响到 oCar2 的数组,所以输出这些数组的值时,oCar1.drivers 显示的是 "Mike,John,Bill",而 oCar2.drivers 显示的是 "Mike,John"。因为使用了原型方式,所以仍然能利用 instanceof 运算符来判断对象的类型。

+ +

这种方式是 ECMAScript 采用的主要方式,它具有其他方式的特性,却没有他们的副作用。不过,有些开发者仍觉得这种方法不够完美。

+
+ + +
+

动态原型方法

+ +

对于习惯使用其他语言的开发者来说,使用混合的构造函数/原型方式感觉不那么和谐。毕竟,定义类时,大多数面向对象语言都对属性和方法进行了视觉上的封装。请考虑下面的 Java 类:

+ +
+class Car {
+  public String color = "blue";
+  public int doors = 4;
+  public int mpg = 25;
+
+  public Car(String color, int doors, int mpg) {
+    this.color = color;
+    this.doors = doors;
+    this.mpg = mpg;
+  }
+  
+  public void showColor() {
+    System.out.println(color);
+  }
+}
+
+ +

Java 很好地打包了 Car 类的所有属性和方法,因此看见这段代码就知道它要实现什么功能,它定义了一个对象的信息。批评混合的构造函数/原型方式的人认为,在构造函数内部找属性,在其外部找方法的做法不合逻辑。因此,他们设计了动态原型方法,以提供更友好的编码风格。

+ +

动态原型方法的基本想法与混合的构造函数/原型方式相同,即在构造函数内定义非函数属性,而函数属性则利用原型属性定义。唯一的区别是赋予对象方法的位置。下面是用动态原型方法重写的 Car 类:

+ +
+function Car(sColor,iDoors,iMpg) {
+  this.color = sColor;
+  this.doors = iDoors;
+  this.mpg = iMpg;
+  this.drivers = new Array("Mike","John");
+  
+  if (typeof Car._initialized == "undefined") {
+    Car.prototype.showColor = function() {
+      alert(this.color);
+    };
+	
+    Car._initialized = true;
+  }
+}
+
+ +

TIY

+ +

直到检查 typeof Car._initialized 是否等于 "undefined" 之前,这个构造函数都未发生变化。这行代码是动态原型方法中最重要的部分。如果这个值未定义,构造函数将用原型方式继续定义对象的方法,然后把 Car._initialized 设置为 true。如果这个值定义了(它的值为 true 时,typeof 的值为 Boolean),那么就不再创建该方法。简而言之,该方法使用标志(_initialized)来判断是否已给原型赋予了任何方法。该方法只创建并赋值一次,传统的 OOP 开发者会高兴地发现,这段代码看起来更像其他语言中的类定义了。

+
+ + +
+

混合工厂方式

+ +

这种方式通常是在不能应用前一种方式时的变通方法。它的目的是创建假构造函数,只返回另一种对象的新实例。

+ +

这段代码看起来与工厂函数非常相似:

+ +
+function Car() {
+  var oTempCar = new Object;
+  oTempCar.color = "blue";
+  oTempCar.doors = 4;
+  oTempCar.mpg = 25;
+  oTempCar.showColor = function() {
+    alert(this.color);
+  };
+
+  return oTempCar;
+}
+
+ +

TIY

+ +

与经典方式不同,这种方式使用 new 运算符,使它看起来像真正的构造函数:

+ +
var car = new Car();
+ +

由于在 Car() 构造函数内部调用了 new 运算符,所以将忽略第二个 new 运算符(位于构造函数之外),在构造函数内部创建的对象被传递回变量 car。

+ +

这种方式在对象方法的内部管理方面与经典方式有着相同的问题。强烈建议:除非万不得已,还是避免使用这种方式。

+
+ + +
+

采用哪种方式

+ +

如前所述,目前使用最广泛的是混合的构造函数/原型方式。此外,动态原始方法也很流行,在功能上与构造函数/原型方式等价。可以采用这两种方式中的任何一种。不过不要单独使用经典的构造函数或原型方式,因为这样会给代码引入问题。

+
+ + +
+

实例

+ +

对象令人感兴趣的一点是用它们解决问题的方式。ECMAScript 中最常见的一个问题是字符串连接的性能。与其他语言类似,ECMAScript 的字符串是不可变的,即它们的值不能改变。请考虑下面的代码:

+ +
+var str = "hello ";
+str += "world";
+
+ +

实际上,这段代码在幕后执行的步骤如下:

+ +
    +
  1. 创建存储 "hello " 的字符串。
  2. +
  3. 创建存储 "world" 的字符串。
  4. +
  5. 创建存储连接结果的字符串。
  6. +
  7. 把 str 的当前内容复制到结果中。
  8. +
  9. 把 "world" 复制到结果中。
  10. +
  11. 更新 str,使它指向结果。
  12. +
+ +

每次完成字符串连接都会执行步骤 2 到 6,使得这种操作非常消耗资源。如果重复这一过程几百次,甚至几千次,就会造成性能问题。解决方法是用 Array 对象存储字符串,然后用 join() 方法(参数是空字符串)创建最后的字符串。想象用下面的代码代替前面的代码:

+ +
+var arr = new Array();
+arr[0] = "hello ";
+arr[1] = "world";
+var str = arr.join("");
+
+ +

这样,无论数组中引入多少字符串都不成问题,因为只在调用 join() 方法时才会发生连接操作。此时,执行的步骤如下:

+ +
    +
  1. 创建存储结果的字符串
  2. +
  3. 把每个字符串复制到结果中的合适位置
  4. +
+ +

虽然这种解决方案很好,但还有更好的方法。问题是,这段代码不能确切反映出它的意图。要使它更容易理解,可以用 StringBuffer 类打包该功能:

+ +
+function StringBuffer () {
+  this._strings_ = new Array();
+}
+
+StringBuffer.prototype.append = function(str) {
+  this._strings_.push(str);
+};
+
+StringBuffer.prototype.toString = function() {
+  return this._strings_.join("");
+};
+
+ +

这段代码首先要注意的是 strings 属性,本意是私有属性。它只有两个方法,即 append() 和 toString() 方法。append() 方法有一个参数,它把该参数附加到字符串数组中,toString() 方法调用数组的 join 方法,返回真正连接成的字符串。要用 StringBuffer 对象连接一组字符串,可以用下面的代码:

+ +
+var buffer = new StringBuffer ();
+buffer.append("hello ");
+buffer.append("world");
+var result = buffer.toString();
+
+ +

TIY

+ +

可用下面的代码测试 StringBuffer 对象和传统的字符串连接方法的性能:

+ +
+var d1 = new Date();
+var str = "";
+for (var i=0; i < 10000; i++) {
+    str += "text";
+}
+var d2 = new Date();
+
+document.write("Concatenation with plus: "
+ + (d2.getTime() - d1.getTime()) + " milliseconds");
+
+var buffer = new StringBuffer();
+d1 = new Date();
+for (var i=0; i < 10000; i++) {
+    buffer.append("text");
+}
+var result = buffer.toString();
+d2 = new Date();
+
+document.write("<br />Concatenation with StringBuffer: "
+ + (d2.getTime() - d1.getTime()) + " milliseconds");
+
+ +

TIY

+ +

这段代码对字符串连接进行两个测试,第一个使用加号,第二个使用 StringBuffer 类。每个操作都连接 10000 个字符串。日期值 d1 和 d2 用于判断完成操作需要的时间。请注意,创建 Date 对象时,如果没有参数,赋予对象的是当前的日期和时间。要计算连接操作历经多少时间,把日期的毫秒表示(用 getTime() 方法的返回值)相减即可。这是衡量 JavaScript 性能的常见方法。该测试的结果可以帮助您比较使用 StringBuffer 类与使用加号的效率差异。

+
+ + +
+ +
+ + + + +
diff --git a/app/src/main/assets/help/w3c/对象作用域.html b/app/src/main/assets/help/w3c/对象作用域.html new file mode 100644 index 00000000..e5d1fd00 --- /dev/null +++ b/app/src/main/assets/help/w3c/对象作用域.html @@ -0,0 +1,148 @@ +
+ +

ECMAScript 对象作用域

+ +
+ +
+ + +
+

作用域指的是变量的适用范围。

+
+ + +
+

公用、私有和受保护作用域

+ +

概念

+ +

在传统的面向对象程序设计中,主要关注于公用和私有作用域。公用作用域中的对象属性可以从对象外部访问,即开发者创建对象的实例后,就可使用它的公用属性。而私有作用域中的属性只能在对象内部访问,即对于外部世界来说,这些属性并不存在。这意味着如果类定义了私有属性和方法,则它的子类也不能访问这些属性和方法。

+ +

受保护作用域也是用于定义私有的属性和方法,只是这些属性和方法还能被其子类访问。

+ +

ECMAScript 只有公用作用域

+ +

对 ECMAScript 讨论上面这些作用域几乎毫无意义,因为 ECMAScript 中只存在一种作用域 - 公用作用域。ECMAScript 中的所有对象的所有属性和方法都是公用的。因此,定义自己的类和对象时,必须格外小心。记住,所有属性和方法默认都是公用的!

+ +

建议性的解决方法

+ +

许多开发者都在网上提出了有效的属性作用域模式,解决了 ECMAScript 的这种问题。

+ +

由于缺少私有作用域,开发者确定了一个规约,说明哪些属性和方法应该被看做私有的。这种规约规定在属性前后加下划线:

+ +
obj._color_ = "blue";
+ +

这段代码中,属性 color 是私有的。注意,下划线并不改变属性是公用属性的事实,它只是告诉其他开发者,应该把该属性看作私有的。

+ +

有些开发者还喜欢用单下划线说明私有成员,例如:obj._color。

+
+ + +
+

静态作用域

+ +

静态作用域定义的属性和方法任何时候都能从同一位置访问。在 Java 中,类可具有属性和方法,无需实例化该类的对象,即可访问这些属性和方法,例如 java.net.URLEncoder 类,它的函数 encode() 就是静态方法。

+ +

ECMAScript 没有静态作用域

+ +

严格来说,ECMAScript 并没有静态作用域。不过,它可以给构造函数提供属性和方法。还记得吗,构造函数只是函数。函数是对象,对象可以有属性和方法。例如:

+ +
+function sayHello() {
+  alert("hello");
+}
+
+sayHello.alternate = function() {
+  alert("hi");
+}
+
+sayHello();		//输出 "hello"
+sayHello.alternate();	//输出 "hi"
+
+ +

TIY

+ +

这里,方法 alternate() 实际上是函数 sayHello 的方法。可以像调用常规函数一样调用 sayHello() 输出 "hello",也可以调用 sayHello.alternate() 输出 "hi"。即使如此,alternate() 也是 sayHello() 公用作用域中的方法,而不是静态方法。

+
+ + +
+

关键字 this

+ +

this 的功能

+ +

在 ECMAScript 中,要掌握的最重要的概念之一是关键字 this 的用法,它用在对象的方法中。关键字 this 总是指向调用该方法的对象,例如:

+ +
+var oCar = new Object;
+oCar.color = "red";
+oCar.showColor = function() {
+  alert(this.color);
+};
+
+oCar.showColor();		//输出 "red"
+
+ +

TIY

+ +

在上面的代码中,关键字 this 用在对象的 showColor() 方法中。在此环境中,this 等于 oCar。下面的代码与上面的代码的功能相同:

+ +
+var oCar = new Object;
+oCar.color = "red";
+oCar.showColor = function() {
+  alert(oCar.color);
+};
+
+oCar.showColor();		//输出 "red"
+
+ +

TIY

+ + +

使用 this 的原因

+ +

为什么使用 this 呢?因为在实例化对象时,总是不能确定开发者会使用什么样的变量名。使用 this,即可在任何多个地方重用同一个函数。请思考下面的例子:

+ +
+function showColor() {
+  alert(this.color);
+};
+
+var oCar1 = new Object;
+oCar1.color = "red";
+oCar1.showColor = showColor;
+
+var oCar2 = new Object;
+oCar2.color = "blue";
+oCar2.showColor = showColor;
+
+oCar1.showColor();		//输出 "red"
+oCar2.showColor();		//输出 "blue"
+
+ +

TIY

+ +

在上面的代码中,首先用 this 定义函数 showColor(),然后创建两个对象(oCar1 和 oCar2),一个对象的 color 属性被设置为 "red",另一个对象的 color 属性被设置为 "blue"。两个对象都被赋予了属性 showColor,指向原始的 showColor () 函数(注意这里不存在命名问题,因为一个是全局函数,而另一个是对象的属性)。调用每个对象的 showColor(),oCar1 输出是 "red",而 oCar2 的输出是 "blue"。这是因为调用 oCar1.showColor() 时,函数中的 this 关键字等于 oCar1。调用 oCar2.showColor() 时,函数中的 this 关键字等于 oCar2。

+ +

注意,引用对象的属性时,必须使用 this 关键字。例如,如果采用下面的代码,showColor() 方法不能运行:

+ +
+function showColor() {
+  alert(color);
+};
+
+ +

如果不用对象或 this 关键字引用变量,ECMAScript 就会把它看作局部变量或全局变量。然后该函数将查找名为 color 的局部或全局变量,但是不会找到。结果如何呢?该函数将在警告中显示 "null"。

+
+ + +
+ +
+ + + + +
diff --git a/app/src/main/assets/help/w3c/对象应用.html b/app/src/main/assets/help/w3c/对象应用.html new file mode 100644 index 00000000..186b1e9a --- /dev/null +++ b/app/src/main/assets/help/w3c/对象应用.html @@ -0,0 +1,80 @@ +
+ +

ECMAScript 对象应用

+ +
+ +
+ + +
+

对象的创建和销毁都在 JavaScript 执行过程中发生,理解这种范式的含义对理解整个语言至关重要。

+
+ + +
+

声明和实例化

+ +

对象的创建方式是用关键字 new 后面跟上实例化的类的名字:

+ +
+var oObject = new Object();
+var oStringObject = new String();
+
+ +

第一行代码创建了 Object 类的一个实例,并把它存储到变量 oObject 中。第二行代码创建了 String 类的一个实例,把它存储在变量 oStringObject 中。如果构造函数无参数,括号则不是必需的。因此可以采用下面的形式重写上面的两行代码:

+ +
+var oObject = new Object;
+var oStringObject = new String;
+
+
+ + +
+

对象引用

+ +

在前面的章节中,我们介绍了引用类型的概念。在 ECMAScript 中,不能访问对象的物理表示,只能访问对象的引用。每次创建对象,存储在变量中的都是该对象的引用,而不是对象本身。

+
+ + +
+

对象废除

+ +

ECMAScript 拥有无用存储单元收集程序(garbage collection routine),意味着不必专门销毁对象来释放内存。当再没有对对象的引用时,称该对象被废除(dereference)了。运行无用存储单元收集程序时,所有废除的对象都被销毁。每当函数执行完它的代码,无用存储单元收集程序都会运行,释放所有的局部变量,还有在一些其他不可预知的情况下,无用存储单元收集程序也会运行。

+ +

把对象的所有引用都设置为 null,可以强制性地废除对象。例如:

+ +
+var oObject = new Object;
+// do something with the object here
+oObject = null;
+
+ +

当变量 oObject 设置为 null 后,对第一个创建的对象的引用就不存在了。这意味着下次运行无用存储单元收集程序时,该对象将被销毁。

+ +

每用完一个对象后,就将其废除,来释放内存,这是个好习惯。这样还确保不再使用已经不能访问的对象,从而防止程序设计错误的出现。此外,旧的浏览器(如 IE/MAC)没有全面的无用存储单元收集程序,所以在卸载页面时,对象可能不能被正确销毁。废除对象和它的所有特性是确保内存使用正确的最好方法。

+ +

注意:废除对象的所有引用时要当心。如果一个对象有两个或更多引用,则要正确废除该对象,必须将其所有引用都设置为 null。

+
+ + +
+

早绑定和晚绑定

+ +

所谓绑定(binding),即把对象的接口与对象实例结合在一起的方法。

+ +

早绑定(early binding)是指在实例化对象之前定义它的属性和方法,这样编译器或解释程序就能够提前转换机器代码。在 Java 和 Visual Basic 这样的语言中,有了早绑定,就可以在开发环境中使用 IntelliSense(即给开发者提供对象中属性和方法列表的功能)。ECMAScript 不是强类型语言,所以不支持早绑定。

+ +

另一方面,晚绑定(late binding)指的是编译器或解释程序在运行前,不知道对象的类型。使用晚绑定,无需检查对象的类型,只需检查对象是否支持属性和方法即可。ECMAScript 中的所有变量都采用晚绑定方法。这样就允许执行大量的对象操作,而无任何惩罚。

+
+ + +
+ +
+ + + + +
diff --git a/app/src/main/assets/help/w3c/对象类型.html b/app/src/main/assets/help/w3c/对象类型.html new file mode 100644 index 00000000..c15e33e7 --- /dev/null +++ b/app/src/main/assets/help/w3c/对象类型.html @@ -0,0 +1,86 @@ +
+ +

ECMAScript 对象类型

+ +
+ +
+ + +
+

在 ECMAScript 中,所有对象并非同等创建的。

+

一般来说,可以创建并使用的对象有三种:本地对象、内置对象和宿主对象。

+
+ + +
+

本地对象

+ +

ECMA-262 把本地对象(native object)定义为“独立于宿主环境的 ECMAScript 实现提供的对象”。简单来说,本地对象就是 ECMA-262 定义的类(引用类型)。它们包括:

+ +
    +
  • Object
  • +
  • Function
  • +
  • Array
  • +
  • String
  • +
  • Boolean
  • +
  • Number
  • +
  • Date
  • +
  • RegExp
  • +
  • Error
  • +
  • EvalError
  • +
  • RangeError
  • +
  • ReferenceError
  • +
  • SyntaxError
  • +
  • TypeError
  • +
  • URIError
  • +
+ +

相关页面

+ +

JavaScript 高级教程:ECMAScript 引用类型

+

JavaScript 高级教程:ECMAScript Function 类

+

JavaScript 参考手册:Array 对象

+

JavaScript 参考手册:Boolean 对象

+

JavaScript 参考手册:Date 对象

+

JavaScript 参考手册:Number 对象

+

JavaScript 参考手册:String 对象

+

JavaScript 参考手册:RegExp 对象

+
+ + +
+

内置对象

+ +

ECMA-262 把内置对象(built-in object)定义为“由 ECMAScript 实现提供的、独立于宿主环境的所有对象,在 ECMAScript 程序开始执行时出现”。这意味着开发者不必明确实例化内置对象,它已被实例化了。ECMA-262 只定义了两个内置对象,即 Global 和 Math (它们也是本地对象,根据定义,每个内置对象都是本地对象)。

+ +

相关页面

+ +

JavaScript 参考手册:Global 对象

+

JavaScript 参考手册:Math 对象

+
+ + +
+

宿主对象

+ +

所有非本地对象都是宿主对象(host object),即由 ECMAScript 实现的宿主环境提供的对象。

+ +

所有 BOM 和 DOM 对象都是宿主对象。

+ +

相关页面

+ +

JavaScript 高级教程:JavaScript 实现

+

W3School 参考手册:JavaScript 参考手册

+

W3School 教程:HTML DOM 教程

+
+ + +
+ +
+ + + + +
diff --git a/app/src/main/assets/help/w3c/引用类型.html b/app/src/main/assets/help/w3c/引用类型.html new file mode 100644 index 00000000..63e09045 --- /dev/null +++ b/app/src/main/assets/help/w3c/引用类型.html @@ -0,0 +1,418 @@ +
+ +

ECMAScript 引用类型

+ +
+ +
+ + +
+

引用类型通常叫做类(class)。

+

本教程会讨论大量的 ECMAScript 预定义引用类型。

+
+ + +
+

引用类型

+ +

引用类型通常叫做类(class),也就是说,遇到引用值,所处理的就是对象。

+ +

本教程会讨论大量的 ECMAScript 预定义引用类型。

+ +

从现在起,将重点讨论与已经讨论过的原始类型紧密相关的引用类型。

+ +

注意:从传统意义上来说,ECMAScript 并不真正具有类。事实上,除了说明不存在类,在 ECMA-262 中根本没有出现“类”这个词。ECMAScript 定义了“对象定义”,逻辑上等价于其他程序设计语言中的类。

+ +

提示:本教程将使用术语“对象”。

+ +

对象是由 new 运算符加上要实例化的对象的名字创建的。例如,下面的代码创建 Object 对象的实例:

+ +
var o = new Object();
+ +

这种语法与 Java 语言的相似,不过当有不止一个参数时,ECMAScript 要求使用括号。如果没有参数,如以下代码所示,括号可以省略:

+ +
var o = new Object;
+ +

注意:尽管括号不是必需的,但是为了避免混乱,最好使用括号。

+ +

提示:我们会在对象基础这一章中更深入地探讨对象及其行为。

+ +

这一节的重点是具有等价的原始类型的引用类型。

+ +
+ + +
+

Object 对象

+ +

Object 对象自身用处不大,不过在了解其他类之前,还是应该了解它。因为 ECMAScript 中的 Object 对象与 Java 中的 java.lang.Object 相似,ECMAScript 中的所有对象都由这个对象继承而来,Object 对象中的所有属性和方法都会出现在其他对象中,所以理解了 Object 对象,就可以更好地理解其他对象。

+ +

Object 对象具有下列属性:

+ +
+
constructor
+
对创建对象的函数的引用(指针)。对于 Object 对象,该指针指向原始的 Object() 函数。
+ +
Prototype
+
对该对象的对象原型的引用。对于所有的对象,它默认返回 Object 对象的一个实例。
+
+ + +

Object 对象还具有几个方法:

+ +
+
hasOwnProperty(property)
+
判断对象是否有某个特定的属性。必须用字符串指定该属性。(例如,o.hasOwnProperty("name"))
+ +
IsPrototypeOf(object)
+
判断该对象是否为另一个对象的原型。
+ +
PropertyIsEnumerable
+
判断给定的属性是否可以用 for...in 语句进行枚举。
+ +
ToString()
+
返回对象的原始字符串表示。对于 Object 对象,ECMA-262 没有定义这个值,所以不同的 ECMAScript 实现具有不同的值。
+ +
ValueOf()
+
返回最适合该对象的原始值。对于许多对象,该方法返回的值都与 ToString() 的返回值相同。
+
+ +

注释:上面列出的每种属性和方法都会被其他对象覆盖。

+
+ + +
+

Boolean 对象

+ +

Boolean 对象是 Boolean 原始类型的引用类型。

+ +

要创建 Boolean 对象,只需要传递 Boolean 值作为参数:

+ +
var oBooleanObject = new Boolean(true);
+ +

Boolean 对象将覆盖 Object 对象的 ValueOf() 方法,返回原始值,即 true 和 false。ToString() 方法也会被覆盖,返回字符串 "true" 或 "false"。

+ +

遗憾的是,在 ECMAScript 中很少使用 Boolean 对象,即使使用,也不易理解。

+ +

问题通常出现在 Boolean 表达式中使用 Boolean 对象时。例如:

+ +
+var oFalseObject = new Boolean(false);
+var bResult = oFalseObject && true;	//输出 true
+
+ +

在这段代码中,用 false 值创建 Boolean 对象。然后用这个值与原始值 true 进行 AND 操作。在 Boolean 运算中,false 和 true 进行 AND 操作的结果是 false。不过,在这行代码中,计算的是 oFalseObject,而不是它的值 false。

+ +

正如前面讨论过的,在 Boolean 表达式中,所有对象都会被自动转换为 true,所以 oFalseObject 的值是 true。然后 true 再与 true 进行 AND 操作,结果为 true。

+ +

注意:虽然你应该了解 Boolean 对象的可用性,不过最好还是使用 Boolean 原始值,避免发生这一节提到的问题。

+ + +

参阅

+ +

如需更多有关 Boolean 对象的信息,请访问 JavaScript Boolean 对象参考手册

+
+ + +
+

Number 对象

+ +

正如你可能想到的,Number 对象是 Number 原始类型的引用类型。要创建 Number 对象,采用下列代码:

+ +
var oNumberObject = new Number(68);
+ +

您应该已认出本章前面小节中讨论特殊值(如 Number.MAX_VALUE)时提到的 Number 对象。所有特殊值都是 Number 对象的静态属性。

+ +

要得到数字对象的 Number 原始值,只需要使用 valueOf() 方法:

+ +
var iNumber = oNumberObject.valueOf();
+ +

当然,Number 类也有 toString() 方法,在讨论类型转换的小节中已经详细讨论过该方法。

+ +

除了从 Object 对象继承的标准方法外,Number 对象还有几个处理数值的专用方法。

+ +

toFixed() 方法

+ +

toFixed() 方法返回的是具有指定位数小数的数字的字符串表示。例如:

+ +
+var oNumberObject = new Number(68);
+alert(oNumberObject.toFixed(2));  //输出 "68.00"
+
+ +

在这里,toFixed() 方法的参数是 2,说明应该显示两位小数。该方法返回 "68.00",空的字符串位由 0 来补充。对于处理货币的应用程序,该方法非常有用。toFixed() 方法能表示具有 0 到 20 位小数的数字,超过这个范围的值会引发错误。

+ +

toExponential() 方法

+ +

与格式化数字相关的另一个方法是 toExponential(),它返回的是用科学计数法表示的数字的字符串形式。

+ +

与 toFixed() 方法相似,toExponential() 方法也有一个参数,指定要输出的小数的位数。例如:

+ +
+var oNumberObject = new Number(68);
+alert(oNumberObject.toExponential(1));  //输出 "6.8e+1"
+
+ +

这段代码的结果是 "6.8e+1",前面解释过,它表示 6.8x101。问题是,如果不知道要用哪种形式(预定形式或指数形式)表示数字怎么办?可以用 toPrecision() 方法。

+ +

toPrecision() 方法

+ +

toPrecision() 方法根据最有意义的形式来返回数字的预定形式或指数形式。它有一个参数,即用于表示数的数字总数(不包括指数)。例如,

+ +
+var oNumberObject = new Number(68);
+alert(oNumberObject.toPrecision(1));  //输出 "7e+1"
+
+ +

这段代码的任务是用一位数字表示数字 68,结果为 "7e+1",以另外的形式表示即 70。的确,toPrecision() 方法会对数进行舍入。不过,如果用 2 位数字表示 68,就容易多了:

+ +
+var oNumberObject = new Number(68);
+alert(oNumberObject.toPrecision(2));  //输出 "68"
+
+ +

当然,输出的是 "68",因为这正是该数的准确表示。不过,如果指定的位数多于需要的位数又如何呢?

+ +
+var oNumberObject = new Number(68);
+alert(oNumberObject.toPrecision(3));  //输出 "68.0"
+
+ +

在这种情况下,toPrecision(3) 等价于 toFixed(1),输出的是 "68.0"。 + +

toFixed()、toExponential() 和 toPrecision() 方法都会进行舍入操作,以便用正确的小数位数正确地表示一个数。 + +

提示:与 Boolean 对象相似,Number 对象也很重要,不过应该少用这种对象,以避免潜在的问题。只要可能,都使用数字的原始表示法。

+ + +

参阅

+ +

如需更多有关 Number 对象的信息,请访问 JavaScript Number 对象参考手册

+
+ + +
+

String 对象

+ +

String 对象是 String 原始类型的对象表示法,它是以下方式创建的:

+ +
var oStringObject = new String("hello world");
+ +

String 对象的 valueOf() 方法和 toString() 方法都会返回 String 类型的原始值:

+ +
alert(oStringObject.valueOf() == oStringObject.toString());	//输出 "true"
+ +

如果运行这段代码,输出是 "true",说明这些值真的相等。

+ +

注释:String 对象是 ECMAScript 中比较复杂的引用类型之一。同样,本节的重点只是 String 类的基本功能。更多的高级功能请阅读本教程相关的章节,或参阅 JavaScript String 对象参考手册

+ + +

length 属性

+ +

String 对象具有属性 length,它是字符串中的字符个数:

+ +
+var oStringObject = new String("hello world");
+alert(oStringObject.length);	//输出 "11"
+
+ +

这个例子输出的是 "11",即 "hello world" 中的字符个数。注意,即使字符串包含双字节的字符(与 ASCII 字符相对,ASCII 字符只占用一个字节),每个字符也只算一个字符。

+ + +

charAt() 和 charCodeAt() 方法

+ +

String 对象还拥有大量的方法。

+ +

首先,两个方法 charAt() 和 charCodeAt() 访问的是字符串中的单个字符。这两个方法都有一个参数,即要操作的字符的位置。

+ +

charAt() 方法返回的是包含指定位置处的字符的字符串:

+ +
+var oStringObject = new String("hello world");
+alert(oStringObject.charAt(1));	//输出 "e"
+
+ +

在字符串 "hello world" 中,位置 1 处的字符是 "e"。在“ECMAScript 原始类型”这一节中我们讲过,第一个字符的位置是 0,第二个字符的位置是 1,依此类推。因此,调用 charAt(1) 返回的是 "e"。

+ +

如果想得到的不是字符,而是字符代码,那么可以调用 charCodeAt() 方法:

+ +
+var oStringObject = new String("hello world");
+alert(oStringObject.charCodeAt(1));	//输出 "101"
+
+ +

这个例子输出 "101",即小写字母 "e" 的字符代码。

+ + +

concat() 方法

+ +

接下来是 concat() 方法,用于把一个或多个字符串连接到 String 对象的原始值上。该方法返回的是 String 原始值,保持原始的 String 对象不变:

+ +
+var oStringObject = new String("hello ");
+var sResult = oStringObject.concat("world");
+alert(sResult);		//输出 "hello world"
+alert(oStringObject);	//输出 "hello "
+
+ +

在上面这段代码中,调用 concat() 方法返回的是 "hello world",而 String 对象存放的仍然是 "hello "。出于这种原因,较常见的是用加号(+)连接字符串,因为这种形式从逻辑上表明了真正的行为:

+ +
+var oStringObject = new String("hello ");
+var sResult = oStringObject + "world";
+alert(sResult);		//输出 "hello world"
+alert(oStringObject);	//输出 "hello "
+
+ + +

indexOf() 和 lastIndexOf() 方法

+ +

迄今为止,已讨论过连接字符串的方法,访问字符串中的单个字符的方法。不过如果无法确定在某个字符串中是否确实存在一个字符,应该调用什么方法呢?这时,可调用 indexOf() 和 lastIndexOf() 方法。

+ +

indexOf() 和 lastIndexOf() 方法返回的都是指定的子串在另一个字符串中的位置,如果没有找不到子串,则返回 -1。

+ +

这两个方法的不同之处在于,indexOf() 方法是从字符串的开头(位置 0)开始检索字符串,而 lastIndexOf() 方法则是从字符串的结尾开始检索子串。例如:

+ +
+var oStringObject = new String("hello world!");
+alert(oStringObject.indexOf("o"));		输出 "4"
+alert(oStringObject.lastIndexOf("o"));	输出 "7"
+
+ +

在这里,第一个 "o" 字符串出现在位置 4,即 "hello" 中的 "o";最后一个 "o" 出现在位置 7,即 "world" 中的 "o"。如果该字符串中只有一个 "o" 字符串,那么 indexOf() 和 lastIndexOf() 方法返回的位置相同。

+ + +

localeCompare() 方法

+ +

下一个方法是 localeCompare(),对字符串进行排序。该方法有一个参数 - 要进行比较的字符串,返回的是下列三个值之一:

+ +
    +
  • 如果 String 对象按照字母顺序排在参数中的字符串之前,返回负数。
  • +
  • 如果 String 对象等于参数中的字符串,返回 0
  • +
  • 如果 String 对象按照字母顺序排在参数中的字符串之后,返回正数。
  • +
+ +

注释:如果返回负数,那么最常见的是 -1,不过真正返回的是由实现决定的。如果返回正数,那么同样的,最常见的是 1,不过真正返回的是由实现决定的。

+ +

示例如下:

+ +
+var oStringObject = new String("yellow");
+alert(oStringObject.localeCompare("brick"));		//输出 "1"
+alert(oStringObject.localeCompare("yellow"));		//输出 "0"
+alert(oStringObject.localeCompare("zoo"));		//输出 "-1"
+
+ +

在这段代码中,字符串 "yellow" 与 3 个值进行了对比,即 "brick"、"yellow" 和 "zoo"。由于按照字母顺序排列,"yellow" 位于 "brick" 之后,所以 localeCompare() 返回 1;"yellow" 等于 "yellow",所以 localeCompare() 返回 0;"zoo" 位于 "yellow" 之后,localeCompare() 返回 -1。再强调一次,由于返回的值是由实现决定的,所以最好以下面的方式调用 localeCompare() 方法:

+ +
+var oStringObject1 = new String("yellow");
+var oStringObject2 = new String("brick");
+
+var iResult = oStringObject1.localeCompare(oStringObject2);
+
+if(iResult < 0) {
+  alert(oStringObject1 + " comes before " + oStringObject2);
+} else if (iResult > 0) {
+  alert(oStringObject1 + " comes after " + oStringObject2);
+} else {
+  alert("The two strings are equal");
+}
+
+ +

采用这种结构,可以确保这段代码在所有实现中都能正确运行。

+ +

localeCompare() 方法的独特之处在于,实现所处的区域(locale,兼指国家/地区和语言)确切说明了这种方法运行的方式。在美国,英语是 ECMAScript 实现的标准语言,localeCompare() 是区分大小写的,大写字母在字母顺序上排在小写字母之后。不过,在其他区域,情况可能并非如此。

+ + +

slice() 和 substring()

+ +

ECMAScript 提供了两种方法从子串创建字符串值,即 slice() 和 substring()。这两种方法返回的都是要处理的字符串的子串,都接受一个或两个参数。第一个参数是要获取的子串的起始位置,第二个参数(如果使用的话)是要获取子串终止前的位置(也就是说,获取终止位置处的字符不包括在返回的值内)。如果省略第二个参数,终止位就默认为字符串的长度。

+ +

与 concat() 方法一样,slice() 和 substring() 方法都不改变 String 对象自身的值。它们只返回原始的 String 值,保持 String 对象不变。

+ +
+var oStringObject = new String("hello world");
+alert(oStringObject.slice("3"));		//输出 "lo world"
+alert(oStringObject.substring("3"));		//输出 "lo world"
+alert(oStringObject.slice("3", "7"));		//输出 "lo w"
+alert(oStringObject.substring("3", "7"));	//输出 "lo w"
+
+ +

在这个例子中,slice() 和 substring() 的用法相同,返回值也一样。当只有参数 3 时,两个方法返回的都是 "lo world",因为 "hello" 中的第二个 "l" 位于位置 3 上。当有两个参数 "3" 和 "7" 时,两个方法返回的值都是 "lo w"("world" 中的字母 "o" 位于位置 7 上,所以它不包括在结果中)。

+ +

为什么有两个功能完全相同的方法呢?事实上,这两个方法并不完全相同,不过只在参数为负数时,它们处理参数的方式才稍有不同。

+ +

对于负数参数,slice() 方法会用字符串的长度加上参数,substring() 方法则将其作为 0 处理(也就是说将忽略它)。例如:

+ +
+var oStringObject = new String("hello world");
+alert(oStringObject.slice("-3"));		//输出 "rld"
+alert(oStringObject.substring("-3"));	//输出 "hello world"
+alert(oStringObject.slice("3, -4"));		//输出 "lo w"
+alert(oStringObject.substring("3, -4"));	//输出 "hel"
+
+ +

这样即可看出 slice() 和 substring() 方法的主要不同。

+ +

当只有参数 -3 时,slice() 返回 "rld",substring() 则返回 "hello world"。这是因为对于字符串 "hello world",slice("-3") 将被转换成 slice("8"),而 substring("-3") 将被转换成 substring("0")。

+ +

同样,使用参数 3 和 -4 时,差别也很明显。slice() 将被转换成 slice(3, 7),与前面的例子相同,返回 "lo w"。而 substring() 方法则将两个参数解释为 substring(3, 0),实际上即 substring(0, 3),因为 substring() 总把较小的数字作为起始位,较大的数字作为终止位。因此,substring("3, -4") 返回的是 "hel"。这里的最后一行代码用来说明如何使用这些方法。

+ + +

toLowerCase()、toLocaleLowerCase()、toUpperCase() 和 toLocaleUpperCase()

+ +

最后一套要讨论的方法涉及大小写转换。有 4 种方法用于执行大小写转换,即

+ +
    +
  • toLowerCase()
  • +
  • toLocaleLowerCase()
  • +
  • toUpperCase()
  • +
  • toLocaleUpperCase()
  • +
+ +

从名字上可以看出它们的用途,前两种方法用于把字符串转换成全小写的,后两种方法用于把字符串转换成全大写的。

+ +

toLowerCase() 和 toUpperCase() 方法是原始的,是以 java.lang.String 中相同方法为原型实现的。

+ +

toLocaleLowerCase() 和 toLocaleUpperCase() 方法是基于特定的区域实现的(与 localeCompare() 方法相同)。在许多区域中,区域特定的方法都与通用的方法完全相同。不过,有几种语言对 Unicode 大小写转换应用了特定的规则(例如土耳其语),因此必须使用区域特定的方法才能进行正确的转换。

+ +
+var oStringObject = new String("Hello World");
+alert(oStringObject.toLocaleUpperCase());	//输出 "HELLO WORLD"
+alert(oStringObject.toUpperCase());		//输出 "HELLO WORLD"
+alert(oStringObject.toLocaleLowerCase());	//输出 "hello world"
+alert(oStringObject.toLowerCase());		//输出 "hello world"
+
+ +

这段代码中,toUpperCase() 和 toLocaleUpperCase() 输出的都是 "HELLO WORLD",toLowerCase() 和 toLocaleLowerCase() 输出的都是 "hello world"。一般来说,如果不知道在以哪种编码运行一种语言,则使用区域特定的方法比较安全。

+ +

提示:记住,String 对象的所有属性和方法都可应用于 String 原始值上,因为它们是伪对象。

+
+ + +
+

instanceof 运算符

+ +

在使用 typeof 运算符时采用引用类型存储值会出现一个问题,无论引用的是什么类型的对象,它都返回 "object"。ECMAScript 引入了另一个 Java 运算符 instanceof 来解决这个问题。

+ +

instanceof 运算符与 typeof 运算符相似,用于识别正在处理的对象的类型。与 typeof 方法不同的是,instanceof 方法要求开发者明确地确认对象为某特定类型。例如:

+ +
+var oStringObject = new String("hello world");
+alert(oStringObject instanceof String);	//输出 "true"
+
+ +

这段代码问的是“变量 oStringObject 是否为 String 对象的实例?”oStringObject 的确是 String 对象的实例,因此结果是 "true"。尽管不像 typeof 方法那样灵活,但是在 typeof 方法返回 "object" 的情况下,instanceof 方法还是很有用的。

+
+ + +
+ +
+ + + + +
diff --git a/app/src/main/assets/help/w3c/条件运算符.html b/app/src/main/assets/help/w3c/条件运算符.html new file mode 100644 index 00000000..0d0e57d5 --- /dev/null +++ b/app/src/main/assets/help/w3c/条件运算符.html @@ -0,0 +1,34 @@ +
+ +

ECMAScript 条件运算符

+ +
+ +
+ + +
+

条件运算符

+ +

条件运算符是 ECMAScript 中功能最多的运算符,它的形式与 Java 中的相同。

+ +
variable = boolean_expression ? true_value : false_value;
+ +

该表达式主要是根据 boolean_expression 的计算结果有条件地为变量赋值。如果 Boolean_expression 为 true,就把 true_value 赋给变量;如果它是 false,就把 false_value 赋给变量。

+ +

例如:

+ +
var iMax = (iNum1 > iNum2) ? iNum1 : iNum2;
+ +

在这里例子中,iMax 将被赋予数字中的最大值。表达式声明如果 iNum1 大于 iNum2,则把 iNum1 赋予 iMax。但如果表达式为 false(即 iNum2 大于或等于 iNum1),则把 iNum2 赋予 iMax。

+
+ + +
+ +
+ + + + +
diff --git a/app/src/main/assets/help/w3c/标签语句.html b/app/src/main/assets/help/w3c/标签语句.html new file mode 100644 index 00000000..c1c439ad --- /dev/null +++ b/app/src/main/assets/help/w3c/标签语句.html @@ -0,0 +1,34 @@ +
+ +

ECMAScript 标签语句

+ +
+ +
+ + +
+

有标签的语句

+ +

可以用下列语句给语句加标签,以便以后调用:

+ +
label : statement
+ +

例如:

+ +
start : i = 5;
+ +

在这个例子中,标签 start 可以被之后的 break 或 continue 语句引用。

+ +

提示:在下面的章节,我们将为您介绍 break 和 continue 语句。

+
+ + +
+ +
+ + + + +
diff --git a/app/src/main/assets/help/w3c/等性运算符.html b/app/src/main/assets/help/w3c/等性运算符.html new file mode 100644 index 00000000..91f135ff --- /dev/null +++ b/app/src/main/assets/help/w3c/等性运算符.html @@ -0,0 +1,145 @@ +
+ +

ECMAScript 等性运算符

+ +
+ +
+ +
+

判断两个变量是否相等是程序设计中非常重要的运算。在处理原始值时,这种运算相当简单,但涉及对象,任务就稍有点复杂。

+

ECMAScript 提供了两套等性运算符:等号和非等号用于处理原始值,全等号和非全等号用于处理对象。

+
+ +
+

等号和非等号

+ +

在 ECMAScript 中,等号由双等号(==)表示,当且仅当两个运算数相等时,它返回 true。非等号由感叹号加等号(!=)表示,当且仅当两个运算数不相等时,它返回 true。为确定两个运算数是否相等,这两个运算符都会进行类型转换。

+ +

执行类型转换的规则如下:

+ +
    +
  • 如果一个运算数是 Boolean 值,在检查相等性之前,把它转换成数字值。false 转换成 0,true 为 1。
  • +
  • 如果一个运算数是字符串,另一个是数字,在检查相等性之前,要尝试把字符串转换成数字。
  • +
  • 如果一个运算数是对象,另一个是字符串,在检查相等性之前,要尝试把对象转换成字符串。
  • +
  • 如果一个运算数是对象,另一个是数字,在检查相等性之前,要尝试把对象转换成数字。
  • +
+ +

在比较时,该运算符还遵守下列规则:

+ +
    +
  • 值 null 和 undefined 相等。
  • +
  • 在检查相等性时,不能把 null 和 undefined 转换成其他值。
  • +
  • 如果某个运算数是 NaN,等号将返回 false,非等号将返回 true。
  • +
  • 如果两个运算数都是对象,那么比较的是它们的引用值。如果两个运算数指向同一对象,那么等号返回 true,否则两个运算数不等。
  • +
+ +

重要提示:即使两个数都是 NaN,等号仍然返回 false,因为根据规则,NaN 不等于 NaN。

+ +

下表列出了一些特殊情况,以及它们的结果:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
表达式
null == undefinedtrue
"NaN" == NaNfalse
5 == NaNfalse
NaN == NaNfalse
NaN != NaNtrue
false == 0true
true == 1true
true == 2false
undefined == 0false
null == 0false
"5" == 5true
+
+ + +
+

全等号和非全等号

+ +

等号和非等号的同类运算符是全等号和非全等号。这两个运算符所做的与等号和非等号相同,只是它们在检查相等性前,不执行类型转换。

+ +

全等号由三个等号表示(===),只有在无需类型转换运算数就相等的情况下,才返回 true。

+ +

例如:

+ +
+var sNum = "66";
+var iNum = 66;
+alert(sNum == iNum);	//输出 "true"
+alert(sNum === iNum);	//输出 "false"
+
+ +

在这段代码中,第一个 alert 使用等号来比较字符串 "66" 和数字 66,输出 "true"。如前所述,这是因为字符串 "66" 将被转换成数字 66,,然后才与另一个数字 66 进行比较。第二个 alert 使用全等号在没有类型转换的情况下比较字符串和数字,当然,字符串不等于数字,所以输出 "false"。

+ +

非全等号由感叹号加两个等号(!==)表示,只有在无需类型转换运算数不相等的情况下,才返回 true。

+ +

例如:

+ +
+var sNum = "66";
+var iNum = 66;
+alert(sNum != iNum);	//输出 "false"
+alert(sNum !== iNum);	//输出 "true"
+
+ +

这里,第一个 alert 使用非等号,把字符串 "66" 转换成数字 66,使得它与第二个运算数 66 相等。因此,计算结果为 "false",因为两个运算数是相等的。第二个 alert 使用的非全等号。该运算是在问:"sNum" 与 "iNum" 不同吗?这个问题的答案是:是的(true),因为 sNum 是字符串,而 iNum 是数字,它们当然不同。

+
+ + +
+ +
+ + + + +
diff --git a/app/src/main/assets/help/w3c/类型转换.html b/app/src/main/assets/help/w3c/类型转换.html new file mode 100644 index 00000000..fb8347f7a --- /dev/null +++ b/app/src/main/assets/help/w3c/类型转换.html @@ -0,0 +1,282 @@ +
+ +

ECMAScript 类型转换

+ + +
+ +
+ + +
+

所有程序设计语言最重要的特征之一是具有进行类型转换的能力。

+

ECMAScript 给开发者提供了大量简单的类型转换方法。

+

大部分类型具有进行简单转换的方法,还有几个全局方法可以用于更复杂的转换。无论哪种情况,在 ECMAScript 中,类型转换都是简短的一步操作。

+
+ + +
+

转换成字符串

+ +

ECMAScript 的 Boolean 值、数字和字符串的原始值的有趣之处在于它们是伪对象,这意味着它们实际上具有属性和方法。

+ +

例如,要获得字符串的长度,可以采用下面的代码:

+ +
+var sColor = "red";
+alert(sColor.length);	//输出 "3"
+
+ +

尽管 "red" 是原始类型的字符串,它仍然具有属性 length,用于存放字符串的大小。

+ +

总而言之,3 种主要的原始类型 Boolean 值、数字和字符串都有 toString() 方法,可以把它们的值转换成字符串。

+ +

提示:您也许会问,“字符串还有 toString() 方法吗,这不是多余吗?”是的,的确如此,不过 ECMAScript 定义所有对象都有 toString() 方法,无论它是伪对象,还是真对象。因为 String 类型属于伪对象,所以它一定有 toString() 方法。

+ +

Boolean 类型的 toString() 方法只是输出 "true" 或 "false",结果由变量的值决定:

+ +
+var bFound = false;
+alert(bFound.toString());	//输出 "false"
+
+ +

Number 类型的 toString() 方法比较特殊,它有两种模式,即默认模式基模式。采用默认模式,toString() 方法只是用相应的字符串输出数字值(无论是整数、浮点数还是科学计数法),如下所示:

+ +
+var iNum1 = 10;
+var iNum2 = 10.0;
+alert(iNum1.toString());	//输出 "10"
+alert(iNum2.toString());	//输出 "10"
+
+ +

注释:在默认模式中,无论最初采用什么表示法声明数字,Number 类型的 toString() 方法返回的都是数字的十进制表示。因此,以八进制或十六进制字面量形式声明的数字输出的都是十进制形式的。

+ +

采用 Number 类型的 toString() 方法的基模式,可以用不同的输出数字,例如二进制的基是 2,八进制的基是 8,十六进制的基是 16。

+ +

只是要转换成的基数的另一种加法而已,它是 toString() 方法的参数:

+ +
+var iNum = 10;
+alert(iNum.toString(2));	//输出 "1010"
+alert(iNum.toString(8));	//输出 "12"
+alert(iNum.toString(16));	//输出 "A"
+
+ +

在前面的示例中,以 3 种不同的形式输出了数字 10,即二进制形式、八进制形式和十六进制形式。HTML 采用十六进制表示每种颜色,在 HTML 中处理数字时这种功能非常有用。

+ +

注释:对数字调用 toString(10) 与调用 toString() 相同,它们返回的都是该数字的十进制形式。

+ +

参阅:

+

请参阅 JavaScript 参考手册提供的有关 toString() 方法的详细信息:

+ +
+ + +
+

转换成数字

+ +

ECMAScript 提供了两种把非数字的原始值转换成数字的方法,即 parseInt() 和 parseFloat()。

+ +

正如您可能想到的,前者把值转换成整数,后者把值转换成浮点数。只有对 String 类型调用这些方法,它们才能正确运行;对其他类型返回的都是 NaN。

+ +

parseInt()

+ +

在判断字符串是否是数字值前,parseInt() 和 parseFloat() 都会仔细分析该字符串。

+ +

parseInt() 方法首先查看位置 0 处的字符,判断它是否是个有效数字;如果不是,该方法将返回 NaN,不再继续执行其他操作。但如果该字符是有效数字,该方法将查看位置 1 处的字符,进行同样的测试。这一过程将持续到发现非有效数字的字符为止,此时 parseInt() 将把该字符之前的字符串转换成数字。

+ +

例如,如果要把字符串 "12345red" 转换成整数,那么 parseInt() 将返回 12345,因为当它检查到字符 r 时,就会停止检测过程。

+ +

字符串中包含的数字字面量会被正确转换为数字,比如 "0xA" 会被正确转换为数字 10。不过,字符串 "22.5" 将被转换成 22,因为对于整数来说,小数点是无效字符。

+ +

一些示例如下:

+ +
+var iNum1 = parseInt("12345red");	//返回 12345
+var iNum1 = parseInt("0xA");	//返回 10
+var iNum1 = parseInt("56.9");	//返回 56
+var iNum1 = parseInt("red");	//返回 NaN
+
+ +

parseInt() 方法还有基模式,可以把二进制、八进制、十六进制或其他任何进制的字符串转换成整数。基是由 parseInt() 方法的第二个参数指定的,所以要解析十六进制的值,需如下调用 parseInt() 方法:

+ +
var iNum1 = parseInt("AF", 16);	//返回 175
+ +

当然,对二进制、八进制甚至十进制(默认模式),都可以这样调用 parseInt() 方法:

+ +
+var iNum1 = parseInt("10", 2);	//返回 2
+var iNum2 = parseInt("10", 8);	//返回 8
+var iNum3 = parseInt("10", 10);	//返回 10
+
+ +

如果十进制数包含前导 0,那么最好采用基数 10,这样才不会意外地得到八进制的值。例如:

+ +
+var iNum1 = parseInt("010");	//返回 8
+var iNum2 = parseInt("010", 8);	//返回 8
+var iNum3 = parseInt("010", 10);	//返回 10
+
+ +

在这段代码中,两行代码都把字符 "010" 解析成一个数字。第一行代码把这个字符串看作八进制的值,解析它的方式与第二行代码(声明基数为 8)相同。最后一行代码声明基数为 10,所以 iNum3 最后等于 10。

+ +

参阅

+ +

请参阅 JavaScript 参考手册提供的有关 parseInt() 方法的详细信息:parseInt()

+ + +

parseFloat()

+ +

parseFloat() 方法与 parseInt() 方法的处理方式相似,从位置 0 开始查看每个字符,直到找到第一个非有效的字符为止,然后把该字符之前的字符串转换成整数。

+ +

不过,对于这个方法来说,第一个出现的小数点是有效字符。如果有两个小数点,第二个小数点将被看作无效的。parseFloat() 会把这个小数点之前的字符转换成数字。这意味着字符串 "11.22.33" 将被解析成 11.22。

+ +

使用 parseFloat() 方法的另一不同之处在于,字符串必须以十进制形式表示浮点数,而不是用八进制或十六进制。该方法会忽略前导 0,所以八进制数 0102 将被解析为 102。对于十六进制数 0xA,该方法将返回 NaN,因为在浮点数中,x 不是有效字符。(注释:经测试,具体的浏览器实现会返回 0,而不是 NaN。)

+ +

此外,parseFloat() 方法也没有基模式。

+ +

下面是使用 parseFloat() 方法的一些示例:

+ +
+var fNum1 = parseFloat("12345red");	//返回 12345
+var fNum2 = parseFloat("0xA");	//返回 NaN
+var fNum3 = parseFloat("11.2");	//返回 11.2
+var fNum4 = parseFloat("11.22.33");	//返回 11.22
+var fNum5 = parseFloat("0102");	//返回 102
+var fNum1 = parseFloat("red");	//返回 NaN
+
+ +

亲自试一试

+ +

参阅

+ +

请参阅 JavaScript 参考手册提供的有关 parseFloat() 方法的详细信息:parseFloat()

+
+ + +
+

强制类型转换

+ +

您还可以使用强制类型转换(type casting)来处理转换值的类型。使用强制类型转换可以访问特定的值,即使它是另一种类型的。

+ +

编者注:cast 有“铸造”之意,很贴合“强制转换”的意思。

+ +

ECMAScript 中可用的 3 种强制类型转换如下:

+ +
    +
  • Boolean(value) - 把给定的值转换成 Boolean 型;
  • +
  • Number(value) - 把给定的值转换成数字(可以是整数或浮点数);
  • +
  • String(value) - 把给定的值转换成字符串;
  • +
+ +

用这三个函数之一转换值,将创建一个新值,存放由原始值直接转换成的值。这会造成意想不到的后果。

+ +

Boolean() 函数

+ +

当要转换的值是至少有一个字符的字符串、非 0 数字或对象时,Boolean() 函数将返回 true。如果该值是空字符串、数字 0、undefined 或 null,它将返回 false。

+ +

可以用下面的代码测试 Boolean 型的强制类型转换:

+ +
+var b1 = Boolean("");		//false - 空字符串
+var b2 = Boolean("hello");		//true - 非空字符串
+var b1 = Boolean(50);		//true - 非零数字
+var b1 = Boolean(null);		//false - null
+var b1 = Boolean(0);		//false - 零
+var b1 = Boolean(new object());	//true - 对象
+
+ +

Number() 函数

+ +

Number() 函数的强制类型转换与 parseInt() 和 parseFloat() 方法的处理方式相似,只是它转换的是整个值,而不是部分值。

+ +

还记得吗,parseInt() 和 parseFloat() 方法只转换第一个无效字符之前的字符串,因此 "1.2.3" 将分别被转换为 "1" 和 "1.2"。

+ +

用 Number() 进行强制类型转换,"1.2.3" 将返回 NaN,因为整个字符串值不能转换成数字。如果字符串值能被完整地转换,Number() 将判断是调用 parseInt() 方法还是 parseFloat() 方法。

+ +

下表说明了对不同的值调用 Number() 方法会发生的情况:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
用法结果
Number(false)0
Number(true)1
Number(undefined)NaN
Number(null)0
Number("1.2")1.2
Number("12")12
Number("1.2.3")NaN
Number(new object())NaN
Number(50)50
+ +

String() 函数

+ +

最后一种强制类型转换方法 String() 是最简单的,因为它可把任何值转换成字符串。

+ +

要执行这种强制类型转换,只需要调用作为参数传递进来的值的 toString() 方法,即把 12 转换成 "12",把 true 转换成 "true",把 false 转换成 "false",以此类推。

+ +

强制转换成字符串和调用 toString() 方法的唯一不同之处在于,对 null 和 undefined 值强制类型转换可以生成字符串而不引发错误:

+ +
+var s1 = String(null);	//"null"
+var oNull = null;
+var s2 = oNull.toString();	//会引发错误
+
+ +

在处理 ECMAScript 这样的弱类型语言时,强制类型转换非常有用,不过应该确保使用值的正确。

+
+ + +
+ +
+ + + + +
diff --git a/app/src/main/assets/help/w3c/继承机制实例.html b/app/src/main/assets/help/w3c/继承机制实例.html new file mode 100644 index 00000000..185f4fb8 --- /dev/null +++ b/app/src/main/assets/help/w3c/继承机制实例.html @@ -0,0 +1,34 @@ +
+ +

ECMAScript 继承机制实例

+ +
+ +
+ +
+

本节使用一个经典的例子解释 ECMAScript 的继承机制。

+
+ +
+

继承机制实例

+ +

说明继承机制最简单的方式是,利用一个经典的例子 - 几何形状。实际上,几何形状只有两种,即椭圆形(是圆形的)和多边形(具有一定数量的边)。圆是椭圆的一种,它只有一个焦点。三角形、矩形和五边形都是多边形的一种,具有不同数量的边。正方形是矩形的一种,所有的边等长。这就构成了一种完美的继承关系。

+ +

在这个例子中,形状(Shape)是椭圆形(Ellipse)和多边形(Polygon)的基类(base class)(所有类都由它继承而来)。椭圆具有一个属性 foci,说明椭圆具有的焦点的个数。圆形(Circle)继承了椭圆形,因此圆形是椭圆形的子类(subclass),椭圆形是圆形的超类(superclass)。同样,三角形(Triangle)、矩形(Rectangle)和五边形(Pentagon)都是多边形的子类,多边形是它们的超类。最后,正方形(Square)继承了矩形。

+ +

最好用图来解释这种继承关系,这是 UML(统一建模语言)的用武之地。UML 的主要用途之一是,可视化地表示像继承这样的复杂对象关系。下面的图示是解释 Shape 和它的子类之间关系的 UML 图示:

+ +继承机制 UML 图示实例 + +

在 UML 中,每个方框表示一个类,由类名说明。三角形 、矩形和五边形顶部的线段汇集在一起,指向形状,说明这些类都由形状继承而来。同样,从正方形指向矩形的箭头说明了它们之间的继承关系。

+ +
+ + +
+ +
+ + +
diff --git a/app/src/main/assets/help/w3c/继承机制实现.html b/app/src/main/assets/help/w3c/继承机制实现.html new file mode 100644 index 00000000..526f441a --- /dev/null +++ b/app/src/main/assets/help/w3c/继承机制实现.html @@ -0,0 +1,329 @@ +
+ +

ECMAScript 继承机制实现

+ +
+ +
+ + +
+

继承机制的实现

+ +

要用 ECMAScript 实现继承机制,您可以从要继承的基类入手。所有开发者定义的类都可作为基类。出于安全原因,本地类和宿主类不能作为基类,这样可以防止公用访问编译过的浏览器级的代码,因为这些代码可以被用于恶意攻击。

+ +

选定基类后,就可以创建它的子类了。是否使用基类完全由你决定。有时,你可能想创建一个不能直接使用的基类,它只是用于给子类提供通用的函数。在这种情况下,基类被看作抽象类。

+ +

尽管 ECMAScript 并没有像其他语言那样严格地定义抽象类,但有时它的确会创建一些不允许使用的类。通常,我们称这种类为抽象类。

+ +

创建的子类将继承超类的所有属性和方法,包括构造函数及方法的实现。记住,所有属性和方法都是公用的,因此子类可直接访问这些方法。子类还可添加超类中没有的新属性和方法,也可以覆盖超类的属性和方法。

+ +

继承的方式

+ +

和其他功能一样,ECMAScript 实现继承的方式不止一种。这是因为 JavaScript 中的继承机制并不是明确规定的,而是通过模仿实现的。这意味着所有的继承细节并非完全由解释程序处理。作为开发者,你有权决定最适用的继承方式。

+ +

下面为您介绍几种具体的继承方式。

+
+ + +
+

对象冒充

+ +

构想原始的 ECMAScript 时,根本没打算设计对象冒充(object masquerading)。它是在开发者开始理解函数的工作方式,尤其是如何在函数环境中使用 this 关键字后才发展出来。

+ +

其原理如下:构造函数使用 this 关键字给所有属性和方法赋值(即采用类声明的构造函数方式)。因为构造函数只是一个函数,所以可使 ClassA 构造函数成为 ClassB 的方法,然后调用它。ClassB 就会收到 ClassA 的构造函数中定义的属性和方法。例如,用下面的方式定义 ClassA 和 ClassB:

+ +
+function ClassA(sColor) {
+    this.color = sColor;
+    this.sayColor = function () {
+        alert(this.color);
+    };
+}
+
+function ClassB(sColor) {
+}
+
+ +

还记得吗?关键字 this 引用的是构造函数当前创建的对象。不过在这个方法中,this 指向的所属的对象。这个原理是把 ClassA 作为常规函数来建立继承机制,而不是作为构造函数。如下使用构造函数 ClassB 可以实现继承机制:

+ +
+function ClassB(sColor) {
+    this.newMethod = ClassA;
+    this.newMethod(sColor);
+    delete this.newMethod;
+}
+
+ +

在这段代码中,为 ClassA 赋予了方法 newMethod(请记住,函数名只是指向它的指针)。然后调用该方法,传递给它的是 ClassB 构造函数的参数 sColor。最后一行代码删除了对 ClassA 的引用,这样以后就不能再调用它。

+ +

所有新属性和新方法都必须在删除了新方法的代码行后定义。否则,可能会覆盖超类的相关属性和方法:

+ +
+function ClassB(sColor, sName) {
+    this.newMethod = ClassA;
+    this.newMethod(sColor);
+    delete this.newMethod;
+
+    this.name = sName;
+    this.sayName = function () {
+        alert(this.name);
+    };
+}
+
+ +

为证明前面的代码有效,可以运行下面的例子:

+ +
+var objA = new ClassA("blue");
+var objB = new ClassB("red", "John");
+objA.sayColor();	//输出 "blue"
+objB.sayColor();	//输出 "red"
+objB.sayName();		//输出 "John"
+
+ +

对象冒充可以实现多重继承

+ +

有趣的是,对象冒充可以支持多重继承。也就是说,一个类可以继承多个超类。用 UML 表示的多重继承机制如下图所示:

+ +继承机制 UML 图示实例 + +

例如,如果存在两个类 ClassX 和 ClassY,ClassZ 想继承这两个类,可以使用下面的代码:

+ +
+function ClassZ() {
+    this.newMethod = ClassX;
+    this.newMethod();
+    delete this.newMethod;
+
+    this.newMethod = ClassY;
+    this.newMethod();
+    delete this.newMethod;
+}
+
+ +

TIY

+ +

这里存在一个弊端,如果存在两个类 ClassX 和 ClassY 具有同名的属性或方法,ClassY 具有高优先级。因为它从后面的类继承。除这点小问题之外,用对象冒充实现多重继承机制轻而易举。

+ +

由于这种继承方法的流行,ECMAScript 的第三版为 Function 对象加入了两个方法,即 call() 和 apply()。

+
+ + +
+

call() 方法

+ +

call() 方法是与经典的对象冒充方法最相似的方法。它的第一个参数用作 this 的对象。其他参数都直接传递给函数自身。例如:

+ +
+function sayColor(sPrefix,sSuffix) {
+    alert(sPrefix + this.color + sSuffix);
+};
+
+var obj = new Object();
+obj.color = "blue";
+
+sayColor.call(obj, "The color is ", "a very nice color indeed.");
+
+ +

在这个例子中,函数 sayColor() 在对象外定义,即使它不属于任何对象,也可以引用关键字 this。对象 obj 的 color 属性等于 blue。调用 call() 方法时,第一个参数是 obj,说明应该赋予 sayColor() 函数中的 this 关键字值是 obj。第二个和第三个参数是字符串。它们与 sayColor() 函数中的参数 sPrefix 和 sSuffix 匹配,最后生成的消息 "The color is blue, a very nice color indeed." 将被显示出来。

+ +

要与继承机制的对象冒充方法一起使用该方法,只需将前三行的赋值、调用和删除代码替换即可:

+ +
+function ClassB(sColor, sName) {
+    //this.newMethod = ClassA;
+    //this.newMethod(color);
+    //delete this.newMethod;
+    ClassA.call(this, sColor);
+
+    this.name = sName;
+    this.sayName = function () {
+        alert(this.name);
+    };
+}
+
+ +

TIY

+ +

这里,我们需要让 ClassA 中的关键字 this 等于新创建的 ClassB 对象,因此 this 是第一个参数。第二个参数 sColor 对两个类来说都是唯一的参数。

+
+ + +
+

apply() 方法

+ +

apply() 方法有两个参数,用作 this 的对象和要传递给函数的参数的数组。例如:

+ +
+function sayColor(sPrefix,sSuffix) {
+    alert(sPrefix + this.color + sSuffix);
+};
+
+var obj = new Object();
+obj.color = "blue";
+
+sayColor.apply(obj, new Array("The color is ", "a very nice color indeed."));
+
+ +

这个例子与前面的例子相同,只是现在调用的是 apply() 方法。调用 apply() 方法时,第一个参数仍是 obj,说明应该赋予 sayColor() 函数中的 this 关键字值是 obj。第二个参数是由两个字符串构成的数组,与 sayColor() 函数中的参数 sPrefix 和 sSuffix 匹配,最后生成的消息仍是 "The color is blue, a very nice color indeed.",将被显示出来。

+ +

该方法也用于替换前三行的赋值、调用和删除新方法的代码:

+ +
+function ClassB(sColor, sName) {
+    //this.newMethod = ClassA;
+    //this.newMethod(color);
+    //delete this.newMethod;
+    ClassA.apply(this, new Array(sColor));
+
+    this.name = sName;
+    this.sayName = function () {
+        alert(this.name);
+    };
+}
+
+ +

同样的,第一个参数仍是 this,第二个参数是只有一个值 color 的数组。可以把 ClassB 的整个 arguments 对象作为第二个参数传递给 apply() 方法:

+ +
+function ClassB(sColor, sName) {
+    //this.newMethod = ClassA;
+    //this.newMethod(color);
+    //delete this.newMethod;
+    ClassA.apply(this, arguments);
+
+    this.name = sName;
+    this.sayName = function () {
+        alert(this.name);
+    };
+}
+
+ +

TIY

+ +

当然,只有超类中的参数顺序与子类中的参数顺序完全一致时才可以传递参数对象。如果不是,就必须创建一个单独的数组,按照正确的顺序放置参数。此外,还可使用 call() 方法。

+
+ + +
+

原型链(prototype chaining)

+ +

继承这种形式在 ECMAScript 中原本是用于原型链的。上一章介绍了定义类的原型方式。原型链扩展了这种方式,以一种有趣的方式实现继承机制。

+ +

在上一章学过,prototype 对象是个模板,要实例化的对象都以这个模板为基础。总而言之,prototype 对象的任何属性和方法都被传递给那个类的所有实例。原型链利用这种功能来实现继承机制。

+ +

如果用原型方式重定义前面例子中的类,它们将变为下列形式:

+ +
+function ClassA() {
+}
+
+ClassA.prototype.color = "blue";
+ClassA.prototype.sayColor = function () {
+    alert(this.color);
+};
+
+function ClassB() {
+}
+
+ClassB.prototype = new ClassA();
+
+ +

原型方式的神奇之处在于突出显示的蓝色代码行。这里,把 ClassB 的 prototype 属性设置成 ClassA 的实例。这很有意思,因为想要 ClassA 的所有属性和方法,但又不想逐个将它们 ClassB 的 prototype 属性。还有比把 ClassA 的实例赋予 prototype 属性更好的方法吗?

+ +

注意:调用 ClassA 的构造函数,没有给它传递参数。这在原型链中是标准做法。要确保构造函数没有任何参数。

+ +

与对象冒充相似,子类的所有属性和方法都必须出现在 prototype 属性被赋值后,因为在它之前赋值的所有方法都会被删除。为什么?因为 prototype 属性被替换成了新对象,添加了新方法的原始对象将被销毁。所以,为 ClassB 类添加 name 属性和 sayName() 方法的代码如下:

+ +
+function ClassB() {
+}
+
+ClassB.prototype = new ClassA();
+
+ClassB.prototype.name = "";
+ClassB.prototype.sayName = function () {
+    alert(this.name);
+};
+
+ +

可通过运行下面的例子测试这段代码:

+ +
+var objA = new ClassA();
+var objB = new ClassB();
+objA.color = "blue";
+objB.color = "red";
+objB.name = "John";
+objA.sayColor();
+objB.sayColor();
+objB.sayName();
+
+ +

TIY

+ +

此外,在原型链中,instanceof 运算符的运行方式也很独特。对 ClassB 的所有实例,instanceof 为 ClassA 和 ClassB 都返回 true。例如:

+ +
+var objB = new ClassB();
+alert(objB instanceof ClassA);	//输出 "true"
+alert(objB instanceof ClassB);	//输出 "true"
+
+ +

在 ECMAScript 的弱类型世界中,这是极其有用的工具,不过使用对象冒充时不能使用它。

+ +

原型链的弊端是不支持多重继承。记住,原型链会用另一类型的对象重写类的 prototype 属性。

+
+ + +
+

混合方式

+ +

这种继承方式使用构造函数定义类,并非使用任何原型。对象冒充的主要问题是必须使用构造函数方式,这不是最好的选择。不过如果使用原型链,就无法使用带参数的构造函数了。开发者如何选择呢?答案很简单,两者都用。

+ +

在前一章,我们曾经讲解过创建类的最好方式是用构造函数定义属性,用原型定义方法。这种方式同样适用于继承机制,用对象冒充继承构造函数的属性,用原型链继承 prototype 对象的方法。用这两种方式重写前面的例子,代码如下:

+ +
+function ClassA(sColor) {
+    this.color = sColor;
+}
+
+ClassA.prototype.sayColor = function () {
+    alert(this.color);
+};
+
+function ClassB(sColor, sName) {
+    ClassA.call(this, sColor);
+    this.name = sName;
+}
+
+ClassB.prototype = new ClassA();
+
+ClassB.prototype.sayName = function () {
+    alert(this.name);
+};
+
+ +

在此例子中,继承机制由两行突出显示的蓝色代码实现。在第一行突出显示的代码中,在 ClassB 构造函数中,用对象冒充继承 ClassA 类的 sColor 属性。在第二行突出显示的代码中,用原型链继承 ClassA 类的方法。由于这种混合方式使用了原型链,所以 instanceof 运算符仍能正确运行。

+ +

下面的例子测试了这段代码:

+ +
+var objA = new ClassA("blue");
+var objB = new ClassB("red", "John");
+objA.sayColor();	//输出 "blue"
+objB.sayColor();	//输出 "red"
+objB.sayName();	//输出 "John"
+
+ +

TIY

+
+ + +
+ +
+ + +
diff --git a/app/src/main/assets/help/w3c/语法.html b/app/src/main/assets/help/w3c/语法.html new file mode 100644 index 00000000..2e9f7b60 --- /dev/null +++ b/app/src/main/assets/help/w3c/语法.html @@ -0,0 +1,105 @@ +
+ +

ECMAScript 语法

+ +
+ +
+ +
+

熟悉 Java、C 和 Perl 这些语言的开发者会发现 ECMAScript 的语法很容易掌握,因为它借用了这些语言的语法。

+

Java 和 ECMAScript 有一些关键的语法特性相同,也有一些完全不同。

+
+ + +
+

区分大小写

+ +

与 Java 一样,变量、函数名、运算符以及其他一切东西都是区分大小写的。

+ +

比如:

+ +

变量 test 与变量 TEST 是不同的。

+
+ + +
+

变量是弱类型的

+ +

与 Java 和 C 不同,ECMAScript 中的变量无特定的类型,定义变量时只用 var 运算符,可以将它初始化为任意值。

+ +

因此,可以随时改变变量所存数据的类型(尽量避免这样做)。

+ +

例子

+ +
+var color = "red";
+var num = 25;
+var visible = true;
+
+
+ + +
+

每行结尾的分号可有可无

+ +

Java、C 和 Perl 都要求每行代码以分号(;)结束才符合语法。

+ +

ECMAScript 则允许开发者自行决定是否以分号结束一行代码。如果没有分号,ECMAScript 就把折行代码的结尾看做该语句的结尾(与 Visual Basic 和 VBScript 相似),前提是这样没有破坏代码的语义。

+ +

最好的代码编写习惯是总加入分号,因为没有分号,有些浏览器就不能正确运行,不过根据 ECMAScript 标准,下面两行代码都是正确的:

+ +
+var test1 = "red"
+var test2 = "blue";
+
+
+ + +
+

注释与 Java、C 和 PHP 语言的注释相同

+ +

ECMAScript 借用了这些语言的注释语法。

+ +

有两种类型的注释:

+ +
    +
  • 单行注释以双斜杠开头(//)
  • +
  • 多行注释以单斜杠和星号开头(/*),以星号和单斜杠结尾(*/)
  • +
+ +
+//this is a single-line comment
+
+/*this is a multi-
+line comment*/
+
+
+ + +
+

括号表示代码块

+ +

从 Java 中借鉴的另一个概念是代码块。

+ +

代码块表示一系列应该按顺序执行的语句,这些语句被封装在左括号({)和右括号(})之间。

+ +

例如:

+ +
+if (test1 == "red") {
+    test1 = "blue";
+    alert(test1);
+}
+
+
+ + +
+ +
+ + + + +
diff --git a/app/src/main/assets/help/w3c/赋值运算符.html b/app/src/main/assets/help/w3c/赋值运算符.html new file mode 100644 index 00000000..cc996926 --- /dev/null +++ b/app/src/main/assets/help/w3c/赋值运算符.html @@ -0,0 +1,55 @@ +
+ +

ECMAScript 赋值运算符

+ +
+ +
+ + +
+

赋值运算符

+ +

简单的赋值运算符由等号(=)实现,只是把等号右边的值赋予等号左边的变量。

+ +

例如:

+ +
var iNum = 10;
+ +

复合赋值运算是由乘性运算符、加性运算符或位移运算符加等号(=)实现的。这些赋值运算符是下列这些常见情况的缩写形式:

+ +
+var iNum = 10;
+iNum = iNum + 10;
+
+ +

可以用一个复合赋值运算符改写第二行代码:

+ +
+var iNum = 10;
+iNum += 10;
+
+ +

每种主要的算术运算以及其他几个运算都有复合赋值运算符:

+ +
    +
  • 乘法/赋值(*=)
  • +
  • 除法/赋值(/=)
  • +
  • 取模/赋值(%=)
  • +
  • 加法/赋值(+=)
  • +
  • 减法/赋值(-=)
  • +
  • 左移/赋值(<<=)
  • +
  • 有符号右移/赋值(>>=)
  • +
  • 无符号右移/赋值(>>>=)
  • +
+
+ + +
+ +
+ + + + +
diff --git a/app/src/main/assets/help/w3c/迭代语句.html b/app/src/main/assets/help/w3c/迭代语句.html new file mode 100644 index 00000000..43ae1586 --- /dev/null +++ b/app/src/main/assets/help/w3c/迭代语句.html @@ -0,0 +1,108 @@ +
+ +

ECMAScript 迭代语句

+ +
+ +
+ +
+

迭代语句又叫循环语句,声明一组要反复执行的命令,直到满足某些条件为止。

+

循环通常用于迭代数组的值(因此而得名),或者执行重复的算术任务。

+

本节为您介绍 ECMAScript 提供的四种迭代语句。

+
+ + +
+

do-while 语句

+ +

do-while 语句是后测试循环,即退出条件在执行循环内部的代码之后计算。这意味着在计算表达式之前,至少会执行循环主体一次。

+ +

它的语法如下:

+ +
do {statement} while (expression);
+ +

例子:

+ +
+var i = 0;
+do {i += 2;} while (i < 10);
+
+
+ + +
+

while 语句

+ +

while 语句是前测试循环。这意味着退出条件是在执行循环内部的代码之前计算的。因此,循环主体可能根本不被执行。

+ +

它的语法如下:

+ +
while (expression) statement
+ +

例子:

+ +
+var i = 0;
+while (i < 10) {
+  i += 2;
+}
+
+
+ + +
+

for 语句

+ +

for 语句是前测试循环,而且在进入循环之前,能够初始化变量,并定义循环后要执行的代码。

+ +

它的语法如下:

+ +
for (initialization; expression; post-loop-expression) statement
+ +

注意:post-loop-expression 之后不能写分号,否则无法运行。

+ +

例子:

+ +
+iCount = 6;
+for (var i = 0; i < iCount; i++) {
+  alert(i);
+}
+
+ +

这段代码定义了初始值为 0 的变量 i。只有当条件表达式(i < iCount)的值为 true 时,才进入 for 循环,这样循环主体可能不被执行。如果执行了循环主体,那么将执行循环后表达式,并迭代变量 i。

+
+ + +
+

for-in 语句

+ +

for 语句是严格的迭代语句,用于枚举对象的属性。

+ +

它的语法如下:

+ +
for (property in expression) statement
+ +

例子:

+ +
+for (sProp in window) {
+  alert(sProp);
+}
+
+ +

这里,for-in 语句用于显示 window 对象的所有属性。

+ +

前面讨论过的 PropertyIsEnumerable() 是 ECMAScript 中专门用于说明属性是否可以用 for-in 语句访问的方法。

+
+ + +
+ +
+ + + + +
diff --git a/app/src/main/assets/help/w3c/逗号运算符.html b/app/src/main/assets/help/w3c/逗号运算符.html new file mode 100644 index 00000000..d439cafa --- /dev/null +++ b/app/src/main/assets/help/w3c/逗号运算符.html @@ -0,0 +1,30 @@ +
+ +

ECMAScript 逗号运算符

+ +
+ +
+ + +
+

逗号运算符

+ +

用逗号运算符可以在一条语句中执行多个运算。

+ +

例如:

+ +
var iNum1 = 1, iNum = 2, iNum3 = 3;
+ +

逗号运算符常用变量声明中。

+
+ + +
+ +
+ + + + +
diff --git a/app/src/main/assets/help/w3c/逻辑运算符.html b/app/src/main/assets/help/w3c/逻辑运算符.html new file mode 100644 index 00000000..c4900005 --- /dev/null +++ b/app/src/main/assets/help/w3c/逻辑运算符.html @@ -0,0 +1,294 @@ +
+ +

ECMAScript Boolean 运算符

+ +
+ +
+ + +
+

Boolean 运算符非常重要,它使得程序语言得以正常运行。

+

Boolean 运算符有三种:NOT、AND 和 OR。

+
+ + +
+

ToBoolean 操作

+ +

在学习各种逻辑运算符之前,让我们先了解一下 ECMAScript-262 v5 规范中描述的 ToBoolean 操作。

+ +

抽象操作 ToBoolean 将其参数按照下表中的规则转换为逻辑值:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
参数类型结果
Undefinedfalse
Nullfalse
Boolean结果等于输入的参数(不转换)
Number如果参数为 +0, -0 或 NaN,则结果为 false;否则为 true。
String如果参数为空字符串,则结果为 false;否则为 true。
Objecttrue
+
+ + +
+

逻辑 NOT 运算符

+ +

在 ECMAScript 中,逻辑 NOT 运算符与 C 和 Java 中的逻辑 NOT 运算符相同,都由感叹号(!)表示。

+ +

与逻辑 OR 和逻辑 AND 运算符不同的是,逻辑 NOT 运算符返回的一定是 Boolean 值

+ +

逻辑 NOT 运算符的行为如下:

+ +
    +
  • 如果运算数是对象,返回 false
  • +
  • 如果运算数是数字 0,返回 true
  • +
  • 如果运算数是 0 以外的任何数字,返回 false
  • +
  • 如果运算数是 null,返回 true
  • +
  • 如果运算数是 NaN,返回 true
  • +
  • 如果运算数是 undefined,发生错误
  • +
+ +

通常,该运算符用于控制循环:

+ +
+var bFound = false;
+var i = 0;
+
+while (!bFound) {
+  if (aValue[i] == vSearchValues) {
+    bFound = true;
+  } else {
+    i++;
+  }
+}
+
+ +

在这个例子中,Boolean 变量(bFound)用于记录检索是否成功。找到问题中的数据项时,bFound 将被设置为 true,!bFound 将等于 false,意味着运行将跳出 while 循环。

+ +

判断 ECMAScript 变量的 Boolean 值时,也可以使用逻辑 NOT 运算符。这样做需要在一行代码中使用两个 NOT 运算符。无论运算数是什么类型,第一个 NOT 运算符返回 Boolean 值。第二个 NOT 将对该 Boolean 值求负,从而给出变量真正的 Boolean 值。

+ +
+var bFalse = false;
+var sRed = "red";
+var iZero = 0;
+var iThreeFourFive = 345;
+var oObject = new Object;
+
+document.write("bFalse 的逻辑值是 " + (!!bFalse));
+document.write("sRed 的逻辑值是 " + (!!sRed));
+document.write("iZero 的逻辑值是 " + (!!iZero));
+document.write("iThreeFourFive 的逻辑值是 " + (!!iThreeFourFive));
+document.write("oObject 的逻辑值是 " + (!!oObject));
+
+ +

结果:

+ +
+bFalse 的逻辑值是 false
+sRed 的逻辑值是 true
+iZero 的逻辑值是 false
+iThreeFourFive 的逻辑值是 true
+oObject 的逻辑值是 true 
+
+
+ + +
+

逻辑 AND 运算符

+ +

在 ECMAScript 中,逻辑 AND 运算符用双和号(&&)表示:

+ +

例如:

+ +
+var bTrue = true;
+var bFalse = false;
+var bResult = bTrue && bFalse;
+
+ +

下面的真值表描述了逻辑 AND 运算符的行为:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
运算数 1运算数 2结果
truetruetrue
truefalsefalse
falsetruefalse
falsefalsefalse
+ +

逻辑 AND 运算的运算数可以是任何类型的,不止是 Boolean 值。

+ +

如果某个运算数不是原始的 Boolean 型值,逻辑 AND 运算并不一定返回 Boolean 值:

+ +
    +
  • 如果一个运算数是对象,另一个是 Boolean 值,返回该对象。
  • +
  • 如果两个运算数都是对象,返回第二个对象。
  • +
  • 如果某个运算数是 null,返回 null。
  • +
  • 如果某个运算数是 NaN,返回 NaN。
  • +
  • 如果某个运算数是 undefined,发生错误。
  • +
+ +

与 Java 中的逻辑 AND 运算相似,ECMAScript 中的逻辑 AND 运算也是简便运算,即如果第一个运算数决定了结果,就不再计算第二个运算数。对于逻辑 AND 运算来说,如果第一个运算数是 false,那么无论第二个运算数的值是什么,结果都不可能等于 true。

+ +

考虑下面的例子:

+ +
+var bTrue = true;
+var bResult = (bTrue && bUnknown);	//发生错误
+alert(bResult);			//这一行不会执行
+
+ +

这段代码在进行逻辑 AND 运算时将引发错误,因为变量 bUnknown 是未定义的。变量 bTrue 的值为 true,因为逻辑 AND 运算将继续计算变量 bUnknown。这样做就会引发错误,因为 bUnknown 的值是 undefined,不能用于逻辑 AND 运算。

+ +

如果修改这个例子,把第一个数设为 false,那么就不会发生错误:

+ +
+var bFalse = false;
+var bResult = (bFalse && bUnknown);
+alert(bResult);			//输出 "false"
+
+ +

在这段代码中,脚本将输出逻辑 AND 运算返回的值,即字符串 "false"。即使变量 bUnknown 的值为 undefined,它也不会被计算,因为第一个运算数的值是 false。

+ +

提示:在使用逻辑 AND 运算符时,必须记住它的这种简便计算特性。

+
+ + +
+

逻辑 OR 运算符

+ +

ECMAScript 中的逻辑 OR 运算符与 Java 中的相同,都由双竖线(||)表示:

+ +
+var bTrue = true;
+var bFalse = false;
+var bResult = bTrue || bFalse;
+
+ +

下面的真值表描述了逻辑 OR 运算符的行为:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
运算数 1运算数 2结果
truetruetrue
truefalsetrue
falsetruetrue
falsefalsefalse
+ +

与逻辑 AND 运算符相似,如果某个运算数不是 Boolean 值,逻辑 OR 运算并不一定返回 Boolean 值:

+ +
    +
  • 如果一个运算数是对象,并且该对象左边的运算数值均为 false,则返回该对象。
  • +
  • 如果两个运算数都是对象,返回第一个对象。
  • +
  • 如果最后一个运算数是 null,并且其他运算数值均为 false,则返回 null。
  • +
  • 如果最后一个运算数是 NaN,并且其他运算数值均为 false,则返回 NaN。
  • +
  • 如果某个运算数是 undefined,发生错误。
  • +
+ +

与逻辑 AND 运算符一样,逻辑 OR 运算也是简便运算。对于逻辑 OR 运算符来说,如果第一个运算数值为 true,就不再计算第二个运算数。

+ +

例如:

+ +
+var bTrue = true;
+var bResult = (bTrue || bUnknown);
+alert(bResult);			//输出 "true"
+
+ +

与前面的例子相同,变量 bUnknown 是未定义的。不过,由于变量 bTrue 的值为 true,bUnknown 不会被计算,因此输出的是 "true"。

+ +

如果把 bTrue 改为 false,将发生错误:

+ +
+var bFalse = false;
+var bResult = (bFalse || bUnknown);	//发生错误
+alert(bResult);			//不会执行这一行
+
+
+ + +
+ +
+ + + + +
diff --git a/app/src/main/assets/help/w3c/闭包(closure).html b/app/src/main/assets/help/w3c/闭包(closure).html new file mode 100644 index 00000000..7253743c --- /dev/null +++ b/app/src/main/assets/help/w3c/闭包(closure).html @@ -0,0 +1,68 @@ +
+ +

ECMAScript 闭包(closure)

+ +
+ +
+ + +
+

ECMAScript 最易让人误解的一点是,它支持闭包(closure)。

+

闭包,指的是词法表示包括不被计算的变量的函数,也就是说,函数可以使用函数之外定义的变量。

+
+ + +
+

简单的闭包实例

+ +

在 ECMAScript 中使用全局变量是一个简单的闭包实例。请思考下面这段代码:

+ +
+var sMessage = "hello world";
+
+function sayHelloWorld() {
+  alert(sMessage);
+}
+
+sayHelloWorld();
+
+ +

在上面这段代码中,脚本被载入内存后,并没有为函数 sayHelloWorld() 计算变量 sMessage 的值。该函数捕获 sMessage 的值只是为了以后的使用,也就是说,解释程序知道在调用该函数时要检查 sMessage 的值。sMessage 将在函数调用 sayHelloWorld() 时(最后一行)被赋值,显示消息 "hello world"。

+
+ + +
+

复杂的闭包实例

+ +

在一个函数中定义另一个会使闭包变得更加复杂。例如:

+ +
+var iBaseNum = 10;
+
+function addNum(iNum1, iNum2) {
+  function doAdd() {
+    return iNum1 + iNum2 + iBaseNum;
+  }
+  return doAdd();
+}
+
+ +

这里,函数 addNum() 包括函数 doAdd() (闭包)。内部函数是一个闭包,因为它将获取外部函数的参数 iNum1 和 iNum2 以及全局变量 iBaseNum 的值。 addNum() 的最后一步调用了 doAdd(),把两个参数和全局变量相加,并返回它们的和。

+ +

这里要掌握的重要概念是,doAdd() 函数根本不接受参数,它使用的值是从执行环境中获取的。

+ +

可以看到,闭包是 ECMAScript 中非常强大多用的一部分,可用于执行复杂的计算。

+ +

提示:就像使用任何高级函数一样,使用闭包要小心,因为它们可能会变得非常复杂。

+
+ + +
+ +
+ + + + +
diff --git a/app/src/main/assets/help/w3c/面向对象技术.html b/app/src/main/assets/help/w3c/面向对象技术.html new file mode 100644 index 00000000..14f899a5 --- /dev/null +++ b/app/src/main/assets/help/w3c/面向对象技术.html @@ -0,0 +1,61 @@ +
+ +

ECMAScript 面向对象技术

+ +
+ +
+ + +
+

面向对象术语

+ +

对象

+ +

ECMA-262 把对象(object)定义为“属性的无序集合,每个属性存放一个原始值、对象或函数”。严格来说,这意味着对象是无特定顺序的值的数组。

+ +

尽管 ECMAScript 如此定义对象,但它更通用的定义是基于代码的名词(人、地点或事物)的表示。

+ +

+ +

每个对象都由类定义,可以把类看做对象的配方。类不仅要定义对象的接口(interface)(开发者访问的属性和方法),还要定义对象的内部工作(使属性和方法发挥作用的代码)。编译器和解释程序都根据类的说明构建对象。

+ +

实例

+ +

程序使用类创建对象时,生成的对象叫作类的实例(instance)。对类生成的对象的个数的唯一限制来自于运行代码的机器的物理内存。每个实例的行为相同,但实例处理一组独立的数据。由类创建对象实例的过程叫做实例化(instantiation)。

+ +

在前面的章节我们提到过,ECMAScript 并没有正式的类。相反,ECMA-262 把对象定义描述为对象的配方。这是 ECMAScript 逻辑上的一种折中方案,因为对象定义实际上是对象自身。即使类并不真正存在,我们也把对象定义叫做类,因为大多数开发者对此术语更熟悉,而且从功能上说,两者是等价的。

+
+ + +
+

面向对象语言的要求

+ +

一种面向对象语言需要向开发者提供四种基本能力:

+ +
    +
  1. 封装 - 把相关的信息(无论数据或方法)存储在对象中的能力
  2. +
  3. 聚集 - 把一个对象存储在另一个对象内的能力
  4. +
  5. 继承 - 由另一个类(或多个类)得来类的属性和方法的能力
  6. +
  7. 多态 - 编写能以多种方法运行的函数或方法的能力
  8. +
+ +

ECMAScript 支持这些要求,因此可被是看做面向对象的。

+
+ + +
+

对象的构成

+ +

在 ECMAScript 中,对象由特性(attribute)构成,特性可以是原始值,也可以是引用值。如果特性存放的是函数,它将被看作对象的方法(method),否则该特性被看作对象的属性(property)。

+
+ + +
+ +
+ + + + +
diff --git a/app/src/main/assets/javasccript_engine_init.js b/app/src/main/assets/javasccript_engine_init.js index fe87a825..a2269859 100644 --- a/app/src/main/assets/javasccript_engine_init.js +++ b/app/src/main/assets/javasccript_engine_init.js @@ -235,6 +235,10 @@ for(var x in __selector__){ return s[method](arguments[0]); }else if(arguments.length == 2){ return s[method](arguments[0], arguments[1]); + }else if(arguments.length == 3){ + return s[method](arguments[0], arguments[1], arguments[2]); + }else if(arguments.length == 4){ + return s[method](arguments[0], arguments[1], arguments[2], arguments[3]); }else{ return s[method].call(s, Array.prototype.slice.call(arguments)); } diff --git a/app/src/main/assets/sample/JavaScript/HelloWorld.js b/app/src/main/assets/sample/JavaScript/HelloWorld.js new file mode 100644 index 00000000..509a574d --- /dev/null +++ b/app/src/main/assets/sample/JavaScript/HelloWorld.js @@ -0,0 +1,2 @@ +log("Hello world!!!"); +openConsole(); \ No newline at end of file diff --git a/app/src/main/assets/help/documentation/语法.md b/app/src/main/assets/sample/JavaScript/字符串.js similarity index 100% rename from app/src/main/assets/help/documentation/语法.md rename to app/src/main/assets/sample/JavaScript/字符串.js diff --git a/app/src/main/assets/sample/JavaScript/数字.js b/app/src/main/assets/sample/JavaScript/数字.js new file mode 100644 index 00000000..d6bbfb5a --- /dev/null +++ b/app/src/main/assets/sample/JavaScript/数字.js @@ -0,0 +1,7 @@ +a = 5; +b = 6; +c = -1; +x = 1.5; +y = a * x * x + b * x * c; +log("y = " + y); +openConsole(); diff --git a/app/src/main/assets/sample_force_qq_chat.js b/app/src/main/assets/sample/界面/QQ强制聊天.js similarity index 100% rename from app/src/main/assets/sample_force_qq_chat.js rename to app/src/main/assets/sample/界面/QQ强制聊天.js diff --git a/app/src/main/assets/sample_simple_calculator.js b/app/src/main/assets/sample/界面/简单计算器.js similarity index 100% rename from app/src/main/assets/sample_simple_calculator.js rename to app/src/main/assets/sample/界面/简单计算器.js diff --git a/app/src/main/assets/sample_qq_hongbao.js b/app/src/main/assets/sample/自动操作/QQ自动抢红包.js similarity index 100% rename from app/src/main/assets/sample_qq_hongbao.js rename to app/src/main/assets/sample/自动操作/QQ自动抢红包.js diff --git a/app/src/main/assets/sample_wechat_scan.js b/app/src/main/assets/sample/自动操作/微信扫一扫.js similarity index 100% rename from app/src/main/assets/sample_wechat_scan.js rename to app/src/main/assets/sample/自动操作/微信扫一扫.js diff --git a/app/src/main/assets/sample_open_running_services.js b/app/src/main/assets/sample/自动操作/打开自动运行的服务.js similarity index 100% rename from app/src/main/assets/sample_open_running_services.js rename to app/src/main/assets/sample/自动操作/打开自动运行的服务.js diff --git a/app/src/main/assets/sample_alipay_scan.js b/app/src/main/assets/sample/自动操作/支付宝扫一扫.js similarity index 100% rename from app/src/main/assets/sample_alipay_scan.js rename to app/src/main/assets/sample/自动操作/支付宝扫一扫.js diff --git a/app/src/main/assets/sample/选择器/QQ图片轰炸.js b/app/src/main/assets/sample/选择器/QQ图片轰炸.js new file mode 100644 index 00000000..c7839da4 --- /dev/null +++ b/app/src/main/assets/sample/选择器/QQ图片轰炸.js @@ -0,0 +1,6 @@ +launchApp("QQ"); +toast("打开一个聊天窗口,并点击下面的图片按钮"); +for(i = 0; i < 100;i++){ + className("CheckBox").untilFindOne().click(); + id("send_btn").click(); +} \ No newline at end of file diff --git a/app/src/main/java/com/stardust/app/Fragment.java b/app/src/main/java/com/stardust/app/Fragment.java index a82daccf..1e792185 100644 --- a/app/src/main/java/com/stardust/app/Fragment.java +++ b/app/src/main/java/com/stardust/app/Fragment.java @@ -36,9 +36,14 @@ public abstract class Fragment extends android.support.v4.app.Fragment { @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { mView = createView(inflater, container, savedInstanceState); + afterCreateView(); return mView; } + protected void afterCreateView() { + + } + @Nullable public abstract View createView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState); diff --git a/app/src/main/java/com/stardust/scriptdroid/App.java b/app/src/main/java/com/stardust/scriptdroid/App.java index 83b710f0..e9997ec8 100644 --- a/app/src/main/java/com/stardust/scriptdroid/App.java +++ b/app/src/main/java/com/stardust/scriptdroid/App.java @@ -17,6 +17,7 @@ import com.squareup.leakcanary.LeakCanary; import com.stardust.scriptdroid.droid.runtime.action.ActionPerformAccessibilityDelegate; import com.stardust.scriptdroid.tool.ViewTool; import com.stardust.scriptdroid.ui.error.ErrorReportActivity; +import com.stardust.theme.ThemeColor; import com.stardust.theme.ThemeColorManager; import com.stardust.util.CrashHandler; import com.stardust.util.StateObserver; @@ -48,6 +49,7 @@ public class App extends Application { public void onCreate() { super.onCreate(); + ThemeColorManager.setDefaultThemeColor(new ThemeColor(getResources().getColor(R.color.colorPrimary), getResources().getColor(R.color.colorPrimaryDark), getResources().getColor(R.color.colorAccent))); ThemeColorManager.init(this); if (LeakCanary.isInAnalyzerProcess(this)) { return; diff --git a/app/src/main/java/com/stardust/scriptdroid/Pref.java b/app/src/main/java/com/stardust/scriptdroid/Pref.java index db1666a1..1514d762 100644 --- a/app/src/main/java/com/stardust/scriptdroid/Pref.java +++ b/app/src/main/java/com/stardust/scriptdroid/Pref.java @@ -13,6 +13,7 @@ public class Pref { public static final String SAMPLE_SCRIPTS_COPIED = "SAMPLE_SCRIPTS_COPIED"; private static final String KEY_MAX_TEXT_LENGTH_FOR_CODE_COMPLETION = "KEY_MAX_TEXT_LENGTH_FOR_CODE_COMPLETION"; public static final String KEY_DRAWER_HEADER_IMAGE_PATH = "KEY_DRAWER_HEADER_IMAGE_PATH"; + public static final String KEY_APP_BAR_IMAGE_PATH = "KEY_APP_BAR_IMAGE_PATH"; public static SharedPreferences def() { return PreferenceManager.getDefaultSharedPreferences(App.getApp()); @@ -53,4 +54,8 @@ public class Pref { return 2000; } } + + public static boolean isFirstUsing() { + return getDisposableBoolean("isFirstUsing", true); + } } diff --git a/app/src/main/java/com/stardust/scriptdroid/droid/Droid.java b/app/src/main/java/com/stardust/scriptdroid/droid/Droid.java index 4e7055ee..88c94f96 100644 --- a/app/src/main/java/com/stardust/scriptdroid/droid/Droid.java +++ b/app/src/main/java/com/stardust/scriptdroid/droid/Droid.java @@ -10,7 +10,7 @@ import com.stardust.scriptdroid.droid.runtime.ScriptStopException; import com.stardust.scriptdroid.droid.script.JavaScriptEngine; import com.stardust.scriptdroid.droid.script.RhinoJavaScriptEngine; import com.stardust.scriptdroid.droid.script.ScriptExecuteActivity; -import com.stardust.scriptdroid.file.FileUtils; +import com.stardust.scriptdroid.tool.FileUtils; import java.io.File; import java.io.FileNotFoundException; diff --git a/app/src/main/java/com/stardust/scriptdroid/droid/runtime/DroidRuntime.java b/app/src/main/java/com/stardust/scriptdroid/droid/runtime/DroidRuntime.java index fceaa9bd..1bf6d5b1 100644 --- a/app/src/main/java/com/stardust/scriptdroid/droid/runtime/DroidRuntime.java +++ b/app/src/main/java/com/stardust/scriptdroid/droid/runtime/DroidRuntime.java @@ -1,6 +1,5 @@ package com.stardust.scriptdroid.droid.runtime; -import android.accessibilityservice.AccessibilityService; import android.content.ClipData; import android.content.ClipboardManager; import android.content.Context; @@ -11,12 +10,10 @@ import android.graphics.Rect; import android.os.Environment; import android.os.Handler; import android.support.annotation.Nullable; -import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; import android.widget.Toast; import com.afollestad.materialdialogs.MaterialDialog; -import com.stardust.automator.AccessibilityEventCommandHost; import com.stardust.scriptdroid.Pref; import com.stardust.scriptdroid.droid.runtime.action.ActionTarget; import com.stardust.scriptdroid.tool.AccessibilityServiceTool; @@ -28,7 +25,7 @@ import com.stardust.scriptdroid.R; import com.stardust.scriptdroid.droid.runtime.action.Action; import com.stardust.scriptdroid.droid.runtime.action.ActionFactory; import com.stardust.scriptdroid.droid.runtime.action.ActionPerformAccessibilityDelegate; -import com.stardust.scriptdroid.file.FileUtils; +import com.stardust.scriptdroid.tool.FileUtils; import com.stardust.scriptdroid.tool.Shell; import com.stardust.scriptdroid.ui.console.ConsoleActivity; import com.stardust.view.accessibility.AccessibilityServiceUtils; diff --git a/app/src/main/java/com/stardust/scriptdroid/droid/runtime/UiSelector.java b/app/src/main/java/com/stardust/scriptdroid/droid/runtime/UiSelector.java index 3da9d8a8..c133c428 100644 --- a/app/src/main/java/com/stardust/scriptdroid/droid/runtime/UiSelector.java +++ b/app/src/main/java/com/stardust/scriptdroid/droid/runtime/UiSelector.java @@ -7,7 +7,6 @@ import android.util.Log; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; -import com.bezyapps.floatieslibrary.Floaty; import com.stardust.automator.AccessibilityEventCommandHost; import com.stardust.automator.ActionArgument; import com.stardust.automator.UiGlobalSelector; diff --git a/app/src/main/java/com/stardust/scriptdroid/droid/script/JavaScriptEngine.java b/app/src/main/java/com/stardust/scriptdroid/droid/script/JavaScriptEngine.java index dbbeb28b..6dd894f8 100644 --- a/app/src/main/java/com/stardust/scriptdroid/droid/script/JavaScriptEngine.java +++ b/app/src/main/java/com/stardust/scriptdroid/droid/script/JavaScriptEngine.java @@ -2,7 +2,7 @@ package com.stardust.scriptdroid.droid.script; import com.stardust.scriptdroid.App; import com.stardust.scriptdroid.BuildConfig; -import com.stardust.scriptdroid.file.FileUtils; +import com.stardust.scriptdroid.tool.FileUtils; import java.io.IOException; import java.util.HashMap; diff --git a/app/src/main/java/com/stardust/scriptdroid/droid/script/file/SharedPrefScriptFileList.java b/app/src/main/java/com/stardust/scriptdroid/droid/script/file/SharedPrefScriptFileList.java index b2dea3aa..930f02d5 100644 --- a/app/src/main/java/com/stardust/scriptdroid/droid/script/file/SharedPrefScriptFileList.java +++ b/app/src/main/java/com/stardust/scriptdroid/droid/script/file/SharedPrefScriptFileList.java @@ -6,7 +6,7 @@ import android.content.SharedPreferences; import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; import com.stardust.scriptdroid.App; -import com.stardust.scriptdroid.file.FileUtils; +import com.stardust.scriptdroid.tool.FileUtils; import java.lang.reflect.Type; import java.util.ArrayList; diff --git a/app/src/main/java/com/stardust/scriptdroid/external/floating_window/FloatingWindowManger.java b/app/src/main/java/com/stardust/scriptdroid/external/floating_window/FloatingWindowManger.java index 6b3a1c23..ac25b2ff 100644 --- a/app/src/main/java/com/stardust/scriptdroid/external/floating_window/FloatingWindowManger.java +++ b/app/src/main/java/com/stardust/scriptdroid/external/floating_window/FloatingWindowManger.java @@ -1,6 +1,9 @@ package com.stardust.scriptdroid.external.floating_window; import android.content.Intent; +import android.net.Uri; +import android.os.Build; +import android.provider.Settings; import android.view.View; import com.stardust.scriptdroid.App; @@ -16,8 +19,32 @@ import com.stardust.view.ResizableFloaty; public class FloatingWindowManger { public static void showFloatingWindow() { - if (!HoverMenuService.isServiceRunning()) - App.getApp().startService(new Intent(App.getApp(), HoverMenuService.class)); + if (!HoverMenuService.isServiceRunning()) { + if (!hasFloatingWindowPermission()) { + goToFloatingWindowPermissionSetting(); + } else { + HoverMenuService.startService(App.getApp()); + } + } + } + + public static void goToFloatingWindowPermissionSetting() { + String packageName = App.getApp().getPackageName(); + Intent intent; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, + Uri.parse("package:" + packageName)); + } else { + intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, Uri.parse("package:" + packageName)); + } + App.getApp().startActivity(intent); + } + + private static boolean hasFloatingWindowPermission() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + return Settings.canDrawOverlays(App.getApp()); + } + return true; } public static boolean isFloatingWindowShowing() { diff --git a/app/src/main/java/com/stardust/scriptdroid/external/floating_window/HoverMenuAdapter.java b/app/src/main/java/com/stardust/scriptdroid/external/floating_window/HoverMenuAdapter.java index 988b29a0..89b46ccd 100644 --- a/app/src/main/java/com/stardust/scriptdroid/external/floating_window/HoverMenuAdapter.java +++ b/app/src/main/java/com/stardust/scriptdroid/external/floating_window/HoverMenuAdapter.java @@ -77,7 +77,7 @@ public class HoverMenuAdapter implements io.mattcarroll.hover.HoverMenuAdapter { private View getTabViewInner(int index) { String menuItemId = mTabIds.get(index); if (ID_MAIN.equals(menuItemId)) { - return createTabView(R.drawable.ic_android_eat_js); + return createTabView(R.drawable.ic_android_eat_js_100); } else if (ID_RECORD.equals(menuItemId)) { return createTabView(R.drawable.ic_video_record); } else if (ID_SCRIPT_LIST.equals(menuItemId)) { diff --git a/app/src/main/java/com/stardust/scriptdroid/external/floating_window/HoverMenuService.java b/app/src/main/java/com/stardust/scriptdroid/external/floating_window/HoverMenuService.java index 5b2b0a08..7193584e 100644 --- a/app/src/main/java/com/stardust/scriptdroid/external/floating_window/HoverMenuService.java +++ b/app/src/main/java/com/stardust/scriptdroid/external/floating_window/HoverMenuService.java @@ -7,13 +7,15 @@ import android.content.Intent; import android.content.SharedPreferences; import android.os.IBinder; import android.support.annotation.Nullable; -import android.util.Log; import android.view.View; import android.view.WindowManager; +import android.widget.Toast; import com.stardust.hover.HoverMenuBuilder; import com.stardust.hover.SimpleHoverMenuTransitionListener; import com.stardust.hover.WindowHoverMenu; +import com.stardust.scriptdroid.App; +import com.stardust.scriptdroid.R; import com.stardust.scriptdroid.external.floating_window.view.FloatingLayoutBoundsView; import com.stardust.scriptdroid.external.floating_window.view.FloatingLayoutHierarchyView; import com.stardust.scriptdroid.layout_inspector.LayoutInspector; @@ -35,31 +37,50 @@ import io.mattcarroll.hover.defaulthovermenu.window.WindowViewController; public class HoverMenuService extends Service { + public static class ServiceStateChangedEvent { + ServiceStateChangedEvent(boolean state) { + this.state = state; + } + + public boolean state; + } + public static final String MESSAGE_SHOW_AND_EXPAND_MENU = "MESSAGE_SHOW_AND_EXPAND_MENU"; public static final String MESSAGE_SHOW_LAYOUT_HIERARCHY = "MESSAGE_SHOW_LAYOUT_HIERARCHY"; public static final String MESSAGE_SHOW_LAYOUT_BOUNDS = "MESSAGE_SHOW_LAYOUT_BOUNDS"; public static final String MESSAGE_COLLAPSE_MENU = "MESSAGE_COLLAPSE_MENU"; public static final String MESSAGE_MENU_COLLAPSING = "MESSAGE_MENU_COLLAPSING"; - private static WeakReference service; + private static WeakReference service = new WeakReference<>(null); + private static boolean sIsRunning; + + public static void startService(Context context) { + context.startService(new Intent(context, HoverMenuService.class)); + setIsRunning(true); + } public static boolean isServiceRunning() { - return service != null && service.get() != null; + return sIsRunning; } public static void stopService() { - if (isServiceRunning()) { + if (isServiceRunning() && service.get() != null) { service.get().stopSelf(); + setIsRunning(false); } } + private static void setIsRunning(boolean isRunning) { + sIsRunning = isRunning; + EventBus.getDefault().post(new ServiceStateChangedEvent(sIsRunning)); + } + private static final String TAG = "HoverMenuService"; private static final String PREF_FILE = "hover_menu"; private static final String PREF_HOVER_MENU_VISUAL_STATE = "hover_menu_visual_state"; - private boolean mIsRunning; private SharedPreferences mPrefs; private WindowViewController mWindowViewController; @@ -80,14 +101,21 @@ public class HoverMenuService extends Service { @Override public void onCreate() { super.onCreate(); - if (isServiceRunning()) { + if (service.get() != null) { stopSelf(); - } else { - service = new WeakReference<>(this); + return; } + service = new WeakReference<>(this); EventBus.getDefault().register(this); mPrefs = getSharedPreferences(PREF_FILE, Context.MODE_PRIVATE); - initViews(); + try { + initViews(); + } catch (Exception e) { + e.printStackTrace(); + Toast.makeText(this, R.string.text_no_floating_window_permission, Toast.LENGTH_SHORT).show(); + FloatingWindowManger.goToFloatingWindowPermissionSetting(); + stopService(); + } } private void initViews() { @@ -138,18 +166,16 @@ public class HoverMenuService extends Service { @Override public int onStartCommand(Intent intent, int flags, int startId) { - if (!mIsRunning) { - mIsRunning = true; - mWindowHoverMenu.show(); - } + mWindowHoverMenu.show(); return START_STICKY; } @Override public void onDestroy() { mWindowHoverMenu.hide(); - mIsRunning = false; - if (isServiceRunning() && service.get() == this) + EventBus.getDefault().unregister(this); + setIsRunning(false); + if (service.get() == this) service.clear(); } @@ -167,14 +193,14 @@ public class HoverMenuService extends Service { public void onMessageEvent(MessageEvent event) { switch (event.message) { case MESSAGE_SHOW_AND_EXPAND_MENU: - mWindowHoverMenu.show(); + showView(mWindowHoverMenu.getHoverMenuView()); break; case MESSAGE_SHOW_LAYOUT_HIERARCHY: - mWindowHoverMenu.hide(); + mWindowHoverMenu.getHoverMenuView().setVisibility(View.GONE); showView(mFloatingLayoutHierarchyView); break; case MESSAGE_SHOW_LAYOUT_BOUNDS: - mWindowHoverMenu.hide(); + mWindowHoverMenu.getHoverMenuView().setVisibility(View.GONE); showView(mFloatingLayoutBoundsView); break; case MESSAGE_COLLAPSE_MENU: diff --git a/app/src/main/java/com/stardust/scriptdroid/external/floating_window/content/MainMenuNavigatorContent.java b/app/src/main/java/com/stardust/scriptdroid/external/floating_window/content/MainMenuNavigatorContent.java index d40f2b01..f53081f0 100644 --- a/app/src/main/java/com/stardust/scriptdroid/external/floating_window/content/MainMenuNavigatorContent.java +++ b/app/src/main/java/com/stardust/scriptdroid/external/floating_window/content/MainMenuNavigatorContent.java @@ -2,11 +2,13 @@ package com.stardust.scriptdroid.external.floating_window.content; import android.content.Context; import android.support.annotation.NonNull; +import android.support.design.widget.Snackbar; import android.view.View; import android.widget.Toast; import com.stardust.scriptdroid.App; import com.stardust.scriptdroid.R; +import com.stardust.scriptdroid.droid.Droid; import com.stardust.scriptdroid.external.floating_window.HoverMenuService; import com.stardust.scriptdroid.layout_inspector.LayoutInspector; import com.stardust.util.MessageEvent; @@ -59,6 +61,15 @@ public class MainMenuNavigatorContent implements NavigatorContent { } } + @ViewBinding.Click(R.id.stop_all_running_scripts) + private void stopAllRunningScripts() { + int n = Droid.getInstance().stopAll(); + if (n > 0) + Toast.makeText(mView.getContext(), String.format(mView.getContext().getString(R.string.text_already_stop_n_scripts), n), Toast.LENGTH_SHORT).show(); + else + Toast.makeText(mView.getContext(), R.string.text_no_running_script, Toast.LENGTH_SHORT).show(); + } + @NonNull @Override public View getView() { diff --git a/app/src/main/java/com/stardust/scriptdroid/layout_inspector/view/LayoutHierarchyView.java b/app/src/main/java/com/stardust/scriptdroid/layout_inspector/view/LayoutHierarchyView.java index 191445ca..258f1f7a 100644 --- a/app/src/main/java/com/stardust/scriptdroid/layout_inspector/view/LayoutHierarchyView.java +++ b/app/src/main/java/com/stardust/scriptdroid/layout_inspector/view/LayoutHierarchyView.java @@ -15,6 +15,7 @@ import android.widget.TextView; import com.stardust.scriptdroid.R; import com.stardust.scriptdroid.layout_inspector.NodeInfo; import com.stardust.scriptdroid.tool.ViewTool; +import com.stardust.widget.LevelBeamView; import java.util.Collections; import java.util.List; diff --git a/app/src/main/java/com/stardust/scriptdroid/sample/Sample.java b/app/src/main/java/com/stardust/scriptdroid/sample/Sample.java new file mode 100644 index 00000000..df768d51 --- /dev/null +++ b/app/src/main/java/com/stardust/scriptdroid/sample/Sample.java @@ -0,0 +1,15 @@ +package com.stardust.scriptdroid.sample; + +/** + * Created by Stardust on 2017/3/13. + */ + +public class Sample { + public String name; + public String path; + + public Sample(String name, String path) { + this.name = name; + this.path = path; + } +} diff --git a/app/src/main/java/com/stardust/scriptdroid/file/SampleFileManager.java b/app/src/main/java/com/stardust/scriptdroid/sample/SampleFileManager.java similarity index 50% rename from app/src/main/java/com/stardust/scriptdroid/file/SampleFileManager.java rename to app/src/main/java/com/stardust/scriptdroid/sample/SampleFileManager.java index 0d75c82d..ab8b3229 100644 --- a/app/src/main/java/com/stardust/scriptdroid/file/SampleFileManager.java +++ b/app/src/main/java/com/stardust/scriptdroid/sample/SampleFileManager.java @@ -1,16 +1,21 @@ -package com.stardust.scriptdroid.file; +package com.stardust.scriptdroid.sample; import android.content.Context; +import android.content.res.AssetManager; import com.stardust.scriptdroid.Pref; import com.stardust.scriptdroid.droid.script.file.ScriptFile; import com.stardust.scriptdroid.droid.script.file.ScriptFileList; import com.stardust.scriptdroid.droid.script.file.SharedPrefScriptFileList; +import com.stardust.scriptdroid.tool.FileUtils; import com.stardust.util.MapEntries; import com.stardust.scriptdroid.App; import com.stardust.scriptdroid.R; +import java.io.IOException; +import java.util.ArrayList; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; /** @@ -19,12 +24,12 @@ import java.util.Map; public class SampleFileManager { private static final Map SAMPLES = new MapEntries<>(new LinkedHashMap()) - .entry("sample_open_running_services.js", R.string.text_sample_open_running_services) - .entry("sample_qq_hongbao.js", R.string.text_sample_qq_hongbao) - .entry("sample_simple_calculator.js", R.string.text_sample_simple_calculator) - .entry("sample_force_qq_chat.js", R.string.text_sample_for_qq_chat) - .entry("sample_wechat_scan.js", R.string.text_sample_wechat_scan) - .entry("sample_alipay_scan.js", R.string.text_sample_alipay_scan) + .entry("sample/自动操作/sample_open_running_services.js", R.string.text_sample_open_running_services) + .entry("sample/自动操作/sample_qq_hongbao.js", R.string.text_sample_qq_hongbao) + .entry("sample/自动操作/sample_simple_calculator.js", R.string.text_sample_simple_calculator) + .entry("sample/自动操作/sample_force_qq_chat.js", R.string.text_sample_for_qq_chat) + .entry("sample/自动操作/sample_wechat_scan.js", R.string.text_sample_wechat_scan) + .entry("sample/自动操作/sample_alipay_scan.js", R.string.text_sample_alipay_scan) .map(); private static SampleFileManager instance = new SampleFileManager(); @@ -64,4 +69,29 @@ public class SampleFileManager { return failCount; } + public List getSamplesFromAssets(AssetManager assets, String path) { + List sampleGroups = new ArrayList<>(); + try { + String[] groups = assets.list(path); + for (String groupName : groups) { + sampleGroups.add(getSampleGroupFromAssets(assets, groupName, path + "/" + groupName)); + } + return sampleGroups; + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private SampleGroup getSampleGroupFromAssets(AssetManager assets, String groupName, String path) { + SampleGroup group = new SampleGroup(groupName); + try { + String[] samples = assets.list(path); + for (String sample : samples) { + group.add(new Sample(FileUtils.getNameWithoutExtension(sample), path + "/" + sample)); + } + return group; + } catch (IOException e) { + throw new RuntimeException(e); + } + } } diff --git a/app/src/main/java/com/stardust/scriptdroid/sample/SampleGroup.java b/app/src/main/java/com/stardust/scriptdroid/sample/SampleGroup.java new file mode 100644 index 00000000..9c376fac --- /dev/null +++ b/app/src/main/java/com/stardust/scriptdroid/sample/SampleGroup.java @@ -0,0 +1,28 @@ +package com.stardust.scriptdroid.sample; + +import java.util.ArrayList; +import java.util.List; + +/** + * Created by Stardust on 2017/3/13. + */ + +public class SampleGroup { + + public String name; + public List sampleList; + + public SampleGroup(String name) { + this(name, new ArrayList()); + } + + public SampleGroup(String name, List samples) { + this.name = name; + sampleList = samples; + } + + public void add(Sample sample){ + sampleList.add(sample); + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/stardust/scriptdroid/tool/BackPressedHandler.java b/app/src/main/java/com/stardust/scriptdroid/tool/BackPressedHandler.java index e274a0df..9bf1663a 100644 --- a/app/src/main/java/com/stardust/scriptdroid/tool/BackPressedHandler.java +++ b/app/src/main/java/com/stardust/scriptdroid/tool/BackPressedHandler.java @@ -5,12 +5,15 @@ import android.widget.Toast; import com.stardust.scriptdroid.R; +import java.util.ArrayList; +import java.util.List; + /** * Created by Stardust on 2017/2/3. */ public interface BackPressedHandler { - void onBackPressed(); + boolean onBackPressed(); class DoublePressExit implements BackPressedHandler { @@ -28,13 +31,33 @@ public interface BackPressedHandler { } @Override - public void onBackPressed() { + public boolean onBackPressed() { if (System.currentTimeMillis() - mLastPressedMillis < mDoublePressInterval) { mActivity.finish(); } else { mLastPressedMillis = System.currentTimeMillis(); Toast.makeText(mActivity, R.string.text_press_again_to_exit, Toast.LENGTH_SHORT).show(); } + return true; + } + } + + class Observer implements BackPressedHandler { + + private List mBackPressedHandlers = new ArrayList<>(); + + @Override + public boolean onBackPressed() { + for (BackPressedHandler handler : mBackPressedHandlers) { + if (handler.onBackPressed()) { + return true; + } + } + return false; + } + + public void registerHandler(BackPressedHandler handler) { + mBackPressedHandlers.add(handler); } } } diff --git a/app/src/main/java/com/stardust/scriptdroid/file/FileUtils.java b/app/src/main/java/com/stardust/scriptdroid/tool/FileUtils.java similarity index 86% rename from app/src/main/java/com/stardust/scriptdroid/file/FileUtils.java rename to app/src/main/java/com/stardust/scriptdroid/tool/FileUtils.java index 706bec3a..5224b25e 100644 --- a/app/src/main/java/com/stardust/scriptdroid/file/FileUtils.java +++ b/app/src/main/java/com/stardust/scriptdroid/tool/FileUtils.java @@ -1,4 +1,4 @@ -package com.stardust.scriptdroid.file; +package com.stardust.scriptdroid.tool; import android.content.Context; import android.database.Cursor; @@ -211,4 +211,32 @@ public class FileUtils { } } + + public static String getNameWithoutExtension(String fileName) { + int a = fileName.lastIndexOf('/'); + if (a < 0) + a = fileName.lastIndexOf('\\'); + int b = fileName.lastIndexOf('.'); + if (a < 0) + a = -1; + if (b < 0) + b = fileName.length(); + fileName = fileName.substring(a + 1, b); + return fileName; + } + + public static File copyAssetToTmpFile(Context context, String path) { + String extension = getExtension(path); + String name = getNameWithoutExtension(path); + if(name.length() < 5){ + name += name.hashCode(); + } + try { + File tmpFile = File.createTempFile(name, "." + extension, context.getCacheDir()); + copyAsset(path, tmpFile.getPath()); + return tmpFile; + } catch (IOException e) { + throw new RuntimeException(e); + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/stardust/scriptdroid/ui/BaseActivity.java b/app/src/main/java/com/stardust/scriptdroid/ui/BaseActivity.java index a8d9c01e..4a2fae2e 100644 --- a/app/src/main/java/com/stardust/scriptdroid/ui/BaseActivity.java +++ b/app/src/main/java/com/stardust/scriptdroid/ui/BaseActivity.java @@ -4,11 +4,13 @@ import android.os.Build; import android.os.Bundle; import android.support.annotation.Nullable; import android.support.annotation.RequiresApi; +import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; import android.view.View; import com.stardust.scriptdroid.R; +import com.stardust.theme.ThemeColorManager; import com.stardust.theme.app.ThemeColorAppCompatActivity; import java.util.ArrayList; @@ -22,7 +24,7 @@ import static android.content.pm.PackageManager.PERMISSION_GRANTED; * Created by Stardust on 2017/1/23. */ -public class BaseActivity extends ThemeColorAppCompatActivity { +public class BaseActivity extends AppCompatActivity { protected static final int PERMISSION_REQUEST_CODE = 11186; @@ -31,6 +33,14 @@ public class BaseActivity extends ThemeColorAppCompatActivity { super.onCreate(savedInstanceState); } + @Override + protected void onStart() { + super.onStart(); + if ((getWindow().getDecorView().getSystemUiVisibility() & View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) == 0) { + ThemeColorManager.addActivityStatusBar(this); + } + } + @SuppressWarnings("unchecked") public T $(int resId) { return (T) findViewById(resId); diff --git a/app/src/main/java/com/stardust/scriptdroid/ui/edit/EditActivity.java b/app/src/main/java/com/stardust/scriptdroid/ui/edit/EditActivity.java index 8aa6900f..717d74a6 100644 --- a/app/src/main/java/com/stardust/scriptdroid/ui/edit/EditActivity.java +++ b/app/src/main/java/com/stardust/scriptdroid/ui/edit/EditActivity.java @@ -17,6 +17,7 @@ import com.afollestad.materialdialogs.DialogAction; import com.afollestad.materialdialogs.MaterialDialog; import com.stardust.scriptdroid.Pref; import com.stardust.scriptdroid.droid.Droid; +import com.stardust.scriptdroid.tool.FileUtils; import com.stardust.scriptdroid.ui.edit.editor920.Editor920Activity; import com.stardust.scriptdroid.ui.edit.sidemenu.EditSideMenuFragment; import com.stardust.scriptdroid.ui.edit.sidemenu.FunctionListRecyclerView; @@ -68,6 +69,15 @@ public class EditActivity extends Editor920Activity { editFile(context, null, path); } + + public static void editAssetFile(Context context, String name, String path) { + context.startActivity(new Intent(context, EditActivity.class) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + .putExtra("fromAssets", true) + .putExtra("path", path) + .putExtra("name", name)); + } + public static void editFile(Context context, String name, String path) { context.startActivity(new Intent(context, EditActivity.class) .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) @@ -81,6 +91,7 @@ public class EditActivity extends Editor920Activity { private DrawerLayout mDrawerLayout; private EditorDelegate mEditorDelegate; private SparseArray mMenuMap; + private boolean mReadOnly = false; private BroadcastReceiver mOnRunFinishedReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { @@ -125,7 +136,13 @@ public class EditActivity extends Editor920Activity { if (path == null) { finish(); } else { - mFile = new File(path); + if (getIntent().getBooleanExtra("fromAssets", false)) { + mReadOnly = true; + //TODO 优化 + mFile = FileUtils.copyAssetToTmpFile(this, path); + } else { + mFile = new File(path); + } if (mName == null) { mName = mFile.getName(); } @@ -150,7 +167,8 @@ public class EditActivity extends Editor920Activity { .setOnFunctionClickListener(new FunctionListRecyclerView.OnFunctionClickListener() { @Override public void onClick(FunctionListRecyclerView.Function function, int position) { - insertText(function.name); + if (!mReadOnly) + insertText(function.name); mDrawerLayout.closeDrawer(GravityCompat.END); } }); @@ -161,25 +179,30 @@ public class EditActivity extends Editor920Activity { mEditorDelegate = new EditorDelegate(0, mFile, 0, null); final EditorView editorView = (EditorView) findViewById(R.id.editor); mEditorDelegate.setEditorView(editorView); + editorView.getEditText().setReadOnly(mReadOnly); InputMethodEnhanceBar inputMethodEnhanceBar = (InputMethodEnhanceBar) findViewById(R.id.input_method_enhance_bar); - inputMethodEnhanceBar.setEditTextBridge(new InputMethodEnhanceBar.EditTextBridge() { - @Override - public void appendText(CharSequence text) { - insertText(text); - } + if (mReadOnly) { + inputMethodEnhanceBar.setVisibility(View.GONE); + } else { + inputMethodEnhanceBar.setEditTextBridge(new InputMethodEnhanceBar.EditTextBridge() { + @Override + public void appendText(CharSequence text) { + insertText(text); + } - @Override - public void backspace(int count) { + @Override + public void backspace(int count) { - } + } - @Override - public TextView getEditText() { - return editorView.getEditText(); - } + @Override + public TextView getEditText() { + return editorView.getEditText(); + } - }); + }); + } } } @@ -264,7 +287,7 @@ public class EditActivity extends Editor920Activity { @Override public void finish() { - if (mEditorDelegate.isChanged()) { + if (!mReadOnly && mEditorDelegate.isChanged()) { showExitConfirmDialog(); } else { super.finish(); @@ -304,4 +327,5 @@ public class EditActivity extends Editor920Activity { super.onDestroy(); unregisterReceiver(mOnRunFinishedReceiver); } + } diff --git a/app/src/main/java/com/stardust/scriptdroid/ui/edit/sidemenu/EditSideMenuFragment.java b/app/src/main/java/com/stardust/scriptdroid/ui/edit/sidemenu/EditSideMenuFragment.java index dba0cbb3..df2ab77e 100644 --- a/app/src/main/java/com/stardust/scriptdroid/ui/edit/sidemenu/EditSideMenuFragment.java +++ b/app/src/main/java/com/stardust/scriptdroid/ui/edit/sidemenu/EditSideMenuFragment.java @@ -11,13 +11,16 @@ import android.view.ViewGroup; import com.stardust.app.Fragment; import com.stardust.scriptdroid.external.floating_window.FloatingWindowManger; +import com.stardust.scriptdroid.external.floating_window.HoverMenuService; import com.stardust.view.ViewBinding; -import com.stardust.scriptdroid.App; import com.stardust.scriptdroid.R; import com.stardust.scriptdroid.ui.console.ConsoleActivity; import com.stardust.scriptdroid.ui.help.HelpCatalogueActivity; import com.stardust.view.ViewBinder; +import org.greenrobot.eventbus.EventBus; +import org.greenrobot.eventbus.Subscribe; + /** * Created by Stardust on 2017/2/4. */ @@ -35,6 +38,12 @@ public class EditSideMenuFragment extends Fragment { private SwitchCompat mFloatingWindowSwitch; + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + EventBus.getDefault().register(this); + } + @Nullable @Override public View createView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { @@ -73,7 +82,7 @@ public class EditSideMenuFragment extends Fragment { @ViewBinding.Click(R.id.syntax_and_api) private void startSyntaxHelpActivity() { - HelpCatalogueActivity.showCatalogue(getActivity()); + HelpCatalogueActivity.showMainCatalogue(getActivity()); } @ViewBinding.Click(R.id.console) @@ -100,4 +109,15 @@ public class EditSideMenuFragment extends Fragment { return this; } + + @Subscribe + public void onHoverMenuServiceStateChanged(HoverMenuService.ServiceStateChangedEvent event) { + mFloatingWindowSwitch.setChecked(event.state); + } + + @Override + public void onDestroy() { + super.onDestroy(); + EventBus.getDefault().unregister(this); + } } diff --git a/app/src/main/java/com/stardust/scriptdroid/ui/edit/sidemenu/FunctionListRecyclerView.java b/app/src/main/java/com/stardust/scriptdroid/ui/edit/sidemenu/FunctionListRecyclerView.java index b54d1828..23703a82 100644 --- a/app/src/main/java/com/stardust/scriptdroid/ui/edit/sidemenu/FunctionListRecyclerView.java +++ b/app/src/main/java/com/stardust/scriptdroid/ui/edit/sidemenu/FunctionListRecyclerView.java @@ -12,7 +12,7 @@ import android.widget.TextView; import com.stardust.scriptdroid.App; import com.stardust.scriptdroid.R; -import com.stardust.scriptdroid.file.FileUtils; +import com.stardust.scriptdroid.tool.FileUtils; import com.stardust.widget.ExpandableRecyclerView; import java.text.Collator; diff --git a/app/src/main/java/com/stardust/scriptdroid/ui/help/DocumentationActivity.java b/app/src/main/java/com/stardust/scriptdroid/ui/help/DocumentationActivity.java index b25a330a..60709df6 100644 --- a/app/src/main/java/com/stardust/scriptdroid/ui/help/DocumentationActivity.java +++ b/app/src/main/java/com/stardust/scriptdroid/ui/help/DocumentationActivity.java @@ -7,7 +7,7 @@ import android.os.Bundle; import com.stardust.scriptdroid.ui.BaseActivity; import com.stardust.widget.CommonMarkdownView; import com.stardust.scriptdroid.R; -import com.stardust.scriptdroid.file.FileUtils; +import com.stardust.scriptdroid.tool.FileUtils; import java.io.IOException; @@ -17,6 +17,8 @@ import java.io.IOException; public class DocumentationActivity extends BaseActivity { + private CommonMarkdownView mCommonMarkdownView; + public static void openDocumentation(Context context, String title, String assetPath) { context.startActivity(new Intent(context, DocumentationActivity.class) .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) @@ -37,22 +39,22 @@ public class DocumentationActivity extends BaseActivity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - handleIntent(); + handleIntent(getIntent()); setUpUI(); } - private void handleIntent() { - mTitle = getIntent().getStringExtra("title"); - String path = getIntent().getStringExtra("path"); - if (path == null) { - int rawId = getIntent().getIntExtra("resId", 0); - mDocumentation = FileUtils.readString(getResources().openRawResource(rawId)); - } else { + private void handleIntent(Intent intent) { + mTitle = intent.getStringExtra("title"); + String path = intent.getStringExtra("path"); + int rawId = intent.getIntExtra("resId", 0); + if (path != null) { try { - mDocumentation = FileUtils.readString(getAssets().open("help/documentation/" + path + ".md")); + mDocumentation = FileUtils.readString(getAssets().open("help/" + path)); } catch (IOException e) { e.printStackTrace(); } + } else if (rawId != 0) { + mDocumentation = FileUtils.readString(getResources().openRawResource(rawId)); } } @@ -63,12 +65,21 @@ public class DocumentationActivity extends BaseActivity { } private void loadDocument() { - CommonMarkdownView markdownView = $(R.id.markdown); + mCommonMarkdownView = $(R.id.markdown); try { - markdownView.loadMarkdown(mDocumentation); + mCommonMarkdownView.loadMarkdown(mDocumentation); } catch (Exception e) { e.printStackTrace(); - markdownView.setText(R.string.text_load_failed); + mCommonMarkdownView.setText(R.string.text_load_failed); + } + } + + @Override + public void onBackPressed() { + if (mCommonMarkdownView.canGoBack()) { + mCommonMarkdownView.goBack(); + } else { + super.onBackPressed(); } } diff --git a/app/src/main/java/com/stardust/scriptdroid/ui/help/HelpCatalogueActivity.java b/app/src/main/java/com/stardust/scriptdroid/ui/help/HelpCatalogueActivity.java index 3b6024fc..546f2bb8 100644 --- a/app/src/main/java/com/stardust/scriptdroid/ui/help/HelpCatalogueActivity.java +++ b/app/src/main/java/com/stardust/scriptdroid/ui/help/HelpCatalogueActivity.java @@ -17,11 +17,12 @@ import com.stardust.scriptdroid.ui.BaseActivity; import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; import com.stardust.scriptdroid.R; -import com.stardust.scriptdroid.file.FileUtils; +import com.stardust.scriptdroid.tool.FileUtils; import org.json.JSONObject; import java.io.IOException; +import java.io.OutputStream; import java.lang.reflect.Type; import java.util.HashMap; import java.util.List; @@ -33,14 +34,17 @@ import java.util.Map; public class HelpCatalogueActivity extends BaseActivity { - public static void showCatalogue(Context context, String catalogue) { + + public static void showCatalogue(Context context, String title, String catalogue) { context.startActivity(new Intent(context, HelpCatalogueActivity.class) .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + .putExtra("title", title) .putExtra("catalogue", catalogue)); } - public static void showCatalogue(Context context) { - showCatalogue(context, "main"); + public static void showMainCatalogue(Context context) { + context.startActivity(new Intent(context, HelpCatalogueActivity.class) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); } private static final Map> CATALOGUES; @@ -49,8 +53,8 @@ public class HelpCatalogueActivity extends BaseActivity { String title; String summary; - private String catalogue; - private String documentation; + private String type; + private String path; Item(String title, String summary) { this.title = title; @@ -61,29 +65,25 @@ public class HelpCatalogueActivity extends BaseActivity { this(title, null); } - Item catalogue(String catalogue) { - this.catalogue = catalogue; - return this; - } - - Item documentation(String documentation) { - this.documentation = documentation; - return this; - } - void redirect(Context context) { - if (documentation != null) { - DocumentationActivity.openDocumentation(context, title, documentation); - } else if (catalogue != null) { - showCatalogue(context, catalogue); - } else { - DocumentationActivity.openDocumentation(context, title, title); + switch (type) { + case "markdown": + DocumentationActivity.openDocumentation(context, title, path + "/" + title + ".md"); + break; + case "catalogue": + showCatalogue(context, title, title); + break; + case "html": + LocalWebViewActivity.openAssetsHtml(context, title, path + "/" + title + ".html"); + break; } + } } List mItems; private RecyclerView mRecyclerView; + private String mTitle; private View.OnClickListener mOnItemClickListener = new View.OnClickListener() { @Override public void onClick(View v) { @@ -105,12 +105,15 @@ public class HelpCatalogueActivity extends BaseActivity { String catalogue = getIntent().getStringExtra("catalogue"); if (catalogue == null) catalogue = "main"; + mTitle = getIntent().getStringExtra("title"); + if (mTitle == null) + mTitle = getString(R.string.text_help); mItems = CATALOGUES.get(catalogue); } private void setUpUI() { setContentView(R.layout.activity_help); - setToolbarAsBack(getString(R.string.text_help)); + setToolbarAsBack(mTitle); setUpRecyclerView(); } diff --git a/app/src/main/java/com/stardust/scriptdroid/ui/help/LocalWebViewActivity.java b/app/src/main/java/com/stardust/scriptdroid/ui/help/LocalWebViewActivity.java new file mode 100644 index 00000000..83894d5e --- /dev/null +++ b/app/src/main/java/com/stardust/scriptdroid/ui/help/LocalWebViewActivity.java @@ -0,0 +1,77 @@ +package com.stardust.scriptdroid.ui.help; + +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.webkit.WebView; + +import com.stardust.scriptdroid.R; +import com.stardust.scriptdroid.tool.FileUtils; +import com.stardust.scriptdroid.ui.BaseActivity; + +import java.io.IOException; + +/** + * Created by Stardust on 2017/3/14. + */ + +public class LocalWebViewActivity extends BaseActivity { + + // TODO: 2017/3/14 缓存asset + + private String mTitle; + private String mHtml; + private WebView mWebView; + + public static void openAssetsHtml(Context context, String title, String path) { + context.startActivity(new Intent(context, LocalWebViewActivity.class) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + .putExtra("title", title) + .putExtra("path", path)); + } + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + handleIntent(getIntent()); + setUpUI(); + } + + private void handleIntent(Intent intent) { + mTitle = intent.getStringExtra("title"); + String path = intent.getStringExtra("path"); + if (path != null) + try { + mHtml = FileUtils.readString(getAssets().open("help/" + path)); + } catch (IOException e) { + e.printStackTrace(); + mHtml = getString(R.string.text_load_failed); + } + } + + private void setUpUI() { + setContentView(R.layout.activity_local_webview); + setToolbarAsBack(mTitle); + loadHtml(); + } + + private void loadHtml() { + mWebView = $(R.id.webView); + try { + mWebView.loadDataWithBaseURL(null, mHtml, "text/html", "utf-8", null); + } catch (Exception e) { + e.printStackTrace(); + mWebView.loadDataWithBaseURL(null, getString(R.string.text_load_failed), "text/html", "utf-8", null); + } + } + + @Override + public void onBackPressed() { + if (mWebView.canGoBack()) { + mWebView.goBack(); + } else { + super.onBackPressed(); + } + } +} diff --git a/app/src/main/java/com/stardust/scriptdroid/ui/main/MainActivity.java b/app/src/main/java/com/stardust/scriptdroid/ui/main/MainActivity.java index ddcb8408..f7c55f3e 100644 --- a/app/src/main/java/com/stardust/scriptdroid/ui/main/MainActivity.java +++ b/app/src/main/java/com/stardust/scriptdroid/ui/main/MainActivity.java @@ -2,22 +2,23 @@ package com.stardust.scriptdroid.ui.main; import android.Manifest; import android.annotation.SuppressLint; -import android.content.BroadcastReceiver; import android.content.ClipData; import android.content.ClipboardManager; import android.content.Context; import android.content.Intent; -import android.content.IntentFilter; -import android.content.pm.PackageManager; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.design.widget.Snackbar; +import android.support.design.widget.TabLayout; +import android.support.v4.app.Fragment; +import android.support.v4.view.ViewPager; import android.support.v4.widget.DrawerLayout; import android.support.v7.app.ActionBarDrawerToggle; import android.support.v7.widget.Toolbar; import android.text.InputType; +import android.view.Gravity; import android.view.View; import android.widget.ImageView; import android.widget.TextView; @@ -33,25 +34,29 @@ import com.stardust.scriptdroid.droid.script.file.ScriptFile; import com.stardust.scriptdroid.droid.script.file.ScriptFileList; import com.stardust.scriptdroid.droid.script.file.SharedPrefScriptFileList; import com.stardust.scriptdroid.external.notification.record.ActionRecordSwitchNotification; -import com.stardust.scriptdroid.file.SampleFileManager; import com.stardust.scriptdroid.record.inputevent.InputEventRecorder; import com.stardust.scriptdroid.record.inputevent.InputEventToJsRecorder; import com.stardust.scriptdroid.service.AccessibilityWatchDogService; import com.stardust.scriptdroid.tool.AccessibilityServiceTool; import com.stardust.scriptdroid.tool.ImageSelector; import com.stardust.scriptdroid.ui.BaseActivity; +import com.stardust.scriptdroid.ui.main.my_script_list.MyScriptListFragment; +import com.stardust.scriptdroid.ui.main.sample_list.SampleScriptListFragment; import com.stardust.scriptdroid.ui.settings.SettingsActivity; import com.stardust.theme.dialog.ThemeColorMaterialDialogBuilder; +import com.stardust.util.MessageEvent; import com.stardust.view.ViewBinding; import com.stardust.widget.SlidingUpPanel; import com.stardust.scriptdroid.BuildConfig; import com.stardust.scriptdroid.R; -import com.stardust.scriptdroid.file.FileUtils; +import com.stardust.scriptdroid.tool.FileUtils; import com.stardust.scriptdroid.tool.BackPressedHandler; import com.stardust.scriptdroid.ui.main.operation.ScriptFileOperation; import com.stardust.view.ViewBinder; import com.stardust.view.accessibility.AccessibilityServiceUtils; +import org.greenrobot.eventbus.EventBus; + import java.io.File; @@ -59,30 +64,29 @@ public class MainActivity extends BaseActivity implements FileChooserDialog.File private static final String EXTRA_ACTION = "EXTRA_ACTION"; - public static final String ACTION_NOTIFY_SCRIPT_LIST_CHANGE = "ACTION_NOTIFY_SCRIPT_LIST_CHANGE"; private static final String ACTION_ON_ACTION_RECORD_STOPPED = "ACTION_ON_ACTION_RECORD_STOPPED"; private static final String ARGUMENT_SCRIPT = "ARGUMENT_SCRIPT"; private static final String ACTION_ON_ROOT_RECORD_STOPPED = "ACTION_ON_ROOT_RECORD_STOPPED"; private SlidingUpPanel mAddFilePanel; - private ScriptListRecyclerView mScriptListRecyclerView; private ScriptFileList mScriptFileList; + private MyScriptListFragment mMyScriptListFragment; + private SampleScriptListFragment mSampleScriptListFragment; private DrawerLayout mDrawerLayout; - private Receiver mReceiver; private OnActivityResultDelegate.Manager mManager = new OnActivityResultDelegate.Manager(); + private BackPressedHandler.Observer mBackPressObserver = new BackPressedHandler.Observer(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setUpUI(); checkPermissions(); - registerReceivers(); + registerBackPressHandler(); handleIntent(getIntent()); } - private void registerReceivers() { - mReceiver = new Receiver(); - registerReceiver(mReceiver, new IntentFilter(ACTION_NOTIFY_SCRIPT_LIST_CHANGE)); + private void registerBackPressHandler() { + mBackPressObserver.registerHandler(new BackPressedHandler.DoublePressExit(this)); } private void checkPermissions() { @@ -119,20 +123,33 @@ public class MainActivity extends BaseActivity implements FileChooserDialog.File private void addScriptFile(String name, String path) { mScriptFileList.add(new ScriptFile(name, path)); - mScriptListRecyclerView.getAdapter().notifyItemInserted(mScriptFileList.size() - 1); + EventBus.getDefault().post(new MessageEvent(MyScriptListFragment.MESSAGE_SCRIPT_FILE_ADDED)); } private void setUpUI() { mDrawerLayout = (DrawerLayout) View.inflate(this, R.layout.activity_main, null); setContentView(mDrawerLayout); - SlideMenuFragment.setFragment(this, R.id.fragment_slide_menu); + setUpFragment(); mAddFilePanel = $(R.id.bottom_menu); - setUpToolbar(); + setUpTabLayout(); setUpDrawerHeader(); - setUpScriptList(); ViewBinder.bind(this); + getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE); + } + private void setUpFragment() { + SlideMenuFragment.setFragment(this, R.id.fragment_slide_menu); + mMyScriptListFragment = new MyScriptListFragment(); + mScriptFileList = SharedPrefScriptFileList.getInstance(); + mSampleScriptListFragment = new SampleScriptListFragment(); + } + + private void setUpTabLayout() { + TabLayout tabLayout = $(R.id.tab); + ViewPager viewPager = $(R.id.viewpager); + viewPager.setAdapter(new FragmentPagerAdapter()); + tabLayout.setupWithViewPager(viewPager); } @SuppressLint("SetTextI18n") @@ -143,13 +160,18 @@ public class MainActivity extends BaseActivity implements FileChooserDialog.File if (path != null) { setDrawerHeaderImage(path); } + path = Pref.def().getString(Pref.KEY_APP_BAR_IMAGE_PATH, null); + if (path != null) { + setAppBarImage(path); + } $(R.id.drawer).setFitsSystemWindows(false); } private void setDrawerHeaderImage(String path) { Drawable d = BitmapDrawable.createFromPath(path); - if (d != null) + if (d != null) { ((ImageView) $(R.id.drawer_header_img)).setImageDrawable(d); + } } private void setUpToolbar() { @@ -163,11 +185,6 @@ public class MainActivity extends BaseActivity implements FileChooserDialog.File mDrawerLayout.addDrawerListener(drawerToggle); } - private void setUpScriptList() { - mScriptListRecyclerView = $(R.id.script_list); - mScriptFileList = SharedPrefScriptFileList.getInstance(); - mScriptListRecyclerView.setScriptFileList(mScriptFileList); - } @ViewBinding.Click(R.id.add) private void showAddFilePanel() { @@ -201,7 +218,7 @@ public class MainActivity extends BaseActivity implements FileChooserDialog.File } } addScriptFile(name, path); - new ScriptFileOperation.Edit().operate(mScriptListRecyclerView, mScriptFileList, mScriptFileList.size() - 1); + new ScriptFileOperation.Edit().operate(mMyScriptListFragment.getScriptListRecyclerView(), mScriptFileList, mScriptFileList.size() - 1); } else { Snackbar.make(mDrawerLayout, R.string.text_file_create_fail, Snackbar.LENGTH_LONG).show(); } @@ -257,15 +274,6 @@ public class MainActivity extends BaseActivity implements FileChooserDialog.File }).select(); } - @Override - public void onRequestPermissionsResult(int requestCode, - @NonNull String[] permissions, @NonNull int[] grantResults) { - if (requestCode == PERMISSION_REQUEST_CODE && grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { - SampleFileManager.getInstance().copySampleScriptFileIfNeeded(); - mScriptListRecyclerView.getAdapter().notifyDataSetChanged(); - } - } - @Override public void onFileSelection(@NonNull FileChooserDialog dialog, @NonNull File file) { addScriptFile(file.getPath()); @@ -323,23 +331,21 @@ public class MainActivity extends BaseActivity implements FileChooserDialog.File } - private BackPressedHandler mBackPressedHandler = new BackPressedHandler.DoublePressExit(this); @Override public void onBackPressed() { - if (mScriptListRecyclerView.getScriptFileOperationPopupMenu().isShowing()) { - mScriptListRecyclerView.getScriptFileOperationPopupMenu().dismiss(); - } else if (mAddFilePanel.isShowing()) { + if (mAddFilePanel.isShowing()) { mAddFilePanel.dismiss(); + } else if (mDrawerLayout.isDrawerOpen(Gravity.START)) { + mDrawerLayout.closeDrawer(Gravity.START); } else { - mBackPressedHandler.onBackPressed(); + mBackPressObserver.onBackPressed(); } } @Override protected void onDestroy() { super.onDestroy(); - unregisterReceiver(mReceiver); } @Override @@ -363,14 +369,43 @@ public class MainActivity extends BaseActivity implements FileChooserDialog.File context.startActivity(intent); } - - private class Receiver extends BroadcastReceiver { - - @Override - public void onReceive(Context context, Intent intent) { - if (intent.getAction().equals(ACTION_NOTIFY_SCRIPT_LIST_CHANGE)) { - mScriptListRecyclerView.getAdapter().notifyDataSetChanged(); + public void OnAppBarClick(View view) { + new ImageSelector(this, mManager, new ImageSelector.ImageSelectorCallback() { + @Override + public void onImageSelected(String path) { + Pref.def().edit().putString(Pref.KEY_APP_BAR_IMAGE_PATH, path).apply(); + setAppBarImage(path); } + }).select(); + } + + private void setAppBarImage(String path) { + Drawable d = BitmapDrawable.createFromPath(path); + if (d != null) { + $(R.id.app_bar).setBackground(d); } } + + private class FragmentPagerAdapter extends android.support.v4.app.FragmentPagerAdapter { + + FragmentPagerAdapter() { + super(MainActivity.this.getSupportFragmentManager()); + } + + @Override + public Fragment getItem(int position) { + return position == 0 ? mMyScriptListFragment : mSampleScriptListFragment; + } + + @Override + public int getCount() { + return 2; + } + + @Override + public CharSequence getPageTitle(int position) { + return getString(position == 0 ? R.string.text_my_script : R.string.text_sample_script); + } + } + } \ No newline at end of file diff --git a/app/src/main/java/com/stardust/scriptdroid/ui/main/SlideMenuFragment.java b/app/src/main/java/com/stardust/scriptdroid/ui/main/SlideMenuFragment.java index fc9c742b..4fe2a21e 100644 --- a/app/src/main/java/com/stardust/scriptdroid/ui/main/SlideMenuFragment.java +++ b/app/src/main/java/com/stardust/scriptdroid/ui/main/SlideMenuFragment.java @@ -11,10 +11,10 @@ import android.view.View; import android.view.ViewGroup; import com.stardust.app.Fragment; -import com.stardust.scriptdroid.App; import com.stardust.scriptdroid.R; import com.stardust.scriptdroid.droid.Droid; import com.stardust.scriptdroid.external.floating_window.FloatingWindowManger; +import com.stardust.scriptdroid.external.floating_window.HoverMenuService; import com.stardust.scriptdroid.service.AccessibilityWatchDogService; import com.stardust.scriptdroid.tool.AccessibilityServiceTool; import com.stardust.scriptdroid.ui.console.ConsoleActivity; @@ -22,6 +22,9 @@ import com.stardust.scriptdroid.ui.help.HelpCatalogueActivity; import com.stardust.view.ViewBinder; import com.stardust.view.ViewBinding; +import org.greenrobot.eventbus.EventBus; +import org.greenrobot.eventbus.Subscribe; + /** * Created by Stardust on 2017/1/30. @@ -35,7 +38,13 @@ public class SlideMenuFragment extends Fragment { activity.getSupportFragmentManager().beginTransaction().replace(viewId, fragment).commit(); } - private SwitchCompat mAutoOperateServiceSwitch, mAssistServiceSwitch; + private SwitchCompat mAutoOperateServiceSwitch, mFloatingWindowSwitch; + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + EventBus.getDefault().register(this); + } @Nullable @Override @@ -61,12 +70,12 @@ public class SlideMenuFragment extends Fragment { mAutoOperateServiceSwitch.setChecked(AccessibilityWatchDogService.isEnable()); } }, 450); - mAssistServiceSwitch.setChecked(FloatingWindowManger.isFloatingWindowShowing()); + mFloatingWindowSwitch.setChecked(FloatingWindowManger.isFloatingWindowShowing()); } private void setUpSwitchCompat() { mAutoOperateServiceSwitch = $(R.id.sw_auto_operate_service); - mAssistServiceSwitch = $(R.id.sw_floating_window); + mFloatingWindowSwitch = $(R.id.sw_floating_window); } @@ -77,7 +86,7 @@ public class SlideMenuFragment extends Fragment { @ViewBinding.Click(R.id.syntax_and_api) private void startSyntaxHelpActivity() { - HelpCatalogueActivity.showCatalogue(getActivity()); + HelpCatalogueActivity.showMainCatalogue(getActivity()); } @ViewBinding.Click(R.id.auto_operate_service) @@ -105,7 +114,7 @@ public class SlideMenuFragment extends Fragment { @ViewBinding.Click(R.id.floating_window) private void toggleAssistServiceSwitch() { - mAssistServiceSwitch.toggle(); + mFloatingWindowSwitch.toggle(); } @@ -121,6 +130,12 @@ public class SlideMenuFragment extends Fragment { @Override public void onDestroy() { super.onDestroy(); + EventBus.getDefault().unregister(this); + } + + @Subscribe + public void onHoverMenuServiceStateChanged(HoverMenuService.ServiceStateChangedEvent event) { + mFloatingWindowSwitch.setChecked(event.state); } } diff --git a/app/src/main/java/com/stardust/scriptdroid/ui/main/my_script_list/MyScriptListFragment.java b/app/src/main/java/com/stardust/scriptdroid/ui/main/my_script_list/MyScriptListFragment.java new file mode 100644 index 00000000..652429c6 --- /dev/null +++ b/app/src/main/java/com/stardust/scriptdroid/ui/main/my_script_list/MyScriptListFragment.java @@ -0,0 +1,99 @@ +package com.stardust.scriptdroid.ui.main.my_script_list; + +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import com.stardust.app.Fragment; +import com.stardust.scriptdroid.R; +import com.stardust.scriptdroid.droid.script.file.ScriptFileList; +import com.stardust.scriptdroid.droid.script.file.SharedPrefScriptFileList; +import com.stardust.scriptdroid.tool.BackPressedHandler; +import com.stardust.util.MessageEvent; +import com.stardust.widget.SimpleAdapterDataObserver; + +import org.greenrobot.eventbus.EventBus; +import org.greenrobot.eventbus.Subscribe; + +/** + * Created by Stardust on 2017/3/13. + */ + +public class MyScriptListFragment extends Fragment implements BackPressedHandler { + + public static final String MESSAGE_SCRIPT_FILE_ADDED = "MESSAGE_SCRIPT_FILE_ADDED"; + + private ScriptListRecyclerView mScriptListRecyclerView; + private ScriptFileList mScriptFileList; + private View mNoScriptHint; + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + EventBus.getDefault().register(this); + } + + @Nullable + @Override + public View createView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.fragment_my_script_list, container, false); + return view; + } + + @Override + public void onStart() { + super.onStart(); + init(); + } + + private void init() { + setUpScriptList(); + } + + private void setUpScriptList() { + mScriptListRecyclerView = $(R.id.script_list); + mScriptFileList = SharedPrefScriptFileList.getInstance(); + mNoScriptHint = $(R.id.hint_no_script); + mScriptListRecyclerView.getAdapter().registerAdapterDataObserver(new SimpleAdapterDataObserver() { + @Override + public void onSomethingChanged() { + if (mScriptListRecyclerView.getAdapter().getItemCount() == 0) { + mNoScriptHint.setVisibility(View.VISIBLE); + } else { + mNoScriptHint.setVisibility(View.GONE); + } + } + }); + mScriptListRecyclerView.setScriptFileList(mScriptFileList); + } + + + @Override + public boolean onBackPressed() { + if (mScriptListRecyclerView.getScriptFileOperationPopupMenu().isShowing()) { + mScriptListRecyclerView.getScriptFileOperationPopupMenu().dismiss(); + return true; + } + return false; + } + + public ScriptListRecyclerView getScriptListRecyclerView() { + return mScriptListRecyclerView; + } + + @Subscribe + public void onMessageEvent(MessageEvent event) { + if (event.message.equals(MESSAGE_SCRIPT_FILE_ADDED)) { + mScriptListRecyclerView.getAdapter().notifyItemInserted(mScriptFileList.size() - 1); + } + } + + @Override + public void onDestroy() { + super.onDestroy(); + EventBus.getDefault().unregister(this); + } +} diff --git a/app/src/main/java/com/stardust/scriptdroid/ui/main/ScriptListRecyclerView.java b/app/src/main/java/com/stardust/scriptdroid/ui/main/my_script_list/ScriptListRecyclerView.java similarity index 96% rename from app/src/main/java/com/stardust/scriptdroid/ui/main/ScriptListRecyclerView.java rename to app/src/main/java/com/stardust/scriptdroid/ui/main/my_script_list/ScriptListRecyclerView.java index 72d14671..451b259e 100644 --- a/app/src/main/java/com/stardust/scriptdroid/ui/main/ScriptListRecyclerView.java +++ b/app/src/main/java/com/stardust/scriptdroid/ui/main/my_script_list/ScriptListRecyclerView.java @@ -1,4 +1,4 @@ -package com.stardust.scriptdroid.ui.main; +package com.stardust.scriptdroid.ui.main.my_script_list; import android.content.Context; import android.os.Environment; @@ -14,15 +14,12 @@ import android.view.View; import android.view.ViewGroup; import android.widget.TextView; -import com.afollestad.materialdialogs.MaterialDialog; import com.stardust.scriptdroid.droid.script.file.ScriptFile; import com.stardust.scriptdroid.droid.script.file.ScriptFileList; import com.stardust.scriptdroid.tool.ViewTool; -import com.stardust.theme.dialog.ThemeColorMaterialDialogBuilder; import com.stardust.scriptdroid.R; import com.stardust.scriptdroid.ui.main.operation.ScriptFileOperation; import com.stardust.scriptdroid.ui.main.operation.ScriptFileOperationPopupMenu; -import com.stardust.scriptdroid.tool.ClassTool; import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.Subscribe; @@ -182,6 +179,11 @@ public class ScriptListRecyclerView extends ThemeColorRecyclerView { public int getItemCount() { return mScriptFileList.size(); } + + @Override + public void registerAdapterDataObserver(AdapterDataObserver observer) { + super.registerAdapterDataObserver(observer); + } } private class ViewHolder extends RecyclerView.ViewHolder { diff --git a/app/src/main/java/com/stardust/scriptdroid/ui/main/operation/ScriptFileOperation.java b/app/src/main/java/com/stardust/scriptdroid/ui/main/operation/ScriptFileOperation.java index a03a5fd8..1ec67daa 100644 --- a/app/src/main/java/com/stardust/scriptdroid/ui/main/operation/ScriptFileOperation.java +++ b/app/src/main/java/com/stardust/scriptdroid/ui/main/operation/ScriptFileOperation.java @@ -4,7 +4,6 @@ import android.content.Context; import android.content.Intent; import android.net.Uri; import android.support.annotation.NonNull; -import android.support.design.widget.Snackbar; import android.support.v7.widget.RecyclerView; import com.afollestad.materialdialogs.MaterialDialog; @@ -13,16 +12,12 @@ import com.stardust.scriptdroid.droid.script.file.ScriptFileList; import com.stardust.scriptdroid.external.shortcut.Shortcut; import com.stardust.scriptdroid.external.shortcut.ShortcutActivity; import com.stardust.scriptdroid.ui.edit.EditActivity; -import com.stardust.scriptdroid.ui.main.ScriptListRecyclerView; import com.stardust.theme.dialog.ThemeColorMaterialDialogBuilder; import com.stardust.scriptdroid.App; import com.stardust.scriptdroid.R; import org.greenrobot.eventbus.EventBus; -import java.util.ArrayList; -import java.util.List; - /** * Created by Stardust on 2017/1/23. */ diff --git a/app/src/main/java/com/stardust/scriptdroid/ui/main/sample_list/SampleScriptListFragment.java b/app/src/main/java/com/stardust/scriptdroid/ui/main/sample_list/SampleScriptListFragment.java new file mode 100644 index 00000000..06532372 --- /dev/null +++ b/app/src/main/java/com/stardust/scriptdroid/ui/main/sample_list/SampleScriptListFragment.java @@ -0,0 +1,34 @@ +package com.stardust.scriptdroid.ui.main.sample_list; + +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import com.stardust.app.Fragment; +import com.stardust.scriptdroid.R; +import com.stardust.scriptdroid.sample.SampleFileManager; + +/** + * Created by Stardust on 2017/3/13. + */ + +public class SampleScriptListFragment extends Fragment { + + private SampleScriptListRecyclerView mSampleScriptListRecyclerView; + + @Nullable + @Override + public View createView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + return inflater.inflate(R.layout.fragment_sample_script_list, container, false); + } + + @Override + protected void afterCreateView() { + mSampleScriptListRecyclerView = $(R.id.script_list); + mSampleScriptListRecyclerView.setSamples(SampleFileManager.getInstance().getSamplesFromAssets(getContext().getAssets(), "sample")); + + } + +} diff --git a/app/src/main/java/com/stardust/scriptdroid/ui/main/sample_list/SampleScriptListRecyclerView.java b/app/src/main/java/com/stardust/scriptdroid/ui/main/sample_list/SampleScriptListRecyclerView.java new file mode 100644 index 00000000..d11cd2de --- /dev/null +++ b/app/src/main/java/com/stardust/scriptdroid/ui/main/sample_list/SampleScriptListRecyclerView.java @@ -0,0 +1,166 @@ +package com.stardust.scriptdroid.ui.main.sample_list; + +import android.content.Context; +import android.support.annotation.NonNull; +import android.support.v7.widget.DividerItemDecoration; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import com.bignerdranch.expandablerecyclerview.ChildViewHolder; +import com.bignerdranch.expandablerecyclerview.ExpandableRecyclerAdapter; +import com.bignerdranch.expandablerecyclerview.ParentViewHolder; +import com.bignerdranch.expandablerecyclerview.model.Parent; +import com.stardust.scriptdroid.R; +import com.stardust.scriptdroid.sample.Sample; +import com.stardust.scriptdroid.ui.edit.EditActivity; +import com.stardust.widget.LevelBeamView; + +import java.util.ArrayList; +import java.util.List; + +/** + * Created by Stardust on 2017/3/13. + */ + +public class SampleScriptListRecyclerView extends RecyclerView { + + + private OnClickListener mOnItemClickListener = new OnClickListener() { + @Override + public void onClick(View v) { + SampleViewHolder viewHolder = (SampleViewHolder) getChildViewHolder(v); + viewSample(viewHolder.getChild()); + } + }; + + + private Adapter mAdapter; + private List mSampleGroups = new ArrayList<>(); + + public SampleScriptListRecyclerView(Context context) { + super(context); + init(); + } + + public SampleScriptListRecyclerView(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + } + + public SampleScriptListRecyclerView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + init(); + } + + public void setSamples(List samples) { + mSampleGroups.clear(); + for (com.stardust.scriptdroid.sample.SampleGroup sampleGroup : samples) { + mSampleGroups.add(new SampleGroup(sampleGroup)); + } + mAdapter = new Adapter(mSampleGroups); + setAdapter(mAdapter); + } + + private void init() { + setLayoutManager(new LinearLayoutManager(getContext())); + addItemDecoration(new DividerItemDecoration(getContext(), VERTICAL)); + } + + + + private void viewSample(Sample sample) { + EditActivity.editAssetFile(getContext(), sample.name, sample.path); + } + + private class SampleGroup implements Parent { + + private com.stardust.scriptdroid.sample.SampleGroup mSampleGroup; + + SampleGroup(com.stardust.scriptdroid.sample.SampleGroup sampleGroup) { + mSampleGroup = sampleGroup; + } + + String getGroupName() { + return mSampleGroup.name; + } + + @Override + public List getChildList() { + return mSampleGroup.sampleList; + } + + @Override + public boolean isInitiallyExpanded() { + return false; + } + } + + private class SampleViewHolder extends ChildViewHolder { + + TextView name; + + SampleViewHolder(@NonNull View itemView) { + super(itemView); + name = (TextView) itemView.findViewById(R.id.name); + ((LevelBeamView) itemView.findViewById(R.id.level)).setLevel(2); + itemView.setOnClickListener(mOnItemClickListener); + } + + void bind(Sample sample) { + name.setText(sample.name); + } + + } + + private class SampleGroupViewHolder extends ParentViewHolder { + + TextView name; + + SampleGroupViewHolder(@NonNull View itemView) { + super(itemView); + name = (TextView) itemView.findViewById(R.id.name); + ((LevelBeamView) itemView.findViewById(R.id.level)).setLevel(0); + } + + void bind(SampleGroup group) { + name.setText(group.getGroupName()); + } + + } + + private class Adapter extends ExpandableRecyclerAdapter { + + public Adapter(@NonNull List parentList) { + super(parentList); + } + + @NonNull + @Override + public SampleGroupViewHolder onCreateParentViewHolder(@NonNull ViewGroup parentViewGroup, int viewType) { + View view = LayoutInflater.from(getContext()).inflate(R.layout.sample_recycler_view_group, parentViewGroup, false); + return new SampleGroupViewHolder(view); + } + + @NonNull + @Override + public SampleViewHolder onCreateChildViewHolder(@NonNull ViewGroup childViewGroup, int viewType) { + View view = LayoutInflater.from(getContext()).inflate(R.layout.sample_recycler_view_item, childViewGroup, false); + return new SampleViewHolder(view); + } + + @Override + public void onBindParentViewHolder(@NonNull SampleGroupViewHolder parentViewHolder, int parentPosition, @NonNull SampleGroup parent) { + parentViewHolder.bind(parent); + } + + @Override + public void onBindChildViewHolder(@NonNull SampleViewHolder childViewHolder, int parentPosition, int childPosition, @NonNull Sample child) { + childViewHolder.bind(child); + } + } +} diff --git a/app/src/main/java/com/stardust/scriptdroid/ui/settings/SettingsActivity.java b/app/src/main/java/com/stardust/scriptdroid/ui/settings/SettingsActivity.java index 7c062d42..90fe05bf 100644 --- a/app/src/main/java/com/stardust/scriptdroid/ui/settings/SettingsActivity.java +++ b/app/src/main/java/com/stardust/scriptdroid/ui/settings/SettingsActivity.java @@ -10,7 +10,8 @@ import android.support.v7.widget.Toolbar; import android.view.View; import android.widget.Toast; -import com.stardust.scriptdroid.file.SampleFileManager; +import com.stardust.scriptdroid.Pref; +import com.stardust.scriptdroid.sample.SampleFileManager; import com.stardust.scriptdroid.tool.IntentTool; import com.stardust.scriptdroid.ui.BaseActivity; import com.stardust.scriptdroid.ui.error.IssueReportActivity; @@ -106,15 +107,13 @@ public class SettingsActivity extends BaseActivity { ColorSelectActivity.startColorSelect(getActivity(), getString(R.string.mt_color_picker_title), COLOR_ITEMS); } }) - .entry(getString(R.string.text_re_import_samples), new Runnable() { + .entry(getString(R.string.text_reset_background), new Runnable() { @Override public void run() { - int failCount = SampleFileManager.getInstance().copySampleScriptFile(); - if (failCount <= 0) { - Toast.makeText(getActivity(), R.string.text_re_import_succeed, Toast.LENGTH_SHORT).show(); - notifyScriptListChanged(); - } else - Toast.makeText(getActivity(), R.string.text_fail, Toast.LENGTH_SHORT).show(); + Pref.def().edit().putString(Pref.KEY_APP_BAR_IMAGE_PATH, null) + .putString(Pref.KEY_DRAWER_HEADER_IMAGE_PATH, null) + .apply(); + Toast.makeText(getActivity(), R.string.text_restart_app_to_apply, Toast.LENGTH_SHORT).show(); } }) .entry(getString(R.string.text_issue_report), new Runnable() { @@ -146,10 +145,6 @@ public class SettingsActivity extends BaseActivity { .map(); } - private void notifyScriptListChanged() { - getActivity().sendBroadcast(new Intent(MainActivity.ACTION_NOTIFY_SCRIPT_LIST_CHANGE)); - } - @Override public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) { Runnable action = ACTION_MAP.get(preference.getTitle().toString()); diff --git a/app/src/main/java/com/stardust/util/AssetsCache.java b/app/src/main/java/com/stardust/util/AssetsCache.java new file mode 100644 index 00000000..ec9b69e5 --- /dev/null +++ b/app/src/main/java/com/stardust/util/AssetsCache.java @@ -0,0 +1,29 @@ +package com.stardust.util; + +import android.content.Context; +import android.content.res.AssetManager; + +import com.stardust.scriptdroid.tool.FileUtils; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +/** + * Created by Stardust on 2017/3/14. + */ + +public class AssetsCache { + + private Map mCache = new HashMap<>(); + + + public String read(AssetManager manager, String path) throws IOException { + String str = mCache.get(path); + if (str == null) { + str = FileUtils.readString(manager.open(path)); + mCache.put(path, str); + } + return str; + } +} diff --git a/app/src/main/java/com/stardust/widget/CommonMarkdownView.java b/app/src/main/java/com/stardust/widget/CommonMarkdownView.java index d963f143..438d4a38 100644 --- a/app/src/main/java/com/stardust/widget/CommonMarkdownView.java +++ b/app/src/main/java/com/stardust/widget/CommonMarkdownView.java @@ -1,16 +1,24 @@ package com.stardust.widget; import android.content.Context; +import android.content.Intent; +import android.net.Uri; import android.os.Build; import android.support.annotation.RequiresApi; import android.util.AttributeSet; +import android.view.KeyEvent; +import android.webkit.WebResourceRequest; import android.webkit.WebView; import android.webkit.WebViewClient; +import org.commonmark.ext.heading.anchor.HeadingAnchorExtension; import org.commonmark.node.Node; import org.commonmark.parser.Parser; import org.commonmark.renderer.html.HtmlRenderer; +import java.util.Collection; +import java.util.Collections; + /** * Created by Stardust on 2017/3/5. */ @@ -18,7 +26,11 @@ import org.commonmark.renderer.html.HtmlRenderer; public class CommonMarkdownView extends WebView { private Parser mParser = Parser.builder().build(); - private HtmlRenderer mHtmlRender = HtmlRenderer.builder().build(); + private HtmlRenderer mHtmlRender = HtmlRenderer.builder() + .extensions(Collections.singleton(new HeadingAnchorExtension.Builder().build())) + .build(); + + private String mMarkdownHtml; public CommonMarkdownView(Context context) { super(context); @@ -41,16 +53,35 @@ public class CommonMarkdownView extends WebView { public void onPageFinished(WebView view, String url) { loadUrl("javascript:document.body.style.margin=\"12%\"; void 0"); } + + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + @Override + public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) { + getContext().startActivity(new Intent(Intent.ACTION_VIEW).setData(request.getUrl())); + return true; + } + + @Override + public boolean shouldOverrideUrlLoading(WebView view, String url) { + getContext().startActivity(new Intent(Intent.ACTION_VIEW).setData(Uri.parse(url))); + return true; + } + }); } + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) public CommonMarkdownView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); } public void loadMarkdown(String markdown) { - String html = renderMarkdown(markdown); + mMarkdownHtml = renderMarkdown(markdown); + loadHtml(mMarkdownHtml); + } + + private void loadHtml(String html) { loadDataWithBaseURL(null, html, "text/html", "utf-8", null); } @@ -66,4 +97,11 @@ public class CommonMarkdownView extends WebView { private void setText(String text) { loadDataWithBaseURL(null, text, "text/plain", "utf-8", null); } -} + + public void goBack() { + super.goBack(); + if (!canGoBack() && mMarkdownHtml != null) { + loadHtml(mMarkdownHtml); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/stardust/scriptdroid/layout_inspector/view/LevelBeamView.java b/app/src/main/java/com/stardust/widget/LevelBeamView.java similarity index 97% rename from app/src/main/java/com/stardust/scriptdroid/layout_inspector/view/LevelBeamView.java rename to app/src/main/java/com/stardust/widget/LevelBeamView.java index 0981e833..a18e739b 100644 --- a/app/src/main/java/com/stardust/scriptdroid/layout_inspector/view/LevelBeamView.java +++ b/app/src/main/java/com/stardust/widget/LevelBeamView.java @@ -1,4 +1,4 @@ -package com.stardust.scriptdroid.layout_inspector.view; +package com.stardust.widget; import android.content.Context; import android.graphics.Canvas; diff --git a/app/src/main/java/com/stardust/widget/SimpleAdapterDataObserver.java b/app/src/main/java/com/stardust/widget/SimpleAdapterDataObserver.java new file mode 100644 index 00000000..4f4aa844 --- /dev/null +++ b/app/src/main/java/com/stardust/widget/SimpleAdapterDataObserver.java @@ -0,0 +1,35 @@ +package com.stardust.widget; + +import android.support.v7.widget.RecyclerView; + +/** + * Created by Stardust on 2017/3/14. + */ + +public class SimpleAdapterDataObserver extends RecyclerView.AdapterDataObserver { + + public void onChanged() { + onSomethingChanged(); + } + + public void onItemRangeChanged(int positionStart, int itemCount) { + onSomethingChanged(); + } + + public void onItemRangeInserted(int positionStart, int itemCount) { + onSomethingChanged(); + } + + public void onItemRangeRemoved(int positionStart, int itemCount) { + onSomethingChanged(); + + } + + public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) { + onSomethingChanged(); + } + + public void onSomethingChanged() { + + } +} diff --git a/app/src/main/res/drawable/background_splash.xml b/app/src/main/res/drawable/background_splash.xml new file mode 100644 index 00000000..c3d3a375 --- /dev/null +++ b/app/src/main/res/drawable/background_splash.xml @@ -0,0 +1,17 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_android_eat_js_100.png b/app/src/main/res/drawable/ic_android_eat_js_100.png new file mode 100644 index 00000000..e42c278e Binary files /dev/null and b/app/src/main/res/drawable/ic_android_eat_js_100.png differ diff --git a/app/src/main/res/drawable/side_menu_bg.png b/app/src/main/res/drawable/side_menu_bg.png new file mode 100644 index 00000000..6387aa6a Binary files /dev/null and b/app/src/main/res/drawable/side_menu_bg.png differ diff --git a/app/src/main/res/drawable/slide_menu_img_200px.png b/app/src/main/res/drawable/slide_menu_img_200px.png deleted file mode 100644 index bae8cb97..00000000 Binary files a/app/src/main/res/drawable/slide_menu_img_200px.png and /dev/null differ diff --git a/app/src/main/res/drawable/toolbar_bg.jpg b/app/src/main/res/drawable/toolbar_bg.jpg new file mode 100644 index 00000000..bcc352ce Binary files /dev/null and b/app/src/main/res/drawable/toolbar_bg.jpg differ diff --git a/app/src/main/res/layout/activity_local_webview.xml b/app/src/main/res/layout/activity_local_webview.xml new file mode 100644 index 00000000..beeb2e55 --- /dev/null +++ b/app/src/main/res/layout/activity_local_webview.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 9eb5de09..38144418 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -10,19 +10,21 @@ + android:layout_height="match_parent"> - @@ -35,7 +37,16 @@ android:padding="12dp" android:src="@drawable/ic_add_white_48dp"/> - + + + diff --git a/app/src/main/res/layout/content_main.xml b/app/src/main/res/layout/content_main.xml index 31d01fac..a9db875c 100644 --- a/app/src/main/res/layout/content_main.xml +++ b/app/src/main/res/layout/content_main.xml @@ -10,10 +10,11 @@ tools:context="com.stardust.scriptdroid.ui.main.MainActivity" tools:showIn="@layout/activity_main"> - - + android:layout_height="match_parent" + app:layout_behavior="@string/appbar_scrolling_view_behavior"/> + diff --git a/app/src/main/res/layout/drawer.xml b/app/src/main/res/layout/drawer.xml index ca1ee56a..a217793f 100644 --- a/app/src/main/res/layout/drawer.xml +++ b/app/src/main/res/layout/drawer.xml @@ -20,7 +20,7 @@ android:layout_height="match_parent" android:clickable="true" android:scaleType="fitXY" - android:src="@drawable/slide_menu_img_200px"/> + android:src="@drawable/side_menu_bg"/> + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_my_script_list.xml b/app/src/main/res/layout/fragment_my_script_list.xml new file mode 100644 index 00000000..7f741c54 --- /dev/null +++ b/app/src/main/res/layout/fragment_my_script_list.xml @@ -0,0 +1,21 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_sample_script_list.xml b/app/src/main/res/layout/fragment_sample_script_list.xml new file mode 100644 index 00000000..7a89e32f --- /dev/null +++ b/app/src/main/res/layout/fragment_sample_script_list.xml @@ -0,0 +1,13 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_hierarchy_view_item.xml b/app/src/main/res/layout/layout_hierarchy_view_item.xml index e290395a..3e075a3a 100644 --- a/app/src/main/res/layout/layout_hierarchy_view_item.xml +++ b/app/src/main/res/layout/layout_hierarchy_view_item.xml @@ -6,7 +6,7 @@ android:layout_height="32dp" android:padding="5dp"> - diff --git a/app/src/main/res/layout/sample_recycler_view_group.xml b/app/src/main/res/layout/sample_recycler_view_group.xml new file mode 100644 index 00000000..cff90d43 --- /dev/null +++ b/app/src/main/res/layout/sample_recycler_view_group.xml @@ -0,0 +1,24 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/sample_recycler_view_item.xml b/app/src/main/res/layout/sample_recycler_view_item.xml new file mode 100644 index 00000000..c2cdcb83 --- /dev/null +++ b/app/src/main/res/layout/sample_recycler_view_item.xml @@ -0,0 +1,27 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/raw/licenses.xml b/app/src/main/res/raw/licenses.xml index d86a1ae9..bb1834dc 100644 --- a/app/src/main/res/raw/licenses.xml +++ b/app/src/main/res/raw/licenses.xml @@ -54,12 +54,6 @@ https://github.com/HeinrichReimer/android-issue-reporter Apache Software License 2.0 - - Floaties - - https://github.com/ericbhatti/floaties - Apache Software License 2.0 - SortableTableView Copyright 2015 Ingo Schwarz @@ -78,4 +72,22 @@ https://github.com/open-rnd/android-multi-level-listview Apache Software License 2.0 + + hover + 2016 (C) Copyright Open-RnD Sp. z o.o. + https://github.com/open-rnd/android-multi-level-listview + Apache Software License 2.0 + + + event-bus + 2016 (C) Copyright Open-RnD Sp. z o.o. + https://github.com/open-rnd/android-multi-level-listview + Apache Software License 2.0 + + + Expandable RecyclerView + Copyright (c) 2014 Big Nerd Ranch + https://github.com/bignerdranch/expandable-recycler-view + MIT License + \ No newline at end of file diff --git a/app/src/main/res/values-v21/styles.xml b/app/src/main/res/values-v21/styles.xml index dbbdd40f..cf96cf61 100644 --- a/app/src/main/res/values-v21/styles.xml +++ b/app/src/main/res/values-v21/styles.xml @@ -6,4 +6,12 @@ true @android:color/transparent + + + diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index cad427a3..90aaf885 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -1,7 +1,8 @@ - #8ea22a - #5e6b1e - #8ea22a + #009688 + #8ea22a + #00796B + #FF4081 #99444444 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 11b0a000..78ea63b9 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -142,4 +142,9 @@ 布局层次分析 布局范围查看 无障碍服务未启动 + 示例脚本 + 我的脚本 + 点击右上角新建第一个脚本吧~(。・・)ノ + 重置背景图片设置 + 重启软件生效 diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 2f7f6f46..26ac379e 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -8,6 +8,26 @@ @color/colorAccent + + + + + +