JavaScript函數式編程究竟是什麼?

来源:https://www.cnblogs.com/fundebug/archive/2019/08/07/javascript-functional-programing-introduction.html
-Advertisement-
Play Games

摘要: 理解函數式編程。 作者:前端小智 原文: "JS中函數式編程基本原理簡介" "Fundebug" 經授權轉載,版權歸原作者所有。 在長時間學習和使用面向對象編程之後,咱們退一步來考慮系統複雜性。 在做了一些研究之後,我發現了函數式編程的概念,比如不變性和純函數。這些概念使你能夠構建無副作用的 ...


摘要: 理解函數式編程。

Fundebug經授權轉載,版權歸原作者所有。

在長時間學習和使用面向對象編程之後,咱們退一步來考慮系統複雜性。

在做了一些研究之後,我發現了函數式編程的概念,比如不變性和純函數。這些概念使你能夠構建無副作用的函數,因此更容易維護具有其他優點的系統。

在這篇文章中,將通大量代碼示例來詳細介紹函數式編程和一些相關重要概念。

什麼是函數式編程

函數式編程是一種編程範式,是一種構建電腦程式結構和元素的風格,它把計算看作是對數學函數的評估,避免了狀態的變化和數據的可變。

純函數

當我們想要理解函數式編程時,需要知道的第一個基本概念是純函數,但純函數又是什麼鬼?

咱們怎麼知道一個函數是否是純函數?這裡有一個非常嚴格的定義:

  • 如果給定相同的參數,則返回相同的結果(也稱為確定性)。
  • 它不會引起任何副作用。

如果給定相同的參數,則得到相同的結果

如果給出相同的參數,它返回相同的結果。 想象一下,我們想要實現一個計算圓的面積的函數。

不是純函數會這樣做,接收radius 作為參數,然後計算radius * radius * PI

    let PI = 3.14;
    
    const calculateArea = (radius) => radius * radius * PI;
    
    calculateArea(10); // returns 314.0

為什麼這是一個不純函數?原因很簡單,因為它使用了一個沒有作為參數傳遞給函數的全局對象。

現在,想象一些數學家認為圓周率的值實際上是42並且修改了全局對象的值。

不純函數得到10 * 10 * 42 = 4200。對於相同的參數(radius = 10),我們得到了不同的結果。

修複它:

    let PI = 3.14;
    
    const calculateArea = (radius, pi) => radius * radius * pi;
    
    calculateArea(10, PI); // returns 314.0

現在把 PI 的值作為參數傳遞給函數,這樣就沒有外部對象引入。

  • 對於參數radius = 10PI = 3.14,始終都會得到相同的結果:314.0
  • 對於 radius = 10PI = 42,總是得到相同的結果:4200

讀取文件

下麵函數讀取外部文件,它不是純函數,文件的內容隨時可能都不一樣。

    const charactersCounter = (text) => `Character count: ${text.length}`;
    
    function analyzeFile(filename) {
      let fileContent = open(filename);
      return charactersCounter(fileContent);
    }

隨機數生成

任何依賴於隨機數生成器的函數都不能是純函數。

    function yearEndEvaluation() {
      if (Math.random() > 0.5) {
        return "You get a raise!";
      } else {
        return "Better luck next year!";
      }
    }

無明顯副作用

純函數不會引起任何可觀察到的副作用。可見副作用的例子包括修改全局對象或通過引用傳遞的參數。

現在,咱們要實現一個函數,該接收一個整數並返對該整數進行加1操作且返回。

    let counter = 1;
    
    function increaseCounter(value) {
      counter = value + 1;
    }
    
    increaseCounter(counter);
    console.log(counter); // 2

該非純函數接收該值並重新分配counter,使其值增加1

函數式編程不鼓勵可變性。我們修改全局對象,但是要怎麼做才能讓它變得純函數呢?只需返回增加1的值。

    let counter = 1;
    
    const increaseCounter = (value) => value + 1;
    
    increaseCounter(counter); // 2
    console.log(counter); // 1

純函數increaseCounter返回2,但是counter值仍然是相同的。函數返回遞增的值,而不改變變數的值。

如果我們遵循這兩條簡單的規則,就會更容易理解我們的程式。現在每個函數都是孤立的,不能影響系統的其他部分。

純函數是穩定的、一致的和可預測的。給定相同的參數,純函數總是返回相同的結果。

咱們不需要考慮相同參數有不同結果的情況,因為它永遠不會發生。

純函數的好處

純函數代碼肯定更容易測試,不需要 mock 任何東西,因此,我們可以使用不同的上下文對純函數進行單元測試:

  • 給定一個參數 A,期望函數返回值 B
  • 給定一個參數C,期望函數返回值D

一個簡單的例子是接收一組數字,並對每個數進行加 1 這種沙雕的操作。

    let list = [1, 2, 3, 4, 5];
    
    const incrementNumbers = (list) => list.map(number => number + 1);

接收numbers數組,使用map遞增每個數字,並返回一個新的遞增數字列表。

    incrementNumbers(list); // [2, 3, 4, 5, 6]

對於輸入[1,2,3,4,5],預期輸出是[2,3,4,5,6]

不可變性

儘管時間變或者不變,純函數大佬都是不變的。

當數據是不可變的時,它的狀態在創建後不能更改。

咱們不能更改不可變對象,如果非要來硬的,剛需要深拷貝一個副本,然後操作這個副本。

在JS中,我們通常使用for迴圈,for的每次遍歷 i是個可變變數。

    var values = [1, 2, 3, 4, 5];
    var sumOfValues = 0;
    
    for (var i = 0; i < values.length; i++) {
      sumOfValues += values[i];
    }
    
    sumOfValues // 15

對於每次遍歷,都在更改isumOfValue狀態,但是我們如何在遍歷中處理可變性呢? 答案就是使用遞歸

    let list = [1, 2, 3, 4, 5];
    let accumulator = 0;
    
    function sum(list, accumulator) {
      if (list.length == 0) {
        return accumulator;
      }
    
      return sum(list.slice(1), accumulator + list[0]);
    }
    
    sum(list, accumulator); // 15
    list; // [1, 2, 3, 4, 5]
    accumulator; // 0

上面代碼有個 sum 函數,它接收一個數值向量。函數調用自身,直到 list為空退出遞歸。對於每次“遍歷”,我們將把值添加到總accumulator中。

使用遞歸,咱們保持變數不變。不會更改listaccumulator變數。它保持相同的值。

觀察:我們可以使用reduce來實現這個功能。這個在接下的高階函數內容中討論。

構建對象的最終狀態也很常見。假設我們有一個字元串,想把這個字元串轉換成url slug

在Ruby的面向對象編程中,咱們可以創建一個類 UrlSlugify,這個類有一個slugify方法來將字元串輸入轉換為url slug

    class UrlSlugify
      attr_reader :text
      
      def initialize(text)
        @text = text
      end
    
      def slugify!
        text.downcase!
        text.strip!
        text.gsub!(' ', '-')
      end
    end
    
    UrlSlugify.new(' I will be a url slug   ').slugify! # "i-will-be-a-url-slug"

上面使用的有命令式編程方式,首先用小寫字母表示我們想在每個slugify進程中做什麼,然後刪除無用的空格,最後用連字元替換剩餘的空格。

這種方式在整個過程中改變了輸入狀態,顯然不符合純函數的概念。

這邊可以通過函數組合或函數鏈來來優化。換句話說,函數的結果將用作下一個函數的輸入,而不修改原始輸入字元串。

    const string = " I will be a url slug   ";
    
    const slugify = string =>
      string
        .toLowerCase()
        .trim()
        .split(" ")
        .join("-");
    
    slugify(string); // i-will-be-a-url-slug

上述代碼主要做了這幾件事:

  • toLowerCase:將字元串轉換為所有小寫字母。
  • trim:刪除字元串兩端的空白。
  • splitjoin:用給定字元串中的替換替換所有匹配實例

代碼部署後可能存在的BUG沒法實時知道,事後為瞭解決這些BUG,花了大量的時間進行log 調試,這邊順便給大家推薦一個好用的BUG監控工具 Fundebug

引用透明性

接著實現一個square 函數:

const square = (n) => n * n;

給定相同的輸入,這個純函數總是有相同的輸出。

    square(2); // 4
    square(2); // 4
    square(2); // 4
    // ...

2作為square函數的參數傳遞始終會返回4。這樣咱們可以把square(2)換成4,我們的函數就是引用透明的。

基本上,如果一個函數對於相同的輸入始終產生相同的結果,那麼它可以看作透明的。

有了這個概念,咱們可以做的一件很酷的事情就是記住這個函數。假設有這樣的函數

    const sum = (a, b) => a + b;

用這些參數來調用它

    sum(3, sum(5, 8));

sum(5, 8) 總等於13,所以可以做些騷操作:

    sum(3, 13);

這個表達式總是得到16,咱們可以用一個數值常數替換整個表達式,並把它記下來。

函數是 JS 中的一級公民

函數作為 JS 中的一級公民,很風騷,函數也可以被看作成值並用作數據使用。

  • 從常量和變數中引用它。
  • 將其作為參數傳遞給其他函數。
  • 作為其他函數的結果返回它。

其思想是將函數視為值,並將函數作為數據傳遞。通過這種方式,我們可以組合不同的函數來創建具有新行為的新函數。

假如我們有一個函數,它對兩個值求和,然後將值加倍,如下所示:

    const doubleSum = (a, b) => (a + b) * 2;

對應兩個值求差,然後將值加倍:

    const doubleSubtraction = (a, b) => (a - b) * 2;

這些函數具有相似的邏輯,但區別在於運算符的功能。 如果我們可以將函數視為值並將它們作為參數傳遞,我們可以構建一個接收運算符函數併在函數內部使用它的函數。

    const sum = (a, b) => a + b;
    const subtraction = (a, b) => a - b;
    
    const doubleOperator = (f, a, b) => f(a, b) * 2;
    
    doubleOperator(sum, 3, 1); // 8
    doubleOperator(subtraction, 3, 1); // 4

f參數並用它來處理ab, 這裡傳遞了sum函數和subtraction並使用doubleOperator函數進行組合併創建新行為。

高階函數

當我們討論高階函數時,通常包括以下幾點:

  • 將一個或多個函數作為參數
  • 返回一個函數作為結果

上面實現的doubleOperator函數是一個高階函數,因為它將一個運算符函數作為參數並使用它。

我們經常用的filtermapreduce都是高階函數,Look see see。

Filter

對於給定的集合,我們希望根據屬性進行篩選。filter函數期望一個truefalse值來決定元素是否應該包含在結果集合中。

如果回調表達式為真,過濾器函數將在結果集合中包含元素,否則,它不會。

一個簡單的例子是,當我們有一個整數集合,我們只想要偶數。

命令式

使用命令式方式來獲取數組中所有的偶數,通常會這樣做:

  • 創建一個空數組evenNumbers
  • 遍曆數組 numbers
  • 將偶數 push 到evenNumbers數組中
    var numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
    var evenNumbers = [];
    
    for (var i = 0; i < numbers.length; i++) {
      if (numbers[i] % 2 == 0) {
        evenNumbers.push(numbers[i]);
      }
    }
    
    console.log(evenNumbers); // (6) [0, 2, 4, 6, 8, 10]

我們還可以使用filter高階函數來接收偶函數並返回一個偶數列表:

const even = n => n % 2 == 0;
const listOfNumbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
listOfNumbers.filter(even); // [0, 2, 4, 6, 8, 10]

我在 [Hacker Rank FP][2] 上解決的一個有趣問題是[Filter Array問題][3]。 問題是過濾給定的整數數組,並僅輸出小於指定值X的那些值。

命令式做法通常是這樣的:

    var filterArray = function(x, coll) {
      var resultArray = [];
    
      for (var i = 0; i < coll.length; i++) {
        if (coll[i] < x) {
          resultArray.push(coll[i]);
        }
      }
    
      return resultArray;
    }
    
    console.log(filterArray(3, [10, 9, 8, 2, 7, 5, 1, 3, 0])); // (3) [2, 1, 0]

聲明式方式

對於上面的總是,我們更想要一種更聲明性的方法來解決這個問題,如下所示:

    function smaller(number) {
      return number < this;
    }
    
    function filterArray(x, listOfNumbers) {
      return listOfNumbers.filter(smaller, x);
    }
    
    let numbers = [10, 9, 8, 2, 7, 5, 1, 3, 0];
    
    filterArray(3, numbers); // [2, 1, 0]

smaller的函數中使用 this,一開始看起來有點奇怪,但是很容易理解。

filter函數中的第二個參數表示上面 this, 也就是 x 值。

我們也可以用map方法做到這一點。想象一下,有一組信息

    let people = [
      { name: "TK", age: 26 },
      { name: "Kaio", age: 10 },
      { name: "Kazumi", age: 30 }
    ]

我們希望過濾 age 大於 21 歲的人,用 filter 方式

    const olderThan21 = person => person.age > 21;
    const overAge = people => people.filter(olderThan21);
    overAge(people); // [{ name: 'TK', age: 26 }, { name: 'Kazumi', age: 30 }]

map

map函數的主要思路是轉換集合。

map方法通過將函數應用於其所有元素並根據返回的值構建新集合來轉換集合。

假如我們不想過濾年齡大於 21 的人,我們想做的是顯示類似這樣的:TK is 26 years old.

使用命令式,我們通常會這樣做:

    var people = [
      { name: "TK", age: 26 },
      { name: "Kaio", age: 10 },
      { name: "Kazumi", age: 30 }
    ];
    
    var peopleSentences = [];
    
    for (var i = 0; i < people.length; i++) {
      var sentence = people[i].name + " is " + people[i].age + " years old";
      peopleSentences.push(sentence);
    }
    
    console.log(peopleSentences); // ['TK is 26 years old', 'Kaio is 10 years old', 'Kazumi is 30 years old']

聲明式會這樣做:

    const makeSentence = (person) => `${person.name} is ${person.age} years old`;
    
    const peopleSentences = (people) => people.map(makeSentence);
      
    peopleSentences(people);
    // ['TK is 26 years old', 'Kaio is 10 years old', 'Kazumi is 30 years old']

整個思想是將一個給定的數組轉換成一個新的數組。

另一個有趣的HackerRank問題是[更新列表問題][3]。我們想要用一個數組的絕對值來更新它的值。

例如,輸入[1,2,3,- 4,5]需要輸出為[1,2,3,4,5]-4的絕對值是4

一個簡單的解決方案是每個集合中值的就地更新,很危險的作法

    var values = [1, 2, 3, -4, 5];
    
    for (var i = 0; i < values.length; i++) {
      values[i] = Math.abs(values[i]);
    }
    
    console.log(values); // [1, 2, 3, 4, 5]

我們使用Math.abs函數將值轉換為其絕對值併進行就地更新。

這種方式不是最做解。

首先,前端我們學習了不變性,知道不可變性讓函數更加一致和可預測,咱們的想法是建立一個具有所有絕對值的新集合。

其次,為什麼不在這裡使用map來“轉換”所有數據

我的第一個想法是測試Math.abs函數只處理一個值。

    Math.abs(-1); // 1
    Math.abs(1); // 1
    Math.abs(-2); // 2
    Math.abs(2); // 2

我們想把每個值轉換成一個正值(絕對值)。

現在知道如何對一個值執行絕對值操作,可以使用此函數作為參數傳遞給map函數。

還記得高階函數可以接收函數作為參數並使用它嗎? 是的,map函數可以做到這一點

    let values = [1, 2, 3, -4, 5];
    
    const updateListMap = (values) => values.map(Math.abs);
    
    updateListMap(values); // [1, 2, 3, 4, 5]

Reduce

reduce函數的思想是接收一個函數和一個集合,並返回通過組合這些項創建的值。

常見的的一個例子是獲取訂單的總金額。

假設你在一個購物網站,已經將產品1、產品2、產品3和產品4添加到購物車(訂單)中。現在,我們要計算購物車的總數量:

以命令式的方式,就是便利訂單列表並將每個產品金額與總金額相加。

    var orders = [
      { productTitle: "Product 1", amount: 10 },
      { productTitle: "Product 2", amount: 30 },
      { productTitle: "Product 3", amount: 20 },
      { productTitle: "Product 4", amount: 60 }
    ];
    
    var totalAmount = 0;
    
    for (var i = 0; i < orders.length; i++) {
      totalAmount += orders[i].amount;
    }
    
    console.log(totalAmount); // 120

使用reduce,我們可以構建一個函數來處理量計算sum並將其作為參數傳遞給reduce函數。

    let shoppingCart = [
      { productTitle: "Product 1", amount: 10 },
      { productTitle: "Product 2", amount: 30 },
      { productTitle: "Product 3", amount: 20 },
      { productTitle: "Product 4", amount: 60 }
    ];
    
    const sumAmount = (currentTotalAmount, order) => currentTotalAmount + order.amount;
    
    const getTotalAmount = (shoppingCart) => shoppingCart.reduce(sumAmount, 0);
    
    getTotalAmount(shoppingCart); // 120

這裡有shoppingCart,接收當前currentTotalAmount的函數sumAmount,以及對它們求和的order對象。

咱們也可以使用mapshoppingCart轉換為一個amount集合,然後使用reduce函數和sumAmount函數。

    const getAmount = (order) => order.amount;
    const sumAmount = (acc, amount) => acc + amount;

    function getTotalAmount(shoppingCart) {
      return shoppingCart
        .map(getAmount)
        .reduce(sumAmount, 0);
    }
    
    getTotalAmount(shoppingCart); // 120

getAmount接收product對象並只返回amount值,即[10,30,20,60],然後,reduce通過相加將所有項組合起來。

三個函數的示例

看了每個高階函數的工作原理。這裡為你展示一個示例,說明如何在一個簡單的示例中組合這三個函數。

說到購物車,假設我們的訂單中有這個產品列表

    let shoppingCart = [
      { productTitle: "Functional Programming", type: "books", amount: 10 },
      { productTitle: "Kindle", type: "eletronics", amount: 30 },
      { productTitle: "Shoes", type: "fashion", amount: 20 },
      { productTitle: "Clean Code", type: "books", amount: 60 }
    ]

假如相要想要購物車裡類型為 books的總數,通常會這樣做:

  • 過濾 type 為 books的
  • 使用map將購物車轉換為amount集合。
  • reduce將所有項加起來。
    let shoppingCart = [
      { productTitle: "Functional Programming", type: "books", amount: 10 },
      { productTitle: "Kindle", type: "eletronics", amount: 30 },
      { productTitle: "Shoes", type: "fashion", amount: 20 },
      { productTitle: "Clean Code", type: "books", amount: 60 }
    ]
    
    const byBooks = (order) => order.type == "books";
    const getAmount = (order) => order.amount;
    const sumAmount = (acc, amount) => acc + amount;
    
    function getTotalAmount(shoppingCart) {
      return shoppingCart
        .filter(byBooks)
        .map(getAmount)
        .reduce(sumAmount, 0);
    }
    
    getTotalAmount(shoppingCart); // 70

代碼部署後可能存在的BUG沒法實時知道,事後為瞭解決這些BUG,花了大量的時間進行log 調試,這邊順便給大家推薦一個好用的BUG監控工具 Fundebug

關於Fundebug

Fundebug專註於JavaScript、微信小程式、微信小游戲、支付寶小程式、React Native、Node.js和Java線上應用實時BUG監控。 自從2016年雙十一正式上線,Fundebug累計處理了20億+錯誤事件,付費客戶有陽光保險、核桃編程、荔枝FM、掌門1對1、微脈、青團社等眾多品牌企業。歡迎大家免費試用!


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

-Advertisement-
Play Games
更多相關文章
  • 自定義導航欄。 效果如下: 1.1.自定義導航欄 我的博客皮膚用的是AnotherEon001,假如你的博客用的是其它皮膚,樣式需要自己修改,要想達到一樣的效果,建議也用同款皮膚 (1)把預設的導航欄給隱藏掉 既然我們想要自定義導航欄,預設的導航欄就不能顯示 (2)頁首html代碼 裡面一個ul包含 ...
  • 獲取時間: 獲取1997年10月1號日期 new Date: 當使用 - 拼接年月日時將會使用UTC時區解析參數,會比北京時間快八小時。 當時用 / 拼接年月日時會使用北京的時區去解析參數,取到的是北京時間。 Date.parse 獲取UTC時間:使用 - 拼接 獲取北京時間: 使用 / 拼接 ...
  • 1.禁止div點擊 //css屬性: pointer-events: none; //或者定義屬性,在js中添加: $(".原類名").addClass("新類名"); //js: //禁用 $.fn.disable = function () { $(this).addClass("disable ...
  • HTML代碼 <select id="s1"> <option value="0">~請選擇省份~</option> <option value="1">湖北省</option> <option value="2">江西省</option> </select> <select name="" id= ...
  • Vue+Typescript中在Vue上掛載axios使用時報錯 在 項目開發過程中,為了方便在各個組件中調用 ,我們通常會在入口文件將axios掛載到vue原型身上,如下: 這樣的話,我們在各個組件中進行請求時,就可以直接使用 ,但是在ts中使用 進行請求時,會進行報錯,如下所示: 從圖中我們可以 ...
  • 一、小程式概述 2017 年 1 月 9 日小程式正式上線,騰訊開放了個人開發者開發小程式,小程式從此就開始火爆,這一年,小程式狂攬 4 億用戶、1.7 億的日常活躍,上線 58 萬個。這是一個巨大的機會,對於企業宣傳,拉新用戶存在變革性的影響。 小程式的本質是:輕應用,就是不用安裝就能使用的手機A ...
  • 最近在研究MUI框架時,看到這樣的代碼: init,這個外面還有一層: 有些明瞭了,這就像java中對象的裡面定義了一個init方法,這裡init方法也是初始化方法,這裡是對整個頁面進行初始化。調用類似java:_App.init()即可. 關於細節說法:這是一種json寫法,把所有的functio ...
  • 下載NEditor 放在 vue 項目下麵 public 文件中。 安裝 vue-neditor-wrap 執行命令 npm install vue-neditor-wrap 代碼使用 <VueNeditorWrap ref="VueNeditorWrap" v-model="content" :c ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...