沐鳴測速登錄地址_chrome transition閃爍BUG

前段時間寫鼠標懸停元素上移效果時,當鼠標恰好放在元素邊緣時,chrome出現一直上下移動的問題,其他瀏覽器表現正常。原因尚不知,可能是實現方式不對吧(PS:使用top實現),雖然不知道原因,但是問題還是要解決的,分享一個能繞開的實現方式。

說到鼠標懸停元素上移,首先想到的是鼠標懸停時元素上移,然後應用transition來實現漸變效果。

1、使用top實現(該實現方式chrome瀏覽器閃爍,避免使用)

<!--html-->
<div class="test"></div>

/*css*/
.test {
     position: relative;
     top: 0; 
     transition: top 0.5s;
 }

.test:hover{
     top: -10px;
 }

2、使用transform實現(推薦)

<!--html-->
<div class="test"></div>

/*css*/
.test {
     transform: translateY(0);
     transition: transform 0.5s;
 }

.test:hover{
     transform: translateY(-10px);
 }

站長推薦

1.雲服務推薦: 國內主流雲服務商,各類雲產品的最新活動,優惠券領取。地址:阿里雲騰訊雲華為雲

2.廣告聯盟: 整理了目前主流的廣告聯盟平台,如果你有流量,可以作為參考選擇適合你的平台點擊進入

鏈接: http://www.fly63.com/article/detial/10014

沐鳴總代平台_如何解決async await處理異常問題

寫在前面

晚上睡不着,決心還是起來把今天白天工作期間反問一個小夥伴的async&await問題。實際上這個問題之前我就一直想寫點什麼,只是奈何懶的很,一直沒有行動。今天還是忍不住想要吐糟一下,如果有什麼說得不對的,歡迎批評指證!

前端項目中異步函數存在的問題

在現在的前端項目中,充斥這大量的異步操作,由於async & await的興起,導致我今天看到小夥伴有一個項目的代碼大量充斥着try catch,代碼大概如下:

function request(type) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            type === 'a' ? resolve('resolve') : reject('reject')
        }, 2000);
    })
}

async function getData() {
    try {
        let ret1 = await request('a');
    }catch(error){
        // todo 
    }

    try {
        let ret2 = await request('b');
    } catch (error) {
        // todo
    }

    try {
        let ret3 = await request('c');
    } catch (error) {
        // todo
    }
}
getData();

解決辦法

在複雜的業務中,這種充斥很多的try catch我實在受不了。勢必會想辦法解決一下這個問題。 首先需要明確的是 await 後面的promise只有是一個resolve狀態,才能正確的拿到其結果。那麼要解決這個問題,勢必要讓異步返回一個resolve狀態,但是錯誤我們不能視而不見,結合nodejs中錯誤優先,我們可以將錯誤和結果封裝成一個數組返回,那麼就有了如下代碼了:

/**
 * 高級的處理,接收一個promise,返回一個resolve狀態的promise
 * 
 * @param {*} promise 
 */
const hRequest = promise  => promise.then(res => [undefined, res]).catch(err => [err,undefined])


async function getData2() {
    let err,result;
    [err,result] = await hRequest(request('a'));
    console.log(err,result);
    [err,result] = await hRequest(request('b'));
    console.log(err,result);
}
getData2();

最後

經過這樣一轉換,你可以優先判斷有沒有錯誤,然後再處理你的業務。

作者:我終於失去了你

鏈接:https://juejin.cn/post/6912112597596635150

來源:掘金


站長推薦

1.雲服務推薦: 國內主流雲服務商,各類雲產品的最新活動,優惠券領取。地址:阿里雲騰訊雲華為雲

2.廣告聯盟: 整理了目前主流的廣告聯盟平台,如果你有流量,可以作為參考選擇適合你的平台點擊進入

鏈接: http://www.fly63.com/article/detial/10001

沐鳴娛樂業務:_Css幾種水平垂直居中的方式的利弊

css 中實現水平垂直居中的方式很多。別看到水平垂直居中就準備右上角 x 掉,本文的重點不是羅列有多少種方式實現水平垂直居中方式,而是探討一下常見的幾種水平垂直居中的方式的利弊。

嗯哼?也就是:

  • 那麼多種水平垂直居中的方式,如果真的在業務中需要使用了,你腦海里第一時間會想到哪個?
  • 不同的水平垂直居中方式,它們肯定存在差異,那麼最顯著的不同是什麼?
  • 有沒有所謂的最完美的水平垂直居中?

本文將討論 4 種水平垂直居中的方式,分別是,並且每個起個名字方面下面看圖:

  1. absolute: position: absolute 配合 top:50%;left:50%;transform:translate(-50%, -50%)
  2. autobot: display:flex 配合 margin:auto
  3. flex: display:flex 配合 align-items:center、justify-content:center
  4. grid: display:grid 配合 place-content:center;

居中單個元素

對於如下簡單的結構:

<div class="g-container">
    <div class="sub"></div>
</div>

居中單個元素而言,上述 4 種方法都很好,沒有問題。

居中多個元素

對於如下稍微複雜點的結構:

<div class="g-container">
    <div class="sub">1</div>
    <div class="sub">123</div>
    <div class="sub">123456</div>
</div>

那麼如果是居中多個子元素,上述 4 種方法,就能體現出明顯的不一樣。稍微也修改一下子元素,不給它設定寬度,通過 padding 撐開即可:

.sub {
    border: 1px solid deeppink;
    padding: 10px;
    border-radius: 5px;
}

看看結果如何:

簡單分析分析:

  1. absolute 的方法明顯有問題,由於用的絕對定位,其實 3 個子元素都疊在了一起
  2. flex 和 grid 的方法,如果不手動添加邊距(margin 或者 gap),會貼在一起
  3. 不限制方向的話,flex 默認是水平排列,grid 是豎直排列
  4. 非常重要的一點,grid 布局下的子元素的寬度,所以子元素的寬度會被強行拉伸至最寬的一個子元素的內容的寬度

對於多個子元素,absolute 方法明顯不適用, 接下來主要看剩餘 3 個方法在一些細節上的差異。

控制間距

如果我們希望控制每個元素之間的間距呢?我們給 autobot、flex、grid 的容器各自加上 gap: 5px,再看看:

.g-container{
    gap: 5px;
}

margin: auto 由於需要均分剩餘空間,所以表現並不好,無法按照我們設想的 5px 寬度進行間隔

讓元素多到溢出

OK,接下來,我們讓內容再多一點,多到溢出整個容器,看看有什麼不一樣。

再來一張豎直方向排列的:

可以看到:

  1. 非常重要的一點,由於沒有了剩餘空間,margin: auto 已經無法做到均勻分配,水平垂直居中了,而是一邊貼着容器邊,另外一邊溢出
  2. flex 和 grid 都做到了即便超出容器空間,依然是水平垂直居中的

總結一下

經由上述幾個 DEMO 可以看出來,在目前比較常用的水平垂直居中方案當中。flex 方案應該是目前而言最優的選擇,它能夠在各種環境下都保持內部元素的水平垂直居中並且不改變子元素的某些特徵:

  1. 便捷的水平垂直居中單個元素
  2. 便捷的水平垂直居中多個元素,無論是橫向、豎向,亦或內容超出
  3. 非常方便控制子元素之間的間距
  4. 不會改變子元素的寬度

當然,美中不足的是,可能相對而言,要敲多幾個字符。

而 margin: auto 和 grid 則或多或少有一些小問題。absolute 無法應付多個元素。

站長推薦

1.雲服務推薦: 國內主流雲服務商,各類雲產品的最新活動,優惠券領取。地址:阿里雲騰訊雲華為雲

2.廣告聯盟: 整理了目前主流的廣告聯盟平台,如果你有流量,可以作為參考選擇適合你的平台點擊進入

鏈接: http://www.fly63.com/article/detial/10044

沐鳴平台註冊登錄_10個JavaScript 字符串技巧

我們稱一個字符序列為字符串。這幾乎是所有編程語言中都有的基本類型之一。這裏跟大家展示關於 js 字符串的10個很棒的技巧,你可能還不知道哦?

1.如何多次複製一個字符串

js 字符串允許簡單的重複,與純手工複製字符串不同,我們可以使用字符串的repeat方法。

const laughing = '小智'.repeat(3)
consol.log(laughing) // "小智小智小智"

const eightBits = '1'.repeat(8)
console.log(eightBits) // "11111111"

2. 如何填充一個字符串到指定的長度

有時,我們希望字符串具有特定長度。 如果字符串太短,則需要填充剩餘空間,直到達到指定的長度為止。

過去,主要還是使用庫 left-pad。 但是,今天我們可以使用padStart和SpadEnd方法,選擇哪種方法取決於是在字符串的開頭還是結尾填充字符串。

// 在開頭添加 "0",直到字符串的長度為 8。
const eightBits = '001'.padStart(8, '0')
console.log(eightBits) // "00000001"

//在末尾添加“ *”,直到字符串的長度為5。
const anonymizedCode = "34".padEnd(5, "*")
console.log(anonymizedCode) // "34***"

3.如何將字符串拆分為字符數組

有多種方法可以將字符串分割成字符數組,我更喜歡使用擴展操作符(…):

const word = 'apple'
const characters = [...word]
console.log(characters) // ["a", "p", "p", "l", "e"]

注意,這並不總是像預期的那樣工作。有關更多信息,請參見下一個技巧。

4.如何計算字符串中的字符

可以使用length屬性。

const word = "apple";
console.log(word.length) // 5

但對於中文來說,這個方法就不太靠譜。

const word = "

站長推薦

1.雲服務推薦: 國內主流雲服務商,各類雲產品的最新活動,優惠券領取。地址:阿里雲騰訊雲華為雲

2.廣告聯盟: 整理了目前主流的廣告聯盟平台,如果你有流量,可以作為參考選擇適合你的平台點擊進入

鏈接: http://www.fly63.com/article/detial/10062

沐鳴總代理_編寫高質量 JS 變量的5種最佳做法

變量無處不在,即使我們在寫一個小的函數,或者一個應用程序:我們總是先聲明,分配和使用變量。編寫好的變量可提高代碼的可讀性和易維護性。

在本文中,主要介紹 5種有關在 JavaScript 中聲明和使用變量的最佳做法。

1.首選 const,再 let

我使用const或let聲明變量,兩者之間的主要區別是const變量需要一個初始值,並且一旦初始化就無法重新分配其值。

另一方面,let聲明不需要初始值,我們可以多次重新分配其值。

// const 需要初始化
const pi = 3.14;
// 不能重新分配const
pi = 4.89; // throws "TypeError: Assignment to constant variable"

另一方面,let聲明不需要初始值,我們可以多次重新分配其值。

// let 初始化是可選的
let result;
// let can be reassigned
result = 14;
result = result * 2;

選擇變量的聲明類型時的一個好習慣是首選const,否則使用let。

function myBigFunction(param1, param2) {
  /* lots of stuff... */

  const result = otherFunction(param1);

  /* lots of stuff... */
  return something;
}

例如,如果我們正在查看函數體,並看到const result = …聲明:

function myBigFunction(param1, param2) {
  /* lots of stuff... */

  const result = otherFunction(param1);

  /* lots of stuff... */
  return something;
}

不知道myBigFunction()內部會發生什麼, 我們可以得出結論,result 變量只分配了一次,聲明之後是只讀的。

在其他情況下,如果必須在執行過程中多次重新分配變量,則可以使用let聲明。

2.最小化變量的範圍

變量存在於它們所創建的作用域內。代碼塊和函數體為const和let變量創建一個作用域。提高變量可讀性的一個好習慣是將變量保持在最小作用域內。

例如,以下函數是二進制搜索算法的實現:

function binarySearch(array, search) {
  let middle;
  let middleItem;
  let left = 0;
  let right = array.length - 1;

  while(left <= right) {
    middle = Math.floor((left + right) / 2);
    middleItem = array[middle];
    if (middleItem === search) { 
      return true; 
    }
    if (middleItem < search) { 
      left = middle + 1; 
    } else {
      right = middle - 1; 
    }
  }
  return false;
}

binarySearch([2, 5, 7, 9], 7); // => true
binarySearch([2, 5, 7, 9], 1); // => false

middle和middleItem變量在函數體的開頭聲明。因此,這些變量在binarySearch()函數體創建的整個作用域內都是可用的。

middle變量保留二進制搜索的中間索引,而middleItem變量保留二進制搜索的中間索引。

但是,middle和middleItem變量只在while循環代碼塊中使用。所以為什麼不直接在while代碼塊中聲明這些變量呢?

function binarySearch(array, search) {
  let left = 0;
  let right = array.length - 1;

  while(left <= right) {
    const middle = Math.floor((left + right) / 2);
    const middleItem = array[middle];
    if (middleItem === search) {
      return true; 
    }
    if (middleItem < search) {
      left = middle + 1; 
    } else {
      right = middle - 1; 
    }
  }
  return false;
}

現在,middle和middleItem變量僅存在於使用變量的作用域。 他們的生命周期和生命周期極短,因此更容易推斷其作用。

3.在接近位置聲明變量

我強烈希望在函數主體的頂部聲明所有變量,尤其是在函數較大的情況下。 不幸的是,這種做法的缺點是使我在函數中使用的意圖變量變得混亂。

盡量在接近使用位置的地方聲明變量。這樣,我們就不用猜了:嘿,我看到了這裏聲明的變量,但是它在哪裡被使用了。

假設我們有一個函數,該函數的主體中包含很多語句。 我們可以在函數的開頭聲明並初始化變量結果,但是只能在return語句中使用result:

function myBigFunction(param1, param2) {
  const result = otherFunction(param1);
  let something;

  /* * calculate something... */

  return something + result;
}

問題在於result 變量在開頭聲明,但僅在結尾使用,沒有足夠的理由在開始時聲明該變量。

讓我們通過將result 變量聲明移到return語句之前來改進這個函數

function myBigFunction(param1, param2) {
  let something;

  /* * calculate something... */

  const result = otherFunction(param1);
  return something + result;
}

現在,result變量在函數中有了它的正確位置。

4.好的命名意味着易於閱讀

從良好的變量命名的眾多規則中,我區分出兩個重要的規則。

第一個很簡單:使用駝峰命名為變量取名,並且在命名所有變量時保持一致。

const message = 'Hello';
const isLoading = true;
let count

有特殊含義的数字或字符串,變量命名通常是大寫的,在單詞之間加下劃線,以區別於常規變量

const SECONDS_IN_MINUTE = 60;
const GRAPHQL_URI = 'http://site.com/graphql';

第二條規則,在變量命名中,我認為這是最重要的:變量名稱應明確無歧義地指出哪些數據保存了該變量。

以下是一些很好的變量命名示例:

let message = 'Hello';
let isLoading = true;
let count;

message 名稱表示此變量包含某種消息,很可能是字符串。

isLoading相同,布爾值指示加載是否在進行中。

count變量表示保存一些計數結果的数字類型變量。

選擇一個明確表明其角色的變量名。

舉個例子,這樣就能看出區別了。假設看到了這樣一個函數:

function salary(ws, r) {
  let t = 0;
  for (w of ws) {
    t += w * r;
  }
  return t;
}

你能總結出這個函數的作用嗎?像ws、r、t、w這樣的變量名幾乎沒有說明它們的意圖。

相反,相同的函數,但使用了解釋性變量命名

function calculateTotalSalary(weeksHours, ratePerHour) {
  let totalSalary = 0;
  for (const weekHours of weeksHours) {
    const weeklySalary = weekHours * ratePerHour;
    totalSalary += weeklySalary;
  }
  return totalSalary;
}

該代碼清楚地說明了它的作用。 這就是良好命名的力量。

5. 引入中間變量

我比較少註釋代碼。我更喜歡編寫代碼即解釋的風格,通過對變量、屬性、函數和類的良好命名來表達意圖。

編寫自文檔代碼的一個好習慣是引入中間變量。 在處理長表達式時很有用。

考慮以下錶達式:

const sum = val1 * val2 + val3 / val4;

我們引入兩个中間變量,增強長表達式的可讀性:

const multiplication = val1 * val2;
const division       = val3 / val4;

const sum = multiplication + division;

另外,讓我們回顧一下二進制搜索實現算法:

function binarySearch(array, search) {
  let left = 0;
  let right = array.length - 1;

  while(left <= right) {
    const middle = Math.floor((left + right) / 2);
    const middleItem = array[middle];
    if (middleItem === search) {
      return true; 
    }
    if (middleItem < search) {
      left = middle + 1; 
    } else {
      right = middle - 1; 
    }
  }
  return false;
}

這裏middleItem是一個保存中間項目的中間變量。 使用中間變量MiddleItem而不是直接使用項目訪問器array [middle]可讀性更好。

與缺少middleItem解釋變量的函數版本進行比較:

function binarySearch(array, search) {
  let left = 0;
  let right = array.length - 1;

  while(left <= right) {
    const middle = Math.floor((left + right) / 2);
    if (array[middle] === search) {
      return true; 
    }
    if (array[middle] < search) {
      left = middle + 1; 
    } else {
      right = middle - 1; 
    }
  }
  return false;
}

這個版本,沒有解釋變量,可讀性就比較差。

6. 總結

變量無處不在,我們總是先聲明,分配和使用變量。

在 js 中使用變量時,第一個好的做法是使用const,否則使用let

試着保持變量的作用域盡可能小。同樣,將變量聲明往盡可能靠近使用位置。

不要低估好的命名的重要性。始終遵循這樣的規則:變量名應該清晰而明確地表示保存變量的數據。不要害怕使用較長的名字:最好是清晰而不是簡潔。

最後,少使用註釋,多寫寫代碼即的效果 。 在高度複雜的地方,我更喜歡引入中間變量。

原文:https://dmitripavlutin.com/

站長推薦

1.雲服務推薦: 國內主流雲服務商,各類雲產品的最新活動,優惠券領取。地址:阿里雲騰訊雲華為雲

2.廣告聯盟: 整理了目前主流的廣告聯盟平台,如果你有流量,可以作為參考選擇適合你的平台點擊進入

鏈接: http://www.fly63.com/article/detial/10060

沐鳴平台註冊登錄_前端WebSocket知識點總結

最近研究了下WebSocket,總結下目前對WebSocket的認知。本文不是基於WebSocket展開的一個從0到1的詳細介紹。如果你從來沒有了解過WebScoket,建議可以先搜一些介紹WebSocket的文章,這類文章還是挺多的,我就不再贅述了。

下面的內容是基於你對WebSocket有基本了解后展開的幾個小的知識點:

  1. ping/pong協議;
  2. 如何使ERROR_INTERNET_DISCONNECTED錯誤信息不显示在控制台;

ping/pong協議

背景:連接WebSocket的時候,發現WebSocket剛連接上沒過多久就斷開了,為了保持長時間的連接,就想到了ping/pong協議。

問題:

  1. ping/pong是一種特殊的幀類型嗎,還是說只是一種設計思想?
  2. js有原生方法支持發送ping/pong消息嗎

通過WebSocket協議,發現ping/pong確實是一種特殊的幀類型:

The Ping frame contains an opcode of 0x9.
The Pong frame contains an opcode of 0xA.

那麼,上面所說的opcode又是什麼東西呢?講opcode就得說到幀數據格式:


通過上圖可以發現,除了最後面的Payload Data,也就是我們要發送的數據之外,還會有一些其他信息。我覺得可以類比http請求的請求頭部分。上圖中第5-8位表示的就是opcode的內容。其餘字段的含義可以參考上述WebSocket規範,或者搜WebSocket協議數據幀格式,這類博客還是挺多的。

拿nodejs舉個例子:
在瀏覽器端發起WebSocket的時候,會發送一個http請求,注意請求頭裡面的Upgrade字段,意思就是我要升級到websocket連接:

GET /chat HTTP/1.1
Host: example.com:8000
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13

此時,nodeJS就可以監聽upgrade事件,去做拒絕或者升級操作,注意下這個事件裏面有個參數socket:

socket: <stream.Duplex> Network socket between the server and client

socket有一個write方法,該方法是可以用來寫幀數據的,也就是上面幀格式裏面的全部數據,而不僅僅是Payload Data。

ws倉庫就是使用了socket的write方法發送了根據WebSocket協議定義的ping/pong,部分關鍵代碼如下:

doPing(data, mask, readOnly, cb) {
  this.sendFrame(
    Sender.frame(data, {
      fin: true,
      rsv1: false,
      opcode: 0x09, // ping opcode
      mask,
      readOnly
    }),
    cb
  );
}
doPong(data, mask, readOnly, cb) {
  this.sendFrame(
    Sender.frame(data, {
      fin: true,
      rsv1: false,
      opcode: 0x0a, // pong opcode
      mask,
      readOnly
    }),
    cb
  );
}
sendFrame(list, cb) {
  if (list.length === 2) {
    this._socket.cork();
    this._socket.write(list[0]);
    this._socket.write(list[1], cb);
    this._socket.uncork();
  } else {
    this._socket.write(list[0], cb);
  }
}

所以,nodeJS是可以實現WebSocket協議定義的ping/pong幀的。原因是我們可以拿到socket對象,並且該對象提供了可以發送完整幀數據的方法。那麼瀏覽器端呢?

瀏覽器提供了原生的WebSocket構造函數用來創建一個WebSocket實例,該實例只提供了一個send方法,並且該send方法只能用來發送上述協議中Payload Data的內容,瀏覽器會根據send的參數自動生成一個完整的幀數據。所以,在瀏覽器端是沒法控制除了Payload Data之外的幀內容的,也就是無法自定義opcode。所以,也就實現不了WebSocket規範定義的ping/pong協議。

此時,我們就可以把ping/pong當成一種用來解決特定問題的設計模式。既然我們只能自定義Payload Data的內容,那麼我們可以簡單的在Payload Data裏面添加一個字段用於區分是ping/pong幀,還是普通的數據幀,比如type。當type字段是ping/pong的時候表明是ping/pong幀,如果是其他字段才是普通的數據幀。

如何使ERROR_INTERNET_DISCONNECTED錯誤信息不显示在控制台

當斷網的時候,連接WebSocket會發現瀏覽器控制台會log一個錯誤信息:

WebSocket connection to 'ws://...' failed: Error in connection establishment: net::ERR_INTERNET_DISCONNECTED

原先的開發經驗是,控制台如果有報錯的話,肯定是代碼某個地方有錯誤,並且沒有被我們的代碼捕獲到,所以就會在控制台拋出,如果使用了try catch 或者全局的window.onerror捕獲到了錯誤信息,就不會在控制台打印了。所以,我就嘗試了上述方法,發現捕捉不到,還是會在控制台log。

另外,WebSocket提供了兩個事件,onerror和onclose。當發生上述錯誤信息的時候,onerror和onclose是會被調用的。但是,此時控制台還是會有上述報錯信息。

經過一番查找,發現無法阻止上述錯誤信息显示在控制台。

那麼,為什麼瀏覽器會設計這樣的行為呢?猜測原因如下:
上面說到通過onerror和onclose事件是可以捕捉到WebSocket創建失敗的,但是,查看這兩個事件的參數,我們只能從中找到一個code是1006的屬性,輸出在控制台的錯誤信息ERR_INTERNET_DISCONNECTED在參數裏面找不到。接着,看一下code1006相關的東西:

User agents must not convey any failure information to scripts in a way that would allow a script to distinguish the following situations:

*   A server whose host name could not be resolved.
*   A server to which packets could not successfully be routed.
*   A server that refused the connection on the specified port.
*   A server that failed to correctly perform a TLS handshake (e.g., the server certificate can't be verified).
*   A server that did not complete the opening handshake (e.g. because it was not a WebSocket server).
*   A WebSocket server that sent a correct opening handshake, but that specified options that caused the client to drop the connection (e.g. the server specified a subprotocol that the client did not offer).
*   A WebSocket server that abruptly closed the connection after successfully completing the opening handshake.

In all of these cases, the the WebSocket connection close code would be 1006, as required by WebSocket Protocol. 

Allowing a script to distinguish these cases would allow a script to probe the user's local network in preparation for an attack.

從上述規範可以看到,規範是禁止瀏覽器向腳本傳遞下述造成WebSocket連接失敗的具體原因的,只允許向腳本傳遞一個1006的code碼,否則,用戶就可以探測到局部網的信息,進而發起攻擊。舉個例子,上面那種斷網的情況,腳本中只能得到1006的狀態碼,比如下面這種報錯

Error in connection establishment: net::ERR_CONNECTION_REFUSED

也只能從onerror中獲得一個1006的code碼。

所以,作為開發人員,瀏覽器要怎麼在告訴我們具體的錯誤信息的同時又阻止有可能發生的攻擊呢?答案就是在控制台把具體的錯誤信息log出來。

站長推薦

1.雲服務推薦: 國內主流雲服務商,各類雲產品的最新活動,優惠券領取。地址:阿里雲騰訊雲華為雲

2.廣告聯盟: 整理了目前主流的廣告聯盟平台,如果你有流量,可以作為參考選擇適合你的平台點擊進入

鏈接: http://www.fly63.com/article/detial/10058

沐鳴平台_通過事件實時獲取