昨天一個話題說關於AngularJS2之後版本的兩個小技巧,不料引出了另一個話題,話題起始很簡單:
「不少的前端框架並不複雜,好比JQuery,引入即用,實時看到效果,多好。到了Angular2一直到如今的版本5,一點改進沒有,還要編譯,還要部署,原有的JS腳本也不能用了。」
細想起來,這個話題的帽子並不小,至少牽扯出來一個關鍵,AngularJS2及之後的版本,其框架之下的JS代碼,跟HTML中<script>
塊之中的JS代碼,究竟是什麼關係?
我試着來回答一下:css
- 首先,在AngularJS2框架之中實際使用的是ES6,全稱ECMAScript6,是Javascript的下一個版本。官方的例子則是基本採用TS,全稱TypeScript,是JS的一個超集。之因此用起來沒有明顯區別的感受,由於的確從經常使用語法上,跟當前使用的JS,或者叫ES5 JS,差異很小,但即使再小,那也算的上不一樣的語言了。
- 爲何採用新的語言,而不是沿用當前的ES5,官網和社區已經有了不少解釋了,新語言固然有新語言的優點,好比定義變量,能夠指定類型,而在程序中用錯類型,則會在編譯過程當中就給出警告,不至於等到上線了才發現BUG。這些優點很是多,這裏就不多此一舉了。反正你確定能理解,新固然有新的好處。
- 既然採用了新的語言,爲了跟當前的瀏覽器系統兼容,固然就有一個翻譯過程,準確的說,甭管是TS仍是ES6,甚至未來可能的ES7,在當下,都要翻譯成ES5,才能在當前流行的瀏覽器之中運行。這個翻譯,行話上講,也就是「編譯」。
- 事實上,編譯不只僅幹這麼一點事,不少的優化工做、查錯工做,也是在這個階段完成的,好比你使用了沒有定義的變量、函數;好比你用錯了函數類型;好比你使用了某個函數庫但只是用了其中一小部分,那麼多沒用的部分應當排除掉避免佔用寶貴的下載帶寬,這些都是在編譯過程作到的。
- 好了,既然通過了這麼複雜的動做,這個編譯也必不可少,那麼實際上答案已經出來了:那就是,不少原有理所應當存在的東西,就好比你在HTML中定義的JS對象、變量、函數,那些都是在執行環節,瀏覽器中才存在的。而在編譯階段,那些東西還只是停留在字符狀態,AngularJS固然並不知道他們存在,也就沒法直接的、像原來咱們使用HTML-JS同樣來使用它們了,這就如同上面那張圖,看上去海天一色,互相映託,但在根本上,它們是在兩個世界。
上面是從技術實現上的限制緣由,實際上還有一個設計哲學邏輯上的緣由:html
- AngularJS設計之初就不是爲了單純的在桌面瀏覽器中運行,還但願可以在手機、移動設備甚至其它設備上執行。你可能會說,如今的手機瀏覽器也很發達啊,至少比不少IE6/IE7之流要強多了,稍等,這裏說的移動設備、其它設備,可不必定是指僅僅瀏覽器,從這種設計邏輯出發,AngularJS成爲一種跨平臺的開發框架,直接編譯成各類系統原生的代碼,徹底是有可能實現的。試想,在那種狀況下,你原來的JS代碼極可能是連存在的空間都沒有,又如何讓AngularJS訪問到呢?
————————————————————————————————————————————前端
那是否是原有的JS代碼和技術都要做廢掉,沒法再使用了呢?
固然不是,你確定早看到了,大量的第三方模塊和代碼庫,經過NPM的管理,共存於這個架構中,彼此友好的相處。你原有的工做,徹底能夠用一樣的方式來工做。
你也可能會說,可我有不少代碼沒有作到那麼好的面向對象化包裝,也不想作那麼複雜,該怎麼辦呢?AngularJS也提供了至少3個方法,來完成兩個世界的打通工做。
第一個方法,使用declare來預聲明:
咱們來先看一個例子,使用ng new testExtJS
來新建一個工程,接着cd testJS
進入項目目錄,使用cnpm install
來初始化依賴包。用cnpm的緣由是若是在中國,速度會快不少,這個在上一篇文章也說了。
接着修改index.html,這裏只貼出最後的結果:web
<head> <meta charset="utf-8"> <title>TestExtJs</title> <base href="/"> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="icon" type="image/x-icon" href="favicon.ico"> </head> <body> <script> var webGlObject = (function() { return { init: function() { alert('webGlObject initialized'); } } })(webGlObject || {}) </script> <app-root>Loading...</app-root> </body> </html>
注意中間的<script>
塊是咱們增長的部分,來模擬咱們在html本地已經有了一段js代碼。
而後在app.component.ts中增長聲明和調用的部分:npm
import { Component } from '@angular/core'; declare var webGlObject: any; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent { title = 'app works!'; constructor() { this.title="constructor works!" webGlObject.init(); } }
注意上面代碼中的declare聲明,和下面添加的constructor構造函數和其中對js對象的調用。
declare的意思就是告訴AngularJS,相信我,雖然如今你看不到對象webGlObject,但相信我,或早或晚,反正你必定會看到它的存在的,你正常編譯、正常執行就好啦。固然這裏的潛臺詞和反作用就是:諾,AngularJS,這部分代碼我負責啦,你不用管它的對錯,反正錯了我也不會怪你。
使用這種方法,相似上一篇文章的問題,你也徹底能夠聲明一個window對象,而後直接訪問其中的userAgent:segmentfault
... declare var window:any; ... console.log(window.navigator.userAgent);
問題又來了,既然直接能訪問到window對象,那還用什麼ng4-device-detector組件,直接從userAgent中判斷設備類型很差嗎?
這就牽涉到我上面解釋的最後一條,未來這段AngularJS代碼,極可能不是運行在一個瀏覽器,其中可能根本沒有window/document對象,那時候,這段代碼就出錯了。固然你可能會說,不不不,我就是在瀏覽器運行,不考慮別的。OK,我也不較勁,你當我沒說,你徹底能夠就這麼用。
可是比較規範的辦法,應當是把window對象以及你須要的其它相似對象,寫成一個服務,而後注入到app.component之中,這樣,即使未來運行環境有變化,只修改服務部分代碼,你的主程序徹底能夠不用修改。
落實到代碼,大體是這樣,首先把window對象包裝成一個服務:瀏覽器
import { Injectable } from '@angular/core'; function _window() : any { // return the global native browser window object return window; } @Injectable() export class WindowRef { get nativeWindow() : any { return _window(); } }
註冊到provider:前端框架
import { WindowRef } from './WindowRef'; ... @NgModule({ ... providers: [ WindowRef ] }) export class AppModule{}
在須要的組件中,引用這個服務,而後就可使用了:架構
... import { WindowRef } from './WindowRef'; ... @Component({...}) class MyComponent { ... constructor(private winRef: WindowRef) { // 獲得window對象 console.log('Native window obj', winRef.nativeWindow); } ... }
我得認可,這樣是麻煩了很多,不過規範、可複用的代碼,自己的確就多了不少限制。
參考資料:https://juristr.com/blog/2016/09/ng2-get-window-ref/app
————————————————————————————————————————————
AngularJS也一直在努力,盡力彌合這種鴻溝,其中HostListener和HostBinding就是具體的兩個實現,也是咱們開始所說的3個方法中的後兩個。
HostListener 是屬性裝飾器,用來爲宿主元素添加事件監聽,這個行爲表示html端某個元素的事件,產生到達TS腳本的調用動做。好比:
import { Directive, HostListener } from '@angular/core'; @Directive({ selector: 'button[counting]' }) class CountClicks { numberOfClicks = 0; @HostListener('click', ['$event.target']) onClick(btn: HTMLElement) { console.log('button', btn, 'number of clicks:', this.numberOfClicks++); } }
使用counting裝飾的button按鈕,每次點擊,都會產生一次計數行爲,而且打印到控制的日誌中去。
HostBinding 是屬性裝飾器,用來動態設置宿主元素的屬性值,這個跟上面的動做相反,表示首先標記在html某元素的某屬性,而後在TS腳本端,對這個屬性進行設置、賦值。好比:
import { Directive, HostBinding, HostListener } from '@angular/core'; @Directive({ selector: '[exeButtonPress]' }) export class ExeButtonPress { @HostBinding('attr.role') role = 'button'; @HostBinding('class.pressed') isPressed: boolean; @HostListener('mousedown') hasPressed() { this.isPressed = true; } @HostListener('mouseup') hasReleased() { this.isPressed = false; } }
上面的代碼表示,若是某個html元素用exeButtonPress屬性修飾以後,會有一個.pressed屬性,能夠監控到鼠標按下、擡起的事件,這表現了html元素到ts端雙向的互動。
HostListener和HostBinding有一個簡寫的形式host,以下所示:
import { Directive, HostListener } from '@angular/core'; @Directive({ selector: '[exeButtonPress]', host: { 'role': 'button', '[class.pressed]': 'isPressed' } }) export class ExeButtonPress { isPressed: boolean; @HostListener('mousedown') hasPressed() { this.isPressed = true; } @HostListener('mouseup') hasReleased() { this.isPressed = false; } }
看看,跟上一篇中快捷鍵綁定的方法很類似了?
這一部分的代碼使用了http://www.javashuo.com/article/p-qjqwltji-bq.html的資料,這篇文章寫的很細緻,想詳細瞭解的建議及早閱讀。