JSBox的学习路线图和知识点精要
JSBox是一个iOS上的JavaScript集成开发环境,可以用JavaScript开发iOS原生应用和小工具。我需要创建一个全面的学习指南,包括:
- JSBox简介
- 基础知识
- UI组件
- API详解
- 高级功能
- 实战项目
- 最佳实践
- 常见问题
我需要确保内容详实、结构清晰,达到10000字的要求。
JSBox 完整学习路线图及知识点精要
目录
-
JSBox 概述与发展历程
-
环境搭建与基础配置
-
JavaScript 基础知识回顾
-
JSBox 核心概念与架构
-
UI 组件系统详解
-
API 接口全面解析
-
数据存储与管理
-
网络请求与通信
-
系统集成与扩展
-
高级功能与技巧
-
实战项目开发
-
性能优化与最佳实践
-
发布与分享
-
常见问题与解决方案
-
未来发展与社区资源
第一章:JSBox 概述与发展历程
1.1 什么是 JSBox
JSBox 是一个革命性的 iOS 平台集成开发环境,它允许开发者使用 JavaScript 语言开发原生 iOS 应用和实用工具。作为一个强大的生产力工具,JSBox 打破了传统 iOS 开发需要 Mac 电脑和 Xcode 的限制,让开发者可以直接在 iPhone 或 iPad 上进行编程。
JSBox 的核心理念是"简单、高效、强大"。它提供了丰富的原生 API 接口,使得 JavaScript 开发者可以轻松调用 iOS 系统功能,创建具有原生体验的应用。无论是简单的脚本工具,还是复杂的应用程序,JSBox 都能胜任。
1.2 JSBox 的特点与优势
JSBox 具有以下显著特点:
移动开发新体验:JSBox 让你可以随时随地在 iOS 设备上编写代码,无需电脑即可完成从开发到调试的全过程。内置的代码编辑器支持语法高亮、自动补全、代码折叠等专业功能,提供了接近桌面 IDE 的开发体验。
强大的 API 支持:JSBox 封装了大量 iOS 原生 API,包括但不限于文件系统、网络请求、UI 组件、系统服务、硬件接口等。这些 API 经过精心设计,既保持了 JavaScript 的简洁性,又充分发挥了 iOS 平台的能力。
丰富的 UI 组件:JSBox 提供了完整的 UI 框架,支持创建各种原生界面。从基础的按钮、文本框,到复杂的列表、网格、图表,都可以通过简单的 JavaScript 代码实现。所有 UI 组件都支持自定义样式和交互。
即时运行与调试:代码编写完成后,可以立即运行查看效果。JSBox 提供了完善的调试工具,包括控制台输出、断点调试、性能分析等,帮助开发者快速定位和解决问题。
社区与生态系统:JSBox 拥有活跃的开发者社区,用户可以分享自己的作品,学习他人的代码。官方商店提供了大量优质脚本和应用,涵盖了生产力工具、娱乐应用、系统增强等各个领域。
1.3 JSBox 的应用场景
JSBox 的应用场景非常广泛,以下是一些典型的使用案例:
自动化工具:创建各种自动化脚本,如批量处理图片、自动整理文件、定时任务等。JSBox 可以访问系统相册、文件系统,实现复杂的自动化流程。
生产力应用:开发个人定制的生产力工具,如待办事项管理、笔记应用、时间追踪器等。这些应用可以充分利用 iOS 的通知、小组件等系统特性。
数据处理:编写数据分析和处理脚本,支持 JSON、CSV、XML 等多种数据格式。可以创建数据可视化工具,生成各种图表和报告。
网络工具:开发网络相关的工具,如 API 测试客户端、网页爬虫、下载管理器等。JSBox 提供了强大的网络请求功能,支持各种 HTTP 方法和自定义请求头。
系统增强:创建系统功能的增强工具,如剪贴板管理器、快捷启动器、系统信息查看器等。通过 JSBox 的系统 API,可以实现很多原本需要越狱才能实现的功能。
第二章:环境搭建与基础配置
2.1 安装 JSBox
JSBox 的安装过程非常简单,但有一些注意事项需要了解:
App Store 下载:JSBox 可以直接从 App Store 下载安装。搜索"JSBox"即可找到官方应用。需要注意的是,JSBox 是付费应用,但考虑到其强大的功能,这个投资是非常值得的。
系统要求:JSBox 需要 iOS 10.0 或更高版本。建议使用较新的 iOS 版本,以获得最佳的性能和兼容性。某些高级功能可能需要特定的 iOS 版本支持。
存储空间:虽然 JSBox 本身占用空间不大,但随着脚本和数据的增加,可能需要较多存储空间。建议预留至少 1GB 的可用空间。
2.2 界面介绍与基本操作
JSBox 的界面设计简洁而功能强大,主要包括以下几个部分:
主界面:打开 JSBox 后,首先看到的是脚本列表界面。这里显示了所有已安装的脚本和应用。可以通过滑动操作进行删除、编辑等操作。底部工具栏提供了快速访问常用功能的入口。
代码编辑器:JSBox 的代码编辑器是其核心组件之一。编辑器支持语法高亮、代码自动补全、括号匹配、代码折叠等专业功能。顶部工具栏提供了运行、保存、查找替换等常用操作。键盘上方的快捷键栏可以快速输入常用符号和代码片段。
运行环境:点击运行按钮后,脚本会在独立的运行环境中执行。运行界面会显示脚本创建的 UI 界面或输出结果。底部有控制台按钮,可以查看日志输出和错误信息。
文件管理器:JSBox 内置了强大的文件管理器,可以管理脚本文件、资源文件、数据文件等。支持创建文件夹、移动复制文件、查看文件属性等操作。文件管理器还支持与其他应用共享文件。
设置界面:在设置中可以配置编辑器主题、字体大小、代码风格等个性化选项。还可以管理扩展插件、查看文档、访问社区等。
2.3 创建第一个脚本
让我们创建一个简单的"Hello World"脚本,熟悉 JSBox 的基本开发流程:
// 创建一个简单的界面
$ui.render({
views: [{
type: "label",
props: {
text: "Hello, JSBox!",
align: $align.center,
font: $font(32)
},
layout: function(make, view) {
make.center.equalTo(view.super)
make.width.equalTo(view.super)
}
}]
})
这个简单的例子展示了 JSBox 的几个核心概念:
-
$ui.render()用于渲染用户界面 -
views数组定义了界面中的组件 -
props对象设置组件的属性 -
layout函数定义组件的布局
运行这个脚本,你会看到屏幕中央显示"Hello, JSBox!"的文字。
2.4 项目结构与文件组织
一个完整的 JSBox 项目通常包含以下文件和目录:
主脚本文件:通常命名为 main.js 或 index.js,是项目的入口文件。这个文件负责初始化应用、设置界面、处理用户交互等核心逻辑。
模块文件:将功能模块化是良好的编程习惯。可以创建多个 .js 文件,每个文件负责特定的功能模块。通过 require() 函数可以在主脚本中引用这些模块。
资源文件夹:通常命名为 assets 或 resources,用于存放图片、音频、数据文件等资源。JSBox 支持多种资源格式,可以通过相对路径引用这些资源。
配置文件:config.json 文件用于存储应用配置信息,如版本号、作者信息、应用描述等。这些信息会显示在脚本列表中。
文档文件:建议创建 README.md 文件,记录项目的使用说明、功能介绍、更新日志等信息。良好的文档有助于他人理解和使用你的脚本。
第三章:JavaScript 基础知识回顾
3.1 JavaScript 语法基础
虽然 JSBox 的目标用户可能已经具备 JavaScript 基础,但有必要回顾一些在 JSBox 开发中特别重要的语法知识:
变量声明与作用域:在 JSBox 中,建议使用 let 和 const 声明变量,避免使用 var。这样可以避免作用域相关的问题,使代码更加清晰和可维护。
// 推荐的变量声明方式
const API_URL = "https://api.example.com" // 常量
let userName = "JSBox User" // 可变变量
let userAge = 25
// 块级作用域
if (userAge > 18) {
let message = "成年用户"
console.log(message) // 正常输出
}
// console.log(message) // 错误:message 在此处不可访问
函数定义与箭头函数:JSBox 支持多种函数定义方式,箭头函数在处理回调时特别有用:
// 传统函数定义
function calculateSum(a, b) {
return a + b
}
// 函数表达式
const calculateProduct = function(a, b) {
return a * b
}
// 箭头函数
const calculateDifference = (a, b) => a - b
// 在 JSBox 的事件处理中常用箭头函数
$ui.render({
views: [{
type: "button",
props: { title: "点击我" },
events: {
tapped: () => {
$ui.alert("按钮被点击了!")
}
}
}]
})
对象和数组操作:JSBox 开发中会频繁使用对象和数组,掌握现代 JavaScript 的操作方法很重要:
// 对象解构
const user = { name: "张三", age: 25, city: "北京" }
const { name, age } = user
// 数组解构
const numbers = [1, 2, 3, 4, 5]
const [first, second, ...rest] = numbers
// 扩展运算符
const newUser = { ...user, email: "zhangsan@example.com" }
const moreNumbers = [...numbers, 6, 7, 8]
// 数组方法
const doubled = numbers.map(n => n * 2)
const evens = numbers.filter(n => n % 2 === 0)
const sum = numbers.reduce((acc, n) => acc + n, 0)
3.2 异步编程
JSBox 中的很多操作都是异步的,如网络请求、文件读写、动画等。理解和正确使用异步编程模式非常重要:
回调函数:这是最基础的异步模式,JSBox 的很多 API 使用回调函数:
// 使用回调函数读取文件
$file.read("data.txt", content => {
if (content) {
console.log("文件内容:", content)
} else {
console.log("读取文件失败")
}
})
Promise:Promise 提供了更优雅的异步处理方式:
// 将回调包装成 Promise
function readFilePromise(path) {
return new Promise((resolve, reject) => {
$file.read(path, content => {
if (content) {
resolve(content)
} else {
reject(new Error("读取文件失败"))
}
})
})
}
// 使用 Promise
readFilePromise("data.txt")
.then(content => console.log("文件内容:", content))
.catch(error => console.error("错误:", error))
async/await:这是最现代的异步编程方式,使异步代码看起来像同步代码:
// 使用 async/await
async function processFile() {
try {
const content = await readFilePromise("data.txt")
console.log("文件内容:", content)
// 可以连续进行多个异步操作
const processedContent = await processContent(content)
await $file.write("output.txt", processedContent)
console.log("处理完成")
} catch (error) {
console.error("处理失败:", error)
}
}
3.3 错误处理
良好的错误处理是稳定应用的基础。在 JSBox 中,需要特别注意以下几点:
try-catch 语句:用于捕获同步代码中的错误:
try {
const data = JSON.parse(jsonString)
processData(data)
} catch (error) {
console.error("JSON 解析失败:", error)
$ui.alert({
title: "错误",
message: "数据格式不正确"
})
}
Promise 错误处理:异步操作的错误需要通过 .catch() 或在 async/await 中使用 try-catch:
// Promise 错误处理
fetchData()
.then(data => processData(data))
.catch(error => {
console.error("获取数据失败:", error)
showErrorMessage(error)
})
// async/await 错误处理
async function loadData() {
try {
const data = await fetchData()
await processData(data)
} catch (error) {
console.error("操作失败:", error)
showErrorMessage(error)
}
}
全局错误处理:JSBox 提供了全局错误处理机制:
$app.errorHandler = function(error) {
console.error("全局错误:", error)
// 可以在这里上报错误到服务器
// 或者显示用户友好的错误提示
}
第四章:JSBox 核心概念与架构
4.1 JSBox 运行时环境
理解 JSBox 的运行时环境对于开发高质量的应用至关重要。JSBox 基于 JavaScriptCore 引擎,这是 Safari 浏览器使用的同一个 JavaScript 引擎,提供了优秀的性能和兼容性。
JavaScript 引擎特性:JSBox 使用的 JavaScriptCore 支持 ES6+ 的大部分特性,包括但不限于:类、模块、解构赋值、模板字符串、Symbol、Map/Set、Promise、async/await 等。这使得开发者可以使用现代 JavaScript 语法编写代码。
内存管理:JSBox 的内存管理是自动的,使用垃圾回收机制。但开发者仍需注意避免内存泄漏,特别是在处理大量数据或创建复杂 UI 时。要注意及时释放不再使用的资源,避免循环引用。
线程模型:JSBox 的 JavaScript 代码运行在主线程上,这意味着长时间运行的同步操作会阻塞 UI。对于耗时操作,应该使用异步方法或考虑使用 Web Worker(如果支持)。
4.2 模块系统
JSBox 支持 CommonJS 风格的模块系统,这允许将代码组织成可重用的模块:
创建模块:
// math.js - 数学工具模块
function add(a, b) {
return a + b
}
function multiply(a, b) {
return a * b
}
module.exports = {
add: add,
multiply: multiply,
PI: 3.14159
}
使用模块:
// main.js - 主程序
const math = require('./math')
console.log(math.add(5, 3)) // 输出: 8
console.log(math.multiply(4, math.PI)) // 输出: 12.56636
模块路径解析:JSBox 的模块路径解析遵循以下规则:
-
如果路径以
./或../开头,视为相对路径 -
如果路径不以
.开头,JSBox 会在内置模块和 node_modules 目录中查找 -
可以省略
.js扩展名
4.3 全局对象与 API 命名空间
JSBox 提供了一系列全局对象,通过 $ 前缀访问。这些对象组织了 JSBox 的所有 API:
核心全局对象:
-
$app:应用程序相关功能 -
$ui:用户界面相关功能 -
$http:网络请求功能 -
$file:文件系统操作 -
$cache:缓存管理 -
$device:设备信息和功能 -
$system:系统功能调用
每个全局对象都包含了相关的方法和属性,例如:
// 应用信息
console.log($app.info.version) // 应用版本
console.log($app.info.bundleID) // Bundle ID
// 设备信息
console.log($device.info.model) // 设备型号
console.log($device.info.language) // 系统语言
// 系统功能
$system.call("tel:10086") // 拨打电话
$system.mailto("example@email.com") // 发送邮件
4.4 事件系统
JSBox 采用事件驱动的编程模型。理解事件系统对于创建响应式的应用至关重要:
UI 事件:
$ui.render({
views: [{
type: "button",
props: {
title: "点击计数"
},
layout: $layout.center,
events: {
tapped: function(sender) {
sender.title = `点击了 ${++clickCount} 次`
},
longPressed: function(sender) {
$ui.alert("长按检测到!")
}
}
}]
})
自定义事件:
// 创建事件发射器
const EventEmitter = require('events')
const emitter = new EventEmitter()
// 监听事件
emitter.on('data-loaded', (data) => {
console.log('数据加载完成:', data)
})
// 触发事件
emitter.emit('data-loaded', { items: [1, 2, 3] })
生命周期事件:
$app.autoKeyboardEnabled = true // 自动键盘管理
// 应用启动
$app.listen({
ready: function() {
console.log("应用已就绪")
},
exit: function() {
console.log("应用即将退出")
// 保存数据等清理工作
}
})
第五章:UI 组件系统详解
5.1 基础 UI 组件
JSBox 提供了丰富的 UI 组件,覆盖了 iOS 开发中的常用控件。每个组件都有其特定的属性和事件:
Label(标签):用于显示文本信息
{
type: "label",
props: {
text: "这是一个标签",
font: $font(18), // 字体大小
textColor: $color("#333333"), // 文字颜色
align: $align.center, // 对齐方式
lines: 0, // 行数限制,0 表示不限制
autoFontSize: true // 自动调整字体大小
},
layout: function(make, view) {
make.center.equalTo(view.super)
make.width.equalTo(200)
}
}
Button(按钮):用于触发用户操作
{
type: "button",
props: {
title: "提交",
bgcolor: $color("#007AFF"),
titleColor: $color("white"),
font: $font("bold", 16),
cornerRadius: 8,
borderWidth: 1,
borderColor: $color("#0051D5")
},
layout: function(make, view) {
make.center.equalTo(view.super)
make.size.equalTo($size(120, 44))
},
events: {
tapped: function(sender) {
handleSubmit()
}
}
}
Input(输入框):用于接收用户输入
{
type: "input",
props: {
placeholder: "请输入用户名",
font: $font(16),
textColor: $color("#333"),
borderWidth: 1,
borderColor: $color("#E0E0E0"),
cornerRadius: 4,
secure: false, // 是否为密码输入
type: $kbType.default, // 键盘类型
autoFontSize: false
},
layout: function(make, view) {
make.left.right.inset(20)
make.top.equalTo(100)
make.height.equalTo(44)
},
events: {
changed: function(sender) {
console.log("输入内容:", sender.text)
},
returned: function(sender) {
sender.blur() // 收起键盘
}
}
}
Image(图片):用于显示图片
{
type: "image",
props: {
src: "https://example.com/image.jpg", // 网络图片
// src: "assets/local_image.png", // 本地图片
contentMode: $contentMode.scaleAspectFit,
cornerRadius: 10,
borderWidth: 2,
borderColor: $color("#E0E0E0")
},
layout: function(make, view) {
make.center.equalTo(view.super)
make.size.equalTo($size(200, 200))
},
events: {
tapped: function(sender) {
previewImage(sender.src)
}
}
}
Switch(开关):用于切换状态
{
type: "switch",
props: {
on: true, // 默认状态
onColor: $color("#4CD964"), // 开启时的颜色
thumbColor: $color("white") // 滑块颜色
},
layout: function(make, view) {
make.right.equalTo(-20)
make.centerY.equalTo(view.super)
},
events: {
changed: function(sender) {
updateSetting("notifications", sender.on)
}
}
}
5.2 容器组件
容器组件用于组织和布局其他组件:
View(视图容器):最基础的容器组件
{
type: "view",
props: {
bgcolor: $color("#F5F5F5"),
cornerRadius: 10,
clipsToBounds: true, // 裁剪超出边界的内容
shadow: {
color: $color("#000000", 0.1),
offset: $size(0, 2),
radius: 4
}
},
layout: function(make, view) {
make.edges.inset(10)
},
views: [
// 子视图
]
}
Scroll(滚动视图):当内容超出屏幕时使用
{
type: "scroll",
props: {
contentSize: $size(0, 1000), // 内容大小
showsVerticalIndicator: true,
showsHorizontalIndicator: false,
pagingEnabled: false, // 分页滚动
bounces: true // 回弹效果
},
layout: $layout.fill,
views: [
// 滚动内容
],
events: {
didScroll: function(sender) {
console.log("滚动位置:", sender.contentOffset)
}
}
}
Stack(堆栈视图):自动布局子视图
{
type: "stack",
props: {
axis: $stackViewAxis.vertical, // 垂直排列
distribution: $stackViewDistribution.fillEqually,
alignment: $stackViewAlignment.fill,
spacing: 10, // 间距
bgcolor: $color("white")
},
layout: function(make, view) {
make.edges.inset(20)
},
views: [
// 自动排列的子视图
]
}
5.3 列表组件
列表是移动应用中最常用的组件之一:
List(列表):
{
type: "list",
props: {
data: [
{
title: "第一项",
subtitle: "这是第一项的描述"
},
{
title: "第二项",
subtitle: "这是第二项的描述"
}
],
rowHeight: 60,
separatorInset: $insets(0, 15, 0, 0),
template: {
views: [
{
type: "label",
props: {
id: "title",
font: $font("bold", 16)
},
layout: function(make, view) {
make.left.equalTo(15)
make.top.equalTo(10)
}
},
{
type: "label",
props: {
id: "subtitle",
font: $font(14),
textColor: $color("#666")
},
layout: function(make, view) {
make.left.equalTo(15)
make.bottom.equalTo(-10)
}
}
]
}
},
layout: $layout.fill,
events: {
didSelect: function(sender, indexPath, data) {
console.log(`选中了第 ${indexPath.row} 项`)
showDetail(data)
},
didLongPress: function(sender, indexPath, data) {
showContextMenu(data)
}
}
}
Matrix(矩阵/网格):用于显示网格布局
{
type: "matrix",
props: {
columns: 3, // 列数
itemHeight: 100, // 项目高度
spacing: 10, // 间距
data: generateGridData(),
template: {
views: [
{
type: "image",
props: {
id: "image",
contentMode: $contentMode.scaleAspectFill
},
layout: $layout.fill
},
{
type: "label",
props: {
id: "label",
align: $align.center,
font: $font(12)
},
layout: function(make, view) {
make.left.right.bottom.inset(0)
make.height.equalTo(30)
}
}
]
}
},
layout: $layout.fill,
events: {
didSelect: function(sender, indexPath, data) {
handleGridItemClick(data)
}
}
}
5.4 高级 UI 组件
JSBox 还提供了一些高级组件,用于实现复杂的交互:
Web(网页视图):
{
type: "web",
props: {
url: "https://www.example.com",
// 或使用 HTML 内容
html: "<html><body><h1>Hello</h1></body></html>",
showsProgress: true, // 显示加载进度
ua: "Mozilla/5.0 ...", // 自定义 User Agent
script: `
// 注入的 JavaScript 代码
document.body.style.backgroundColor = '#f0f0f0';
`
},
layout: $layout.fill,
events: {
didStart: function(sender, navigation) {
console.log("开始加载:", navigation.url)
},
didFinish: function(sender, navigation) {
console.log("加载完成")
},
didFail: function(sender, navigation, error) {
console.error("加载失败:", error)
}
}
}
Chart(图表):
{
type: "chart",
props: {
type: "line", // 图表类型:line, bar, pie 等
data: {
labels: ["一月", "二月", "三月", "四月"],
datasets: [{
label: "销售额",
data: [65, 59, 80, 81],
borderColor: $color("#007AFF"),
backgroundColor: $color("#007AFF", 0.1)
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
y: {
beginAtZero: true
}
}
}
},
layout: function(make, view) {
make.edges.inset(20)
make.height.equalTo(300)
}
}
第六章:API 接口全面解析
6.1 文件系统 API
JSBox 提供了完整的文件系统访问能力,可以读写文件、管理目录等:
基本文件操作:
// 读取文本文件
const content = $file.read("data.txt")
if (content) {
console.log("文件内容:", content.string)
}
// 写入文本文件
const success = $file.write({
data: $data({string: "Hello, JSBox!"}),
path: "output.txt"
})
console.log("写入成功:", success)
// 检查文件是否存在
const exists = $file.exists("data.txt")
console.log("文件存在:", exists)
// 删除文件
const deleted = $file.delete("temp.txt")
console.log("删除成功:", deleted)
// 复制文件
$file.copy({
src: "source.txt",
dst: "destination.txt"
})
// 移动文件
$file.move({
src: "old_path.txt",
dst: "new_path.txt"
})
目录操作:
// 创建目录
const created = $file.mkdir("documents/images")
console.log("目录创建成功:", created)
// 列出目录内容
const files = $file.list("documents")
files.forEach(file => {
console.log("文件:", file)
})
// 获取文件信息
const info = $file.info("document.pdf")
if (info) {
console.log("文件大小:", info.size)
console.log("修改时间:", info.modificationDate)
console.log("是否为目录:", info.isDirectory)
}
// 递归遍历目录
function traverseDirectory(path) {
const items = $file.list(path)
items.forEach(item => {
const fullPath = path + "/" + item
const info = $file.info(fullPath)
if (info.isDirectory) {
traverseDirectory(fullPath)
} else {
console.log("文件:", fullPath)
}
})
}
文件共享:
// 分享文件
$share.sheet({
items: [$file.read("document.pdf")],
handler: function(success) {
console.log("分享成功:", success)
}
})
// 从其他应用导入文件
$drive.open({
handler: function(data) {
if (data) {
$file.write({
data: data,
path: "imported_file"
})
}
}
})
6.2 网络请求 API
网络功能是现代应用的核心,JSBox 提供了强大的网络 API:
基本 HTTP 请求:
// GET 请求
$http.get({
url: "https://api.example.com/users",
header: {
"Authorization": "Bearer token123"
},
handler: function(resp) {
if (resp.error) {
console.error("请求失败:", resp.error)
} else {
const data = resp.data
console.log("响应数据:", data)
}
}
})
// POST 请求
$http.post({
url: "https://api.example.com/users",
header: {
"Content-Type": "application/json"
},
body: {
name: "张三",
email: "zhangsan@example.com"
},
handler: function(resp) {
if (resp.response.statusCode === 201) {
console.log("创建成功")
}
}
})
// 带进度的下载
$http.download({
url: "https://example.com/large_file.zip",
progress: function(percentage) {
console.log(`下载进度:${(percentage * 100).toFixed(2)}%`)
updateProgressBar(percentage)
},
handler: function(resp) {
if (resp.data) {
$file.write({
data: resp.data,
path: "downloads/large_file.zip"
})
}
}
})
高级网络功能:
// 并发请求
async function fetchMultipleData() {
const urls = [
"https://api.example.com/data1",
"https://api.example.com/data2",
"https://api.example.com/data3"
]
const promises = urls.map(url =>
new Promise((resolve, reject) => {
$http.get({
url: url,
handler: resp => {
if (resp.error) {
reject(resp.error)
} else {
resolve(resp.data)
}
}
})
})
)
try {
const results = await Promise.all(promises)
console.log("所有数据:", results)
} catch (error) {
console.error("请求失败:", error)
}
}
// WebSocket 连接
const ws = $websocket.new({
url: "wss://echo.websocket.org",
handler: function(message) {
console.log("收到消息:", message)
},
eventHandler: {
open: function() {
console.log("WebSocket 已连接")
ws.send("Hello Server!")
},
error: function(error) {
console.error("WebSocket 错误:", error)
},
close: function(code, reason) {
console.log("WebSocket 已关闭:", code, reason)
}
}
})
6.3 系统功能 API
JSBox 可以调用多种 iOS 系统功能:
剪贴板操作:
// 复制文本到剪贴板
$clipboard.text = "这是复制的文本"
// 读取剪贴板文本
const clipboardText = $clipboard.text
console.log("剪贴板内容:", clipboardText)
// 复制图片到剪贴板
$clipboard.image = image
// 清空剪贴板
$clipboard.clear()
系统分享:
// 分享文本
$share.sheet({
items: ["分享这段文字"],
handler: function(success) {
if (success) {
console.log("分享成功")
}
}
})
// 分享图片和文字
$share.sheet({
items: [
"看看这张图片",
$("imageView").snapshot // 获取视图截图
]
})
// 分享文件
$share.sheet({
items: [$file.read("document.pdf")]
})
通知功能:
// 发送本地通知
$push.schedule({
title: "提醒",
body: "该休息一下了",
delay: 5, // 5秒后触发
handler: function(result) {
console.log("通知已安排")
}
})
// 重复通知
$push.schedule({
title: "每日提醒",
body: "记得喝水",
date: new Date(2023, 0, 1, 9, 0), // 指定时间
repeat: "day", // 每天重复
handler: function(result) {
console.log("重复通知已设置")
}
})
// 取消通知
$push.cancel({
title: "提醒"
})
设备功能:
// 震动反馈
$device.taptic(0) // 0: light, 1: medium, 2: heavy
// 获取电池信息
console.log("电池电量:", $device.info.battery.level)
console.log("充电状态:", $device.info.battery.state)
// 屏幕亮度
$device.brightness = 0.8 // 设置亮度 (0-1)
console.log("当前亮度:", $device.brightness)
// 手电筒
$device.torch = true // 打开手电筒
// 获取网络状态
console.log("网络类型:", $device.networkType)
// 0: 无网络, 1: Wi-Fi, 2: 蜂窝网络
6.4 数据存储 API
JSBox 提供了多种数据存储方案:
缓存系统:
// 设置缓存
$cache.set("user_name", "张三")
$cache.set("user_data", {
id: 123,
email: "zhangsan@example.com"
})
// 读取缓存
const userName = $cache.get("user_name")
const userData = $cache.get("user_data")
// 删除缓存
$cache.remove("user_name")
// 清空所有缓存
$cache.clear()
// 设置带过期时间的缓存
$cache.setAsync({
key: "temp_token",
value: "abc123",
ttl: 3600 // 1小时后过期
})
数据库操作:
// 创建/打开数据库
const db = $sqlite.open("app.db")
// 创建表
db.update({
sql: `CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT UNIQUE,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
)`
})
// 插入数据
db.update({
sql: "INSERT INTO users (name, email) VALUES (?, ?)",
args: ["李四", "lisi@example.com"]
})
// 查询数据
const result = db.query({
sql: "SELECT * FROM users WHERE name LIKE ?",
args: ["%李%"]
})
result.forEach(row => {
console.log(`ID: ${row.id}, Name: ${row.name}, Email: ${row.email}`)
})
// 事务处理
db.transaction(() => {
db.update("INSERT INTO users (name) VALUES ('王五')")
db.update("INSERT INTO users (name) VALUES ('赵六')")
// 如果任何操作失败,所有更改都会回滚
})
// 关闭数据库
db.close()
第七章:数据存储与管理
7.1 数据持久化策略
在开发 JSBox 应用时,选择合适的数据持久化策略非常重要:
选择存储方案的考虑因素:
-
数据量大小:小量数据使用缓存或文件,大量结构化数据使用数据库
-
访问频率:高频访问的数据应该缓存在内存中
-
数据结构:简单键值对使用缓存,复杂关系使用数据库
-
安全性需求:敏感数据需要加密存储
混合存储策略示例:
class DataManager {
constructor() {
this.cache = new Map() // 内存缓存
this.db = $sqlite.open("app.db")
this.initDatabase()
}
initDatabase() {
this.db.update({
sql: `CREATE TABLE IF NOT EXISTS cache (
key TEXT PRIMARY KEY,
value TEXT,
expire_at INTEGER
)`
})
}
// 多级缓存策略
async get(key) {
// 1. 检查内存缓存
if (this.cache.has(key)) {
return this.cache.get(key)
}
// 2. 检查持久化缓存
const result = this.db.query({
sql: "SELECT value, expire_at FROM cache WHERE key = ?",
args: [key]
})
if (result.length > 0) {
const { value, expire_at } = result[0]
if (!expire_at || expire_at > Date.now()) {
const data = JSON.parse(value)
this.cache.set(key, data) // 写入内存缓存
return data
} else {
// 已过期,删除
this.db.update({
sql: "DELETE FROM cache WHERE key = ?",
args: [key]
})
}
}
return null
}
async set(key, value, ttl = null) {
// 写入内存缓存
this.cache.set(key, value)
// 写入持久化存储
const expire_at = ttl ? Date.now() + ttl * 1000 : null
this.db.update({
sql: `INSERT OR REPLACE INTO cache (key, value, expire_at)
VALUES (?, ?, ?)`,
args: [key, JSON.stringify(value), expire_at]
})
}
}
7.2 文件管理最佳实践
文件组织结构:
const FileManager = {
// 定义标准目录结构
paths: {
documents: "documents",
images: "documents/images",
cache: "cache",
temp: "temp",
backups: "backups"
},
// 初始化目录结构
initialize() {
Object.values(this.paths).forEach(path => {
if (!$file.exists(path)) {
$file.mkdir(path)
}
})
},
// 获取文件路径
getPath(type, filename) {
return `${this.paths[type]}/${filename}`
},
// 清理临时文件
cleanTemp() {
const tempFiles = $file.list(this.paths.temp)
tempFiles.forEach(file => {
$file.delete(`${this.paths.temp}/${file}`)
})
},
// 文件大小管理
getDirectorySize(path) {
let totalSize = 0
const files = $file.list(path)
files.forEach(file => {
const fullPath = `${path}/${file}`
const info = $file.info(fullPath)
if (info.isDirectory) {
totalSize += this.getDirectorySize(fullPath)
} else {
totalSize += info.size
}
})
return totalSize
},
// 自动清理策略
autoClean(maxSize = 50 * 1024 * 1024) { // 50MB
const cacheSize = this.getDirectorySize(this.paths.cache)
if (cacheSize > maxSize) {
// 删除最老的文件
const files = $file.list(this.paths.cache)
.map(file => ({
name: file,
info: $file.info(`${this.paths.cache}/${file}`)
}))
.sort((a, b) => a.info.modificationDate - b.info.modificationDate)
let deletedSize = 0
for (const file of files) {
if (deletedSize >= cacheSize - maxSize) break
$file.delete(`${this.paths.cache}/${file.name}`)
deletedSize += file.info.size
}
}
}
}
7.3 数据同步与备份
自动备份机制:
class BackupManager {
constructor() {
this.backupPath = "backups"
this.maxBackups = 5
}
// 创建备份
async createBackup() {
const timestamp = new Date().toISOString().replace(/[:.]/g, "-")
const backupName = `backup_${timestamp}`
const backupDir = `${this.backupPath}/${backupName}`
// 创建备份目录
$file.mkdir(backupDir)
// 备份数据库
if ($file.exists("app.db")) {
$file.copy({
src: "app.db",
dst: `${backupDir}/app.db`
})
}
// 备份配置文件
const config = $cache.get("app_config")
if (config) {
$file.write({
data: $data({string: JSON.stringify(config, null, 2)}),
path: `${backupDir}/config.json`
})
}
// 备份用户数据
const userData = await this.collectUserData()
$file.write({
data: $data({string: JSON.stringify(userData, null, 2)}),
path: `${backupDir}/userdata.json`
})
// 清理旧备份
this.cleanOldBackups()
return backupName
}
// 恢复备份
async restoreBackup(backupName) {
const backupDir = `${this.backupPath}/${backupName}`
if (!$file.exists(backupDir)) {
throw new Error("备份不存在")
}
// 恢复数据库
if ($file.exists(`${backupDir}/app.db`)) {
$file.copy({
src: `${backupDir}/app.db`,
dst: "app.db"
})
}
// 恢复配置
const configData = $file.read(`${backupDir}/config.json`)
if (configData) {
const config = JSON.parse(configData.string)
$cache.set("app_config", config)
}
// 恢复用户数据
const userData = $file.read(`${backupDir}/userdata.json`)
if (userData) {
await this.restoreUserData(JSON.parse(userData.string))
}
return true
}
// 清理旧备份
cleanOldBackups() {
const backups = $file.list(this.backupPath)
.filter(name => name.startsWith("backup_"))
.sort()
.reverse()
if (backups.length > this.maxBackups) {
backups.slice(this.maxBackups).forEach(backup => {
this.deleteBackup(backup)
})
}
}
// 删除备份
deleteBackup(backupName) {
const backupDir = `${this.backupPath}/${backupName}`
if ($file.exists(backupDir)) {
// 递归删除目录
this.deleteDirectory(backupDir)
}
}
deleteDirectory(path) {
const items = $file.list(path)
items.forEach(item => {
const fullPath = `${path}/${item}`
const info = $file.info(fullPath)
if (info.isDirectory) {
this.deleteDirectory(fullPath)
} else {
$file.delete(fullPath)
}
})
$file.delete(path)
}
}
第八章:网络请求与通信
8.1 RESTful API 客户端
创建一个功能完整的 API 客户端:
class APIClient {
constructor(baseURL) {
this.baseURL = baseURL
this.headers = {
"Content-Type": "application/json",
"Accept": "application/json"
}
this.timeout = 30 // 秒
}
// 设置认证令牌
setAuthToken(token) {
if (token) {
this.headers["Authorization"] = `Bearer ${token}`
} else {
delete this.headers["Authorization"]
}
}
// 通用请求方法
async request(method, endpoint, options = {}) {
const url = `${this.baseURL}${endpoint}`
const config = {
method: method,
url: url,
header: { ...this.headers, ...options.headers },
timeout: options.timeout || this.timeout
}
// 添加请求体
if (options.body) {
if (method === "GET") {
// GET 请求使用查询参数
const params = new URLSearchParams(options.body)
config.url = `${url}?${params.toString()}`
} else {
config.body = options.body
}
}
// 处理文件上传
if (options.files) {
config.files = options.files
delete config.header["Content-Type"] // 让系统自动设置
}
return new Promise((resolve, reject) => {
$http.request({
...config,
handler: (resp) => {
if (resp.error) {
reject(this.handleError(resp))
} else {
resolve(this.handleResponse(resp))
}
}
})
})
}
// 处理响应
handleResponse(resp) {
const response = {
data: resp.data,
status: resp.response.statusCode,
headers: resp.response.headers
}
// 自动解析 JSON
if (response.headers["Content-Type"]?.includes("application/json")) {
try {
response.data = JSON.parse(response.data)
} catch (e) {
console.warn("JSON 解析失败")
}
}
return response
}
// 处理错误
handleError(resp) {
const error = new Error(resp.error.localizedDescription || "网络请求失败")
error.response = resp.response
error.code = resp.error.code
// 尝试解析错误响应
if (resp.data) {
try {
error.data = JSON.parse(resp.data)
} catch (e) {
error.data = resp.data
}
}
return error
}
// 便捷方法
get(endpoint, params) {
return this.request("GET", endpoint, { body: params })
}
post(endpoint, data) {
return this.request("POST", endpoint, { body: data })
}
put(endpoint, data) {
return this.request("PUT", endpoint, { body: data })
}
delete(endpoint) {
return this.request("DELETE", endpoint)
}
// 文件上传
upload(endpoint, files, data = {}) {
return this.request("POST", endpoint, {
files: files,
body: data
})
}
}
// 使用示例
const api = new APIClient("https://api.example.com")
api.setAuthToken("your-auth-token")
// 获取用户列表
api.get("/users", { page: 1, limit: 20 })
.then(response => {
console.log("用户列表:", response.data)
})
.catch(error => {
console.error("获取失败:", error)
})
8.2 WebSocket 实时通信
实现一个完整的 WebSocket 管理器:
class WebSocketManager {
constructor(url) {
this.url = url
this.ws = null
this.reconnectAttempts = 0
this.maxReconnectAttempts = 5
this.reconnectDelay = 1000
this.heartbeatInterval = 30000
this.messageHandlers = new Map()
this.eventHandlers = {}
}
// 连接 WebSocket
connect() {
return new Promise((resolve, reject) => {
this.ws = $websocket.new({
url: this.url,
header: this.headers,
handler: (message) => {
this.handleMessage(message)
},
eventHandler: {
open: () => {
console.log("WebSocket 已连接")
this.reconnectAttempts = 0
this.startHeartbeat()
resolve()
this.emit("open")
},
error: (error) => {
console.error("WebSocket 错误:", error)
this.emit("error", error)
reject(error)
},
close: (code, reason) => {
console.log("WebSocket 已关闭:", code, reason)
this.stopHeartbeat()
this.emit("close", { code, reason })
this.attemptReconnect()
}
}
})
})
}
// 发送消息
send(type, data) {
if (!this.ws || this.ws.readyState !== 1) {
throw new Error("WebSocket 未连接")
}
const message = {
type: type,
data: data,
timestamp: Date.now()
}
this.ws.send(JSON.stringify(message))
}
// 处理接收到的消息
handleMessage(rawMessage) {
try {
const message = JSON.parse(rawMessage)
// 处理心跳响应
if (message.type === "pong") {
return
}
// 调用对应的消息处理器
const handler = this.messageHandlers.get(message.type)
if (handler) {
handler(message.data)
} else {
console.warn("未处理的消息类型:", message.type)
}
// 触发通用消息事件
this.emit("message", message)
} catch (error) {
console.error("消息解析失败:", error)
}
}
// 注册消息处理器
on(messageType, handler) {
this.messageHandlers.set(messageType, handler)
}
// 注册事件处理器
addEventListener(event, handler) {
if (!this.eventHandlers[event]) {
this.eventHandlers[event] = []
}
this.eventHandlers[event].push(handler)
}
// 触发事件
emit(event, data) {
const handlers = this.eventHandlers[event]
if (handlers) {
handlers.forEach(handler => handler(data))
}
}
// 心跳机制
startHeartbeat() {
this.heartbeatTimer = setInterval(() => {
this.send("ping", {})
}, this.heartbeatInterval)
}
stopHeartbeat() {
if (this.heartbeatTimer) {
clearInterval(this.heartbeatTimer)
this.heartbeatTimer = null
}
}
// 重连机制
attemptReconnect() {
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
console.error("WebSocket 重连失败")
this.emit("reconnectFailed")
return
}
this.reconnectAttempts++
const delay = this.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1)
console.log(`${delay}ms 后尝试第 ${this.reconnectAttempts} 次重连`)
setTimeout(() => {
this.connect().catch(error => {
console.error("重连失败:", error)
})
}, delay)
}
// 断开连接
disconnect() {
this.stopHeartbeat()
if (this.ws) {
this.ws.close()
this.ws = null
}
}
}
// 使用示例
const wsManager = new WebSocketManager("wss://example.com/socket")
// 注册消息处理器
wsManager.on("chat", (data) => {
console.log("收到聊天消息:", data)
appendChatMessage(data)
})
wsManager.on("notification", (data) => {
showNotification(data)
})
// 注册事件处理器
wsManager.addEventListener("open", () => {
console.log("连接已建立")
wsManager.send("subscribe", { channel: "updates" })
})
// 连接
wsManager.connect()
8.3 网络状态监控
实现网络状态监控和自适应策略:
class NetworkMonitor {
constructor() {
this.isOnline = true
this.networkType = $device.networkType
this.listeners = []
this.startMonitoring()
}
// 开始监控
startMonitoring() {
// 定期检查网络状态
this.monitorTimer = setInterval(() => {
this.checkNetworkStatus()
}, 5000)
// 立即检查一次
this.checkNetworkStatus()
}
// 检查网络状态
async checkNetworkStatus() {
const currentType = $device.networkType
const wasOnline = this.isOnline
// 网络类型变化
if (currentType !== this.networkType) {
this.networkType = currentType
this.notifyListeners("typeChanged", {
oldType: this.networkType,
newType: currentType
})
}
// 测试网络连通性
try {
const response = await this.pingServer()
this.isOnline = response.success
} catch (error) {
this.isOnline = false
}
// 网络状态变化
if (wasOnline !== this.isOnline) {
this.notifyListeners("statusChanged", {
online: this.isOnline
})
}
}
// 测试服务器连通性
pingServer() {
return new Promise((resolve) => {
$http.get({
url: "https://www.apple.com/library/test/success.html",
timeout: 3,
handler: (resp) => {
resolve({
success: !resp.error,
latency: resp.response ? resp.response.suggestedFilename : null
})
}
})
})
}
// 添加监听器
addListener(listener) {
this.listeners.push(listener)
}
// 通知监听器
notifyListeners(event, data) {
this.listeners.forEach(listener => {
listener(event, data)
})
}
// 获取网络质量
getNetworkQuality() {
if (!this.isOnline) return "offline"
switch (this.networkType) {
case 1: return "excellent" // Wi-Fi
case 2: return "good" // 蜂窝网络
default: return "unknown"
}
}
// 根据网络状态调整策略
getAdaptiveStrategy() {
const quality = this.getNetworkQuality()
return {
// 图片质量
imageQuality: quality === "excellent" ? "high" : "medium",
// 是否预加载
enablePreload: quality === "excellent",
// 缓存策略
cacheStrategy: quality === "offline" ? "cacheOnly" : "networkFirst",
// 同步频率
syncInterval: quality === "excellent" ? 30 : 300, // 秒
// 批量大小
batchSize: quality === "excellent" ? 100 : 20
}
}
// 停止监控
stopMonitoring() {
if (this.monitorTimer) {
clearInterval(this.monitorTimer)
this.monitorTimer = null
}
}
}
// 使用网络监控
const networkMonitor = new NetworkMonitor()
networkMonitor.addListener((event, data) => {
if (event === "statusChanged") {
if (data.online) {
console.log("网络已恢复")
// 执行离线期间的同步任务
syncOfflineData()
} else {
console.log("网络已断开")
// 切换到离线模式
enableOfflineMode()
}
} else if (event === "typeChanged") {
console.log(`网络类型变化:${data.oldType} -> ${data.newType}`)
// 调整应用策略
updateAppStrategy(networkMonitor.getAdaptiveStrategy())
}
})
第九章:系统集成与扩展
9.1 系统服务调用
JSBox 可以调用多种 iOS 系统服务:
相机和相册:
// 拍照
$photo.take({
handler: function(resp) {
if (resp.image) {
processImage(resp.image)
}
}
})
// 选择照片
$photo.pick({
multi: true, // 允许多选
max: 9, // 最多选择9张
handler: function(resp) {
if (resp.results) {
resp.results.forEach(result => {
console.log("选择了图片:", result.filename)
processImage(result.image)
})
}
}
})
// 保存图片到相册
$photo.save({
image: processedImage,
handler: function(success) {
if (success) {
$ui.toast("图片已保存到相册")
}
}
})
// 扫描二维码
$qrcode.scan({
handler: function(text) {
if (text) {
console.log("扫描结果:", text)
handleQRCodeData(text)
}
}
})
// 生成二维码
const qrcodeImage = $qrcode.encode({
text: "https://example.com",
size: $size(200, 200),
color: $color("black"),
background: $color("white"),
correction: $qrcode.correction.high
})
位置服务:
// 获取当前位置
$location.fetch({
handler: function(resp) {
if (resp.error) {
console.error("获取位置失败:", resp.error)
} else {
const { lat, lng, alt } = resp
console.log(`位置:${lat}, ${lng}, 海拔:${alt}`)
// 反向地理编码
reverseGeocode(lat, lng)
}
}
})
// 持续监听位置变化
$location.start({
accuracy: "best", // 精度:best, nearestTenMeters, hundredMeters, kilometer
distance: 10, // 最小移动距离(米)
handler: function(resp) {
updateUserLocation(resp.lat, resp.lng)
}
})
// 停止位置监听
$location.stop()
// 计算两点间距离
function calculateDistance(lat1, lng1, lat2, lng2) {
const R = 6371 // 地球半径(公里)
const dLat = (lat2 - lat1) * Math.PI / 180
const dLng = (lng2 - lng1) * Math.PI / 180
const a = Math.sin(dLat/2) * Math.sin(dLat/2) +
Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) *
Math.sin(dLng/2) * Math.sin(dLng/2)
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a))
return R * c
}
音频播放:
// 播放系统声音
$device.taptic(1) // 触觉反馈
$system.vibrate() // 震动
// 播放音频文件
const audio = $audio.play({
path: "assets/sound.mp3",
events: {
didStart: function() {
console.log("开始播放")
},
didFinish: function() {
console.log("播放完成")
},
didPause: function() {
console.log("播放暂停")
}
}
})
// 控制播放
audio.pause()
audio.resume()
audio.stop()
// 设置音量
audio.volume = 0.8
// 文字转语音
$text.speech({
text: "你好,这是语音合成示例",
rate: 0.5, // 语速
language: "zh-CN", // 语言
handler: function() {
console.log("语音播放完成")
}
})
9.2 Widget 开发
JSBox 支持创建 iOS 小组件:
// Widget 配置
$widget.setTimeline({
entries: [
{
date: new Date(),
info: {
title: "今日任务",
content: "完成 5 个待办事项",
progress: 0.6
}
}
],
policy: {
atEnd: true
},
render: function(ctx) {
const info = ctx.entry.info
return {
type: "vstack",
props: {
alignment: $widget.alignment.leading,
spacing: 10,
padding: 15,
background: $color("#F0F0F0")
},
views: [
{
type: "text",
props: {
text: info.title,
font: $font("bold", 18),
color: $color("#333333")
}
},
{
type: "text",
props: {
text: info.content,
font: $font(14),
color: $color("#666666")
}
},
{
type: "hstack",
props: {
spacing: 5
},
views: [
{
type: "progress",
props: {
value: info.progress,
color: $color("#007AFF")
}
},
{
type: "text",
props: {
text: `${Math.round(info.progress * 100)}%`,
font: $font(12),
color: $color("#999999")
}
}
]
}
]
}
}
})
// 更新 Widget 数据
function updateWidget(data) {
$widget.setTimeline({
entries: data.map(item => ({
date: item.date,
info: item
})),
policy: {
after: new Date(Date.now() + 3600 * 1000) // 1小时后更新
}
})
}
9.3 快捷指令集成
JSBox 可以与 iOS 快捷指令集成:
// 注册快捷指令
$shortcuts.register({
name: "运行 JSBox 脚本",
description: "运行指定的 JSBox 脚本",
icon: {
glyph: "command",
color: "#FF6B6B"
},
params: [
{
name: "scriptName",
type: "text",
default: "",
required: true
},
{
name: "params",
type: "dictionary",
default: {}
}
],
handler: function(params) {
const { scriptName, params: scriptParams } = params
// 运行指定脚本
$addin.run(scriptName, scriptParams)
// 返回结果
return {
success: true,
result: "脚本运行完成"
}
}
})
// 从快捷指令接收数据
if ($context.query) {
const { action, data } = $context.query
switch (action) {
case "process":
processShortcutData(data)
break
case "share":
handleSharedData(data)
break
default:
console.log("未知操作:", action)
}
}
// 调用快捷指令
$shortcuts.run({
name: "发送通知",
input: {
title: "任务提醒",
message: "您有新的任务待处理"
},
handler: function(result) {
console.log("快捷指令执行结果:", result)
}
})
第十章:高级功能与技巧
10.1 性能优化
内存优化:
class MemoryManager {
constructor() {
this.cache = new Map()
this.maxCacheSize = 50 * 1024 * 1024 // 50MB
this.currentSize = 0
}
// 添加缓存(LRU 策略)
set(key, value, size) {
// 如果已存在,先删除旧的
if (this.cache.has(key)) {
const oldItem = this.cache.get(key)
this.currentSize -= oldItem.size
this.cache.delete(key)
}
// 检查是否需要清理
while (this.currentSize + size > this.maxCacheSize && this.cache.size > 0) {
const firstKey = this.cache.keys().next().value
const item = this.cache.get(firstKey)
this.currentSize -= item.size
this.cache.delete(firstKey)
}
// 添加新项
this.cache.set(key, { value, size, lastAccess: Date.now() })
this.currentSize += size
}
// 获取缓存
get(key) {
const item = this.cache.get(key)
if (item) {
// 更新访问时间(LRU)
item.lastAccess = Date.now()
// 移到末尾
this.cache.delete(key)
this.cache.set(key, item)
return item.value
}
return null
}
// 清理缓存
clear() {
this.cache.clear()
this.currentSize = 0
}
// 获取内存使用情况
getMemoryInfo() {
return {
used: this.currentSize,
max: this.maxCacheSize,
items: this.cache.size,
percentage: (this.currentSize / this.maxCacheSize * 100).toFixed(2) + '%'
}
}
}
// 图片缓存优化
class ImageCache {
constructor() {
this.memoryManager = new MemoryManager()
}
async loadImage(url) {
// 先检查内存缓存
let image = this.memoryManager.get(url)
if (image) {
return image
}
// 检查磁盘缓存
const filename = this.urlToFilename(url)
const cachePath = `cache/images/${filename}`
if ($file.exists(cachePath)) {
const data = $file.read(cachePath)
if (data) {
image = $image(data)
// 添加到内存缓存
this.memoryManager.set(url, image, data.length)
return image
}
}
// 从网络加载
return new Promise((resolve, reject) => {
$http.download({
url: url,
handler: (resp) => {
if (resp.data) {
// 保存到磁盘
$file.write({
data: resp.data,
path: cachePath
})
// 创建图片对象
image = $image(resp.data)
// 添加到内存缓存
this.memoryManager.set(url, image, resp.data.length)
resolve(image)
} else {
reject(new Error("图片加载失败"))
}
}
})
})
}
urlToFilename(url) {
// 将 URL 转换为安全的文件名
return $text.MD5(url) + ".jpg"
}
}
渲染优化:
// 虚拟列表实现
class VirtualList {
constructor(options) {
this.itemHeight = options.itemHeight
this.items = options.items
this.visibleCount = Math.ceil($device.info.screen.height / this.itemHeight) + 2
this.startIndex = 0
}
render() {
return {
type: "list",
props: {
id: "virtualList",
rowHeight: this.itemHeight,
data: this.getVisibleItems()
},
layout: $layout.fill,
events: {
didScroll: (sender) => {
this.handleScroll(sender)
},
didSelect: (sender, indexPath, data) => {
const realIndex = this.startIndex + indexPath.row
this.options.onSelect(this.items[realIndex], realIndex)
}
}
}
}
getVisibleItems() {
const endIndex = Math.min(
this.startIndex + this.visibleCount,
this.items.length
)
return this.items.slice(this.startIndex, endIndex)
}
handleScroll(sender) {
const offset = sender.contentOffset.y
const newStartIndex = Math.floor(offset / this.itemHeight)
if (newStartIndex !== this.startIndex) {
this.startIndex = Math.max(0, newStartIndex)
sender.data = this.getVisibleItems()
}
}
updateItems(items) {
this.items = items
$("virtualList").data = this.getVisibleItems()
}
}
// 防抖和节流
function debounce(func, delay) {
let timeoutId
return function (...args) {
clearTimeout(timeoutId)
timeoutId = setTimeout(() => func.apply(this, args), delay)
}
}
function throttle(func, limit) {
let inThrottle
return function (...args) {
if (!inThrottle) {
func.apply(this, args)
inThrottle = true
setTimeout(() => inThrottle = false, limit)
}
}
}
// 使用示例
const searchHandler = debounce((text) => {
performSearch(text)
}, 300)
const scrollHandler = throttle((offset) => {
updateScrollPosition(offset)
}, 100)
10.2 安全性考虑
数据加密:
class SecurityManager {
// 使用 AES 加密
encrypt(text, password) {
return $text.AESEncrypt(text, password)
}
// 使用 AES 解密
decrypt(encryptedText, password) {
return $text.AESDecrypt(encryptedText, password)
}
// 生成安全的随机密钥
generateKey(length = 32) {
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*'
let key = ''
for (let i = 0; i < length; i++) {
key += chars.charAt(Math.floor(Math.random() * chars.length))
}
return key
}
// 哈希密码
hashPassword(password, salt) {
return $text.SHA256(password + salt)
}
// 安全存储敏感数据
secureStore(key, value, password) {
const encrypted = this.encrypt(JSON.stringify(value), password)
$keychain.set(key, encrypted)
}
// 安全读取敏感数据
secureRetrieve(key, password) {
const encrypted = $keychain.get(key)
if (encrypted) {
try {
const decrypted = this.decrypt(encrypted, password)
return JSON.parse(decrypted)
} catch (error) {
console.error("解密失败")
return null
}
}
return null
}
// 清理敏感数据
clearSensitiveData() {
// 清理内存中的敏感变量
sensitiveData = null
// 清理剪贴板
if ($clipboard.text && $clipboard.text.includes("password")) {
$clipboard.clear()
}
// 清理临时文件
const tempFiles = $file.list("temp")
tempFiles.forEach(file => {
if (file.includes("sensitive")) {
$file.delete(`temp/${file}`)
}
})
}
}
// API 请求签名
class APISecurityManager {
constructor(apiKey, apiSecret) {
this.apiKey = apiKey
this.apiSecret = apiSecret
}
// 生成请求签名
generateSignature(method, path, params, timestamp) {
// 1. 排序参数
const sortedParams = Object.keys(params)
.sort()
.map(key => `${key}=${params[key]}`)
.join('&')
// 2. 构建签名字符串
const signatureString = `${method}\n${path}\n${sortedParams}\n${timestamp}`
// 3. 计算 HMAC-SHA256
return $text.HMACSHA256(signatureString, this.apiSecret)
}
// 创建安全的请求头
createSecureHeaders(method, path, params = {}) {
const timestamp = Date.now()
const signature = this.generateSignature(method, path, params, timestamp)
return {
'X-API-Key': this.apiKey,
'X-Timestamp': timestamp.toString(),
'X-Signature': signature
}
}
}
10.3 插件系统
创建可扩展的插件架构:
// 插件管理器
class PluginManager {
constructor() {
this.plugins = new Map()
this.hooks = new Map()
}
// 注册插件
register(plugin) {
if (!plugin.name || !plugin.version) {
throw new Error("插件必须包含 name 和 version")
}
console.log(`注册插件:${plugin.name} v${plugin.version}`)
// 检查依赖
if (plugin.dependencies) {
for (const [dep, version] of Object.entries(plugin.dependencies)) {
if (!this.checkDependency(dep, version)) {
throw new Error(`缺少依赖:${dep} >= ${version}`)
}
}
}
// 初始化插件
if (plugin.init) {
plugin.init(this)
}
// 注册钩子
if (plugin.hooks) {
for (const [hookName, handler] of Object.entries(plugin.hooks)) {
this.addHook(hookName, handler)
}
}
this.plugins.set(plugin.name, plugin)
}
// 检查依赖
checkDependency(name, requiredVersion) {
const plugin = this.plugins.get(name)
if (!plugin) return false
return this.compareVersions(plugin.version, requiredVersion) >= 0
}
// 版本比较
compareVersions(v1, v2) {
const parts1 = v1.split('.').map(Number)
const parts2 = v2.split('.').map(Number)
for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
const part1 = parts1[i] || 0
const part2 = parts2[i] || 0
if (part1 > part2) return 1
if (part1 < part2) return -1
}
return 0
}
// 添加钩子
addHook(name, handler) {
if (!this.hooks.has(name)) {
this.hooks.set(name, [])
}
this.hooks.get(name).push(handler)
}
// 执行钩子
async executeHook(name, ...args) {
const handlers = this.hooks.get(name) || []
const results = []
for (const handler of handlers) {
try {
const result = await handler(...args)
results.push(result)
} catch (error) {
console.error(`钩子执行错误 (${name}):`, error)
}
}
return results
}
// 获取插件
getPlugin(name) {
return this.plugins.get(name)
}
// 卸载插件
unregister(name) {
const plugin = this.plugins.get(name)
if (!plugin) return
// 执行清理
if (plugin.cleanup) {
plugin.cleanup()
}
// 移除钩子
if (plugin.hooks) {
// 这里需要更复杂的实现来准确移除特定插件的钩子
}
this.plugins.delete(name)
console.log(`插件已卸载:${name}`)
}
}
// 示例插件
const analyticsPlugin = {
name: "analytics",
version: "1.0.0",
description: "应用分析插件",
init(pluginManager) {
this.startTime = Date.now()
this.events = []
},
hooks: {
beforeRequest: async (config) => {
// 在请求前记录
this.logEvent("api_request", {
url: config.url,
method: config.method
})
},
afterRender: async (view) => {
// 界面渲染后记录
this.logEvent("view_rendered", {
viewType: view.type
})
}
},
logEvent(name, data) {
this.events.push({
name,
data,
timestamp: Date.now()
})
},
getReport() {
return {
sessionDuration: Date.now() - this.startTime,
totalEvents: this.events.length,
events: this.events
}
},
cleanup() {
// 发送分析数据
this.sendAnalytics(this.getReport())
}
}
第十一章:实战项目开发
11.1 项目一:任务管理应用
创建一个功能完整的任务管理应用:
// 主应用类
class TaskManager {
constructor() {
this.tasks = []
this.categories = ["工作", "生活", "学习", "其他"]
this.db = $sqlite.open("tasks.db")
this.initDatabase()
this.loadTasks()
}
initDatabase() {
this.db.update({
sql: `CREATE TABLE IF NOT EXISTS tasks (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
description TEXT,
category TEXT,
priority INTEGER DEFAULT 0,
completed BOOLEAN DEFAULT 0,
due_date INTEGER,
created_at INTEGER DEFAULT (strftime('%s', 'now')),
updated_at INTEGER DEFAULT (strftime('%s', 'now'))
)`
})
}
loadTasks() {
const results = this.db.query("SELECT * FROM tasks ORDER BY priority DESC, due_date ASC")
this.tasks = results.map(row => ({
id: row.id,
title: row.title,
description: row.description,
category: row.category,
priority: row.priority,
completed: Boolean(row.completed),
dueDate: row.due_date ? new Date(row.due_date * 1000) : null,
createdAt: new Date(row.created_at * 1000),
updatedAt: new Date(row.updated_at * 1000)
}))
}
addTask(task) {
const result = this.db.update({
sql: `INSERT INTO tasks (title, description, category, priority, due_date)
VALUES (?, ?, ?, ?, ?)`,
args: [
task.title,
task.description || "",
task.category || "其他",
task.priority || 0,
task.dueDate ? Math.floor(task.dueDate.getTime() / 1000) : null
]
})
if (result.error) {
throw new Error("添加任务失败")
}
task.id = result.insertId
task.completed = false
task.createdAt = new Date()
task.updatedAt = new Date()
this.tasks.push(task)
this.notifyUpdate()
return task
}
updateTask(id, updates) {
const sets = []
const args = []
Object.entries(updates).forEach(([key, value]) => {
if (key === 'dueDate') {
sets.push("due_date = ?")
args.push(value ? Math.floor(value.getTime() / 1000) : null)
} else if (key === 'completed') {
sets.push("completed = ?")
args.push(value ? 1 : 0)
} else {
sets.push(`${key} = ?`)
args.push(value)
}
})
sets.push("updated_at = strftime('%s', 'now')")
args.push(id)
this.db.update({
sql: `UPDATE tasks SET ${sets.join(', ')} WHERE id = ?`,
args: args
})
// 更新内存中的任务
const taskIndex = this.tasks.findIndex(t => t.id === id)
if (taskIndex !== -1) {
Object.assign(this.tasks[taskIndex], updates)
this.tasks[taskIndex].updatedAt = new Date()
}
this.notifyUpdate()
}
deleteTask(id) {
this.db.update({
sql: "DELETE FROM tasks WHERE id = ?",
args: [id]
})
this.tasks = this.tasks.filter(t => t.id !== id)
this.notifyUpdate()
}
getTasksByCategory(category) {
return this.tasks.filter(t => t.category === category)
}
getIncompleteTasks() {
return this.tasks.filter(t => !t.completed)
}
getOverdueTasks() {
const now = new Date()
return this.tasks.filter(t =>
!t.completed && t.dueDate && t.dueDate < now
)
}
notifyUpdate() {
// 触发界面更新
$("taskList").data = this.formatTasksForList()
this.updateStatistics()
}
formatTasksForList() {
return this.tasks.map(task => ({
id: task.id,
title: {
text: task.title
},
subtitle: {
text: this.formatTaskSubtitle(task)
},
completed: {
on: task.completed
},
priority: {
color: this.getPriorityColor(task.priority)
}
}))
}
formatTaskSubtitle(task) {
const parts = []
if (task.category) parts.push(task.category)
if (task.dueDate) {
const dueText = this.formatDueDate(task.dueDate)
parts.push(dueText)
}
return parts.join(" • ")
}
formatDueDate(date) {
const now = new Date()
const diff = date - now
const days = Math.floor(diff / (1000 * 60 * 60 * 24))
if (days < 0) return `已过期 ${-days} 天`
if (days === 0) return "今天到期"
if (days === 1) return "明天到期"
if (days <= 7) return `${days} 天后到期`
return date.toLocaleDateString()
}
getPriorityColor(priority) {
switch (priority) {
case 3: return $color("#FF3B30") // 高优先级 - 红色
case 2: return $color("#FF9500") // 中优先级 - 橙色
case 1: return $color("#FFCC00") // 低优先级 - 黄色
default: return $color("#34C759") // 无优先级 - 绿色
}
}
updateStatistics() {
const total = this.tasks.length
const completed = this.tasks.filter(t => t.completed).length
const overdue = this.getOverdueTasks().length
$("statsLabel").text = `总计: ${total} | 完成: ${completed} | 过期: ${overdue}`
}
}
// UI 渲染
function renderTaskManagerUI() {
const taskManager = new TaskManager()
$ui.render({
props: {
title: "任务管理"
},
views: [
{
type: "button",
props: {
id: "addButton",
title: "添加任务",
bgcolor: $color("#007AFF")
},
layout: function(make, view) {
make.top.equalTo(view.super.safeAreaTop).offset(10)
make.left.right.inset(15)
make.height.equalTo(44)
},
events: {
tapped: function() {
showAddTaskDialog(taskManager)
}
}
},
{
type: "label",
props: {
id: "statsLabel",
font: $font(14),
textColor: $color("#666666"),
align: $align.center
},
layout: function(make, view) {
make.top.equalTo($("addButton").bottom).offset(10)
make.left.right.inset(15)
make.height.equalTo(20)
}
},
{
type: "list",
props: {
id: "taskList",
rowHeight: 80,
template: {
views: [
{
type: "switch",
props: {
id: "completed",
onColor: $color("#34C759")
},
layout: function(make, view) {
make.left.equalTo(15)
make.centerY.equalTo(view.super)
},
events: {
changed: function(sender) {
const data = sender.info
taskManager.updateTask(data.id, { completed: sender.on })
}
}
},
{
type: "view",
props: {
id: "priority"
},
layout: function(make, view) {
make.left.equalTo($("completed").right).offset(10)
make.centerY.equalTo(view.super)
make.size.equalTo($size(4, 40))
}
},
{
type: "label",
props: {
id: "title",
font: $font("bold", 16)
},
layout: function(make, view) {
make.left.equalTo($("priority").right).offset(10)
make.top.equalTo(15)
make.right.inset(15)
}
},
{
type: "label",
props: {
id: "subtitle",
font: $font(14),
textColor: $color("#666666")
},
layout: function(make, view) {
make.left.equalTo($("title"))
make.bottom.equalTo(-15)
make.right.inset(15)
}
}
]
}
},
layout: function(make, view) {
make.top.equalTo($("statsLabel").bottom).offset(10)
make.left.right.bottom.equalTo(view.super)
},
events: {
didSelect: function(sender, indexPath, data) {
showTaskDetail(taskManager, data.id)
},
didLongPress: function(sender, indexPath, data) {
showTaskOptions(taskManager, data.id)
}
}
}
]
})
taskManager.notifyUpdate()
}
// 添加任务对话框
function showAddTaskDialog(taskManager) {
$input.text({
type: $kbType.default,
placeholder: "任务标题",
handler: function(title) {
if (!title) return
// 选择分类
$ui.menu({
items: taskManager.categories,
handler: function(title, idx) {
const category = taskManager.categories[idx]
// 选择优先级
$ui.menu({
items: ["无", "低", "中", "高"],
handler: function(title, idx) {
const task = {
title: title,
category: category,
priority: idx
}
taskManager.addTask(task)
$ui.toast("任务已添加")
}
})
}
})
}
})
}
// 启动应用
renderTaskManagerUI()
11.2 项目二:天气预报应用
创建一个天气预报应用,展示 API 集成和数据可视化:
// 天气应用主类
class WeatherApp {
constructor() {
this.apiKey = "your_api_key_here"
this.baseURL = "https://api.openweathermap.org/data/2.5"
this.cities = this.loadCities()
this.currentCity = this.cities[0] || { name: "北京", lat: 39.9042, lon: 116.4074 }
this.weatherData = null
this.forecastData = null
}
loadCities() {
const saved = $cache.get("weather_cities")
return saved || [
{ name: "北京", lat: 39.9042, lon: 116.4074 },
{ name: "上海", lat: 31.2304, lon: 121.4737 },
{ name: "广州", lat: 23.1291, lon: 113.2644 }
]
}
saveCities() {
$cache.set("weather_cities", this.cities)
}
async fetchWeather(city) {
try {
// 获取当前天气
const currentWeather = await this.apiRequest("/weather", {
lat: city.lat,
lon: city.lon,
units: "metric",
lang: "zh_cn"
})
// 获取天气预报
const forecast = await this.apiRequest("/forecast", {
lat: city.lat,
lon: city.lon,
units: "metric",
lang: "zh_cn",
cnt: 40 // 5天预报
})
this.weatherData = this.processWeatherData(currentWeather)
this.forecastData = this.processForecastData(forecast)
return true
} catch (error) {
console.error("获取天气失败:", error)
$ui.alert({
title: "错误",
message: "无法获取天气数据,请检查网络连接"
})
return false
}
}
async apiRequest(endpoint, params) {
return new Promise((resolve, reject) => {
const url = `${this.baseURL}${endpoint}`
const fullParams = { ...params, appid: this.apiKey }
$http.get({
url: url,
parameters: fullParams,
handler: function(resp) {
if (resp.error) {
reject(resp.error)
} else if (resp.data.cod !== 200) {
reject(new Error(resp.data.message))
} else {
resolve(resp.data)
}
}
})
})
}
processWeatherData(data) {
return {
city: data.name,
country: data.sys.country,
temp: Math.round(data.main.temp),
feelsLike: Math.round(data.main.feels_like),
tempMin: Math.round(data.main.temp_min),
tempMax: Math.round(data.main.temp_max),
pressure: data.main.pressure,
humidity: data.main.humidity,
weather: data.weather[0].description,
icon: data.weather[0].icon,
windSpeed: data.wind.speed,
windDeg: data.wind.deg,
clouds: data.clouds.all,
visibility: data.visibility,
sunrise: new Date(data.sys.sunrise * 1000),
sunset: new Date(data.sys.sunset * 1000),
dt: new Date(data.dt * 1000)
}
}
processForecastData(data) {
const dailyData = {}
data.list.forEach(item => {
const date = new Date(item.dt * 1000)
const day = date.toLocaleDateString()
if (!dailyData[day]) {
dailyData[day] = {
date: date,
temps: [],
weather: [],
humidity: [],
wind: []
}
}
dailyData[day].temps.push(item.main.temp)
dailyData[day].weather.push(item.weather[0])
dailyData[day].humidity.push(item.main.humidity)
dailyData[day].wind.push(item.wind.speed)
})
return Object.entries(dailyData).map(([day, data]) => ({
date: data.date,
tempMin: Math.round(Math.min(...data.temps)),
tempMax: Math.round(Math.max(...data.temps)),
weather: this.getMostFrequent(data.weather, 'main'),
icon: this.getMostFrequent(data.weather, 'icon'),
humidity: Math.round(data.humidity.reduce((a, b) => a + b) / data.humidity.length),
windSpeed: Math.round(data.wind.reduce((a, b) => a + b) / data.wind.length * 10) / 10
})).slice(0, 5) // 只保留5天
}
getMostFrequent(arr, key) {
const frequency = {}
let maxCount = 0
let mostFrequent = null
arr.forEach(item => {
const value = item[key]
frequency[value] = (frequency[value] || 0) + 1
if (frequency[value] > maxCount) {
maxCount = frequency[value]
mostFrequent = value
}
})
return mostFrequent
}
getWeatherIcon(iconCode) {
const iconMap = {
"01d": "☀️", "01n": "🌙",
"02d": "⛅", "02n": "☁️",
"03d": "☁️", "03n": "☁️",
"04d": "☁️", "04n": "☁️",
"09d": "🌧️", "09n": "🌧️",
"10d": "🌦️", "10n": "🌧️",
"11d": "⛈️", "11n": "⛈️",
"13d": "🌨️", "13n": "🌨️",
"50d": "🌫️", "50n": "🌫️"
}
return iconMap[iconCode] || "❓"
}
getWindDirection(deg) {
const directions = ["北", "东北", "东", "东南", "南", "西南", "西", "西北"]
const index = Math.round(deg / 45) % 8
return directions[index]
}
formatTime(date) {
return date.toLocaleTimeString('zh-CN', {
hour: '2-digit',
minute: '2-digit'
})
}
}
// 渲染天气界面
function renderWeatherUI() {
const app = new WeatherApp()
$ui.render({
props: {
title: "天气预报",
bgcolor: $color("#F0F0F0")
},
views: [
// 城市选择器
{
type: "button",
props: {
id: "citySelector",
title: app.currentCity.name,
bgcolor: $color("clear"),
titleColor: $color("#007AFF"),
font: $font("bold", 18)
},
layout: function(make, view) {
make.top.equalTo(view.super.safeAreaTop).offset(10)
make.centerX.equalTo(view.super)
},
events: {
tapped: function() {
showCitySelector(app)
}
}
},
// 当前天气容器
{
type: "view",
props: {
id: "currentWeather",
bgcolor: $color("white"),
cornerRadius: 15,
shadow: {
color: $color("#000000", 0.1),
offset: $size(0, 2),
radius: 10
}
},
layout: function(make, view) {
make.top.equalTo($("citySelector").bottom).offset(20)
make.left.right.inset(15)
make.height.equalTo(200)
},
views: [
{
type: "label",
props: {
id: "tempLabel",
font: $font("bold", 60),
align: $align.center
},
layout: function(make, view) {
make.centerX.equalTo(view.super)
make.top.equalTo(20)
}
},
{
type: "label",
props: {
id: "weatherLabel",
font: $font(20),
align: $align.center,
textColor: $color("#666666")
},
layout: function(make, view) {
make.centerX.equalTo(view.super)
make.top.equalTo($("tempLabel").bottom).offset(10)
}
},
{
type: "label",
props: {
id: "detailsLabel",
font: $font(14),
align: $align.center,
lines: 0,
textColor: $color("#999999")
},
layout: function(make, view) {
make.left.right.inset(20)
make.bottom.inset(20)
}
}
]
},
// 预报列表
{
type: "list",
props: {
id: "forecastList",
rowHeight: 70,
bgcolor: $color("clear"),
separatorHidden: true,
template: {
props: {
bgcolor: $color("white"),
cornerRadius: 10
},
views: [
{
type: "label",
props: {
id: "dateLabel",
font: $font("bold", 16)
},
layout: function(make, view) {
make.left.equalTo(15)
make.centerY.equalTo(view.super)
}
},
{
type: "label",
props: {
id: "iconLabel",
font: $font(30),
align: $align.center
},
layout: function(make, view) {
make.center.equalTo(view.super)
}
},
{
type: "label",
props: {
id: "tempRangeLabel",
font: $font(16),
align: $align.right
},
layout: function(make, view) {
make.right.inset(15)
make.centerY.equalTo(view.super)
}
}
]
}
},
layout: function(make, view) {
make.top.equalTo($("currentWeather").bottom).offset(20)
make.left.right.inset(15)
make.bottom.equalTo(view.super.safeAreaBottom)
},
events: {
didSelect: function(sender, indexPath, data) {
showForecastDetail(app, data)
}
}
}
]
})
// 加载天气数据
loadWeatherData(app)
}
// 加载天气数据
async function loadWeatherData(app) {
$ui.loading(true)
const success = await app.fetchWeather(app.currentCity)
$ui.loading(false)
if (success) {
updateWeatherUI(app)
}
}
// 更新UI
function updateWeatherUI(app) {
const weather = app.weatherData
const forecast = app.forecastData
// 更新当前天气
$("tempLabel").text = `${weather.temp}°`
$("weatherLabel").text = `${app.getWeatherIcon(weather.icon)} ${weather.weather}`
$("detailsLabel").text = `体感 ${weather.feelsLike}° | 湿度 ${weather.humidity}% | ${app.getWindDirection(weather.windDeg)}风 ${weather.windSpeed}m/s\n日出 ${app.formatTime(weather.sunrise)} | 日落 ${app.formatTime(weather.sunset)}`
// 更新预报列表
$("forecastList").data = forecast.map(day => ({
dateLabel: {
text: day.date.toLocaleDateString('zh-CN', { weekday: 'short', month: 'numeric', day: 'numeric' })
},
iconLabel: {
text: app.getWeatherIcon(day.icon)
},
tempRangeLabel: {
text: `${day.tempMin}° / ${day.tempMax}°`
},
forecast: day // 保存完整数据
}))
}
// 启动应用
renderWeatherUI()
第十二章:性能优化与最佳实践
12.1 代码优化技巧
模块化和代码组织:
// 使用模块模式组织代码
const AppModules = {
// 工具模块
Utils: {
formatDate(date, format = 'YYYY-MM-DD') {
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
return format
.replace('YYYY', year)
.replace('MM', month)
.replace('DD', day)
},
debounce(func, wait) {
let timeout
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout)
func(...args)
}
clearTimeout(timeout)
timeout = setTimeout(later, wait)
}
},
throttle(func, limit) {
let inThrottle
return function(...args) {
if (!inThrottle) {
func.apply(this, args)
inThrottle = true
setTimeout(() => inThrottle = false, limit)
}
}
},
memoize(fn) {
const cache = new Map()
return function(...args) {
const key = JSON.stringify(args)
if (cache.has(key)) {
return cache.get(key)
}
const result = fn.apply(this, args)
cache.set(key, result)
return result
}
}
},
// 性能监控模块
Performance: {
markers: new Map(),
mark(name) {
this.markers.set(name, Date.now())
},
measure(name, startMark, endMark) {
const start = this.markers.get(startMark)
const end = endMark ? this.markers.get(endMark) : Date.now()
if (!start) {
console.warn(`Performance mark '${startMark}' not found`)
return
}
const duration = end - start
console.log(`${name}: ${duration}ms`)
return duration
},
profile(name, fn) {
const start = Date.now()
const result = fn()
const duration = Date.now() - start
console.log(`${name} took ${duration}ms`)
if (duration > 100) {
console.warn(`${name} is taking too long!`)
}
return result
}
},
// 资源管理模块
ResourceManager: {
resources: new Map(),
maxSize: 50 * 1024 * 1024, // 50MB
currentSize: 0,
add(key, resource, size) {
// 实现 LRU 缓存
if (this.resources.has(key)) {
this.remove(key)
}
while (this.currentSize + size > this.maxSize && this.resources.size > 0) {
const firstKey = this.resources.keys().next().value
this.remove(firstKey)
}
this.resources.set(key, {
data: resource,
size: size,
lastAccess: Date.now()
})
this.currentSize += size
},
get(key) {
const resource = this.resources.get(key)
if (resource) {
resource.lastAccess = Date.now()
// 移到末尾(LRU)
this.resources.delete(key)
this.resources.set(key, resource)
return resource.data
}
return null
},
remove(key) {
const resource = this.resources.get(key)
if (resource) {
this.currentSize -= resource.size
this.resources.delete(key)
}
},
clear() {
this.resources.clear()
this.currentSize = 0
},
getStats() {
return {
itemCount: this.resources.size,
totalSize: this.currentSize,
maxSize: this.maxSize,
usage: (this.currentSize / this.maxSize * 100).toFixed(2) + '%'
}
}
}
}
// 使用示例
const debouncedSearch = AppModules.Utils.debounce((query) => {
performSearch(query)
}, 300)
const memoizedCalculation = AppModules.Utils.memoize((n) => {
// 复杂计算
return fibonacci(n)
})
12.2 内存管理最佳实践
// 对象池模式 - 减少内存分配
class ObjectPool {
constructor(createFn, resetFn, maxSize = 100) {
this.createFn = createFn
this.resetFn = resetFn
this.maxSize = maxSize
this.pool = []
this.activeObjects = new Set()
}
acquire() {
let obj
if (this.pool.length > 0) {
obj = this.pool.pop()
} else {
obj = this.createFn()
}
this.activeObjects.add(obj)
return obj
}
release(obj) {
if (!this.activeObjects.has(obj)) {
return
}
this.activeObjects.delete(obj)
this.resetFn(obj)
if (this.pool.length < this.maxSize) {
this.pool.push(obj)
}
}
clear() {
this.pool = []
this.activeObjects.clear()
}
getStats() {
return {
poolSize: this.pool.length,
activeCount: this.activeObjects.size,
totalCreated: this.pool.length + this.activeObjects.size
}
}
}
// 使用对象池
const particlePool = new ObjectPool(
// 创建函数
() => ({
x: 0,
y: 0,
vx: 0,
vy: 0,
life: 1.0,
color: null
}),
// 重置函数
(particle) => {
particle.x = 0
particle.y = 0
particle.vx = 0