3、JavaScript基础速成:变量、数据类型、函数、循环、条件语句、数组与对象,以及GEE中常用的JavaScript语法要点

Google Earth Engine(GEE)的Code Editor是一个基于Web的IDE,其核心编程语言是JavaScript。虽然GEE封装了大量遥感与地理空间分析API,但所有对影像、矢量、图表的操作最终都归结为JavaScript代码的执行。对于遥感从业者而言,不必成为JavaScript高级工程师,但必须掌握其核心语法骨架,才能流畅地编写、调试和复用GEE脚本。本章将聚焦于GEE开发中最常用的JavaScript语法要素,包括变量声明、数据类型、函数定义、流程控制、数组与对象操作,并结合GEE特有的客户端-服务端异步机制,帮助你建立扎实的编码基础。

GEE JavaScript 核心语法知识框架 JavaScript 基础 变量与数据类型 var/let/const Number/String/Boolean 函数定义与调用 function / 箭头函数 map() / filter() 回调 流程控制 if/else / for / while 逻辑判断与循环 数组与对象 ee.List / ee.Dictionary / 原生Array 索引、遍历、属性访问 GEE 特有机制 客户端 vs 服务端 ee.xxx 对象与延迟执行 核心原则:客户端代码立即执行,服务端对象延迟计算 使用 getInfo() / evaluate() 调试,但避免在循环中调用

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对象则是在服务器端延迟计算。

核心原理:GEE采用客户端-服务端架构。客户端(浏览器)负责解析JavaScript语法并构建请求树,服务端(Google服务器)执行实际的地球引擎计算。因此,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代码。

实战经验:在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);
});
常见误区:在GEE的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最大值:
  1. 使用ee.List.sequence()生成月份序列。
  2. 用map()对每个月执行过滤和计算。
  3. 将结果合并为新的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()获取所有值。

操作原生Arrayee.List原生Objectee.Dictionary
添加元素push()add()obj.key = valueset(key, value)
获取元素arr[index]get(index)obj.keyget(key)
长度/大小length属性size()Object.keys().lengthsize()
遍历for / forEachmap() / iterate()for...inkeys() + map()
实战场景:在GEE中,经常需要将客户端数组转换为服务端列表,以便在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的调试流程通常遵循以下步骤:

  1. 使用print()打印对象元数据,检查数据集名称、波段、投影等信息。
  2. 使用Map.addLayer()可视化中间结果,确认空间范围和处理效果。
  3. 使用getInfo()获取小规模计算结果(如区域统计值),验证数值合理性。
  4. 使用Export导出最终结果,并在任务面板中监控进度。
案例拆解:假设你需要计算某区域2020年所有Sentinel-2影像的NDVI均值,并筛选出NDVI大于0.4的影像数量。首先,定义区域和影像集合;然后,使用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声明不可变引用,使用箭头函数简化回调,并牢记客户端与服务端的边界,这样才能写出既高效又易维护的遥感云平台脚本。