查看當前所有正在運行的進程,可以看到80端口被httpd占用了(80端口希望分配給nginx使用,而不是httpd)
netstat -tpnul
這里以殺死httpd進程為例:
先查看 httpd 進程
flaskApi) [root@67 flaskDemo]# ps aux |grep httpd root 6732 0.0 0.0 230488 5252 ? Ss 8月06 2:27 /usr/sbin/httpd -DFOREGROUND
apache 22570 0.0 0.0 232572 3888 ? S 9月15 0:00 /usr/sbin/httpd -DFOREGROUND
apache 22571 0.0 0.0 232572 3888 ? S 9月15 0:00 /usr/sbin/httpd -DFOREGROUND
apache 22572 0.0 0.0 232572 3904 ? S 9月15 0:00 /usr/sbin/httpd -DFOREGROUND
apache 22573 0.0 0.0 232572 3904 ? S 9月15 0:00 /usr/sbin/httpd -DFOREGROUND
apache 22574 0.0 0.0 232572 3900 ? S 9月15 0:00 /usr/sbin/httpd -DFOREGROUND
apache 27544 0.0 0.0 232572 3896 ? S 15:41 0:00 /usr/sbin/httpd -DFOREGROUND
apache 27546 0.0 0.0 232572 3900 ? S 15:41 0:00 /usr/sbin/httpd -DFOREGROUND
apache 27548 0.0 0.0 232572 3172 ? S 15:41 0:00 /usr/sbin/httpd -DFOREGROUND
apache 27550 0.0 0.0 232572 3172 ? S 15:41 0:00 /usr/sbin/httpd -DFOREGROUND
apache 27552 0.0 0.0 232572 3172 ? S 15:41 0:00 /usr/sbin/httpd -DFOREGROUND
root 27665 0.0 0.0 112728 988 pts/0 S+ 15:43 0:00 grep --color=auto httpd
這個就是 apache 的所有進程
我們可以用 kill -9 加進程ID 如下
kill -9 6732 kill -9 22570 kill -9 22571 kill -9 22572 kill -9 22573 kill -9 22574 kill -9 27544 kill -9 27546 kill -9 27548 kill -9 27550 kill -9 27552 kill -9 27665
再次查看一下httpd正在運行的進程:
(flaskApi) [root@67 flaskDemo]# ps aux |grep httpd root 27835 0.0 0.0 112724 988 pts/0 S+ 15:46 0:00 grep --color=auto httpd
全部殺完了... 殺死進程方法有很多種...我這個 只是其中的一種
通過netstat確認一下,httpd已經不再占用80端口了
(flaskApi) [root@67 flaskDemo]# netstat -tpnul
另如果不想殺死進程,而想修改端口號,
操作方法參照:centos7 ngxin啟動失敗:Job for nginx.service failed(80端口被占用的解決辦法)
參照文檔:
轉載于:https://www.cnblogs.com/kaerxifa/p/11534539.html
藍藍設計( www.syprn.cn )是一家專注而深入的界面設計公司,為期望卓越的國內外企業提供卓越的UI界面設計、BS界面設計 、 cs界面設計 、 ipad界面設計 、 包裝設計 、 圖標定制 、 用戶體驗 、交互設計、 網站建設 、平面設計服務
因為自己學習了前端大部分知識,然后想自己做網站,于是學習了node.js,可不知道如何將項目發布到網上,所以花了很多天的時間,搜集了很多的資料,才將項目部署到服務器上,這里給大家分享一下我的部署過程,以免大家走彎路。
- 公眾號:前端印象
- 不定時有送書活動,記得關注~
- 關注后回復對應文字領?。骸久嬖囶}】、【前端必看電子書】、【數據結構與算法完整代碼】、【前端技術交流群】
這里我們就用騰訊云的服務器吧,因為優惠感覺還是比較大的,性價比也高。
先進入學生頁面,購買優惠的服務器套餐,每個月才10元,學生服務器優惠套餐鏈接 。也可以參與限時的秒殺活動,一年才99,用來學習再合適不過了,服務器顯示秒殺鏈接。 如果需求大的話,也可以直接買那些高配的服務器其他服務器鏈接
購買中,所有都默認選項。
購買完成后, 進入控制臺
然后重置一下密碼,一定要記住
我們鼠標移到這看一下服務器的系統是不是CentOS, 因為我們要用到這個版本
如果不是的話,就可以點擊重裝系統, 自己選擇一下CentOS這個系統即可,并且重裝時設置的密碼也一定要記住哦。
這樣一臺服務器也就購買成功了。
網上下載一個xshell5, 用于我們登錄我們的服務器
Xshell5下載地址
下載好以后,打開Xshell5, 點擊新建
去復制一下我們的公網ip
然后按以下提示輸入
以下配置完成后直接點確定
在下圖輸入框中,輸入以下代碼,并按回車
yum install -y wget && wget -O install.sh http://download.bt.cn/install/install_6.0.sh && sh install.sh
遇到該命令,直接輸入y 然后回車,就他自動安裝吧,時間就點長,耐心等待一下
安裝好后,會出現這個圖示界面
訪問該頁面, 并輸入相應的賬號密碼進行登錄
登錄了以后點擊 直接安裝
這時候別閑著,去軟件商店里,找到這兩個軟件安裝一下
先回到我們的騰訊云控制臺
按下圖輸入,并點完成
接下來就可以將我們的項目放到壓縮文件中,然后上傳到寶塔面板中了
,上傳好后直接點解壓就可以了
找到我們的pm2, 開始設置我們的項目
然后點擊映射,將我們的公網ip 映射一下
如果這里的端口是3000,我們需要將入口文件中的端口號改一下,我這里是改為5000了
入口文件的端口號修改好后,我們需要放行一下我們項目網站的端口號,即做以下兩個步驟
然后重啟一下項目
這樣一個node.js項目就部署完成啦,接下來就通過公網ip + 端口號的方式進行訪問
可以看到訪問成功了。
這是我查閱了大量資料,才部署上去的node.js 項目,因為我是做前端的,所以不太懂運維這些的,只能做這樣一個簡單的部署, 不過對于新手學習已經完全足夠了,希望這篇文章能幫助到你們。
轉自:csdn 作者:「零一」
藍藍設計( www.syprn.cn )是一家專注而深入的界面設計公司,為期望卓越的國內外企業提供卓越的UI界面設計、BS界面設計 、 cs界面設計 、 ipad界面設計 、 包裝設計 、 圖標定制 、 用戶體驗 、交互設計、 網站建設 、平面設計服務
接口測試:
接口:是傳遞數據的通道。接口測試主要用于檢測外部系統與系統之間以及內部各個子系統之間的交互點。
測試的重點是要檢測數據的交換,傳遞和控制管理過程,以及系統間的相互邏輯依賴關系等。
接口的分類:
外部接口:調用第三方平臺的接口
內部接口:登陸和注冊這種單獨的內部接口
1、首先下載好tomcat軟件包
2、通過Xftp軟件傳輸到我們Linux虛擬機的 /opt目錄下
3、通過Xshell我們可以在/opt目錄下看到tomcat軟件包(如下圖紅色框內)
4、我們解壓tomcat是用:tar -xzvf apache-tomcat-8.0.30.tar.gz 命令進行解壓,可以看到解壓后的文件(如下圖綠色框內)因為我已經解壓過了,就不給大家示范了(這里要注意的是因為我在opt目錄下的,所以用這個解壓命令,如果你沒在你要解壓的目錄下就要在后面加 -c 這個參數)
5、接下來我們下載JDK(注意JDK的位數和系統區別):我是在64位Linux系統部署的
6、上傳和解壓和tomact一樣都是tar.gz格式的,解壓命令都一樣,也是放在opt目錄下
7、驗證JDK是否安裝好(java -version)
8、在其他目錄下執行(java -version)是不成功的,所以要配置JDK環境變量,通過編輯 vi etc/profile 在最后段加上
JAVA_HOME=/opt/jdk1.8.0_141 -----》改成自己的jdk路徑
JAVA_BIN=$JAVA_HOME/bin
JRE_HOME=$JAVA_HOME/jre
JRE_BIN=$JRE_HOME/bin
PATH=$JAVA_BIN:$JRE_BIN:$PATH
CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar:$JRE_HOME/lib
export JAVA_HOME JRE_HOME PATH CLASSPATH
9、然后輸入 source /etc/profile命令,使環境變量生效,現在使用Java -version目錄下驗證JDK是否已經全局生效
10、在bin目錄啟動tomcat,查看tomcat環境是否能可以啟動( ./startup.sh 啟動 ./startup.sh 關閉 )
11、訪問地址出現如下圖證明tomcat環境搭建好了(你自己虛擬機的IP+tomcat的端口號)
12、修改tomcat的端口號,目錄:在/opt/apache-tomcat-8.0.30/conf,用vi server.xml 修改端口號
13、注意:啟動tomcat要關閉防火墻,最后養成打開日志文件習慣
轉自:csdn 作者:寒門子弟
藍藍設計( www.syprn.cn )是一家專注而深入的界面設計公司,為期望卓越的國內外企業提供卓越的UI界面設計、BS界面設計 、 cs界面設計 、 ipad界面設計 、 包裝設計 、 圖標定制 、 用戶體驗 、交互設計、 網站建設 、平面設計服務
n 次比較相鄰兩數并交換找到最值,然后 n-1 次比較找到次值……較小的數字像氣泡一樣浮出。
這里我引入flag排除,已經有序卻一直遍歷
function bubbleSort(arr){ const n=arr.length; let flag=1,i,j; for(i=0;i<n-1&&flag;i++){ //最壞的情況需要依次比較出最小值、次小值、次次小值 flag=0;//如果交換了就變 for(j=0;j<n-i-1;j++){ if(arr[j]>arr[j+1]){ const swap=arr[j]; arr[j]=arr[j+1]; arr[j+1]=swap; flag=1; } } } return arr; }
和冒泡原理相似,但是少了很多交換,多了一個暫存最值的空間。
n 次比較相鄰兩數后最多只交換一次將最值放到位置上,然后 n-1 次比較找到次值
冒泡更適合基本有序的序列
function selectSort(arr) { const n=arr.length; let i,j,minIndex; for(i=0;i<n-1;i++){ minIndex=i;//先決定誰最小 for(j=i+1;j<n;j++){ if(arr[j]<arr[minIndex]){ minIndex=j; } } if(minIndex!=i){ const swap=arr[i]; arr[i]=arr[minIndex]; arr[minIndex]=swap; } } return arr; }
平均時間復雜度=O(n logn)
function quick_part(arr,left,right){ //留下left和right后面遞歸 var l = left; var r = right; var basic= arr[left]; //arr[left]即basic的原位置 if(left >= right){ //如果數組只有一個元素 return; } while(l!=r){//兩者相遇,意味著一直到找到basic的位置 while(arr[r] > basic && l < r){ //l<r隨時注意嚴格控制哨兵不能錯過,從右邊向左找第一個比key小的數,找到或者兩個哨兵相碰,跳出循環 r--; } while(arr[l] <= basic && l < r){ //這里的=號保證在本輪循環結束前,key的位置不變,否則的話跳出循環,交換l和left的位置的時候,left位置的上元素有可能不是key l++; } //1、兩個哨兵到找到了目標值。2、j哨兵找到了目標值。3、兩個哨兵都沒找到(key是當前數組最小值) if(l!=r){ //交換兩個元素的位置 const swap = arr[l]; arr[l] = arr[r]; arr[r] = swap; } } arr[left] = arr[l] //arr[left]即basic的原位置 arr[l] = basic; quick_part(arr,left,l-1); quick_part(arr,l+1,right); } function quickSort(arr){ quick_part(arr,0,arr.length-1); }
藍藍設計( www.syprn.cn )是一家專注而深入的界面設計公司,為期望卓越的國內外企業提供卓越的UI界面設計、BS界面設計 、 cs界面設計 、 ipad界面設計 、 包裝設計 、 圖標定制 、 用戶體驗 、交互設計、 網站建設 、平面設計服務
轉自:csdn 作者:tututu333
從上往下執行,非常簡單,不做過多贅述。
與c語言不同的是,java的if(布爾表達式)必須是布爾表達式
eg:判斷某一年是否是閏年
public static void main(String[] args) { Scanner scan = new Scanner(System.in); int year = scan.nextInt(); if((year%4==0 && year%100 !=10)||(year%400==0)) System.out.println("閏年!"); else{ System.out.println("不是閏年!"); } }
基礎語法:
switch(整數|枚舉|字符|字符串){ case 內容1 : { 內容滿足時執行語句; [break;] } case 內容2 : { 內容滿足時執行語句; [break;] } ... default:{ 內容都不滿足時執行語句; [break;] } }
面試問題:
不能做switch參數的類型有哪些?
long float double boolean
注意事項:
public static void main(String[] args) { int i=1; int ret=1; while(i<=5) { ret *= i; i++; } System.out.println(ret);
public static void main(String[] args) { Scanner scan = new Scanner(System.in); int num = scan.nextInt(); int sum=0; for(int j=1;j<=num;j++){ int ret=1; for(int i=1;i <= j; i++){ ret*=1; } sum+=ret; } } }
基本語法:
do{
循環語句;
}while(循環條件)
先執行語句再判斷循環條件。
注意事項:
方法就是一個代碼片段. 類似于 C 語言中的 "函數“。
方法:功能
public static 返回值 方法名(形式參數列表){
方法體;
}
方法名:要采用小駝峰的形式,maxNum
public static:因為當前所有的方法寫完之后會在Main方法中調用。
方法體:就是具體方法的實現。
public static void main(String[] args) { int a = 10; int b = 20; // 方法的調用 int ret = add(a, b); System.out.println("ret = " + ret); } // 方法的定義 public static int add(int x, int y) { return x + y; }
方法的重載:
1.方法名相同
2.返回值可以不同
3.參數列表不同(參數的個數和參數的類型不同)
4.必須要在同一個類當中。
eg:
public static void main(String[] args) { int a = 10; int b = 20; int ret = add(a, b); System.out.println("ret = " + ret); double a2 = 10.5; double b2 = 20.5; double ret2 = add(a2, b2); System.out.println("ret2 = " + ret2); double a3 = 10.5; double b3 = 10.5; double c3 = 20.5; double ret3 = add(a3, b3, c3); System.out.println("ret3 = " + ret3); } public static int add(int x, int y) { return x + y; } public static double add(double x, double y) { return x + y; } public static double add(double x, double y, double z) { return x + y + z; } }
方法的名字都叫 add. 但是有的 add 是計算 int 相加, 有的是 double 相加; 有的計算兩個數字相加, 有的是計算三個數字相加.
同一個方法名字, 提供不同版本的實現, 稱為方法重載
一個方法在執行過程中調用自身, 就稱為 “遞歸”。
遞歸相當于數學上的 “數學歸納法”, 有一個起始條件, 然后有一個遞推公式。
遞歸:
1.要調用自己本身。
2.要有一個趨近于終止的條件。
3.推導出遞歸的公式。
eg:求N的階乘
public static void main(String[] args) { int n = 5; int ret = factor(n); System.out.println("ret = " + ret); } public static int factor(int n) { if (n == 1) { return 1; } return n * factor(n - 1); // factor 調用函數自身 }
藍藍設計( www.syprn.cn )是一家專注而深入的界面設計公司,為期望卓越的國內外企業提供卓越的UI界面設計、BS界面設計 、 cs界面設計 、 ipad界面設計 、 包裝設計 、 圖標定制 、 用戶體驗 、交互設計、 網站建設 、平面設計服務
轉自:csdn 作者:flyyyya
在平時獲取后臺數據渲染頁面的時候可能會出現后臺返回的數據是帶有 html 特殊標簽的
需求是附帶的樣式也不要, 意思就是直接刪掉那些內容
但是在網上找挺久都沒有找到現成的方法 最后是自己找了兩個方法拼接出來的 所以在這里總結一下 方便以后直接 cv
// 返回數據: ret : { list: { "introduct": '<p style="color: lightcoral;">就 當文字就是內容吧。</p>', } } // 或者 ret : { list: { "introduct": '<span style="color: skyblue">就當文字就是內容吧。 </p>', } }
// 返回數據帶有html特殊字符的話直接用 v-html 在頁面上顯示的是 html標簽 <body> <div id="app"> <div class="fd"> {{message}} // 如果需要數據中的樣式的可以直接 v-html 指令渲染這個字段就能渲染出來了 <div class="box" v-html="'v-html: ' + message"></div> <button class="btn" @click="click1">dianwo</button> </div> </div> </body> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> var app = new Vue({ el: '#app', data: { message: '<p style="color: lightcoral;">就 當文字就是內容吧。</p>' }, methods: { click1() { this.message = this.escapeHtml(this.message) }, // 處理方法 escapeHtml(str) { var arrEntities = { 'lt': '<', 'gt': '>', 'nbsp': ' ', 'amp': '&', 'quot': '"' }; return str.replace(/&(lt|gt|nbsp|amp|quot);/ig, function(all, t) { return arrEntities[t]; }); }, } }) </script>
上面這個方法來自:https://blog.csdn.net/weixin_49186680/article/details/108746341
// 如果不想要后臺返回在數據終的樣式的話可以這樣處理 <body> <div id="app"> <div class="fd"> {{message}} <div class="box" v-html="'v-html: ' + message"></div> <button class="btn" @click="click1">dianwo</button> </div> </div> </body> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> var app = new Vue({ el: '#app', data: { message: '<span style="color: skyblue">就當文字就是內容吧。 </span>' }, methods: { click1() { this.message = this.delHtmlTag(this.message) }, // 處理方法 delHtmlTag(str) { return str.replace(/<[^>]+>/g, '').replace(/ /ig, "") } } }) </script>
上面的方法來自:https://blog.csdn.net/weixin_44565191/article/details/109716908
// 如果返回的帶有 html特殊字符 都不要 結合上面兩個方法 改吧改吧 就能瞞住要求了 <body> <div id="app"> <div class="fd"> {{message}} <div class="box" v-html="'v-html: ' + message"></div> <button class="btn" @click="click1">dianwo</button> </div> </div> </body> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> var app = new Vue({ el: '#app', data: { message: '<span style="color: skyblue">就當文字就是 內 容吧</span>' }, methods: { click1() { this.message = this.escapeHtml(this.message) }, // 終極 處理方法 escapeHtml(str) { var arrEntities = { 'lt': '<', 'gt': '>', 'amp': '&', 'quot': '"' }; let htmlTag = str.replace(/&(lt|gt|amp|quot);/ig, function(all, t) { return arrEntities[t]; }); // console.log(htmlTag); return htmlTag.replace(/<[^>]+>/g, '').replace(/ /ig, "") }, } }) </script>
感謝一下引用的這些大佬的內容
還有附上 樣式 要想親自試試效果的我把樣式放這里
.fd { margin: 100px auto; display: flex; flex-direction: column; justify-content: center; align-items: center; } .box { margin: 30px 0; display: flex; align-items: center; } .btn { width: 100px; }
藍藍設計( www.syprn.cn )是一家專注而深入的界面設計公司,為期望卓越的國內外企業提供卓越的UI界面設計、BS界面設計 、 cs界面設計 、 ipad界面設計 、 包裝設計 、 圖標定制 、 用戶體驗 、交互設計、 網站建設 、平面設計服務
轉自:csdn 作者:小王幾pl
不管哪種模式,前端路由都是客戶端路由的實現方式,也就是當路徑發生變化時,不會向服務器發送請求,是利用js監視路徑的變化。然后根據不同的地址渲染不同的內容,如果需要服務器內容,會發送Ajax請求來獲取。
https://music.163.com/#/discover/toplist
地址中會存在 # 號
https://music.163.com/discover/toplist
地址中沒有# 類似于普通的地址,但是需要服務端配置支持
在 node 環境下,啟用對history模式的支持可以通過 connect-history-api-fallback 這個中間件來完成
// 導入處理 history 模式的模塊 const history = require('connect-history-api-fallback') // 導入 express const express = require('express') const app = express() // 注冊處理 history 模式的中間件 app.use(history())
運行nginx服務器基本指令
啟動
start nginx
重啟
nginx -s reload
停止
nginx -s stop
location / { root html; index index.html index.htm; #新添加內容
#嘗試讀取$uri(當前請求的路徑),如果讀取不到讀取$uri/這個文件夾下的首頁
#如果都獲取不到返回根目錄中的 index.html
try_files $uri $uri/ /index.html; }
從上圖,可以大致了解一下 VueRouter 這個類中的結構:
上半部分是屬性,下半部分是方法,其中+ 是實例方法,- 是靜態方法。
install 是用來實現Vue.use 插件機制的方法。
要實現install方法,首先先分析一下該方法要做的事情:
let _Vue; export default class VueRouter { static install(Vue) { // 1. 判斷當前插件是否已經被安裝 if(VueRouter.install.installed) return VueRouter.install.installed = true // 2. 把Vue構造函數記錄到全局變量 _Vue = Vue // 3. 把創建Vue實例時候傳入的router對象注入到所有的Vue實例上 // 利用混入讓所有的vue實例加載router _Vue.mixin({ beforeCreate(){ // this.$options.name用來獲取vue實例 data以外的屬性 // new Vue( { router } ) if(this.$options.router) { _Vue.prototype.$router = this.$options.router } } }) } }
VueRouter 的構造函數要初始化三個屬性,分別是: options、data、routeMap。
constructor(options){ this.options = options this.data = _Vue.observable({ current:'/' }) this.routeMap = {} }
接下來我們來實現VueRouter類中 createRouterMap 這個方法,它的作用就是把 options 中rules 路由規則解析出來以鍵值對的形式存儲在routeMap上。
createRouterMap() { this.options.rules.forEach(route => this.routeMap[route.path] = route.component) }
下一步,來創建initComponents 方法,這個方法里我們要創建兩個組件。分別是:RouterLink 和 RouterView
let _Vue; export default class VueRouter { static install(Vue) { // 1. 判斷當前插件是否已經被安裝 if (VueRouter.install.installed) return VueRouter.install.installed = true // 2. 把Vue構造函數記錄到全局變量 _Vue = Vue // 3. 把創建Vue實例時候傳入的router對象注入到所有的Vue實例上 // 利用混入讓所有的vue實例加載router _Vue.mixin({ beforeCreate() { // this.$options.name用來獲取vue實例 data以外的屬性 // new Vue( { router } ) if (this.$options.router) { _Vue.prototype.$router = this.$options.router this.$options.router.init() } } }) } constructor(options) { this.options = options this.routeMap = {} this.data = _Vue.observable({ current: '/' }) } createRouterMap() { this.options.routes.forEach(route => this.routeMap[route.path] = route.component) } initComponents(Vue) { // 創建RouterLink組件 Vue.component('router-link', { props: { 'to': { type: String } }, template: `<a :href="to"><slot></slot></a>` }) } init() { this.createRouterMap() this.initComponents(_Vue) } }
用自己的VueRouter 替換掉官方的運行后,發現報錯
報錯的意思是,運行時版本的Vue 不支持 tempalte 模板,需要打包的時候提前編譯。
如果要讓我們的template被支持可以使用完整版的Vue,完整包包含運行時和編譯器,體積比運行時版本大10k左右,程序運行的時候把模板轉換成render函數
@vue/cli 自動安裝的就是 運行時版本
第一種方案——引入完整版Vue,可以在vue.config.js中 加入配置
module.exports = { runtimeCompiler: true }
第二種方案——使用render函數替換掉tempalte
render(h) { return h('a', { attrs: { href: this.to } }, [this.$slots.default]) } // template: `<a :href="to"><slot></slot></a>`
// 記錄一下this let self = this Vue.component('router-view',{ render(h){ // routeMap以key value形式記錄了path和component // data.current 記錄了當前頁面的path return h(self.routeMap[self.data.current]) } })
為了能夠讓鏈接成功完成跳轉展示組件,我們需要對routerlink中的a標簽添加點擊事件
并且要在點擊的時候,把最新的path更新到router實例的current上.
我們借助于history的pushState方法 該方法會修改瀏覽器地址欄中的地址,但不會向服務器發起請求,并且還可以將新地址記錄在歷史中
Vue.component('router-link', { props: { 'to': { type: String } }, render(h) { return h('a', { attrs: { href: this.to }, on: { click: this.clickHandle } }, [this.$slots.default]) }, methods: { clickHandle(e) { history.pushState({}, "", this.to) // 把點擊的鏈接地址 更新到 current 上 this.$router.data.current = this.to
e.preventDefault() } } // template: `<a :href="to"><slot></slot></a>` })
現在功能基本上已經差不多了,但是還存在一個小問題,就是當我們點擊瀏覽器的前進或者后退按鈕的時候,組件不能實現切換展示,主要思路就是通過添加popstate監聽地址變化,下面我們來完善該功能
initEvent(){ // window.addEventListener("popstate",()=>{ this.data.current = window.location.pathname }) }
完整代碼
let _Vue; export default class VueRouter { static install(Vue) { // 1. 判斷當前插件是否已經被安裝 if (VueRouter.install.installed) return VueRouter.install.installed = true // 2. 把Vue構造函數記錄到全局變量 _Vue = Vue // 3. 把創建Vue實例時候傳入的router對象注入到所有的Vue實例上 // 利用混入讓所有的vue實例加載router _Vue.mixin({ beforeCreate() { // this.$options.name用來獲取vue實例 data以外的屬性 // new Vue( { router } ) if (this.$options.router) { _Vue.prototype.$router = this.$options.router console.log(this.$options.router.init); this.$options.router.init() } } }) } constructor(options) { this.options = options this.routeMap = {} this.data = _Vue.observable({ current: '/' }) } createRouterMap() { this.options.routes.forEach(route => this.routeMap[route.path] = route.component) } initComponents(Vue) { // 創建RouterLink組件 Vue.component('router-link', { props: { 'to': { type: String } }, render(h) { return h('a', { attrs: { href: this.to }, on: { click: this.clickHandle } }, [this.$slots.default]) }, methods: { clickHandle(e) { history.pushState({}, "", this.to) // 把點擊的鏈接地址 更新到 current 上 this.$router.data.current = this.to
e.preventDefault() } } // template: `<a :href="to"><slot></slot></a>` }) let self = this Vue.component('router-view', { render(h) { // routeMap以key value形式記錄了path和component // data.current 記錄了當前頁面的path return h(self.routeMap[self.data.current]) } }) } init() { this.createRouterMap() this.initComponents(_Vue) this.initEvent() } initEvent() { // window.addEventListener("popstate", () => { this.data.current = window.location.pathname }) } }
轉自:csdn 作者:Holyforsaken_FHC
藍藍設計( www.syprn.cn )是一家專注而深入的界面設計公司,為期望卓越的國內外企業提供卓越的UI界面設計、BS界面設計 、 cs界面設計 、 ipad界面設計 、 包裝設計 、 圖標定制 、 用戶體驗 、交互設計、 網站建設 、平面設計服務
在談回調函數之前,先看下下面兩段代碼:
不妨猜測一下代碼的結果。
function say (value) {
alert(value);
} alert(say); alert(say('hi js.'));
如果你測試了,就會發現:
只寫變量名 say 返回的將會是 say方法本身,以字符串的形式表現出來。
而在變量名后加()如say()返回的就會使say方法調用后的結果,這里是彈出value的值。
再看下面的兩段代碼:
function say (value) { alert(value);
} function execute (someFunction, value) { someFunction(value);
}
execute(say, 'hi js.');
與
function execute (someFunction, value) { someFunction(value);
}
execute(function(value){alert(value);}, 'hi js.');
上面第一段代碼是將say方法作為參數傳遞給execute方法
第二段代碼則是直接將匿名函數作為參數傳遞給execute方法
實際上:
function say (value) { alert(value);
} // 注意看下面,直接寫say方法的方法名與下面的匿名函數可以認為是一個東西 // 這樣再看上面兩段代碼是不是對函數可以作為參數傳遞就更加清晰了 say; function (value) { alert(value);
}
這里的say或者匿名函數就被稱為回調函數。
如果回調函數需要傳參,如何做到,這里介紹兩種解決方案。
回調函數應用場景多用在使用 js 寫組件時,尤其是組件的事件很多都需要回調函數的支持。
轉自:csdn 作者:dkvirus
藍藍設計( www.syprn.cn )是一家專注而深入的界面設計公司,為期望卓越的國內外企業提供卓越的UI界面設計、BS界面設計 、 cs界面設計 、 ipad界面設計 、 包裝設計 、 圖標定制 、 用戶體驗 、交互設計、 網站建設 、平面設計服務
(1)在父組件的子組件標簽上綁定一個屬性,掛載要傳輸的變量
(2)在子組件中通過props來接受數據,props可以是數組也可以是對象,接受的數據可以直接使用 props: [“屬性 名”] props:{屬性名:數據類型}
代碼示例:
//父組件
<template>
<div>
<i>父組件</i>
<!--頁面使用-->
<son :data='name'></son>
</div>
</template>
<script>
import son from "./son.vue";//導入父組件
export default {
components: { son },//注冊組件
name: "父組件",
data() {
return {
name: "Frazier", //父組件定義變量
};
},
};
</script>
//子組件
<template>
<div>{{data}}</div>
</template>
<script>
export default {
components: { },
name: '子組件',
props:["data"],
};
</script>
(1)在父組件的子組件標簽上自定義一個事件,然后調用需要的方法
(2)在子組件的方法中通過 this.$emit(“事件”)來觸發在父組件中定義的事件,數據是以參數的形式進行傳遞的
代碼示例:
//父組件
<template>
<div>
<i>父組件</i>
<!--頁面使用-->
<son @lcclick="lcclick"></son>//自定義一個事件
</div>
</template>
<script>
import son from "./son.vue"; //導入父組件
export default {
components: { son }, //注冊組件
name: "父組件",
data() {
return {};
},
methods: {
lcclick(){
alert('子傳父')
}
},
};
</script>
//子組件
<template>
<div>
<button @click="lcalter">點我</button>
</div>
</template>
<script>
export default {
components: { },
name: '子組件',
methods: {
lcalter(){
this.$emit('lcclick')//通過emit來觸發事件
}
},
};
</script>
(1)在src中新建一個Bus.js的文件,然后導出一個空的vue實例
(2)在傳輸數據的一方引入Bus.js 然后通過Bus.e m i t ( “ 事 件 名 ” , " 參 數 " ) 來 來 派 發 事 件 , 數 據 是 以 emit(“事件名”,"參數")來來派發事件,數據是以emit(“事件名”,"參數")來來派發事件,數據是以emit()的參 數形式來傳遞
(3)在接受的數據的一方 引入 Bus.js 然后通過 Bus.$on(“事件名”,(data)=>{data是接受的數據})
圖片示例:
(1)ref 如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子組件上,引用就指向組件實例,
(2)可以通過實例直接調用組件的方法或訪問數據。也算是子組件向父組件傳值的一種
代碼示例:
//父組件
<template>
<div>
<button @click="sayHello">sayHello</button>
<child ref="childForRef"></child>
</div>
</template>
<script>
import child from './child.vue'
export default {
components: { child },
data () {
return {
childForRef: null,
}
},
mounted() {
this.childForRef = this.$refs.childForRef;
console.log(this.childForRef.name);
},
methods: {
sayHello() {
this.childForRef.sayHello()
}
}
}
</script>
//子組件
<template>
<div>child 的內容</div>
</template>
<script>
export default {
data () {
return {
name: '我是 child',
}
},
methods: {
sayHello () {
console.log('hello');
alert('hello');
}
}
}
</script>
組件通過 dispatch 到 actions,actions 是異步操作,再 actions中通過 commit 到 mutations,mutations 再通過邏輯操作改變 state,從而同步到組件,更新其數據狀態
代碼示例:
//父組件
template>
<div id="app">
<ChildA/>
<ChildB/>
</div>
</template>
<script>
import ChildA from './ChildA' // 導入A組件
import ChildB from './ChildB' // 導入B組件
export default {
components: {ChildA, ChildB} // 注冊組件
}
</script>
//子組件A
<template>
<div id="childA">
<h1>我是A組件</h1>
<button @click="transform">點我讓B組件接收到數據</button>
<p>因為點了B,所以信息發生了變化:{{BMessage}}</p>
</div>
</template>
<script>
export default {
data() {
return {
AMessage: 'Hello,B組件,我是A組件'
}
},
computed: {
BMessage() {
// 這里存儲從store里獲取的B組件的數據
return this.$store.state.BMsg
}
},
methods: {
transform() {
// 觸發receiveAMsg,將A組件的數據存放到store里去
this.$store.commit('receiveAMsg', {
AMsg: this.AMessage
})
}
}
}
</script>
//子組件B
<template>
<div id="childB">
<h1>我是B組件</h1>
<button @click="transform">點我讓A組件接收到數據</button>
<p>點了A,我的信息發生了變化:{{AMessage}}</p>
</div>
</template>
<script>
export default {
data() {
return {
BMessage: 'Hello,A組件,我是B組件'
}
},
computed: {
AMessage() {
// 這里存儲從store里獲取的A組件的數據
return this.$store.state.AMsg
}
},
methods: {
transform() {
// 觸發receiveBMsg,將B組件的數據存放到store里去
this.$store.commit('receiveBMsg', {
BMsg: this.BMessage
})
}
}
}
</script>
//vuex
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const state = {
AMsg: '',
BMsg: ''
}
const mutations = {
receiveAMsg(state, payload) {
// 將A組件的數據存放于state
state.AMsg = payload.AMsg
},
receiveBMsg(state, payload) {
// 將B組件的數據存放于state
state.BMsg = payload.BMsg
}
}
export default new Vuex.Store({
state,
mutations
})
通過parent可以獲父組件實例 ,然 后通過這個實例就可以訪問父組件的屬 性和方法 ,它還有一個兄弟parent可以獲父組件實例,然后通過這個實例就可以訪問父組件的屬性和方法,它還有一個兄弟parent可以獲父組件實例,然后通過這個實例就可以訪問父組件的屬性和方法,它還有一個兄弟root,可以獲取根組件實例。
代碼示例:
// 獲父組件的數據
this.$parent.foo
// 寫入父組件的數據
this.$parent.foo = 2
// 訪問父組件的計算屬性
this.$parent.bar
// 調用父組件的方法
this.$parent.baz()
//在子組件傳給父組件例子中,可以使用this.$parent.getNum(100)傳值給父組件。
sessionStorage 是瀏覽器的全局對象,存在它里面的數據會在頁面關閉時清除 。運用這個特性,我們可以在所有頁面共享一份數據。
代碼示例:
// 保存數據到 sessionStorage
sessionStorage.setItem('key', 'value');
// 從 sessionStorage 獲取數據
let data = sessionStorage.getItem('key');
// 從 sessionStorage 刪除保存的數據
sessionStorage.removeItem('key');
// 從 sessionStorage 刪除所有保存的數據
sessionStorage.clear();
注意:里面存的是鍵值對,只能是字符串類型,如果要存對象的話,需要使用 let objStr = JSON.stringify(obj) 轉成字符串然后再存儲(使用的時候 let obj = JSON.parse(objStr) 解析為對象)。
推薦一個庫 good-storage ,它封裝了sessionStorage ,可以直接用它的API存對象
//localStorage
storage.set(key,val)
storage.get(key, def)
//sessionStorage
storage.session.set(key, val)
storage.session.get(key, val)
使用問號傳值
A頁面跳轉B頁面時使用 this.r o u t e r . p u s h ( ’ / B ? n a m e = d a n s e e k ’ ) B 頁 面 可 以 使 用 t h i s . router.push(’/B?name=danseek’) B頁面可以使用 this.router.push(’/B?name=danseek’)B頁面可以使用this.route.query.name 來獲取A頁面傳過來的值
上面要注意router和route的區別
使用冒號傳值
配置如下路由:
{
path: '/b/:name',
name: 'b',
component: () => import( '../views/B.vue')
},
在B頁面可以通過 this.$route.params.name 來獲取路由傳入的name的值
使用父子組件傳值
由于router-view本身也是一個組件,所以我們也可以使用父子組件傳值方式傳值,然后在對應的子頁面里加上props,因為type更新后沒有刷新路由,所以不能直接在子頁面的mounted鉤子里直接獲取最新type的值,而要使用watch
<router-view :type="type"></router-view>
// 子頁面
props: ['type']
watch: {
type(){
// console.log("在這個方法可以時刻獲取最新的數據:type=",this.type)
},
},
正常情況下需要借助父親的props作為中間過渡,但是這樣在父親組件就會多了一些跟父組件業務無關的屬性,耦合度高,借助$attrs可以簡化些,而且祖跟孫都無需做修改
祖組件:
<template>
<section>
<parent name="grandParent" sex="男" age="88" hobby="code" @sayKnow="sayKnow"></parent>
</section>
</template>
<script>
import Parent from './Parent'
export default {
name: "GrandParent",
components: {
Parent
},
data() {
return {}
},
methods: {
sayKnow(val){
console.log(val)
}
},
mounted() {
}
}
</script>
父組件
template>
<section>
<p>父組件收到</p>
<p>祖父的名字:{{name}}</p>
<children v-bind="$attrs" v-on="$listeners"></children>
</section>
</template>
<script>
import Children from './Children'
export default {
name: "Parent",
components: {
Children
},
// 父組件接收了name,所以name值是不會傳到子組件的
props:['name'],
data() {
return {}
},
methods: {},
mounted() {
}
}
</script>
子組件
<template>
<section>
<p>子組件收到</p>
<p>祖父的名字:{{name}}</p>
<p>祖父的性別:{{sex}}</p>
<p>祖父的年齡:{{age}}</p>
<p>祖父的愛好:{{hobby}}</p>
<button @click="sayKnow">我知道啦</button>
</section>
</template>
<script>
export default {
name: "Children",
components: {},
// 由于父組件已經接收了name屬性,所以name不會傳到子組件了
props:['sex','age','hobby','name'],
data() {
return {}
},
methods: {
sayKnow(){
this.$emit('sayKnow','我知道啦')
}
},
mounted() {
}
}
</script>
文字內容同第九個
祖組件
<template>
<div id="app">
<children-one @eventOne="eventOne"></children-one>
{{ msg }}
</div>
</template>
<script>
import ChildrenOne from '../src/components/children.vue'
export default {
name: 'App',
components: {
ChildrenOne,
},
data() {
return {
msg: ''
}
},
methods: {
eventOne(value) {
this.msg = value
}
}
}
</script>
父組件
<template>
<div>
<children-two v-on="$listeners"></children-two>
</div>
</template>
<script>
import ChildrenTwo from './childrenTwo.vue'
export default {
name: 'childrenOne',
components: {
ChildrenTwo
}
}
</script>
子組件
<template>
<div>
<button @click="setMsg">點擊傳給祖父</button>
</div>
</template>
<script>
export default {
name: 'children',
methods: {
setMsg() {
this.$emit('eventOne', '123')
}
}
}
</script>
promise 中 resolve 如何傳遞多個參數
//類似與這樣使用,但實際上后面兩個參數無法獲取
promise = new Promise((resolve,reject)=>{
let a = 1
let b = 2
let c = 3
resolve(a,b,c)
})
promise.then((a,b,c)=>{
console.log(a,b,c)
})
resolve() 只能接受并處理一個參數,多余的參數會被忽略掉。
如果想多個用數組,或者對象方式。。
數組
promise = new Promise((resolve,reject)=>{
resolve([1,2,3])
})
promise.then((arr)=>{
console.log(arr[0],arr[1],arr[2])
})
對象
promise = new Promise((resolve,reject)=>{
resolve({a:1,b:2,c:3})
})
promise.then(obj=>{
console.log(obj.a,obj.b,obj.c)
})
定義一個全局變量,在有值的組件直接賦值,在需要的組件內直接使用就可以了。
轉自:csdn 作者:Frazier_梁超
藍藍設計( www.syprn.cn )是一家專注而深入的界面設計公司,為期望卓越的國內外企業提供卓越的UI界面設計、BS界面設計 、 cs界面設計 、 ipad界面設計 、 包裝設計 、 圖標定制 、 用戶體驗 、交互設計、 網站建設 、平面設計服務
最近幾年,如果你是一名前端開發者,如果你沒有使用甚至聽說過 babel,可能會被當做穿越者吧?
說到 babel,一連串名詞會蹦出來:
這些都是 babel 嗎?他們分別是做什么的?有區別嗎?
簡單來說把 JavaScript 中 es2015/2016/2017/2046 的新語法轉化為 es5,讓低端運行環境(如瀏覽器和 node )能夠認識并執行。本文以 babel 6.x 為基準進行討論。最近 babel 出了 7.x,放在最后聊。
嚴格來說,babel 也可以轉化為更低的規范。但以目前情況來說,es5 規范已經足以覆蓋絕大部分瀏覽器,因此常規來說轉到 es5 是一個安全且流行的做法。
如果你對 es5/es2015 等等也不了解的話,那你可能真的需要先補補課了。
總共存在三種方式:
其中后面兩種比較常見。第二種多見于 package.json 中的 scripts
段落中的某條命令;第三種就直接集成到構建工具中。
這三種方式只有入口不同而已,調用的 babel 內核,處理方式都是一樣的,所以我們先不糾結入口的問題。
babel 總共分為三個階段:解析,轉換,生成。
babel 本身不具有任何轉化功能,它把轉化的功能都分解到一個個 plugin 里面。因此當我們不配置任何插件時,經過 babel 的代碼和輸入是相同的。
插件總共分為兩種:
舉個簡單的例子,當我們定義或者調用方法時,最后一個參數之后是不允許增加逗號的,如 callFoo(param1, param2,)
就是非法的。如果源碼是這種寫法,經過 babel 之后就會提示語法錯誤。
但最近的 JS 提案中已經允許了這種新的寫法(讓代碼 diff 更加清晰)。為了避免 babel 報錯,就需要增加語法插件 babel-plugin-syntax-trailing-function-commas
比起語法插件,轉譯插件其實更好理解,比如箭頭函數 (a) => a
就會轉化為 function (a) {return a}
。完成這個工作的插件叫做 babel-plugin-transform-es2015-arrow-functions
。
同一類語法可能同時存在語法插件版本和轉譯插件版本。如果我們使用了轉譯插件,就不用再使用語法插件了。
既然插件是 babel 的根本,那如何使用呢?總共分為 2 個步驟:
babel
里面,格式相同)
npm install babel-plugin-xxx
進行安裝
具體書寫格式就不詳述了。
比如 es2015 是一套規范,包含大概十幾二十個轉譯插件。如果每次要開發者一個個添加并安裝,配置文件很長不說,npm install
的時間也會很長,更不談我們可能還要同時使用其他規范呢。
為了解決這個問題,babel 還提供了一組插件的集合。因為常用,所以不必重復定義 & 安裝。(單點和套餐的差別,套餐省下了巨多的時間和配置的精力)
preset 分為以下幾種:
例如 syntax-dynamic-import
就是 stage-2 的內容,transform-object-rest-spread
就是 stage-3 的內容。
此外,低一級的 stage 會包含所有高級 stage 的內容,例如 stage-1 會包含 stage-2, stage-3 的所有內容。
stage-4 在下一年更新會直接放到 env 中,所以沒有單獨的 stage-4 可供使用。
arrow-functions
,es2017 包含 syntax-trailing-function-commas
。但因為 env 的出現,使得 es2016 和 es2017 都已經廢棄。所以我們經常可以看到 es2015 被單獨列出來,但極少看到其他兩個。很簡單的幾條原則:
preset 的逆向順序主要是為了保證向后兼容,因為大多數用戶的編寫順序是 ['es2015', 'stage-0']
。這樣必須先執行 stage-0
才能確保 babel 不報錯。因此我們編排 preset 的時候,也要注意順序,其實只要按照規范的時間順序列出即可。
簡略情況下,插件和 preset 只要列出字符串格式的名字即可。但如果某個 preset 或者插件需要一些配置項(或者說參數),就需要把自己先變成數組。第一個元素依然是字符串,表示自己的名字;第二個元素是一個對象,即配置對象。
最需要配置的當屬 env,如下:
"presets": [ // 帶了配置項,自己變成數組 [ // 第一個元素依然是名字 "env", // 第二個元素是對象,列出配置項 { "module": false } ], // 不帶配置項,直接列出名字 "stage-2" ]
因為 env 最為常用也最重要,所以我們有必要重點關注。
env 的核心目的是通過配置得知目標環境的特點,然后只做必要的轉換。例如目標瀏覽器支持 es2015,那么 es2015 這個 preset 其實是不需要的,于是代碼就可以小一點(一般轉化后的代碼總是更長),構建時間也可以縮短一些。
如果不寫任何配置項,env 等價于 latest,也等價于 es2015 + es2016 + es2017 三個相加(不包含 stage-x 中的插件)。env 包含的插件列表維護在這里
下面列出幾種比較常用的配置方法:
{ "presets": [ ["env", { "targets": { "browsers": ["last 2 versions", "safari >= 7"] } }] ] }
如上配置將考慮所有瀏覽器的最新2個版本(safari大于等于7.0的版本)的特性,將必要的代碼進行轉換。而這些版本已有的功能就不進行轉化了。這里的語法可以參考 browserslist
{ "presets": [ ["env", { "targets": { "node": "6.10" } }] ] }
如上配置將目標設置為 nodejs,并且支持 6.10 及以上的版本。也可以使用 node: 'current'
來支持最新穩定版本。例如箭頭函數在 nodejs 6 及以上將不被轉化,但如果是 nodejs 0.12 就會被轉化了。
另外一個有用的配置項是 modules
。它的取值可以是 amd
, umd
, systemjs
, commonjs
和 false
。這可以讓 babel 以特定的模塊化格式來輸出代碼。如果選擇 false
就不進行模塊化處理。
以上討論了 babel 的核心處理機制和配置方法等,不論任何入口調用 babel 都走這一套。但文章開頭提的那一堆 babel-*
還是讓人一頭霧水。實際上這些 babel-*
大多是不同的入口(方式)來使用 babel,下面來簡單介紹一下。
顧名思義,cli 就是命令行工具。安裝了 babel-cli
就能夠在命令行中使用 babel
命令來編譯文件。
在開發 npm package 時經常會使用如下模式:
babel-cli
安裝為 devDependencies
scripts
(比如 prepublish
),使用 babel
命令編譯文件
npm publish
這樣既可以使用較新規范的 JS 語法編寫源碼,同時又能支持舊版環境。因為項目可能不太大,用不到構建工具 (webpack 或者 rollup),于是在發布之前用 babel-cli
進行處理。
babel-node
是 babel-cli
的一部分,它不需要單獨安裝。
它的作用是在 node 環境中,直接運行 es2015 的代碼,而不需要額外進行轉碼。例如我們有一個 js 文件以 es2015 的語法進行編寫(如使用了箭頭函數)。我們可以直接使用 babel-node es2015.js
進行執行,而不用再進行轉碼了。
可以說:babel-node
= babel-polyfill
+ babel-register
。那這兩位又是誰呢?
babel-register 模塊改寫 require
命令,為它加上一個鉤子。此后,每當使用 require
加載 .js
、.jsx
、.es
和 .es6
后綴名的文件,就會先用 babel 進行轉碼。
使用時,必須首先加載 require('babel-register')
。
需要注意的是,babel-register 只會對 require
命令加載的文件轉碼,而 不會對當前文件轉碼。
另外,由于它是實時轉碼,所以 只適合在開發環境使用。
babel 默認只轉換 js 語法,而不轉換新的 API,比如 Iterator、Generator、Set、Maps、Proxy、Reflect、Symbol、Promise 等全局對象,以及一些定義在全局對象上的方法(比如 Object.assign
)都不會轉碼。
舉例來說,es2015 在 Array 對象上新增了 Array.from
方法。babel 就不會轉碼這個方法。如果想讓這個方法運行,必須使用 babel-polyfill
。(內部集成了 core-js
和 regenerator
)
使用時,在所有代碼運行之前增加 require('babel-polyfill')
?;蛘吒R幍牟僮魇窃?nbsp;webpack.config.js
中將 babel-polyfill
作為第一個 entry。因此必須把 babel-polyfill
作為 dependencies
而不是 devDependencies
babel-polyfill
主要有兩個缺點:
babel-polyfill
會導致打出來的包非常大,因為 babel-polyfill
是一個整體,把所有方法都加到原型鏈上。比如我們只使用了 Array.from
,但它把 Object.defineProperty
也給加上了,這就是一種浪費了。這個問題可以通過單獨使用 core-js
的某個類庫來解決,core-js
都是分開的。babel-polyfill
會污染全局變量,給很多類的原型鏈上都作了修改,如果我們開發的也是一個類庫供其他開發者使用,這種情況就會變得非常不可控。
因此在實際使用中,如果我們無法忍受這兩個缺點(尤其是第二個),通常我們會傾向于使用 babel-plugin-transform-runtime
。
但如果代碼中包含高版本 js 中類型的實例方法 (例如 [1,2,3].includes(1)
),這還是要使用 polyfill。
我們時常在項目中看到 .babelrc 中使用 babel-plugin-transform-runtime
,而 package.json
中的 dependencies
(注意不是 devDependencies
) 又包含了 babel-runtime
,那這兩個是不是成套使用的呢?他們又起什么作用呢?
先說 babel-plugin-transform-runtime
。
babel 會轉換 js 語法,之前已經提過了。以 async/await
舉例,如果不使用這個 plugin (即默認情況),轉換后的代碼大概是:
// babel 添加一個方法,把 async 轉化為 generator function _asyncToGenerator(fn) { return function () {....}} // 很長很長一段 // 具體使用處 var _ref = _asyncToGenerator(function* (arg1, arg2) { yield (0, something)(arg1, arg2); });
不用過于糾結具體的語法,只需看到,這個 _asyncToGenerator
在當前文件被定義,然后被使用了,以替換源代碼的 await
。但每個被轉化的文件都會插入一段 _asyncToGenerator
這就導致重復和浪費了。
在使用了 babel-plugin-transform-runtime
了之后,轉化后的代碼會變成
// 從直接定義改為引用,這樣就不會重復定義了。 var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); // 具體使用處是一樣的 var _ref = _asyncToGenerator3(function* (arg1, arg2) { yield (0, something)(arg1, arg2); });
從定義方法改成引用,那重復定義就變成了重復引用,就不存在代碼重復的問題了。
但在這里,我們也發現 babel-runtime
出場了,它就是這些方法的集合處,也因此,在使用 babel-plugin-transform-runtime
的時候必須把 babel-runtime
當做依賴。
再說 babel-runtime
,它內部集成了
core-js
: 轉換一些內置類 (Promise
, Symbols
等等) 和靜態方法 (Array.from
等)。絕大部分轉換是這里做的。自動引入。regenerator
: 作為 core-js
的拾遺補漏,主要是 generator/yield
和 async/await
兩組的支持。當代碼中有使用 generators/async
時自動引入。asyncToGenerator
就是其中之一,其他還有如 jsx
, classCallCheck
等等,可以查看 babel-helpers。在代碼中有內置的 helpers 使用時(如上面的第一段代碼)移除定義,并插入引用(于是就變成了第二段代碼)。
babel-plugin-transform-runtime
不支持 實例方法 (例如 [1,2,3].includes(1)
)
此外補充一點,把 helpers 抽離并統一起來,避免重復代碼的工作還有一個 plugin 也能做,叫做 babel-plugin-external-helpers
。但因為我們使用的 transform-runtime
已經包含了這個功能,因此不必重復使用。而且 babel 的作者們也已經開始討論這兩個插件過于類似,正在討論在 babel 7 中把 external-helpers
刪除,討論在 issue#5699 中。
前面提過 babel 的三種使用方法,并且已經介紹過了 babel-cli
。但一些大型的項目都會有構建工具 (如 webpack 或 rollup) 來進行代碼構建和壓縮 (uglify)。理論上來說,我們也可以對壓縮后的代碼進行 babel 處理,但那會非常慢。因此如果在 uglify 之前就加入 babel 處理,豈不完美?
所以就有了 babel 插入到構建工具內部這樣的需求。以(我還算熟悉的) webpack 為例,webpack 有 loader 的概念,因此就出現了 babel-loader
。
和 babel-cli
一樣,babel-loader
也會讀取 .babelrc 或者 package.json 中的 babel
段作為自己的配置,之后的內核處理也是相同。唯一比 babel-cli
復雜的是,它需要和 webpack 交互,因此需要在 webpack 這邊進行配置。比較常見的如下:
module: { rules: [ { test: /\.js$/, exclude: /(node_modules|bower_components)/, loader: 'babel-loader' } ] }
如果想在這里傳入 babel 的配置項,也可以把改成:
// loader: 'babel-loader' 改成如下: use: { loader: 'babel-loader', options: { // 配置項在這里 } }
這里的配置項優先級是最高的。但我認為放到單獨的配置文件中更加清晰合理,可讀性強一些。
最近 babel 發布了 7.0。因為上面部分都是針對 6.x 編寫的,所以我們關注一下 7.0 帶來的變化(核心機制方面沒有變化,插件,preset,解析轉譯生成這些都沒有變化)
我只挑選一些和開發者關系比較大的列在這里,省略的多數是針對某一個 plugin 的改動。完整的列表可以參考官網。
淘汰 es201x 的目的是把選擇環境的工作交給 env 自動進行,而不需要開發者投入精力。凡是使用 es201x 的開發者,都應當使用 env 進行替換。但這里的淘汰 (原文 deprecated) 并不是刪除,只是不推薦使用了,不好說 babel 8 就真的刪了。
與之相比,stage-x 就沒那么好運了,它們直接被刪了。這是因為 babel 團隊認為為這些 “不穩定的草案” 花費精力去更新 preset 相當浪費。stage-x 雖然刪除了,但它包含的插件并沒有刪除(只是被更名了,可以看下面一節),我們依然可以顯式地聲明這些插件來獲得等價的效果。完整列表
為了減少開發者替換配置文件的機械工作,babel 開發了一款 babel-upgrade
的工具,它會檢測 babel 配置中的 stage-x 并且替換成對應的 plugins。除此之外它還有其他功能,我們一會兒再詳細看。(總之目的就是讓你更加平滑地遷移到 babel 7)
這是 babel 7 的一個重大變化,把所有 babel-*
重命名為 @babel/*
,例如:
babel-cli
變成了 @babel/cli
。
babel-preset-env
變成了 @babel/preset-env
。進一步,還可以省略 preset
而簡寫為 @babel/env
。
babel-plugin-transform-arrow-functions
變成了 @babel/plugin-transform-arrow-functions
。和 preset
一樣,plugin
也可以省略,于是簡寫為 @babel/transform-arrow-functions
。
這個變化不單單應用于 package.json 的依賴中,包括 .babelrc 的配置 (plugins
, presets
) 也要這么寫,為了保持一致。例如
{
"presets": [ - "env" + "@babel/preset-env" ]
}
順帶提一句,上面提過的 babel 解析語法的內核 babylon
現在重命名為 @babel/parser
,看起來是被收編了。
上文提過的 stage-x 被刪除了,它包含的插件雖然保留,但也被重命名了。babel 團隊希望更明顯地區分已經位于規范中的插件 (如 es2015 的 babel-plugin-transform-arrow-functions
) 和僅僅位于草案中的插件 (如 stage-0 的 @babel/plugin-proposal-function-bind
)。方式就是在名字中增加 proposal
,所有包含在 stage-x 的轉譯插件都使用了這個前綴,語法插件不在其列。
最后,如果插件名稱中包含了規范名稱 (-es2015-
, -es3-
之類的),一律刪除。例如 babel-plugin-transform-es2015-classes
變成了 @babel/plugin-transform-classes
。(這個插件我自己沒有單獨用過,慚愧)
babel 7.0 開始不再支持 nodejs 0.10, 0.12, 4, 5 這四個版本,相當于要求 nodejs >= 6 (當前 nodejs LTS 是 8,要求也不算太過分吧)。
這里的不再支持,指的是在這些低版本 node 環境中不能使用 babel 轉譯代碼,但 babel 轉譯后的代碼依然能在這些環境上運行,這點不要混淆。
在 babel 6 時,ignore
選項如果包含 *.foo.js
,實際上的含義 (轉化為 glob) 是 ./**/*.foo.js
,也就是當前目錄 包括子目錄 的所有 foo.js
結尾的文件。這可能和開發者常規的認識有悖。
于是在 babel 7,相同的表達式 *.foo.js
只作用于當前目錄,不作用于子目錄。如果依然想作用于子目錄的,就要按照 glob 的完整規范書寫為 ./**/*.foo.js
才可以。only
也是相同。
這個規則變化只作用于通配符,不作用于路徑。所以 node_modules
依然包含所有它的子目錄,而不單單只有一層。(否則全世界開發者都要爆炸)
和 babel 6 不同,如果要使用 @babel/node
,就必須單獨安裝,并添加到依賴中。
在提到刪除 stage-x 時候提過這個工具,它的目的是幫助用戶自動化地從 babel 6 升級到 7。
這款升級工具的功能包括:(這里并不列出完整列表,只列出比較重要和常用的內容)
babel-*
替換為 @babel/*
@babel/*
依賴的版本更新為最新版 (例如 ^7.0.0
)
scripts
中有使用 babel-node
,自動添加 @babel/node
為開發依賴
babel
配置項,檢查其中的 plugins
和 presets
,把短名 (env
) 替換為完整的名字 (@babel/preset-env
)plugins
和 presets
,把短名 (env
) 替換為完整的名字 (@babel/preset-env
)
preset-stage-x
,如有替換為對應的插件并添加到 plugins
使用方式如下:
# 不安裝到本地而是直接運行命令,npm 的新功能 npx babel-upgrade --write # 或者常規方式 npm i babel-upgrade -g
babel-upgrade --write
babel-upgrade
工具本身也還在開發中,還列出了許多 TODO 沒有完成,因此之后的功能可能會更加豐富,例如上面提過的 ignore
的通配符轉化等等。
轉自:知乎。作者:前端解憂雜貨鋪
藍藍設計( www.syprn.cn )是一家專注而深入的界面設計公司,為期望卓越的國內外企業提供卓越的UI界面設計、BS界面設計 、 cs界面設計 、 ipad界面設計 、 包裝設計 、 圖標定制 、 用戶體驗 、交互設計、 網站建設 、平面設計服務
藍藍設計的小編 http://www.syprn.cn