文前說明:關於style就是頁面的css暫時不作評論,由於官方給的例子的樣式實在太簡單了,照抄閱讀便可。javascript
這篇文章有着大量AJS 4.x版本添加的內容,如監聽watch、Promise對象、回調函數、異步處理等內容,原理性的東西我會在文末解釋,各位看官不用擔憂看不懂,我儘可能用通俗的語言解釋這些。css
慣例,若是不習慣從頭看到尾,能夠直接跳到後面看總結。html
你們應該看過商業地圖的縮略圖功能吧?以度娘地圖爲例,在使用街景地圖的時候,左下角會出現一個地點同樣的2D小地圖:java
這個就是鷹眼功能的應用,在不少桌面軟件中如Erdas、Envi,鷹眼是很常見的。es6
//若是如下超連接往後更新了4.3或更高版本,請自行尋找4.2的sample配合本文學習~編程
此次就解讀2D overview map in SceneView這個例子。數組
源代碼:點我promise
其實就是一個2D的MapView在3D的SceneView的顯示而已,關鍵就在數據的同步,官方指出了watch()方法是關鍵。服務器
話很少說,先上最終效果圖:dom
結構大概就是,大的DIV裏放SceneView,小的DIV裏放MapView。
小的DIV裏又有一個黑色的區域來標識當前SceneView的區域。小的DIV的widgets被移除。
html代碼爲:
<body> <div id="viewDiv"></div> <div id="overviewDiv"> <div id="extentDiv"></div> </div> </body>
老樣子,require給出引用(之前都叫第一個字符串數組參數,爲了省事,之後直接叫引用了)
require( [ "esri/Map", "esri/views/SceneView", "esri/views/MapView", "esri/core/watchUtils", "dojo/dom", "dojo/promise/all", "dojo/domReady!" ], function(Map, SceneView, MapView, watchUtils, dom, all){ //你的代碼 } );
重點應該是:
view的watch()方法、watchUtils的when方法、view的toScreen方法、view的extent屬性、view的then方法。
既然有兩個view(DIV),那麼確定要有兩份map(數據)。
因此第二參數(之前的文章叫函數參數,以後都叫第二參數)先將map和view定義以下:
var mainMap = new Map({ basemap: "hybrid", ground: "world-elevation" }); var overviewMap = new Map({ basemap: "osm" }); var mainView = new SceneView({ container: "viewDiv", map: mainMap }); var mapView = new MapView({ container: "overviewDiv", map: overviewMap });
mainMap、mainView是3D的,overviewMap、mapView是2D的。
固然,咱們看到的2D的小地圖是沒有放大縮小那些控件的,只需1行代碼,就能夠置空那些控件。
mapView.ui.components = [];
查閱API,能夠知道ui屬性是DefaultUI類,DefaultUI繼承自UI類。components是字符串數組,若賦值爲空數組則清空。相應的,DefaultUI類有remove和empty方法能夠清除控件,就不細說了。
爲了便於操做,把當前區域的DIV「extendDiv」的DOM元素獲取爲變量:
var extentDiv = dom.byId("extentDiv");
以上就完成了準備部分。
接下來,數據加載完成後,就要對2D的地圖和3D的地圖進行「同步」了,須要用到兩個view的then方法。
then()方法是Promise對象的特有方法,而Promise是什麼暫時無需瞭解,只要知道在AJS 4.x中Promise是一個很重要的東西。
並且,MapView和SceneView類都繼承了Promise類。不只如此,AJS 4.x中不少方法返回的都是Promise對象。
先看看mainView(3D視圖)的then方法看看它作了什麼:
mainView.then(function() { mainView.goTo({ center: [7, 46], scale: 200000, heading: 35, tilt: 60 },
{ animate: true, duration: 100000 }) });
很好,它接受了一個參數,類型是方法。這個匿名方法幹了什麼呢?這不就是上一篇文章裏說的縮放動畫嘛!(goTo)跳過,看mapView(2D視圖)的then方法看它作了什麼:
mapView.then(function() { mainView.watch("extent", updateOverviewExtent); mapView.watch("extent", updateOverviewExtent); watchUtils.when(mainView, "stationary", updateOverview); function updateOverview() { mapView.goTo({ center: mainView.center, scale: mainView.scale * 2 * Math.max(mainView.width / mapView.width, mainView.height / mapView.height) }); } function updateOverviewExtent() { var extent = mainView.extent; var bottomLeft = mapView.toScreen(extent.xmin, extent.ymin); var topRight = mapView.toScreen(extent.xmax, extent.ymax); extentDiv.style.top = topRight.y + "px"; extentDiv.style.left = bottomLeft.x + "px"; extentDiv.style.height = (bottomLeft.y - topRight.y) + "px"; extentDiv.style.width = (topRight.x - bottomLeft.x) + "px"; } });
很長的樣子。
我慢慢解釋。
仍然是接受一個方法做爲參數(爲何then接受的參數那麼奇怪?文末會解釋的)
//題外話:在javascript裏頭傳函數/方法是很常見的,函數/方法是js的一種變量類型,在C/C++裏頭能夠傳遞函數指針,在C#裏頭能夠傳遞委託變量。
這個方法裏有兩個方法,命名爲 updateOverview 和 updateOverviewExtent,咱們根據這兩個方法把這個then方法的代碼拆開看,發現watch和watchUtils.when是跟這兩個方法配對的。
即:
//兩個視圖都與updateOverviewExtent方法綁定 mainView.watch("extent", updateOverviewExtent); mapView.watch("extent", updateOverviewExtent); function updateOverviewExtent() { var extent = mainView.extent; var bottomLeft = mapView.toScreen(extent.xmin, extent.ymin); var topRight = mapView.toScreen(extent.xmax, extent.ymax); extentDiv.style.top = topRight.y + "px"; extentDiv.style.left = bottomLeft.x + "px"; extentDiv.style.height = (bottomLeft.y - topRight.y) + "px"; extentDiv.style.width = (topRight.x - bottomLeft.x) + "px"; }
查閱API,得知視圖的父類Accessor就支持watch方法了。值得一提的是,爲了實現監聽變化,AJS4.x版本專門提供了watch方法代替了之前的舊方法。
watch的用法是:
對象.watch("該須要監聽的屬性名", 屬性變化後須要執行的回調函數);
即某對象監聽了它的某個屬性後,這個屬性一旦發生改變,就會去執行某些代碼。
在本例中,須要監聽的是兩個view對象的extent(範圍)屬性,一旦extent發生變化,那麼updateOverviewExtent()方法就會被執行。
updateOverviewExtent()方法的大概意思就是:獲取3D視圖的範圍->獲取2D視圖的對角線兩個角點->更改2D視圖上方的區域框的DOM元素的尺寸屬性(top、left、height、width)
光改變區域框是不行的,還要改變2D地圖的範圍。
watchUtils.when(mainView, "stationary", updateOverview); function updateOverview() { mapView.goTo({ center: mainView.center, scale: mainView.scale * 2 * Math.max(mainView.width / mapView.width, mainView.height / mapView.height) }); }
watchUtils這個對象,是位於esri/core/watchUtils模塊下的一個類。
它表明的含義是:監聽某個對象,當這個對象的某個屬性是true時,執行給定的方法。
查閱API得知,這個類提供了when這個靜態方法,when方法的意義是:
因此,在本例中,意思就是:
當mainView這個3D視圖對象的"stationary"屬性是true時,刷新mapView這個2D視圖對象。
刷新2D視圖對象主要用的是上一篇中說到的goTo()方法,本例只指定了center和scale這兩個屬性組成的Object匿名對象。
SceneView類的stationary屬性是布爾類型的,意義是當前視圖是否已經靜止(通常視圖會由鼠標拖拽或者goTo()方法產生動態效果,一旦中止下來,stationary就會變成true)
總結一下。
這個例子大概思路就是:
·先實例化兩個map和兩個view,對3D的mainView在建立完成後使用then()方法縮放到指定位置。
·其中,對2D的mapView建立完成後使用then()方法,分別監聽兩個view的extent屬性,還監聽3D視圖的stationary屬性。
·當extent屬性發生變化時,2D視圖上方範圍框先進行變化,而後2D地圖緊隨變化。
·當3D視圖靜止下來後,刷新2D視圖。
監聽還算比較好理解,須要注意的很少,注意到watch和watchUtils.when這兩個方法返回的都是WatchHandle對象。待之後研究多了監聽後,再仔細看看別的監聽方法。
難點就在於then方法。
難點。
then()方法怎麼來的?這要從ES6(全名ECMAScript 2015)的新規範Promise對象提及。ECMAScript是JavaScript的標準,JS是ES的實現。
Promise是什麼?這個東西說複雜也很複雜,它是:
爲了處理異步操做多層回調函數的寫法枯燥、難以閱讀維護而產生的,由CommonJS社區發起的一個新規範的類。
最顯著的特徵是它實例化的對象都有then()和catch()方法(PromiseA+規範?好像是)
在AJS中,繼承了Promise的類有:
所有的Layers
MapView、SceneView、LayerView
ViewAnimation
能返回Promise對象的類數不勝數。
因此說,爲何要用Promise?
這又要從異步操做提及了。
————
在AJS 4.x中,數據(Map類)和視圖(View類)是分開的,3.x版本繪圖渲染是Map本身完成的。
因爲View視圖類被分離開,繪圖邏輯就成了它的主要功能。固然,繪圖不會很快,每每有一個過程,尤爲是超大數據量的繪圖的時候會有一個比較長的等待過程。
因此,在JS裏,較長的處理會丟給異步處理(就是同時進行好幾個操做)
可是可是,咱們知道JS是單線程的,它是怎麼處理異步處理的呢?簡單說說,JS的異步處理實際上是個「僞異步」,是先完成同步代碼才執行異步代碼的。
一般,異步代碼會作一些計算量比較大的事情,而同步代碼則作一些不怎麼耗時間的初始化工做。就是說
同步代碼花少許的時間去初始化一些事情,其間有n個異步任務丟給異步隊列。當同步代碼完成初始化後(時間短),異步代碼開始按順序執行。
好比:界面的構建交給同步代碼,而其間有n個後臺數據交換、處理、計算的任務,就丟給異步隊列去準備。當界面構造好(時間每每很短,幾乎是秒速),異步代碼就在後面開始執行。
這先看到的界面會讓體驗好不少,若是異步代碼(就是耗時比較大的任務)放在同步代碼裏執行,那麼因爲同步的性質,必須等待這些耗時大的任務執行完成才能繼續往下走(js的特色,單線程)
【在本例中】
初始化view,我不知道在雲端是怎麼運行的(由於我用的是CDN來運行AJS程序),可是我知道view的實例化確定是用了異步操做。
即先完成網頁的加載(出現3D地球和2D地圖,同步),再進行視圖的渲染(山體拔高等,異步)。
sometimes,異步操做固然會有一個結果,好比異步在後面花好長時間算出個矩陣,可是同步代碼已經結束了,異步任務丟過去的時候結果還沒出來,怎麼獲取它?
咱們能夠用一個方法去獲取它。這個方法,古時候叫回調函數。
在沒有Promise類的時候,一般用回調函數這種辦法實現(也能用事件、監聽)異步是很正常的一種。
可是當回調函數自己也是個異步操做的時候,就會顯得暈頭轉向。
異步第一層,有結果要用回調函數返回給同步代碼->回調函數是第二層,這個回調函數裏頭須要用二級回調函數返回結果給第一層->……
舉個例子:
我是領導,我如今有兩件事:有個事兒要作,和喝茶。
這兩件事不衝突,雖然這個事兒很無聊,耗時大(如文字錄入)。
因此我把這個事兒丟給經理(異步第一層),我繼續喝茶(同步)
異步第一層就是經理要作這個事,可是這個事情絕大部分是無聊的,最後的整理比較簡單。
因此經理就把這個無聊的部分丟給職員(異步第二層),等待職員把這部分作完的同時,也去喝茶(同步)。
因而,職員的結果就是二級回調函數,職員把結果完成後,「回調」給經理。
經理拿着職員的結果整理好,「回調」給領導。(第二層異步完成)
此時領導茶已經喝完了(同步完成),而任務也完成了(第一層異步完成)。
這裏若是用老的寫法將會很是的煩,若是用Promise的then寫法就是
領導要作事兒.then(function(){讓經理去作})
.then(function(){讓職工作});
鏈式寫法,簡單,容易看,也容易維護。
then裏面的function就是回調函數,告訴異步任務完成後,要怎麼處理異步結果的一段代碼。
最後看看then方法的語法:
then(function resolve, function rejected);
咱們通常只用前一個參數,即異步成功要怎麼處理。然後一個參數是異步任務處理失敗後要作什麼。
甚至AJS官方還給出了處理中要作什麼的第三個參數...這個就不說那麼多了。
——
大概清楚是這麼個過程後,咱們知道View對象是Promise對象(繼承),並且有異步操做的過程。
因此,mainView.then(function(){...});的意義就是
當3D視圖在服務器端異步操做成功後,使用goTo()縮放到指定的位置。
文末,我還想說說監聽,監聽在AJS 3.x版本里是經過事件完成的,而AJS 4.x全新使用了watch一派寫法。有關這些能夠參考AJS 4.2的Guide文檔。
最後的最後,關於異步和回調函數部分我也是學了一天後纔給出的模糊定義,但願你們能看懂吧...我也不是很能理解,官方給的多層then()是這樣的:
出處:點我
then裏頭固然是方法,無參的。只有子一層的結果完成的時候,父一層的then才能憑藉子一層的結果的回調完成異步。
給一些我閱讀中以爲不錯的對異步、回調函數講解的文章: