2. JavaScript基础速通:变量与常量、数据类型、函数定义、条件与循环、对象与数组、回调函数与匿名函数
说实话,很多搞遥感的同学一听到「编程」两个字就头大。我当年也是这么过来的。但后来我发现,JavaScript 在 GEE 里其实就那几板斧——变量、函数、循环、对象。你只要把这几个核心概念吃透了,写 GEE 代码就跟搭积木一样简单。
这一节,咱们就把这些基础过一遍。别怕,我尽量用最直白的话讲。
2.1 变量与常量:存东西的盒子
变量和常量,说白了就是用来存数据的「盒子」。变量可以换东西,常量不行。
在 JavaScript 里,声明变量用 var 或 let,声明常量用 const。
// 变量:可以重新赋值
var city = 'Beijing';
city = 'Shanghai'; // 没问题
// 常量:一旦赋值就不能改
const PI = 3.14159;
PI = 3.14; // 报错!
var 和 const。let 用得少。为什么?因为 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);
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');
// 处理影像...
}
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 代码的时候,遇到不会的回来翻翻就行。我当年也是这么过来的——边写边查,慢慢就熟了。
公众号:蓝海资料掘金营,微信deep3321