3、JavaScript基础速成:变量、数据类型、函数、循环、条件语句、数组与对象,以及GEE中常用的JavaScript语法要点
Google Earth Engine(GEE)的Code Editor是一个基于Web的IDE,其核心编程语言是JavaScript。虽然GEE封装了大量遥感与地理空间分析API,但所有对影像、矢量、图表的操作最终都归结为JavaScript代码的执行。对于遥感从业者而言,不必成为JavaScript高级工程师,但必须掌握其核心语法骨架,才能流畅地编写、调试和复用GEE脚本。本章将聚焦于GEE开发中最常用的JavaScript语法要素,包括变量声明、数据类型、函数定义、流程控制、数组与对象操作,并结合GEE特有的客户端-服务端异步机制,帮助你建立扎实的编码基础。
3.1 变量声明与数据类型:从var到const的演进
在GEE Code Editor中,变量是存储数据的命名容器。JavaScript提供了三种声明关键字:var、let和const。在GEE的早期教程中,var几乎无处不在,但现代JavaScript开发更推荐使用const(常量)和let(块级作用域变量)。var存在函数作用域和变量提升问题,容易在复杂脚本中引发难以察觉的Bug。例如,在循环中重复声明同名变量,var会覆盖外部变量,而let则限定在循环块内。
GEE中的数据类型分为两大类:原始类型(Number、String、Boolean、null、undefined)和引用类型(Array、Object、Function)。此外,GEE还引入了大量地球引擎对象,如ee.Number、ee.String、ee.List、ee.Dictionary等。这些对象是服务端对象,与原生JavaScript类型有本质区别。原生类型在客户端立即计算,而ee对象则是在服务器端延迟计算。
var num = 5是客户端变量,而var eeNum = ee.Number(5)是服务端对象。对eeNum进行数学运算时,实际上是在构建一个计算表达式,直到调用print()或getInfo()时才真正执行。
| 关键字 | 作用域 | 可变性 | GEE推荐用法 |
|---|---|---|---|
| var | 函数级 | 可重新赋值 | 旧脚本兼容,新代码尽量避免 |
| let | 块级 | 可重新赋值 | 循环计数器、临时变量 |
| const | 块级 | 不可重新赋值 | 影像集合、几何对象、函数定义 |
在GEE中,推荐使用const声明那些不会改变的引用,例如const s2 = ee.ImageCollection("COPERNICUS/S2_SR")。这不仅能防止意外覆盖,还能让代码意图更清晰。对于循环中的临时变量,使用let。尽量避免使用var,除非你正在阅读或维护旧版GEE代码。
print()函数是调试利器。当你打印一个ee.Number时,控制台会显示其值;但如果你打印一个ee.Image,控制台只会显示影像的元数据,而不是像素值。要获取像素值,必须使用reduceRegion()或sample()等操作。
3.2 函数定义:从function到箭头函数
函数是JavaScript的一等公民。在GEE中,函数主要用于两类场景:一是封装重复的代码逻辑,二是作为回调函数传递给map()、filter()、reduce()等集合操作。GEE的集合操作大量依赖回调函数,例如对影像集合逐景处理:
// 传统函数定义
function addNDVI(image) {
var ndvi = image.normalizedDifference(['B8', 'B4']).rename('NDVI');
return image.addBands(ndvi);
}
var s2 = ee.ImageCollection("COPERNICUS/S2_SR");
var withNdvi = s2.map(addNDVI);
// 箭头函数(更简洁)
var withNdvi2 = s2.map(img => img.addBands(
img.normalizedDifference(['B8', 'B4']).rename('NDVI')
));
箭头函数(参数) => 表达式是ES6引入的语法糖,它不绑定自己的this,且写法更紧凑。在GEE中,箭头函数非常适合短小的回调逻辑。但要注意,如果函数体包含多条语句,需要使用花括号和显式return:
// 多语句箭头函数
var withNdvi3 = s2.map(img => {
var ndvi = img.normalizedDifference(['B8', 'B4']).rename('NDVI');
return img.addBands(ndvi);
});
map()回调中,不能使用客户端循环或print()。例如,s2.map(function(img) { print(img); })不会打印任何内容,因为map()是延迟执行的,且print()是客户端函数。正确的调试方式是使用Map.addLayer()或print()整个集合的元数据。
3.3 条件语句与循环:控制脚本逻辑流
条件语句if/else在GEE中用于客户端逻辑判断,例如根据用户输入选择不同的数据集或参数。但要注意,if/else是客户端控制流,不能直接用于服务端对象(如ee.Image)的逐像素判断。对于逐像素条件,应使用image.where()或image.expression()。
// 客户端条件判断
var threshold = 0.5;
var bandName = 'NDVI';
if (threshold > 0.3) {
bandName = 'EVI';
} else {
bandName = 'NDVI';
}
print('Selected band:', bandName);
// 服务端逐像素条件(正确方式)
var ndvi = ee.Image('MODIS/006/MOD13A1/2019_01_01').select('NDVI');
var high = ndvi.gt(0.5).rename('high_veg');
var low = ndvi.lte(0.5).rename('low_veg');
var classified = high.addBands(low);
循环在GEE中分为客户端循环和服务端循环。客户端循环(for、while)在浏览器中执行,适合处理小规模客户端数据,例如遍历数组中的日期字符串。服务端循环则通过ee.List.iterate()或ee.ImageCollection.map()实现,这些操作在服务器端并行执行,效率远高于客户端循环。
map()而不是for循环。例如,计算每个月的NDVI最大值:
- 使用
ee.List.sequence()生成月份序列。 - 用
map()对每个月执行过滤和计算。 - 将结果合并为新的
ImageCollection。
3.4 数组与对象:组织和管理数据
JavaScript原生数组(Array)和对象(Object)在GEE中用于客户端数据管理。例如,存储波段名称列表、定义参数配置对象等。GEE还提供了对应的服务端类型:ee.List和ee.Dictionary。原生数组支持push()、splice()、indexOf()等方法,而ee.List则提供add()、get()、slice()等方法。
// 原生数组(客户端)
var bandNames = ['B2', 'B3', 'B4', 'B8'];
bandNames.push('B11');
print('Client array length:', bandNames.length);
// ee.List(服务端)
var eeBandNames = ee.List(['B2', 'B3', 'B4', 'B8']);
var eeBandNames2 = eeBandNames.add('B11');
print('Server list size:', eeBandNames2.size());
// 原生对象(客户端)
var params = {
scale: 10,
region: geometry,
bands: ['B4', 'B3', 'B2']
};
print('Scale:', params.scale);
// ee.Dictionary(服务端)
var eeParams = ee.Dictionary({
scale: 10,
region: geometry,
bands: ['B4', 'B3', 'B2']
});
print('Server dict keys:', eeParams.keys());
在GEE中,ee.Dictionary常用于存储影像属性或区域统计结果。例如,reduceRegion()返回一个ee.Dictionary,包含各波段的均值。你可以通过get('B4')提取特定值,或通过values()获取所有值。
| 操作 | 原生Array | ee.List | 原生Object | ee.Dictionary |
|---|---|---|---|---|
| 添加元素 | push() | add() | obj.key = value | set(key, value) |
| 获取元素 | arr[index] | get(index) | obj.key | get(key) |
| 长度/大小 | length属性 | size() | Object.keys().length | size() |
| 遍历 | for / forEach | map() / iterate() | for...in | keys() + map() |
map()中使用。例如,将日期字符串数组转换为ee.Date列表,然后过滤影像集合。使用ee.List(array)即可完成转换。但要注意,数组中的元素必须是可序列化的(如数字、字符串、对象),不能包含函数或DOM元素。
3.5 GEE中的JavaScript语法要点与调试技巧
GEE的Code Editor提供了一些特有的全局函数和对象,如Map、print()、Export等。理解这些工具与JavaScript语法的交互至关重要。例如,Map.addLayer()接受一个ee.Image或ee.Geometry作为参数,并可选地接受一个可视化参数对象(visParams)。这个参数对象是原生JavaScript对象,其键值对必须符合GEE的规范。
// 可视化参数对象
var visParams = {
bands: ['B4', 'B3', 'B2'],
min: 0,
max: 3000,
gamma: 1.4
};
Map.addLayer(s2.first(), visParams, 'True Color');
另一个关键点是客户端与服务端数据的转换。当你需要将服务端计算结果用于客户端逻辑(如条件判断)时,必须使用getInfo()或evaluate()。但这两个方法会阻塞浏览器,且不适合在循环中调用。推荐的做法是:在脚本末尾一次性获取结果,或使用print()输出到控制台。
map()回调中调用getInfo()会导致性能急剧下降,因为每次迭代都会发起一次独立的服务器请求。正确做法是让map()返回一个ee.Image或ee.Feature,最后再统一导出或可视化。
最后,GEE的调试流程通常遵循以下步骤:
- 使用
print()打印对象元数据,检查数据集名称、波段、投影等信息。 - 使用
Map.addLayer()可视化中间结果,确认空间范围和处理效果。 - 使用
getInfo()获取小规模计算结果(如区域统计值),验证数值合理性。 - 使用
Export导出最终结果,并在任务面板中监控进度。
map()计算每景影像的NDVI均值;接着,使用filter()筛选NDVI大于0.4的影像;最后,使用size()获取数量。整个过程完全基于服务端对象,无需客户端循环。
var region = ee.Geometry.Point([116.4, 39.9]).buffer(1000);
var s2 = ee.ImageCollection("COPERNICUS/S2_SR")
.filterBounds(region)
.filterDate('2020-01-01', '2020-12-31')
.filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', 20));
var ndviList = s2.map(function(img) {
var ndvi = img.normalizedDifference(['B8', 'B4']).rename('NDVI');
var meanNdvi = ndvi.reduceRegion({
reducer: ee.Reducer.mean(),
geometry: region,
scale: 10
}).get('NDVI');
return img.set('mean_ndvi', meanNdvi);
});
var highVeg = ndviList.filter(ee.Filter.gt('mean_ndvi', 0.4));
print('High vegetation count:', highVeg.size());
通过上述案例,你可以看到GEE中JavaScript语法的核心价值:用简洁的客户端代码构建复杂的地理计算管道。掌握变量、函数、流程控制和数据结构,是迈向高效GEE开发的第一步。在实际项目中,建议始终使用const声明不可变引用,使用箭头函数简化回调,并牢记客户端与服务端的边界,这样才能写出既高效又易维护的遥感云平台脚本。