JavaScript進階之高階函數篇

来源:https://www.cnblogs.com/MyYTT/archive/2020/03/21/12539268.html
-Advertisement-
Play Games

JavaScript進階之高階函數篇 簡介:歡迎大家來到woo爺說前端;今天給你們帶來的是JavaScript進階的知識,接下來的系列都是圍繞著JavaScript進階進行闡述;首先我們第一篇講的是高階函數。 高階函數定義:高階函數是指操作函數的函數;一般情況在項目開發過程中都會分兩種情況 函數可以 ...


JavaScript進階之高階函數篇

   簡介:歡迎大家來到woo爺說前端;今天給你們帶來的是JavaScript進階的知識,接下來的系列都是圍繞著JavaScript進階進行闡述;首先我們第一篇講的是高階函數。

  高階函數定義:高階函數是指操作函數的函數;一般情況在項目開發過程中都會分兩種情況  

  1.   函數可以作為參數傳遞到另外一個函數執行
  2.   函數可以作為返回值輸出被執行

 讓我們來用一張圖描述一下高階函數

    

 

    以上是高階函數的要求。我們在開發項目使用到的JavaScript的函數明顯滿足高階函數的要求;因此我們再寫代碼過程中可以利用高階函數對基本函數已經業務邏輯進行再次封裝,或者作為回調函數。接下來我們開始見識一下平時業務中怎麼使用高階函數;如何去封裝高階函數?、

    第一種模式:作為參數傳遞

    在業務代碼中經常遇到兩個基本函數邏輯相同但業務邏輯不用的情況下;我們可以把這兩個相同的邏輯封裝成一個高階函數,從而把不同的邏輯寫在函數裡面作為參數傳遞到封裝好的函數裡面。這樣可以實現業務邏輯一些變化一些不變的場景,這種是我們最常見的場景;簡稱為回調函數。

   接下來讓我們來舉例子說明一下

    例子1:  

 1 //兩個不同的函數,但是其中一部分邏輯是相同的一部分是可變的
 2 function a(){
 3   console.log("我是一個函數");
 4   console.log("我是a函數");        
 5 }
 6 
 7 
 8 function b(){
 9   console.log("我是一個函數");
10   console.log("我是b函數")    
11 }
12 
13 /*
14 以上就是我們兩個基本得函數,我們分別執行這兩個函數
15 */
16 
17 a();
18 b()

  這就是我們上面執行的結果,可以發現兩個函數存在著相同點。那麼我們接下來對兩個函數進行下一步的處理

function c(fn){
 console.log("我是一個函數")
  fn()  
}

//把相同的邏輯封裝成一個c函數,不同邏輯的作為fn參數函數傳遞進去執行

c(function(){
   console.log("我是a函數") 
})

c(function(){
   console.log("我是b函數") 
})

//由此可見我們實現我們想要的呈現方式,接下來我們看一下執行的結果是怎麼樣的

   這是我們最後執行的結果,跟上面的執行結果是一樣的,可見高階函數可以讓代碼更加多變性,更加簡潔明瞭、易懂;這是我們平時常見的場景。

    例子2:

      其實我們還有一種場景更加經常在項目裡面遇到。我們經常會在項目中使用ajax請求或者使用axios請求等等一些非同步請求;一般往往我們不關心請求過程(請求過程是相同的)、只想要請求的結果處理不同業務邏輯。我們可以利用高階函數對請求統一封裝,也叫請求攔截

var httpsAjax = function(obj,callback){
    var {url,data,type} = obj;
    $.ajax({
        url:url,
        type:type || 'POST' ,
        data:dara || {},
        success:function(res){
          //利用typeof 判斷數據類型,如果是傳進來的是函數,我們就執行回調函數
          if(typeof callback === 'function'){
             callback(res)
          }
        }   
   })
}

    httpsAjax({
        url:"xxx/get/user",
        type:"GET",
        data:{}
    },function(res){
      //操作一定的業務邏輯
      console.log(res)
   })

    第一種模式總結:以上就是我們最常見的基本高階函數的使用,一般我們會用函數作為參數傳遞到另外一個參數裡面,然後另外一個參數執行傳遞進去的函數,從而形成了回調函數(高階函數)

    第二種模式:作為返回值輸出

     相比把函數當作參數傳遞,函數當作返回值輸出的應用場景也有很多。讓函數繼續返回一個可執行的函數,這樣意味著運算過程是可延續的,就比如我們經常用到數組排序Array.sort()方法

     下麵是使用Object.prototype.toString方法判斷數據類型一系列的isType函數例子:

var isString = function(obj){
  return object.prototype.toString.call(obj) === '[object String]'  
}


var isArray = function(obj){
  return object.prototype.toString.call(obj) === '[object Array]' 
}

var isNumber = function(obj){
  rturn object.prototype.toString.call(obj) === '[object Number]'  
}


isString("我是一個數組串");//true
isArray(["1","2"]);//true
isNumber(1)//true

    註意:其實我們會發現上面的三個方法有大部分相同的邏輯object.prototype.toString.call(obj),不同的是處理邏輯返回的字元串結果,為了避免冗餘的代碼,我們將其封裝成一個函數is_type();

var is_type = function(obj,type){
  return object.prototype.toString.call(obj) == '[object ' + type + ']'  
}

console.log(is_type(11,'Number'));//true
console.log(is_type(['a','b'],"Array");//true

    註意:上面就是我們進行封裝的方法,可以發現我們提取出來之後,代碼量少了很多,更加明確,但是我們會發現我們需要傳遞兩個參數,而且兩個參數的含義要一一對上,不然我們在業務代碼上一旦寫錯沒對應上,那我們寫的邏輯就會出現bug。所以我們將這個方法再次封裝一下,把type先區分類型作為參數傳遞進去,利用高階函數擁有保存變數的作用,返回一個函數,分析我們傳遞的obj時到底是什麼類型。

var isType = function(type){
    //先傳遞一個type參數作為數據類型,利用高階函數擁有保存變數的特性,返回一個可持續執行的函數
    return function(obj){
          return object.prototype.toStirng.call(obj) === '[object '+ type +']'      
    }
}

//先細分到每種類型,這樣我們就可以明確知道具體類型調用什麼方法

var isString = isType("String");

var isArray = isType("Array");

var isNumber = isType("Number");

console.log(isArray([1,2,3]))//true

    第二種模式總結:以上就是我們第二種模式的例子。顯然而見我們可以利用這種模式讓一個函數返回另外一個函數,讓函數的運算繼續延續下去,這就是第二種模式作為返回值輸出

     以上就是高階函數兩種模式的基本理解與演示。相信對你在平時開發項目中有很多幫助;同時也要註意平時開發項目不是每個方法都要使用高階函數,高階函數使用場景就是以上兩種。不然會導致大材小用。接下來我們來講一下JavaScript經常用到的高階函數map()/reduce()、filter()、sort()四個方法。

  1. map()方法
    1. 定義:map()方法遍歷原有數組返回一個新數組,數組中的元素為原始數組元素調用函數處理後的值,按照原始數組元素順序依次處理元素。
    2. 註意:不會對空數組進行檢測、返回的是新數組不會改變原始的數組
    3. 語法格式 :
 newArray.map(function(item){ 
    //遍歷newArray item是newArray元素;類似於newArray[i]
  return item
  //必須有return,反正不會產生新的數組
})

 

    map()方法定義在JavaScript得Array中,我們調用Array得map方法,傳入我麽自己想要得函數,就等到一個新得array作為結果:

    例子:

function pow(x){
  return x*x  
}

var arr = [1,2,3,4,5];
console.log(arr.map(pow));//[1,4,9,16,25]


     這就是一個簡單得map方式調用,傳遞一個pow自定義函數,對原數組每個元素進行乘冪,得到一個新的數組

     再比如我們平時經常會遇到這種,需要取數組對象中某個屬性用來做一組數組

    例子:

var arr = [
    {
        name:"張三",
        type:2
    },
    {
         name:"李四",
         type:3
    }

]   

//要求拿到type屬性組成新的數組


//以往的寫法for迴圈
var new_arr = []
for(var i  = 0 ; i < arr.length ; i++){
    var obj = arr[i]
    for(var key in obj){
       if(key == 'type'){
           new_arr.push(obj[key])
        }
    }

}

//map的寫法

var new_arr = arr.map(function(item){

    return item.type
    
})         

    從上面可以看到一個寫法是for迴圈的寫法,一個是map的寫法;雖然兩個得到的結果都是一樣的[2,3];但是從代碼量來說,for迴圈過於冗餘,map簡單方便。所以以後遇到這種場景可以使用map方法更加方便

  2.reduce()方法

    1、定義:接收一個函數作為累加器,數組中的每一個值(從左到右)開始遍歷,最終計算為一個值

     2、註意:對空數組是不會執行回調函數的

     3、語法格式 : new Array.reduce(callback,initialValue)

   對語法格式的理解:

    reduce(callback,initialValue)會傳入兩個變數,第一個參數是回調函數(callback)和第二個初始值(initialValue)。

    第一個參數回調函數(callback)有四個傳入參數,prevnextindexarrayprevnext是必傳的參數。

    第一個參數初始值(initialValue)決定回調函數的第一個參數prev的取值結果,當reduce傳入initialValue時,prev的預設值就是initialValue,當reduce沒有傳入initialValue時,那麼prev的預設值就是原數值的第一個元素值。

    下麵解釋一下

var arr = ["apple","orange"];

//第一種沒有傳遞initialValue的情況
function getValue(){
  return arr.reduce(function(prev,next){
        console.log("prev",prev);
        console.log("next",next)
        return prev;
    })
}


console.log("getValue",getValue())

 

     運行結果可以看出來我們沒有傳遞initialValue的情況,prev取得是arr得第一個元素值開始遍歷

    接下來我們看一下傳遞initialValue得情況

var arr = ["a","b"];


//傳遞initialValue情況時

function getValue(){
    return arr.reduce(function(prev,next){
       console.log("prev",prev);
       console.log("next",next);
       prev[next] =1;
       return prev;   
    },{})
    //initialValue傳遞一個空對象
}

console.log("getValue",getValue());

 

     可以看到我們運行得結果,當傳遞initialValue得時候,prev得預設值就是你傳遞的initialValue;而我們就可以利用傳遞的預設值進行一系列業務邏輯處理。達到我們想要的效果

 

    接下來我們來看一下經常業務中是怎麼使用reduce()的。

    案例1:計算數組總和

var numArray = [1,2,3,4,5];


//用for迴圈來計算
var num = 0;

for(var i = 0 ; i < numArray.length ; i++){
   num = num + numArray[i]  
}

console.log(num);//15

//利用reduce方法

var res = numArray.reduce(function(prev,next){
    return prev + next
},0)

console.log(res) ;//15

 

 

 

     利用for迴圈我們要先聲明一個全局變數作為預設值。我們知道開發項目過程中,儘量不要使用全局變數,而使用reduce我們可以完全避過,而且更加直接。這種是簡單的使用reduce()。

    案例2:合併二維數組

var arr = [[0,1],[2,3],[4,5]];

var res = arr.reduce(function(prev,next){
    return prev.concat(next)
},[])

console.log(res);//[0,1,2,3,4,5];

 

     我們可以傳遞一個空數組作為預設值,對原始的數組元素進行合併成一個新的數組。

    案例3:統計一個數組中的單詞重覆有幾個
var arr = ["apple","orange","apple","orange","pear","orange"];


//不用reduce時

function getWorad(){
  var obj = {};
  for(var i = 0 ; i < arr.length ; i++){
    var item = arr[i];
    if(obj[item]){
      obj[item] = obj[item] + 1
    }else{
      obj[item] = 1
    }
  }
  return obj
}

console.log(getWorad());

function getWorad2(){
  return arr.reduce(function(prev,next){
    prev[next] = (prev[next] + 1 ) || 1;
    return prev;
  },{})
}
console.log(getWorad2())

   最後兩個的結果都是一樣的,其實我們使用reduce()方法會讓我們的邏輯變得簡單易處理。從而拋棄我們冗餘的代碼,讓代碼看起來更加明瞭。相信你們再平時的業務中也會遇到這種業務邏輯的。

    3、filter()方法

      1、定義:filter()也是一個常用的操作,它用於把Array的某些元素過濾調,然後返回剩下的元素,和map方法類似,Array的filter也接收一個函數,和map()不同的是;filter()把傳入的函數依次作用於每個元素,然後根據返回值是true還是false決定保留還是丟失該元素

    案列1:例如,在一個Array中,刪掉偶數,只保留奇數,可以這麼寫:

 

var arr = [1,2,3,4,5];

var r = arr.filter(function(item){
   return item%2 !== 0 ;
})

console.log(r)

 

 

 

     可以看到,我們利用filter對元素的過濾,只要元素取餘不等於零,就返回來,等於零就拋棄,最後組成一個新的數組。

    案例2:把一個arr中的空字元串去掉,可以這麼寫:

var arr = ['a','b','','c']
var r = arr.filter(function(item){
   return item && item.trim();
})

console.log(r) 

 

 

 

     我們可以利用filter過濾數組中空的字元串,返回一個沒有空字元串的數組。方便簡單,接下來我們來解析一下filter方法回調函數所帶的參數有哪些

//先看一下filter的語法格式
var r = Array.filter(function(ele,index,_this){
    console.log(ele);
    console.log(index);
    console.log(_this);
    return ture;
})

/*

可見用filter()這個高階函數,關鍵在於正確實現一個篩選函數
回調函數:filter()接收的回調函數,其實可以有多個參數。通常我們僅使用第一個參數,表示Array的某個元素,回調函數還可以接收另外兩個參數,表示元素的位置,和數組本身。

*/

  業務中最經常用到的還是用filter來去除數組中重覆的元素

var r,arr = [1,1,3,4,5,3,4];

r= arr.filter(function(ele,index,self){
   return self.indexOf(ele) === index;
})

console.log(r)

 

     這樣我們就很快速等到一個沒有重覆元素的數組。這也是我們經常遇到的去重,也是一種經常面試問到的。

    4、sort()方法

      1、定義:sort()  方法用於對數組的元素進行排序,並返回數組。

 

      2、語法格式:arrayObject.sort(sortby);

 

      3、註意:參數sortby  可選,用來規定排序的順序,但必須是函數。

//接下來我們就來看一下到底如何排序

//從小到大的排序,輸出:[1,2,3,9,56,87]
var arr = [9,87,56,1,3,2];
arr.sort(function(x,y){
   if(x < y){
      return -1
   }
   if(x > y){
      return 1
   }
      return 0;
})

//如果我們想要倒序排序;我們可以把大的放在前面[5,4,3,2,1]
var arr = [1,2,3,4,5];
arr.sort(function(x , y){
     if(x < y ){
        return 1
     }
     if(x > y){
        return -1
     }
      return 0
})

//在比如我們想要對字元串排序也可以實現,以字元串的首個字元,按照ASCII的大小    
//進行比較的,忽略大小寫字母 ['good','apple','moide']

var arr = ['good','apple','moide'];

arr.sort(function(s1,s2){
    var x1 = s1.toUpperCase();//轉成大寫
    var x2 = s2.toUpperCase();
    if(x1 < x2){
       return -1
     }
     if(x1 > x2){
        return 1
     }
     return 0
})
//忽略大小寫其實是把值轉成大寫或者全部小寫,再去做比較

//註意:sort()方法會直接對原有的Array數組進行修改,他返回的結果仍是當前的Array    
//還有往往我們會在代碼里見到很多數組對象,相對數組對象的某個屬性做排序那要怎麼實現呢
//下麵就是對數組對象的排序例子

var arr = [
  {
     Name:'zopp',
     Age:10
   },
   {
      Name:'god',
      Age:1,
    },
    {
       Name:'ytt',
       Age:18
     }
];

//簡單的寫法;預設的升序

function compare(prototype){
 return function(a,b){
        var value1 = a[prototype];
        Var value2 = b[prototype];
        return value1 - value2
}
}
//一般我們往往會把參數函數單獨寫一個函數;達到清晰明確,多變性

console.log(arr.sort(compare(“Age”)));
/*最後我們寫了這麼多例子,也發現作為可變的部分參數函數其實無非就是兩種;一種是升序一種是降序。其實我們還可以把這可變的參數函數封裝成一個方法,利用傳參的方式來說明我們是要升序還是降序
*/
/**數組根據數組對象中的某個屬性值進行排序的方法 
     * 使用例子:newArray.sort(sortBy(type,rev,[,key])) 
     * @param type 代表newArray的類型,’number’表示數值數組[1,2],’string’表示字元串數組[‘abhs’,’as’],’obj’表示對象數組[{},{}],如果是obj的話第三個參數必填
     * @param rev true表示升序排列,false降序排序
     * @param key 非必填的第三個函數,是作為如果type是obj的話作為屬性傳遞
     * */

var sortBy = function(type,rev,key){
    //第二個參數不傳預設就是升序排列

   if(!rev){
     rev = 1
   }else{
     rev = rev ? 1 : -1;
    }
return function(a,b){
  //如果是string的話我們就要處理一下統一大寫還是小寫

  if(type == 'string'){
     a = a.toUpperCase();
     b = b.toUpperCase();
  }
  //如果是obj的話我們就是取對應的屬性值
  if(type == 'obj'){
     a = a[key];
     b = b[key];
  }
   if(a < b){
     return rev * -1;
   }
   if(a > b){
     return rev * 1;
   }
    return 0;

   }
}

//這就是我們最後想要的統一封裝,大家也可以拿著自己去修改一下,這種封裝是最後返回一個處理好的匿名函數,也是高階函數中的一種,後面我們也會提到

    總結: 

  • 如果要得到自己想要的結果,不管是升序還是降序,就需要提供比較函數了。該函數比較兩個值的大小,然後返回一個用於說明這兩個值的相對順序的數字。
  • 比較函數應該具有兩個參數 a 和 b,其返回值如下:
  • 若 a 小於 b,即 a - b 小於零,則返回一個小於零的值,數組將按照升序排列。
  • 若 a 等於 b,則返回 0。
  • 若 a 大於 b, 即 a - b 大於零,則返回一個大於零的值,數組將按照降序排列。

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 產生原因 如果從後端返回過來的數組數據,進行遍歷的時候不在success回調函數內,則會產生如下的數據格式,雖然其也是數組格式,但是內部的值取不出來,給後臺也傳不過去。 原因:其實跟__ob__: Observer這個屬性沒有多少關係,原因還是在於非同步,因為wx.chooseImage是一個非同步執行 ...
  • 1、數組扁平化(數組降維) 數組扁平化是指將一個多維數組變為一維數組 [1, [2, 3, [4, 5]]] > [1, 2, 3, 4, 5] 2、給定一個數組,將數組中的所有0移動到末尾,並保持非0元素的順序不改變。如 [0,1,0,3,12] 移動後的期望數組為 [1,3,12,0,0] 要求 ...
  • 經過昨天對移動端基礎的瞭解,今天就來用原生JS實現一下我們的幻燈片。 因為是用原生實現,所以本文篇幅較長,各位看官只需理解思路即可,代碼部分可以粗略看看。 畢竟我們有better-scroll這樣封裝好的框架能更快速實現效果。b( ̄▽ ̄)d 首先根據我們昨天的滑屏操作,先將幻燈片的滑屏效果做出來。這 ...
  • 排序演算法之堆排序 [Toc] 什麼是堆? + 堆是一顆完全二叉樹 + 堆分為 最大堆和最小堆 + 最大堆父節點都大於子節點, 最小堆父節點都小於子節點 + 左子節點: 2 i +1 (i: 父節點index) + 右子節點: 2 i+2 堆排序 利用最大堆實現升序, 最小堆實現降序. 因為最大堆的根 ...
  • JS排序演算法之快排和歸併 [Toc] 快速排序 原理: 選擇一個key(一般是第一個元素), 將數組劃分為兩個區域. 左邊全部區域小於等於key, 右邊全部大於key. 然後在通過這種方法將每個區域劃分為兩個區域. 整個過程可以遞歸實現,以此實現整個數據有序 + 時間複雜度: O(n log(n)) ...
  • 效果 貼上效果展示: 實現思路 樣式方面不多贅述,滾動區域是給固定高度,設置 來實現。 接下來看看js方面的實現,其實也很簡單,觸發的條件是: + = 。例子我會使用 來實現,和原生實現是一樣的。 可視高度(offsetHeight):通過 的 獲得,表示區域固定的高度。這裡我推薦通過 來獲取高度, ...
  • 為什麼要三次握手:① 防止失效的鏈接到達伺服器伺服器打開錯誤鏈接。② 客戶端如果長時間等待,會有一個超時重傳,但是之前那個被耽誤的請求還是會回到伺服器,伺服器會打開兩個連接。③ 有三次握手後客戶端會忽視滯留請求。 為什麼要四次揮手。 ① 當客戶端發送連接釋放報名後,伺服器端數據可能還沒有傳輸完。 為 ...
  • 淺拷貝 定義:直接將一個引用數據類型的地址,賦值給另一個變數存儲,兩個變數存儲的是相同的記憶體地址,在一個變數操作數據,另一個變數中的數據也會改變 案例: 總結: a給b賦值時是給的記憶體地址,兩個變數中的數據改變隨意一個變數的數據,另外一個變數中的數據也跟著改變,這種操作就被稱為淺拷貝 深拷貝 定義: ...
一周排行
    -Advertisement-
    Play Games
  • Timer是什麼 Timer 是一種用於創建定期粒度行為的機制。 與標準的 .NET System.Threading.Timer 類相似,Orleans 的 Timer 允許在一段時間後執行特定的操作,或者在特定的時間間隔內重覆執行操作。 它在分散式系統中具有重要作用,特別是在處理需要周期性執行的 ...
  • 前言 相信很多做WPF開發的小伙伴都遇到過表格類的需求,雖然現有的Grid控制項也能實現,但是使用起來的體驗感並不好,比如要實現一個Excel中的表格效果,估計你能想到的第一個方法就是套Border控制項,用這種方法你需要控制每個Border的邊框,並且在一堆Bordr中找到Grid.Row,Grid. ...
  • .NET C#程式啟動閃退,目錄導致的問題 這是第2次踩這個坑了,很小的編程細節,容易忽略,所以寫個博客,分享給大家。 1.第一次坑:是windows 系統把程式運行成服務,找不到配置文件,原因是以服務運行它的工作目錄是在C:\Windows\System32 2.本次坑:WPF桌面程式通過註冊表設 ...
  • 在分散式系統中,數據的持久化是至關重要的一環。 Orleans 7 引入了強大的持久化功能,使得在分散式環境下管理數據變得更加輕鬆和可靠。 本文將介紹什麼是 Orleans 7 的持久化,如何設置它以及相應的代碼示例。 什麼是 Orleans 7 的持久化? Orleans 7 的持久化是指將 Or ...
  • 前言 .NET Feature Management 是一個用於管理應用程式功能的庫,它可以幫助開發人員在應用程式中輕鬆地添加、移除和管理功能。使用 Feature Management,開發人員可以根據不同用戶、環境或其他條件來動態地控制應用程式中的功能。這使得開發人員可以更靈活地管理應用程式的功 ...
  • 在 WPF 應用程式中,拖放操作是實現用戶交互的重要組成部分。通過拖放操作,用戶可以輕鬆地將數據從一個位置移動到另一個位置,或者將控制項從一個容器移動到另一個容器。然而,WPF 中預設的拖放操作可能並不是那麼好用。為瞭解決這個問題,我們可以自定義一個 Panel 來實現更簡單的拖拽操作。 自定義 Pa ...
  • 在實際使用中,由於涉及到不同編程語言之間互相調用,導致C++ 中的OpenCV與C#中的OpenCvSharp 圖像數據在不同編程語言之間難以有效傳遞。在本文中我們將結合OpenCvSharp源碼實現原理,探究兩種數據之間的通信方式。 ...
  • 一、前言 這是一篇搭建許可權管理系統的系列文章。 隨著網路的發展,信息安全對應任何企業來說都越發的重要,而本系列文章將和大家一起一步一步搭建一個全新的許可權管理系統。 說明:由於搭建一個全新的項目過於繁瑣,所有作者將挑選核心代碼和核心思路進行分享。 二、技術選擇 三、開始設計 1、自主搭建vue前端和. ...
  • Csharper中的表達式樹 這節課來瞭解一下表示式樹是什麼? 在C#中,表達式樹是一種數據結構,它可以表示一些代碼塊,如Lambda表達式或查詢表達式。表達式樹使你能夠查看和操作數據,就像你可以查看和操作代碼一樣。它們通常用於創建動態查詢和解析表達式。 一、認識表達式樹 為什麼要這樣說?它和委托有 ...
  • 在使用Django等框架來操作MySQL時,實際上底層還是通過Python來操作的,首先需要安裝一個驅動程式,在Python3中,驅動程式有多種選擇,比如有pymysql以及mysqlclient等。使用pip命令安裝mysqlclient失敗應如何解決? 安裝的python版本說明 機器同時安裝了 ...