沐鳴娛樂_利用Proxy,如何優雅實現JSBridge模塊化封裝

背景

關於jsBridge的一些基礎知識,在網絡上有很多文章可以參考:

《H5與Native交互之JSBridge技術》

《JSBridge的原理》

最近公司在做一個項目,通過把我們自己的Webview植入第三方APP,然後我們的業務全部通過H5實現。至於為什麼不直接用第三方APP WebView,主要是身處金融行業,需要做一些風控相關功能。

由於是Hybrid APP的性質,所以web與Native的通信是無法避免的;而為什麼我要封裝jsBridge,主要在於下面兩點:

公司APP的JSBridge提供了數據的序列化和全局函數的注入,而我們這次由於包大小考慮,這一塊需要H5自己來實現;

原生提供的接口協議太多,記住麻煩;

回調的寫法不太人性化,期望Promise;

由於本次項目只涉及到Andriod,所以沒有關於ios的處理,但我自認為他們只是協議的不同,Web的處理可以相同。

原理淺談

看上圖的通信實現(圖片來源於文章開頭的文章),簡單說一下通信過程;

Webview加載時會將原生提供的JSBridge方法注入到window對象上,比如:window.JSBridge.getDeviceInfo就是原生提供的可以讀取一些設備標識信息的接口;

H5通過window調用原生接口,基本都需要傳參,比如這次處理成功或則處理失敗的結果回調的,還有一些參數設置,拿上面給的方法來舉例:

window.JSBridge.getDeviceInfo({
  token: '*&^%$$#*',
  onOk(data) {
    save(data);
  },
  onError(error) {
    console.log(error.message);
  }
});

原生響應H5的調用成功或失敗后,就執行H5傳遞過來的回調函數;

過程結束;

看上面的通信過程,貌似很簡單。但這裏面存在一些協議的問題:

首先H5與原生端的通信消息,是只支持字符串的,如果要傳JS對象,那就先序列化;

序列化帶來的後果又是,對象中的函數就無法傳遞;

而就算函數傳過去了,也是存在問題的,由於安全的限制,webview和js的執行沒有在一個容器中,回調這種局部函數是找不到的,所以是需要將回調函數註冊到全局;

所以下面就來解決這些問題

一步一步的具體實現

接口協議封裝

什麼意思喃?看下面的圖:

由於APP端協議及分包問題, 存在多個Bridge, 比如MBDevice、MBControl、MBFinance,上面列出來的只是一小部分,對於web來說記憶這些接口是一件很費事的事;還有就是以前我調APP的JSBridge, 總有下面這樣的代碼:

window.JSBridge && window.JSBridge.getDeviceInfo && window.JSBridge.getDeviceInfo({ ... })

至於上面,所以加了一層封裝,實現的核心就是Proxy和Map,具體實現看下面的偽代碼:

const MBSDK = {
};

// sdk 提供的方法白名單
const whiteList = new Map([
  ['setMaxTime', 'MBVideo'],
  ['getDeviceInfo', 'MBDevice.getInfo'],
  ['close', 'MBControl'],
  ['getFinaceInfo', 'MBFinance.getInfo'],
]);

const handler = {
  get(target, key) {
    if (!whiteList.has(key)) {
      throw new Error('方法不存在');
    }
    const parentKey = whiteList.get(key);
    function callback() {
      return [...parentKey.split('.'), key];
    }
    return new Proxy(callback, applyHandler); // funcHandler後面再展開
  },
};
export default new Proxy(MBSDK, handler);

基於上面的封裝,調用時,代碼就是下面這樣

sdk.setMaxTime({
      maxTime: 10,
    }).then(() => {
      console.log('設置成功');
    }, () => {
      window.alert('調用失敗');
    });

序列化與回調註冊

上面已經列了為什麼需要回調函數全局註冊和序列化,這裏主要說一下實現原理,總得來說分兩步;

回調函數剝離,全局註冊;

參數序列化;

回調函數剝離和參數序列化

其實很好實現,直接展開運算符搞定:

  const { onOk, onError, ...others } = params; // 回調函數剝離
  const str = JSON.stringify(others); // 參數序列化

函數全局註冊

看了很多文章的一些實現,思路基本一致,比如下面這樣

window.bridgeCallbacks = {};
const callBacks = window.bridgeCallbacks;
const { onOk, onError, ...others } = params; // 回調函數剝離

const callbackId = generateId(); // 產生一個唯一的隨機數Id

callBacks[`success_${callbackId}`] = onOk;
callBacks[`onError${callbackId}`] = onError;

others.success = `window.bridgeCallbacks.success_${callbackId}`
// ....
// 調用jdk代碼

這是一種很容易想到的問題,但卻存在一些問題,比如:

bridgeCallbacks全局會註冊很多屬性,因為Native調用並沒有清理,而onOk這種很多時候是一個閉包,由於有引用,最後導致的問題就是內存泄露;

就算處理了第一步的問題,webview無響應怎麼辦,那回調就會被一直掛起,確少超時響應邏輯

callbackId的唯一性不好保證;

基於以上考慮,我換了一個方案,採用回調隊列,因為APP端說過,回調是按順序的,不會插隊;

class CallHeap {
  constructor() {
    this.okQueue = [];
    this.errorQueue = [];
  }
  success = (args) => {
    // 成對彈出回調:成功時,不止要處理成功的回調,失敗的也要同時彈出,
    const target = this.okQueue.shift();
    this.errorQueue.shift();
    target && target(args);
  }
  error = (args) => {
    const target = this.errorQueue.shift();
    this.okQueue.shift();
    target && target(args);
  }
  addQueue(onOk = Null, onError = Null) {
    this.okQueue.push(onOk);
    this.errorQueue.push(onError);
  }
}

window.bridgeCallbacks = {};
const callBacks = window.bridgeCallbacks;

function applyhandler() {
  const { onOk, onError, ...others } = params; // 回調函數剝離
  if (onOk || onError) {
      const callKey = transferKey || key; // transferKey || key後面會提到
      // 如果全局未註冊,則先註冊對應的調用域
      if (!callbacks[callKey]) {
        callbacks[callKey] = new CallHeap();
      }
      // 添加回調
      callbacks[callKey].addQueue(onOk, onError);

      others.success = `callBacks.${callKey}.success`;
      others.error = `callBacks.${callKey}.error`;
    }
    // 調用jdk代碼
}

基於以上的實現,就可以保證發起多個Native請求,並保證有序回調;如果成功,成功回調被響應時,響應的失敗回調也會被彈出,因為回調函數式存在數組中的,所以執行完后,引用就不會再存在。

完整實現

看了上面的代碼實現,但核心好像還沒有提及,那就是調用參數的攔截。前面我們用Proxy的get優雅的實現了SDK方法的攔截,這裡會接着採用Proxy的apply方法來攔截方法調用的傳參,直接看代碼吧:

// 結合最上面接口協議封裝的代碼一起看
const applyHandler = {
  apply(target, object, args) {
    // transferKey 用於getFinaceInfo與getDeviceInfo這種數據命名重複的
    const [parentKey, key, transferKey] = target();
    console.log('res', parentKey, key);
    const func = (SDK[parentKey] || {})[key];

    const { onOk, onError, ...params } = args[0] || {};

    if (onOk || onError) {
      const callKey = transferKey || key;
      if (!callbacks[callKey]) {
        callbacks[callKey] = new CallHeap();
      }
      callbacks[callKey].addQueue(onOk, onError);

      others.success = `callBacks.${callKey}.success`;
      others.error = `callBacks.${callKey}.error`;
    }

    return func && (window[parentKey][key])(JSON.stringify(params));;
  }
};

Promise 封裝

前面吹過的牛逼還有兩個沒實現,比如:

promise支持

超時調用

首先來複習一下,怎麼封裝一個支持Promise的setTimeout函數:

function promiseTimeOut(time) {
  return new Promise((resolve, reject) => {
    setTimeout(resolve, time);
  });
}

promiseTimeOut(1000).then(() => {
  console.log('time is ready');
})

如果對上面這個封裝不陌生,那基於回調函數的Promise化就變得簡單了

talk is cheap, show me your code

完整實現:

const MBSDK = {
};

// sdk 提供的方法白名單
const whiteList = new Map([
  ['setMaxTime', 'MBVideo'],
  ['getDeviceInfo', 'MBDevice.getInfo'],
  ['close', 'MBControl'],
  ['getFinaceInfo', 'MBFinance.getInfo'],
]);

const applyHandler = {
  apply(target, object, args) {
    // transferKey 用於getFinaceInfo與getDeviceInfo這種數據命名重複的
    const [parentKey, key, transferKey] = target();
    // FYX 編程
    const func = (window[parentKey] || {})[key];
    // 設置一個默認的超時參數,支持配置
    const { timeout = 5000, ...params } = args[0] || {};

    return new Promise((resolve, reject) => {
      const callKey = transferKey || key;
      if (!callbacks[callKey]) {
        callbacks[callKey] = new CallHeap();
      }
      const timeoutId = setTimeout(() => {
        // 超時,主動發起錯誤回調
        window.callBacks[callKey].error({ message: '請求超時' });
      }, timeout);
      callbacks[callKey].addQueue((data) => {
        clearTimeout(timeoutId);
        resolve(data);
      }, (data) => {
        clearTimeout(timeoutId);
        reject(data);
      });
      params.success = `callBacks.${callKey}.success`;
      params.error = `callBacks.${callKey}.error`;
      func && (window[parentKey][key])(JSON.stringify(params));
    }).catch((error) => {
      console.log('error:', error.message);
    });
  }
};

const handler = {
  get(target, key) {
    if (!whiteList.has(key)) {
      throw new Error('方法不存在');
    }
    const parentKey = whiteList.get(key);
    function callback() {
      return [...parentKey.split('.'), key];
    }
    return new Proxy(callback, applyHandler); // funcHandler後面再展開
  },
};

export default new Proxy(MBSDK, handler);

而調用時,基本上,就可以這樣玩了:

sdk.setMaxTime({
      maxTime: 10,
    }).then(() => {
      console.log('設置成功');
    }, () => {
      window.alert('調用失敗');
    });

解惑

- func.call(null, JSON.stringify(params))  // 以前的
+ func && (window[parentKey][key])(JSON.stringify(params)); // 現在的

開始函數的調用是採用func.call來實現的,當時我本地mock過,沒有問題。但在webview中就彈出了下面這樣一個錯誤:

java bridge method can’t be invoked on a non-injected object

經過各種goggle,百度,查到的都是一條關於Andriod的注入漏洞。而至於我這裏通過JS的方式把bridge指向的函數地址,賦值給一個變量名,然後再通過變量名來調用就會報上面這個錯誤,我個人的猜測有兩個:一是協議這樣規定的;二是this指向問題。

如果有知道為什麼的大佬,還請不吝賜教,謝謝。

通過這一次的封裝,自己對Proxy的應用更加熟練了,將所學的知識運用到工作中,不得不說是一件非常愉快的事情。這也是自己第二篇關於ES6深入理解的文章;
第一篇: 重新認識ES6中的Set

原文見:issue , 如有不嚴謹之處,還請及時指正。

站長推薦

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

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

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

沐鳴登錄網站_把同事的代碼重寫得乾淨又整潔,老闆卻讓我做回滾?

夜深了。

我的同事把這周寫的代碼提交了。我們在開發一個圖形編輯器畫布,已經實現了形狀調整功能,即通過拖拽形狀邊緣的手柄來調整形狀(比如矩形和橢圓形)。

代碼可以運行。

但重複代碼有點多。每一種形狀(比如矩形和橢圓形)有不同的手柄,往不同方向拖拽手柄對形狀的位置和大小影響也不一樣。如果用戶同時按住 Shift 鍵,在改變大小的同時要保持比例不變。這裏涉及了很多數學運算。

代碼看起來像這樣:

letRectangle = {
resizeTopLeft(position,size,preserveAspect,dx,dy){
// 10 行重複的數學運算代碼
},
resizeTopRight(position,size,preserveAspect,dx,dy){
// 10 行重複的數學運算代碼
},
resizeBottomLeft(position,size,preserveAspect,dx,dy){
// 10 行重複的數學運算代碼
},
resizeBottomRight(position,size,preserveAspect,dx,dy){
// 10 行重複的數學運算代碼
},
};

letOval = {
resizeLeft(position,size,preserveAspect,dx,dy){
// 10 行重複的數學運算代碼
},
resizeRight(position,size,preserveAspect,dx,dy){
// 10 行重複的數學運算代碼
},
resizeTop(position,size,preserveAspect,dx,dy){
// 10 行重複的數學運算代碼
},
resizeBottom(position,size,preserveAspect,dx,dy){
// 10 行重複的數學運算代碼
},
};

letHeader = {
resizeLeft(position,size,preserveAspect,dx,dy){
// 10 行重複的數學運算代碼
},
resizeRight(position,size,preserveAspect,dx,dy){
// 10 行重複的數學運算代碼
},
}

letTextBlock = {
resizeTopLeft(position,size,preserveAspect,dx,dy){
// 10 行重複的數學運算代碼
},
resizeTopRight(position,size,preserveAspect,dx,dy){
// 10 行重複的數學運算代碼
},
resizeBottomLeft(position,size,preserveAspect,dx,dy){
// 10 行重複的數學運算代碼
},
resizeBottomRight(position,size,preserveAspect,dx,dy){
// 10 行重複的數學運算代碼
},
};

這些重複代碼看起來真的很礙眼。

這樣的代碼不夠乾淨。

大部分重複是因為朝相同方向調整形狀的代碼都差不多,比如 Oval.resizeLeft() 和 Header.resizeLeft() 就很類似。

其他重複是因為同一種形狀的方法之間很相像,比如 Oval.resizeLeft() 和 Oval 其他的方法就很類似。另外,Rectangle、Header 和 TextBlock 之間也有重複的地方,因為文本框也是矩形。

我想到了一個辦法。

我們可以給代碼分組,把重複代碼移除掉,比如像下面這樣。

letDirections = {
top(...) {
// 5 行不一樣的數學運算代碼
},
left(...) {
// 5 行不一樣的數學運算代碼
},
bottom(...) {
// 5 行不一樣的數學運算代碼
},
right(...) {
// 5 行不一樣的數學運算代碼
},
};

letShapes = {
Oval(...) {
// 5 行不一樣的數學運算代碼
},
Rectangle(...) {
// 5 行不一樣的數學運算代碼
},
}

然後,把它們的行為組合起來。

let{top, bottom, left, right} = Directions;

functioncreateHandle(directions){
// 20 行代碼
}

letfourCorners =[
createHandle([top,left]),
createHandle([top,right]),
createHandle([bottom,left]),
createHandle([bottom,right]),
];
letfourSides =[
createHandle([top]),
createHandle([left]),
createHandle([right]),
createHandle([bottom]),
];
lettwoSides =[
createHandle([left]),
createHandle([right]),
];

functioncreateBox(shape,handles){
// 20 行代碼
}

letRectangle = createBox(Shapes.Rectangle,fourCorners);
letOval = createBox(Shapes.Oval,fourSides);
letHeader = createBox(Shapes.Rectangle,twoSides);
letTextBox = createBox(Shapes.Rectangle,fourCorners);

代碼量減少了一半,重複代碼完全消失了!多麼乾淨。如果要修改某個形狀或方向的行為,只需要在一個地方做出改動,不需要修改所有的方法。

夜已深,我把改好的代碼提交到 master 分支,然後上床睡覺。因為幫同事把雜亂的代碼清理乾淨了,我心裏還引以為豪。

第二天

事情並沒有像我期待的那樣發生。

老闆找我談話,他們希望我把代碼回滾回去。我感到很驚訝,畢竟原先的代碼簡直就是一團亂麻,而我改得很乾凈啊!

我很不情願地答應了,但幾年之後,我才意識到他們其實是對的。

必經之路

痴迷於“乾淨代碼”和刪除重複代碼是我們很多人都會經歷的一個階段。當我們對自己的代碼不是很自信時,就很容易將自我價值感和職業自豪感與一些可以被衡量的東西聯繫在一起,比如嚴格的 lint 規則、命名模式、文件結構、不重複代碼實踐。

我們沒辦法自動去除重複代碼,但可以自己動手做。每次修改代碼之後,我們可以很容易地知道重複代碼是少了還是多了。所以,去除重複代碼感覺就像是在改進代碼質量。更糟糕的是,它擾亂了人們的認同感,讓他們覺得“我是那種編寫乾淨代碼的人”,但這其實無異於自我欺騙。

一旦學會了抽象,我們就很容易對這種能力產生很高的期望,每當看到有重複代碼就會想要對它們進行抽象。在寫了幾年代碼之後,我們發現重複代碼到處都是,而抽象成了我們獲得的一項超級能力。如果有人告訴我們說抽象是一種美德,那我們肯定會深信不疑,並且會因為別人不崇尚“乾淨代碼”而對他們品頭論足。

現在,我知道之前的代碼重構就是一個災難,原因如下。

  • 首先,我沒有事先和寫代碼的人溝通。我直接修改了他們的代碼並提交,沒有和他們討論。即使這是一種改進(但我現在不這麼認為了),但我這樣的行事方式並不值得稱道。一個健康的工程團隊應該以信任為基礎,不經過討論就修改他人的代碼會對團隊協作造成沉重的打擊。

  • 其次,天下沒有免費的午餐。我以犧牲靈活性為代價,以此來減少重複代碼,這算不上是一個好的權衡。例如,後來我們要求不同形狀的不同手柄具備一些特殊的行為,被我重構過的代碼需要修改多次才能滿足需求,而原先“雜亂”的代碼卻可以很容易實現這些需求。

那麼,我的意思是我們應該盡量寫“臟”代碼嗎?當然不是。我只是建議大家在考慮什麼是“乾淨”或“臟”代碼時進行深度思考。你當時有什麼樣的感覺?厭惡?正義?美麗?優雅?你可以肯定這些品質會帶來實質性的工程成果嗎?它們又是如何影響代碼的編寫和修改方式的?

我確實沒有深入思考過這些事情。我只考慮到代碼本身,但從來沒有想過代碼與團隊之間的演化關係。

編碼就像是一段旅程,想想你從寫第一行代碼到現在走了多遠。當第一次通過提取函數或重構類讓複雜的代碼變簡單,我覺得那是一種樂趣。如果你對自己的“傑作”感到自豪,那麼就很容易掉入追求乾淨代碼的旋渦。

但請不要止步於此,不要只做一個乾淨代碼狂熱者。寫出乾淨的代碼並不是我們的終極目標,我們只是通過這種方式嘗試找到處理系統複雜性的方法。當你不確定代碼改動會對代碼庫造成怎樣的影響,在未知的海洋中需要燈塔的指引,那麼這不失為一種防禦機制。

寫出乾淨的代碼可以作為一種指引,但後面的路還是要自己走。

原文鏈接:Goodbye clean code!

鏈接:https://www.infoq.cn/article/dNO484YEeumvC6b6ZNWL

站長推薦

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

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

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

沐鳴總代_理解 OAuth 2.0 認證流程

OAuth 2.0 標準的 RFC 比較難讀懂,本文盡量把認證流程說明白。

認證方式

OAuth 2.0 共有 4 種訪問模式:

  • 授權碼模式(Authorization Code),適用於一般服務器端應用
  • 簡化模式(Implicit),適用於純網頁端應用,不過現在推薦使用 PKCE 作為替代
  • 密碼模式(Resource owner password credentials),不介紹
  • 客戶端模式(Client credentials),不介紹

另外注意 OAuth 服務本身必須是 HTTPS 的,而三方應用可以是 HTTP 的。

Authorization Code

假設我們的網站有一個功能是同步用戶在 Github 的所有倉庫。對接 OAuth 流程大致分為 5 個步驟:

  1. 在 Github 的 OAuth 頁面上註冊網站信息。在網站發布前就要做好
  2. 用戶點擊網站上的“同步 Github 倉庫”按鈕,開始 OAuth 認證流程
  3. 瀏覽器彈出 Github 認證窗口,詢問“是否允許網站 XXX 的訪問”,用戶點擊“允許”
  4. Github 得知用戶點了“允許”后,生成授權碼(Authorization Code),並將用戶重定向到我們的網站里,網站後台收到授權碼后,向 Github 請求ACCESS_TOKEN
  5. 網站後台從 Github 收到 ACCESS_TOKEN,接着向 Github 拉取該用戶所有的倉庫

具體流程如下圖:

在授權碼方式下,ACCESS_TOKEN 只會存在我們網站的服務器里,用戶端從始至終都獲取不到這個信息,我們不必害怕用戶的電腦中毒了而導致 ACCESS_TOKEN 泄露。

(更多安全相關的考慮參考最後的參考文章)

Implicit

Implicit 是為純網頁應用設計的,與 Authorization Code 模式相比:

CLIENT_SECRET

整體流程如下圖:

Implicit 設計之初,由於瀏覽器的同源策略,不允許跨站請求,因此 Authorization Code 不可行。現在由於瀏覽器普遍支持 CORS ,且 Implicit 本身也在安全風險,目前建議使用 PKCE。

原文 https://lotabout.me/2020/OAuth-2-workflow/

站長推薦

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

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

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

沐鳴下載_React 最重要也最容易被遺忘的屬性 $$typeof

為什麼說 $$typeof 是最重要的屬性?因為它是代碼安全的一道重要防線。

如果你用過 react,對 type、 props、 key、 和 ref 應該熟悉。 但你不一定知道 $$typeof

首先簡單介紹下jsX

當你在寫 jsX 時,其實你在調用createElement方法。

react.createElement(
  /* type */ 'marquee',
  /* props */ { bgcolor: '#ffa7c4' },
  /* children */ 'hi'
)

createElement 會返回一個對象,我們稱此對象為React的 元素(element),它告訴 React 下一個要渲染什麼。你的組件(component)返回一個它們組成的樹(tree)。

{
  type: 'marquee',
  props: { //... },
  key: null,
  ref: null,
  $$typeof: Symbol.for('react.element'),
}

html的插入轉義

在客戶端 UI 庫變得普遍且具有基本保護作用之前,應用程序代碼通常是先構建 html,然後把它插入 DOM 中:

const messageEl = document.getElementById('message');
messageEl.innerHTML = '<p>' + message.text + '</p>';

這樣看起來沒什麼問題,但當你 message.text 的值類似 ‘<img src onerror=”stealYourPassword()”>’ 時, 你不會希望別人寫的內容在你應用的 HTML 中逐字显示的。

為什麼防止此類攻擊,你可以用只處理文本的 document.createTextNode() 或者 textContent等安全的 API。你也可以事先將用戶輸入的內容,用轉義符把潛在危險字符( <、 >等)替換掉。

儘管如此,這個問題的成本代價很高,且很難做到用戶每次輸入都記得轉換一次。 因此像React等新庫會默認進行文本轉義:

如果 message.text 是一個帶有 <img> 或其他標籤的惡意字符串,它不會被當成真的 <img> 標籤處理,React 會先進行轉義然後插入 DOM 里。所以 <img> 標籤會以文本的形式展現出來。

在 React 中如果元素要渲染 HTML,那麼需要使用 dangerouslySetInnerHTML={{ __html: message.text }}

這意味着React完全不懼注入攻擊了嗎?不,HTML 和 DOM 暴露了大量攻擊點,對 React 或者其他 UI 庫來說,要減輕傷害太難或進展緩慢。大部分存在的攻擊方向涉及到屬性,例如,如果你渲染 <a href={user.website},要提防用戶的網址是 ‘JavaScript: stealYourPassword()’。 像 <div {…userData}> 寫法幾乎不受用戶輸入影響,但也有危險。

不過,轉義文本這第一道防線可以攔下許多潛在攻擊,知道這樣的代碼是安全的就夠了嗎?不一定,所以我們需要$$typeof

關於 $$typeof

如果你的服務器有允許用戶存儲任意 JSON 對象的漏洞,而前端需要一個字符串,這可能會發生一個問題:

// 服務端允許用戶存儲 JSON
let expectedTextButGotJSON = {
  type: 'div',
  props: {
    dangerouslySetInnerHTML: {
      __html: '/* 把你想的放在這裏 */'
    },
  },
  // ...
};
let message = { text: expectedTextButGotJSON };

// React 0.13 中有風險
<p>
  {message.text}
</p>

在這個例子中,React 0.13 很容易受到 XSS 攻擊。雖然 這個攻擊是服務端存在漏洞導致的。不過,從 React 0.14 開始,這個問題修復了。

React 0.14 修復手段是在虛擬DOM中添加 $$typeof,使用 Symbol 標記每個 React 元素(element):

Symbol類型是非常重要的,因為JSON不支持 Symbol 類型。 所以即使服務器存在用JSON作為文本返回安全漏洞,JSON 里也不包含 Symbol.for(‘react.element’)。React 會檢測 element.$$typeof,如果元素丟失或者無效,會拒絕處理該元素。

特意用 Symbol.for() 的好處是 Symbols 通用於 iframes 和 workers 等環境中。因此無論在多奇怪的條件下,這方案也不會影響到應用不同部分傳遞可信的元素。同樣,即使頁面上有很多個 React 副本,它們也 「接受」 有效的 $$typeof 值。

為什麼是這個数字?因為 0xeac7 看起來有點像 「React」。

站長推薦

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

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

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

沐鳴平台_當疫情過後,“遠程辦公”命運幾何?

曾經有一個經典的問題:假如給你一間房屋,溫度適中,食物充足,且配有手機,WiFi和充電器,你能待多久?是天荒地老,還是終日難熬?

如今在疫情肆虐下,全國人民都在用親身實踐,回答上述問題,但與直覺相悖,不少人感受到的卻並非是閑暇時光帶來的愉悅,而是無聊和空虛,以及對回公司上班某種淡淡的渴望。

這不難理解,畢竟說到底,人類是社會化動物,大腦80%思考的都是與“社交”相關的東西,就像遠古部落時期對個體的最重懲罰不是處死,而是驅逐出部落(這種懲罰後來轉化為古代的流放和現代的囚禁),人類的群體協作需求,是一種底層心理訴求。

當然,一定程度上,這種需求能夠被技術手段滿足。

最近两天,有些企業已經通過遠程辦公的方式復工了,遠程辦公類軟件也紛紛宣布免費,譬如阿里釘釘發布的《在家辦公,在線辦公指南》就宣布,面向1000萬家企業組織免費開放。據億歐智庫統計,目前已有17家企業的21款產品對外宣布免費開放遠程辦公軟件,這些免費開放的遠程辦公產品當中以即時通訊工具為主。

這也讓許多人好奇:當疫情過後,遠程辦公能否從非常時期的“不得不”,變成更多人們主動選擇的新趨勢?

我的答案是:很難。

其實追溯歷史,遠程辦公並非一種新生事物。

早在1979年,IBM就曾提出這一概念。為緩解總部主機擁堵問題,IBM將終端機安到了五位員工家裡——某種意義上,這就是現代企業遠程辦公的“雛形”。

到了1983年,大概已有2000名IBM員工通過遠程方式辦公。2009年,IBM 的一份報告稱,“IBM 在全球173 個國家共計 386000 名員工當中,大約有 40% 的員工根本就沒有任何實體辦公場所”,這為IBM節省了5800萬平方英尺的辦公空間和將近20億美元的成本。

進入信息時代,所謂生產資料往往就是一台聯網的電腦加上員工大腦,新技術工具對時間和空間雙重約束的打破,讓遠程辦公迎來了迅猛增長,且看起來正在愈演愈烈,根據領英的數據,自2016年以來,在領英上提及彈性工作制的職位發布數量增長了78%。

另外,易被忽視的是,越是在人口稠密,協作複雜的發達城市,社會配套設施對遠程辦公的支持力度越大。譬如除了咖啡館這種老牌“第三空間”,在東京都世田穀區的小田急線經堂站檢票口內,就出現過類似電話亭的單間隔間,1.2米見方,高2.3米,配有桌子,沙發,電源和USB,目的是讓通勤路上的員工在更專業的環境中辦公。

嗯,至少在直覺層面,遠程辦公對協作密度(注意,不是效率)的提升日趨成為共識,但隨着信息技術對“工作時間”的模糊化處理,許多僱員發現,他們的工作時間變長而非變短了,這種不確定性往往令人感到不悅。

聯合國勞工組織高級研究員喬恩·梅辛傑在一份報告中還稱,遠程辦公容易導致精神高度緊張和失眠,在那些在家辦公的人中,42%的人有失眠癥狀,而這個比例在辦公室職員中為29%。

如此說來,遠程辦公似乎更多是“利於”僱主,不“利於”僱員?

答案沒那麼簡單。

最典型的案例還是IBM。2017年,作為遠程辦公的鼻祖,IBM就開始取消遠程辦公。至於原因(拋去商業層面不談),最著名的理論就是“艾倫曲線”:1977年,麻省理工教授托馬斯·艾倫在觀察科學家和工程師的交流模式時發現,兩張辦公桌距離越遠,他們就越沒可能交流,若兩張辦公桌距離超過30米,他們定期交流的可能性接近於零。

那麼新技術工具的出現,能否改變艾倫曲線?答案是並沒有。科學家本·瓦貝爾就曾與IBM做過一項研究,他們發現同一辦公室里的員工對於一個潛在問題平均要交流38次,而不同工作場地的員工遇到問題時交流只有8次,且越是相熟的人交流越密切。

為什麼會這樣?一個不錯的解釋是“八分之一秒延遲”,在遠程協作中,這道看似細弱的物理門檻,會讓人與人間不可名狀的協作體驗大打折扣。

另外,人與人之間綿密的協作細節不止於語言。作家Jerry Useem舉過一個很好的例子:波音 727逼仄的駕駛艙只夠容納三名成員,但他們之間無需說太多話就能對很多事瞬間達成共識,“工程師指一下燃油量表,燃料很低的訊息就能傳達給其他兩人。他下面採取的步驟也足以通過儀錶板上的各種手勢來傳達,且只要用幾個很短的詞就能表達出來……整個過程只需 24 秒,如果是要通過电子郵件溝通,也許至少也得需要幾十條信息才能搞定。”

所以如今看來,如無意外,實體辦公永遠不會被取代。僅憑線上工具,人們互相之間建立信任所花的時間,要比實體辦公更久。

當然,遠程辦公依舊不可或缺。它不僅是特殊時期的“不得不”,也將永遠與實體辦公互補共存——更重要的是,隨着新一代信息技術的持續迭代,這種共存的意義也更為深遠,尤其是在人口稠密,協作複雜的發達城市。

譬如扎克伯格就曾表示,到2030年,AR和VR的遠程辦公技術能夠讓員工在世界任何地方進行遠程工作,AR和VR更為出色的“臨場感”,或許能幫助人們解決不斷上漲的住房成本,人口稠密的城市管理,以及地理因素造成的機會不平等現象等社會問題。

在我看來,這也是遠程辦公最光明的未來。

作者:李北辰,獨立撰稿人,國內數十家媒體專欄作家,曾供職《南都周刊》《華夏時報》《財經》等媒體

本文系投稿稿件,作者:李北辰;轉載請註明作者姓名和“來源:億歐”;文章內容系作者個人觀點,不代表億歐對觀點贊同或支持。

原文 https://www.iyiou.com/p/122971.html

站長推薦

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

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

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

沐鳴網址_10 個實用的 JavaScript 小技巧

我一直在尋找提高效率的新方法。JavaScript 總是充滿令人出乎意料的驚喜。

1. 將 arguments 對象轉換為數組

arguments 對象是函數內部可訪問的類似數組的對象,其中包含傳遞給該函數的參數的值。

但它與其他數組不同,我們可以訪問其元素值並獲得長度,但是不能在其上使用其他的數組方法。

幸運的是,我們可以將其轉換為常規數組:

var argArray = Array.prototype.slice.call(arguments);

2. 對數組中所有的值求和

我最初的想法是使用循環,但是那樣做太費事了。

var numbers = [3, 5, 7, 2];
var sum = numbers.reduce((x, y) => x + y);
console.log(sum); // returns 17

3. 條件短路

我們有以下代碼:

if (hungry) {
    goToFridge();
}

通過將變量與函數一起使用,我們可以使其更短:

hungry && goToFridge()

4. 對條件使用邏輯或

我曾經在函數的開頭聲明自己的變量,只是為了避免在出現任何意外錯誤的情況下得到 undefined。

function doSomething(arg1){ 
    arg1 = arg1 || 32; // 如果變量尚未設置,則 arg1 將以 32 作為默認值
}

5. 逗號運算符

逗號運算符( ,)用來評估其每個操作數(從左到右)並返回最後一個操作數的值。

let x = 1;

x = (x++, x);

console.log(x);
// expected output: 2

x = (2, 3);

console.log(x);
// expected output: 3

6. 用 length 調整數組大小

你可以調整數組大小或清空數組。

var array = [11, 12, 13, 14, 15];  
console.log(array.length); // 5  

array.length = 3;  
console.log(array.length); // 3  
console.log(array); // [11,12,13]

array.length = 0;  
console.log(array.length); // 0  
console.log(array); // []

7. 通過數組解構對值進行交換

解構賦值語法是一種 JavaScript 表達式,可以將數組中的值或對象中的屬性解壓縮為不同的變量。

let a = 1, b = 2
[a, b] = [b, a]
console.log(a) // -> 2
console.log(b) // -> 1

8. 隨機排列數組中的元素

我每天我都在洗牌’

var list = [1, 2, 3, 4, 5, 6, 7, 8, 9];
console.log(list.sort(function() {
    return Math.random() - 0.5
})); 
// [4, 8, 2, 9, 1, 3, 6, 5, 7]

9. 屬性名可以是動態的

你可以在聲明對象之前分配動態屬性。

const dynamic = 'color';
var item = {
    brand: 'Ford',
    [dynamic]: 'Blue'
}
console.log(item); 
// { brand: "Ford", color: "Blue" }

10. 過濾唯一值

對於所有 ES6 愛好者,我們可以通過使用帶有展開運算符的 Set 對象來創建一個僅包含唯一值的新數組。

const my_array = [1, 2, 2, 3, 3, 4, 5, 5]
const unique_array = [...new Set(my_array)];
console.log(unique_array); // [1, 2, 3, 4, 5]

站長推薦

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

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

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

沐鳴註冊_Vue3中的Vue Router初探

對於大多數單頁應用程序而言,管理路由是一項必不可少的功能。隨着新版本的vue Router處於Alpha階段,我們已經可以開始查看下一個版本的vue中它是如何工作的。

Vue3中的許多更改都會稍微改變我們訪問插件和庫的方式,其中包括Vue Router。我們將結合使用Alpha版本的Vue Router和當前的Vue3 Alpha版本進行研究。

本文告訴你如何將Vue Router添加到Vue3項目中,並有一個很好的小例子!

設置

首先,我們將使用由Evan You 發布的Vue3 Webpack預覽版。

讓我們使用 git clone https://github.com/vuejs/vue-next-webpack-preview.git 克隆倉庫。

然後,要將vue-router alpha添加到我們的項目中,我們要修改 package.json 文件。

在我們的依賴關係中,我們想添加以下版本的vue-router

"dependencies": {
  "vue": "^3.0.0-alpha.10",
  "vue-router": "4.0.0-alpha.4"
}

現在,我們終於可以從命令行運行 npm install 來安裝所有依賴項。

我們最終要做的設置是實際創建你的路由文件以及一些映射到它的視圖。

在 src/ 文件夾中,我們將添加三個文件。

  • router/index.js
  • views/Home.vue
  • views/Contact.vue

我們的路由器文件將包含我們的路由器,並且我們的 Home/Contact 視圖將只輸出一個單詞,以便我們了解發生了什麼。

建立路由

一切準備就緒,讓我們開始使用Vue Router!

簡而言之,Vue Router的Vue3版本的主要區別在於我們必須導入新方法才能使代碼正常工作。其中最重要的是createRouter 和 createWebHistory。

在我們的 router/index.js 文件中,讓我們導入這兩個方法以及前面的兩個視圖。

import { createRouter, createWebHistory } from 'vue-router'
import Home from '../views/Home.vue'
import Contact from '../views/Contact.vue'

接下來,我們要做的是使用createWebHistory方法創建一個routerHistory對象。

import { createRouter, createWebHistory } from 'vue-router'
import Home from '../views/Home.vue'
import Contact from '../views/Contact.vue'

const routerHistory = createWebHistory()

在此之前,我們可以只輸入 mode: history 來從哈希模式切換到 history 模式,但是現在我們使用 history: createWebHistory() 來實現這一點。

接下來,我們實際上可以使用 createRouter 創建路由器,它接受一個對象,我們希望傳遞 routerHistory 變量以及兩個路由的數組。

const router = createRouter({
  history: routerHistory,
  routes: [
    {
      path: '/',
      component: Home
    },
    {
      path: '/contact',
      component: Contact
    }
  ]
})

最後,讓我們將路由導出。

export default router

如你所見,它仍然與Vue2非常相似。但是,通過所有這些更改,可以更好地支持Typescript和進行優化,因此熟悉新方法是很不錯的。

使用vue路由器

現在我們的Vue Router文件已經設置好了,我們可以將其添加到項目中了。以前,我們可以導入它並Vue.use(router),但這在Vue3中不一樣。

在 main.js 文件中,你會看到我們正在使用Vue中的 createApp 方法來實際創建我們的項目。在默認項目中,它鏈接 createApp 和 mount 方法。

const app = createApp(App)

app.mount('#app')

然後,在掛載我們的應用程序之前,我們想告訴它使用路由器文件。

import router from './router'

const app = createApp(App)

app.use(router)

app.mount('#app')

最後,在我們的App.vue文件中,讓我們實際显示我們的 router-view 並提供一些 router-link,以便我們能夠四處導航。

<template>
  <div id='root'>
    <img src='./logo.png' />
    <div id='nav'>
      <router-link to='/'> Home</router-link>
      <router-link to='/contact'>Contact </router-link>
    </div>
    <router-view />
  </div>
</template>

所以現在,如果我們單擊一下,我們將看到實際上可以在兩個頁面之間導航!

但是,如果我們嘗試直接進入我們的 /contact 路由,那將不起作用!我們遇到某種錯誤。

幸運的是,這是可以非常快速的用webpack修復。

在我們的 webpack.config.js 文件中,我們希望通過更改配置使devServer能夠使用 history api,使它看起來像這樣。

devServer: {
    inline: true,
    hot: true,
    stats: 'minimal',
    contentBase: __dirname,
    overlay: true,
    historyApiFallback: true
}

現在,如果我們直接導航到 /contact 路由,那麼一切都應該正常運行

總結

我們已成功將vue-router添加到我們的Vue3項目中。其他大多數功能(例如導航守衛,處理滾動條)和這些功能大致相同。

這是本教程最終Github倉庫的鏈接。如果您想在Vue3測試版中安裝vue-router,這是一個很好的模板代碼。

文章首發《前端外文精選》微信公眾號


站長推薦

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

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

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

沐鳴代理:_圖片加載失敗后CSS樣式處理最佳實踐

一、傳統的圖片異常處理

<img> 如果因為網絡或者跨域限制等原因無法正常加載,在默認情況下會显示瀏覽器默認的“裂開”的圖片效果,如果設置了 alt 屬性值,則 alt 屬性對應的內容也會一併显示。例如:

<img src="//www.zhangxinxu.com/zxx.ico" alt="鑫空間鑫生活">

例如Chrome瀏覽器下的效果截圖如下所示:

可以看到圖片加載異常之後的視覺效果實在是太粗糙了,程序員可忍設計師不可忍,因此,為了更好的視覺效果,實際項目開發中,我們總會對圖片加載異常的邊界場景進行額外的處理。

傳統的圖片加載異常會使用一個加載失敗的佔位符代替,這個佔位圖通常會是一張裂開的圖片。

例如下面這張圖:

觸發使用佔位圖可以通過 onerror 事件,代碼示意如下:

<img src="xxx.png" alt="鑫空間鑫生活" onerror="this.src='break.svg';">

配合css:

img[src$="break.svg"] {
    object-fit: contain;
}

就可以保證佔位圖的橫寬比例是正常的。

眼見為實,您可以狠狠地點擊這裏: 圖片加載異常佔位圖片處理demo

其中第2張圖片故意使用了一個錯誤的地址,結果如下截圖所示:

然而上面這種實現方式有一個比較致命的問題,那就是用戶並不清楚無法显示的圖片具體表示的含義是什麼。

對於用戶而言,內容和功能絕對比視覺表現更重要。

原生的圖片出錯會显示圖片的alt信息,這樣,用戶是能夠知道圖片描述的內容是什麼,而這裏使用佔位圖片兜底處理后,這些信息都沒有了。

因此,傳統的圖片出錯的處理方法可以有進一步的優化。

//zxx: 如果你看到這段文字,說明你現在訪問是體驗糟糕的垃圾盜版網站,你可以訪問原文獲得很好的體驗:https://www.zhangxinxu.com/wordpress/?p=9618(作者張鑫旭)

二、最佳實踐,同時显示alt信息

為了便於維護,圖像加載error的時候不再是替換src地址,而是新增一個錯誤類名,例如 .error :

<img src="zxx.png" alt="css新世界封面" onerror="this.classList.add('error');">

然後配合使用如下所示的CSS(部分CSS樣式細節大家可以根據自己的審美進行修改):

img.error {
  display: inline-block;
  transform: scale(1);
  content: '';
  color: transparent;
}
img.error::before {
  content: '';
  position: absolute;
  left: 0; top: 0;
  width: 100%; height: 100%;
  background: #f5f5f5 url(break.svg) no-repeat center / 50% 50%;
}
img.error::after {
  content: attr(alt);
  position: absolute;
  left: 0; bottom: 0;
  width: 100%;
  line-height: 2;
  background-color: rgba(0,0,0,.5);
  color: white;
  font-size: 12px;
  text-align: center;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

此時就可以看到失敗的圖片和alt文字信息同時出現的效果了。

眼見為實,您可以狠狠地點擊這裏: 圖片加載異常同時显示alt信息demo

對應的显示效果參見下面的截圖:

可以看到,內容展示和視覺表現同時兼顧了,是我這些年打磨出來的目前最好的圖像加載異常兜底處理實踐,一段代碼整站通用,低成本高收益。

兼容性

替換元素支持 ::before , ::after 偽元素也是最近幾年瀏覽器才支持的,Chrome支持的比較早,應該3-5年有的,Firefox瀏覽器是最近幾年支持的。

也就是不僅 <img> 元素可以使用 ::before , ::after 偽元素創建新的UI, <input> 元素(例如單選框、複選框)也是可以使用偽元素創建額外的UI效果的。

這個特性在樣式表現這塊給我們帶來的很大的便捷,然後有一個小小的問題,那就是IE瀏覽器不支持,只有現代瀏覽器下才有效果(Chrome、Firefox以及Safari瀏覽器均有效果)。

不過這個兼容性問題對我們應用該技術沒有任何影響,原因很簡單,例如本文所介紹的圖片異常處理屬於漸進增強特性,也就是就算瀏覽器不支持,也就是回到原本的狀態,有則更好,無則老樣子。

比方說在Edge瀏覽器(非Chrome內核版本)下,上述demo效果就會是下面這樣:

布局良好,語義明確,很OK的效果。

所以,本文的技能小tips大可放心使用。

三、其他注意點和結語

上面提供的CSS代碼中的兜底圖像均使用的外鏈地址,例如:

img.error::before {
  background: #f5f5f5 url(break.svg) no-repeat center / 50% 50%;
}

其中的 url(break.svg) 就是使用的外鏈地址。

然後這裡有個需要注意的地方,這個 break.svg 的域名地址建議不要和網站的靜態圖片的地址一樣。

舉個例子,靜態圖片使用的都是某某雲,此時, break.svg 對應的地址就不推薦還是某某雲生成的地址。因為某某雲出現異常導致圖片掛掉的話,這個 break.svg 顯然也是显示不出來的,就完全起不到兜底和佔位的作用。

因此, break.svg 可以和主站域名一致,或跟CSS域名一致,或者直接內聯在CSS文件中,例如:

img.error::before {
  background: #f5f5f5 url("data:image/svg+xml,%3Csvg viewBox='0 0 1024 1024' xmlns='http://www.w3.org/2000/svg' width='200' height='200'%3E%3Cpath d='M304.128 456.192c48.64 0 88.064-39.424 88.064-88.064s-39.424-88.064-88.064-88.064-88.064 39.424-88.064 88.064 39.424 88.064 88.064 88.064zm0-116.224c15.36 0 28.16 12.288 28.16 28.16s-12.288 28.16-28.16 28.16-28.16-12.288-28.16-28.16 12.288-28.16 28.16-28.16z' fill='%23e6e6e6'/%3E%3Cpath d='M887.296 159.744H136.704C96.768 159.744 64 192 64 232.448v559.104c0 39.936 32.256 72.704 72.704 72.704h198.144L500.224 688.64l-36.352-222.72 162.304-130.56-61.44 143.872 92.672 214.016-105.472 171.008h335.36C927.232 864.256 960 832 960 791.552V232.448c0-39.936-32.256-72.704-72.704-72.704zm-138.752 71.68v.512H857.6c16.384 0 30.208 13.312 30.208 30.208v399.872L673.28 408.064l75.264-176.64zM304.64 792.064H165.888c-16.384 0-30.208-13.312-30.208-30.208v-9.728l138.752-164.352 104.96 124.416-74.752 79.872zm81.92-355.84l37.376 228.864-.512.512-142.848-169.984c-3.072-3.584-9.216-3.584-12.288 0L135.68 652.8V262.144c0-16.384 13.312-30.208 30.208-30.208h474.624L386.56 436.224zm501.248 325.632c0 16.896-13.312 30.208-29.696 30.208H680.96l57.344-93.184-87.552-202.24 7.168-7.68 229.888 272.896z' fill='%23e6e6e6'/%3E%3C/svg%3E") no-repeat center / 50% 50%;
}

好,以上就是本文的內容,相信不難理解。

原文 https://www.zhangxinxu.com/wordpress/2020/10/css-style-image-load-fail/

站長推薦

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

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

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

沐鳴登錄_原生JavaScript手寫Ajax

代碼實現如下:

<script>
// 封裝通用的xhr對象,兼容各個版本
function creatXHR() {
// 判斷瀏覽器是否將XMLHttpRequest作為本地對象實現,針對IE7,firefox, opera等
if (typeof XMLHttpRequest != "undefined") {
return new XMLHttpRequest();
}
else if (typeof ActiveXObject != "undefined") {
// 將所有可能出現的ActiveXObject版本放在一個數組中
var xhrArr = [
'Microsoft.XMLHTTP',
'MSXML2.XMLHTTP.6.0',
'MSXML2.XMLHTTP.5.0',
'MSXML2.XMLHTTP.4.0',
'MSXML2.XMLHTTP.3.0',
'MSXML2.XMLHTTP.2.0'
];
// 遍歷創建XMLHttpRequest對象
var xhr;
for (let i = 0; i < array.length; i++) {
try {
// 創建XMLHrrpRequest對象
xhr = new ActiveXObject(xhrArr[i]);
break;
} catch (error) {

}

}
return xhr;
}
else{
throw new Error('不支持XMLHttpRequest對象');
}
}
// 創建xhr
var xhr = creatXHR();
// 存儲獲取到的數據
var data;

// 響應XMLHttpRequest對象狀態變化的函數,onreadystatechange在readyState屬性發生改變時觸發
xhr.onreadystatechange = function (/* callback */) {
// 異步調用成功,響應內容解析完成,可以在客戶端調用
if (xhr.readyState == 4) {
// 200 OK,304 讀取緩存
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) {
// 獲取服務器返回的數據
// 數據以字符串類型存放在 xhr.responseText
// json序列化解析xhr.responseText
// 將ajax獲取到的數據傳遞出去
data = jsON.parse(xhr.responseText);
// 或者在參數中傳入callback,在此處執行回調函數
// callback && callback();
}
}
};


// 創建請求,這裏只是創建,並不發送
xhr.open("get", "./xxx.json", true);

// 發送請求
xhr.send(null);

/* 使用post方法,傳參方式
// 創建請求,這裏只是創建,並不發送
xhr.open("post", "./xxx.json", true);
// 設置http頭部信息
xhr.setRequestHeader("Content-type", "application/x-www-form-urlcoded");
xhr.send({param1:'strrr', param2:789});
*/
</script>

站長推薦

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

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

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

沐鳴總代理_理解 TypeScript 類型收窄

一、類型收窄

TypeScript 類型收窄就是從寬類型轉換成窄類型的過程。類型收窄常用於處理聯合類型變量的場景,一個常見的例子是非空檢查:

// Type is htmlElement | null
const el = document.getElementById("foo");
if (el) {
  // Type is htmlElement
  el.innerhtml = "semlinker"; 
} else {
  // Type is null
  alert("id為foo的元素不存在");
}

如果 el 為 null,則第一個分支中的代碼將不會執行。因此,TypeScript 能夠從此代碼塊內的聯合類型中排除 null 類型,從而產生更窄的類型,更易於使用。

此外,你還可以通過拋出異常或從分支返回,來收窄變量的類型。例如:

// Type is htmlElement | null
const el = document.getElementById("foo");
if (!el) throw new Error("找不到id為foo的元素");
// Type is htmlElement
el.innerhtml = "semlinker";

其實在 TypeScript 中,有許多方法可以收窄變量的類型。比如使用 instanceof 運算符:

function contains(text: string, search: string | RegExp) {
  if (search instanceof RegExp) {
    // Type is RegExp
    return !!search.exec(text);
  }
  // Type is string
  return text.includes(search);
}

當然屬性檢查也是可以的:

interface Admin {
  name: string;
  privileges: string[];
}

interface Employee {
  name: string;
  startDate: Date;
}

type UnknownEmployee = Employee | Admin;

function printEmployeeInformation(emp: UnknownEmployee) {
  console.log("Name: " + emp.name);
  if ("privileges"in emp) {
    // Type is Admin
    console.log("Privileges: " + emp.privileges);
  }
  if ("startDate"in emp) {
    // Type is Employee
    console.log("Start Date: " + emp.startDate);
  }
}

使用一些內置的函數,比如 Array.isArray 也能夠收窄類型:

function contains(text: string, terms: string | string[]) {
  const termList = Array.isArray(terms) ? terms : [terms];
  termList; // Type is string[]
  // ...
}

一般來說 TypeScript 非常擅長通過條件來判別類型,但在處理一些特殊值時要特別注意 —— 它可能包含你不想要的東西!例如,以下從聯合類型中排除 null 的方法是錯誤的:

const el = document.getElementById("foo"); // Type is htmlElement | null
if (typeof el === "object") {
  el; // Type is htmlElement | null
}

因為在 JavaScript 中 typeof null 的結果是 “object” ,所以你實際上並沒有通過這種檢查排除 null 值。除此之外,falsy 的原始值也會產生類似的問題:

function foo(x?: number | string | null) {
  if (!x) {
    x; // Type is string | number | null | undefined
  }
}

因為空字符串和 0 都屬於 falsy 值,所以在分支中 x 的類型可能是 string 或 number 類型。幫助類型檢查器縮小類型的另一種常見方法是在它們上放置一個明確的 “標籤”:

interface UploadEvent {
  type: "upload";
  filename: string;
  contents: string;
}

interface DownloadEvent {
  type: "download";
  filename: string;
}

type AppEvent = UploadEvent | DownloadEvent;

function handleEvent(e: AppEvent) {
  switch (e.type) {
    case "download":
      e; // Type is DownloadEvent 
      break;
    case "upload":
      e; // Type is UploadEvent 
      break;
  }
}

這種模式也被稱為 ”標籤聯合“ 或 ”可辨識聯合“,它在 TypeScript 中的應用範圍非常廣。

如果 TypeScript 不能識別出類型,你甚至可以引入一個自定義函數來幫助它:

function isInputElement(el: htmlElement): el is htmlInputElement {
  return "value" in el;
}

function getElementContent(el: HTMLElement) {
  if (isInputElement(el)) {
    // Type is HTMLInputElement
    return el.value;
  }
  // Type is HTMLElement
  return el.textContent;
}

這就是所謂的 “用戶定義類型保護”。 el is HTMLInputElement ,作為返回類型告訴類型檢查器,如果函數返回true,則 el 變量的類型就是 HTMLInputElement。

類型保護是可執行運行時檢查的一種表達式,用於確保該類型在一定的範圍內。換句話說,類型保護可以保證一個字符串是一個字符串,儘管它的值也可以是一個數值。類型保護與特性檢測並不是完全不同,其主要思想是嘗試檢測屬性、方法或原型,以確定如何處理值。

一些函數能夠使用類型保護來執行數組或對象的類型收窄。例如,如果你在一個數組中進行一些查找,你可能會得到一個 nullable 類型的數組:

const supermans = ["Qinhw", "Pingan8787", "Semlinker", "Kaquko", "Lolo"];
const members = ["Semlinker", "Lolo"]
  .map((who) => supermans.find((n) => n === who))
// Type is (string | undefined)[]

這時你可能想到使用 filter 方法過濾掉未定義的值:

const supermans = ["Qinhw", "Pingan8787", "Semlinker", "Kaquko", "Lolo"];
const members = ["Semlinker", "Lolo"]
  .map((who) => supermans.find((n) => n === who))
  .filter((who) => who !== undefined);
// Type is (string | undefined)[]

可惜的是 TypeScript 也無法理解你的意圖,但是如果你使用一個類型保護函數的話就可以:

function isDefined<T>(x: T | undefined): x is T {
  return x !== undefined;
}

const supermans = ["Qinhw", "Pingan8787", "Semlinker", "Kaquko", "Lolo"];
const members = ["Semlinker", "Lolo"]
  .map((who) => supermans.find((n) => n === who))
  .filter(isDefined);
// Type is string[]

二、全面性檢查

在 TypeScript 中我們可以利用類型收窄和 never 類型的特性來全面性檢查,比如:

type Foo = string | number;

function controlFlowAnalysisWithNever(foo: Foo) {
  if(typeof foo === "string") {
    // 這裏 foo 被收窄為 string 類型
  } else if(typeof foo === "number") {
    // 這裏 foo 被收窄為 number 類型
  } else {
    // foo 在這裡是 never
    const check: never = foo;
  }
}

注意在 else 分支裏面,我們把收窄為 never 的 foo 賦值給一個显示聲明的 never 變量。如果一切邏輯正確,那麼這裏應該能夠編譯通過。但是假如後來有一天你的同事修改了 Foo 的類型:

type Foo = string | number | boolean;

然而他忘記同時修改 controlFlowAnalysisWithNever 方法中的控制流程,這時候 else 分支的 foo 類型會被收窄為 boolean 類型,導致無法賦值給 never 類型,這時就會產生一個編譯錯誤。通過這個方式,我們可以確保

controlFlowAnalysisWithNever 方法總是窮盡了 Foo 的所有可能類型。 通過這個示例,我們可以得出一個結論: 使用 never 避免出現新增了聯合類型沒有對應的實現,目的就是寫出類型絕對安全的代碼。

三、總結

理解 TypeScript 中的類型收窄將幫助你建立一個關於類型推斷如何工作的認知,進一步理解錯誤,它通常與類型檢查器有更緊密的聯繫。

Dan Vanderkam 大神寫的 ”62 Specific Ways to Improve Your TypeScript“ 這本書內容挺不錯的,有興趣的讀者可以閱讀一下。

原文 https://semlinker.com/ts-type-narrowing/

站長推薦

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

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

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