2. JavaScript基础速通:变量与常量、数据类型、函数定义、条件与循环、对象与数组、回调函数与匿名函数

说实话,很多搞遥感的同学一听到「编程」两个字就头大。我当年也是这么过来的。但后来我发现,JavaScript 在 GEE 里其实就那几板斧——变量、函数、循环、对象。你只要把这几个核心概念吃透了,写 GEE 代码就跟搭积木一样简单。

这一节,咱们就把这些基础过一遍。别怕,我尽量用最直白的话讲。

2.1 变量与常量:存东西的盒子

变量和常量,说白了就是用来存数据的「盒子」。变量可以换东西,常量不行。

在 JavaScript 里,声明变量用 varlet,声明常量用 const

// 变量:可以重新赋值
var city = 'Beijing';
city = 'Shanghai';  // 没问题

// 常量:一旦赋值就不能改
const PI = 3.14159;
PI = 3.14;  // 报错!
我的习惯:在 GEE 里,我几乎只用 varconstlet 用得少。为什么?因为 GEE 的代码大多是线性执行的,变量作用域没那么复杂。var 够用了。

嗯,这里要注意一点:const 声明的常量,如果是对象或数组,它的内容是可以修改的。我刚开始学的时候就被这个坑过。

const imageCollection = ee.ImageCollection('LANDSAT/LC08/C01/T1');
imageCollection = ee.ImageCollection('MOD09GA');  // 报错!不能重新赋值

// 但是可以修改它的属性
imageCollection = imageCollection.filterDate('2020-01-01', '2020-12-31');  // 这样也不行!
避坑指南:我曾经在项目里用 const 声明了一个影像集合,然后试图重新赋值,结果调试了半天。记住:const 是「绑定」不可变,不是「值」不可变。

2.2 数据类型:数字、字符串、布尔值

JavaScript 的数据类型不多,常用的就这几种:

类型 示例 说明
Number 42, 3.14 数字,不分整数和浮点数
String 'Hello', "World" 字符串,单引号双引号都行
Boolean true, false 布尔值,判断用
Array [1, 2, 3] 数组,有序列表
Object {name: 'Landsat'} 对象,键值对集合
null / undefined null, undefined 空值 / 未定义

你想想看,在 GEE 里最常用的是什么?字符串和数字。比如筛选日期、指定波段名称,全是字符串操作。

var startDate = '2020-01-01';
var endDate = '2020-12-31';
var bandName = 'NDVI';

var collection = ee.ImageCollection('LANDSAT/LC08/C01/T1')
  .filterDate(startDate, endDate)
  .select(bandName);
重点:JavaScript 是弱类型语言。你不需要声明变量是什么类型,它会自动推断。但这也意味着你要小心——比如字符串和数字相加,结果可能不是你想要的。

2.3 函数定义:把重复的事打包

函数,就是把一段逻辑打包起来,方便重复调用。在 GEE 里,函数无处不在——写一个 NDVI 计算函数、写一个云掩膜函数,都是家常便饭。

定义函数有三种方式:

// 方式一:函数声明(最常用)
function add(a, b) {
  return a + b;
}

// 方式二:函数表达式
var multiply = function(a, b) {
  return a * b;
};

// 方式三:箭头函数(ES6 新语法,GEE 里也支持)
var subtract = (a, b) => a - b;

我个人习惯用第一种——函数声明。为什么?因为它的可读性最好,而且可以在定义之前调用(这叫「提升」)。

在 GEE 里,你经常需要把函数传给 map() 方法。比如给影像集合里的每一景影像做云掩膜:

function maskClouds(image) {
  var cloudMask = image.select('pixel_qa').bitwiseAnd(1 << 3).eq(0);
  return image.updateMask(cloudMask);
}

var cleanCollection = collection.map(maskClouds);
小技巧:写函数时,尽量让函数只做一件事。比如上面的 maskClouds 只做云掩膜,不干别的。这样调试起来方便,复用性也高。

2.4 条件与循环:让代码有脑子

条件判断和循环,是编程的「大脑」。没有它们,代码就是一条道走到黑。

条件判断:if / else

var ndvi = 0.6;

if (ndvi > 0.5) {
  print('植被茂盛');
} else if (ndvi > 0.2) {
  print('有植被覆盖');
} else {
  print('裸地或水体');
}

在 GEE 里,条件判断经常用在 ee.Algorithms.If() 里。不过说实话,我更喜欢用原生的 if,因为更直观。

循环:for / while

循环在 GEE 里用得相对少,因为 GEE 鼓励用 map() 代替循环。但有时候你还是需要循环,比如遍历年份:

var years = [2015, 2016, 2017, 2018, 2019, 2020];

for (var i = 0; i < years.length; i++) {
  var year = years[i];
  var image = ee.Image('LANDSAT/LC08/C01/T1/LC08_' + year + '_001');
  // 处理影像...
}
注意:在 GEE 的客户端(Code Editor)里,for 循环是同步执行的。如果循环次数太多,可能会超时。我建议能用 map() 就用 map(),它是在服务器端并行执行的,效率高得多。

2.5 对象与数组:组织数据的利器

对象和数组,是 JavaScript 里最常用的两种数据结构。

数组:有序列表

var bands = ['B2', 'B3', 'B4', 'B5', 'B6', 'B7'];
var values = [0.1, 0.2, 0.3, 0.4];

// 访问元素
print(bands[0]);  // B2
print(bands.length);  // 6

// 常用方法
bands.push('B8');  // 末尾添加
bands.pop();       // 末尾删除

对象:键值对集合

var landsatInfo = {
  name: 'Landsat 8',
  bands: 11,
  resolution: 30,
  launchYear: 2013
};

// 访问属性
print(landsatInfo.name);       // Landsat 8
print(landsatInfo['bands']);   // 11

// 添加新属性
landsatInfo.swathWidth = 185;  // 幅宽185公里

在 GEE 里,对象用得特别多。比如影像的元数据、几何信息,都是以对象形式存储的。

实战经验:我在做土地利用分类时,经常用对象来存储分类参数。比如每种地类的阈值、颜色、名称,都放在一个对象里。这样代码整洁,改参数也方便。

2.6 回调函数与匿名函数:GEE 的灵魂

这两个概念,是 GEE 编程里最重要的。你如果不理解它们,后面写 GEE 代码会非常痛苦。

回调函数:把函数当参数传

回调函数,说白了就是把一个函数作为参数传给另一个函数。在 GEE 里,最常见的回调就是 map() 方法。

// 定义一个回调函数
function addNDVI(image) {
  var ndvi = image.normalizedDifference(['B5', 'B4']).rename('NDVI');
  return image.addBands(ndvi);
}

// 把回调函数传给 map()
var withNDVI = collection.map(addNDVI);

你看,addNDVI 这个函数被传给了 map()map() 会对集合里的每一景影像调用它。这就是回调。

匿名函数:用完就扔的函数

匿名函数,就是没有名字的函数。它通常用在回调里,因为很多时候你只需要用一次,没必要专门起个名字。

// 匿名函数写法
var withNDVI = collection.map(function(image) {
  var ndvi = image.normalizedDifference(['B5', 'B4']).rename('NDVI');
  return image.addBands(ndvi);
});

// 箭头函数写法(更简洁)
var withNDVI = collection.map(image => {
  var ndvi = image.normalizedDifference(['B5', 'B4']).rename('NDVI');
  return image.addBands(ndvi);
});
我的建议:刚开始学的时候,先用有名字的函数。等熟练了,再用匿名函数。因为匿名函数调试起来不方便——报错了你不知道是哪个函数出的问题。

为什么会这样?因为匿名函数没有名字,控制台报错只会显示 anonymous function,你根本不知道是哪个。我曾经因为这个浪费了整整一个下午。

知识体系总览

下面这张图,把这一节的核心知识点串起来了。你可以把它当作一个「速查地图」:

JavaScript 基础速通 变量与常量 数据类型 函数定义 条件与循环 对象与数组 回调与匿名函数 GEE 编程核心:理解回调 + 函数式编程 每个模块都是 GEE 编程的基石,建议逐个击破 var / const / let Number / String function / => if / for / while {} / [] / .map()

好了,这一节的内容就到这儿。JavaScript 基础其实就这么多东西,你不需要一下子全记住。写 GEE 代码的时候,遇到不会的回来翻翻就行。我当年也是这么过来的——边写边查,慢慢就熟了。


公众号:蓝海资料掘金营,微信deep3321