沐鳴:_拋開lodash,手寫節流和防抖函數

面試的時候我們經常會問別人是理解什麼是節流和防抖,嚴格的可能要求你寫出節流和防抖函數,這裏我們拋開loadsh工具庫手寫節流和防抖

 節流函數throttle

// 節流方案1,每delay的時間執行一次,通過開關控制
function throttle(fn, delay, ctx) {
    let isAvail = true
    return function () {
        let args = arguments // 開關打開時,執行任務 
        if (isAvail) {
            fn.apply(ctx, args)
            isAvail = false // delay時間之後,任務開關打開 
            setTimeout(function () { isAvail = true }, delay)
        }
    }
}
// 節流方案2,通過計算開始和結束時間
function throttle(fn,delay){
            // 記錄上一次函數出發的時間
            var lastTime = 0
            return function(){
            // 記錄當前函數觸發的時間
            var nowTime = new Date().getTime()
            // 噹噹前時間減去上一次執行時間大於這個指定間隔時間才讓他觸發這個函數
            if(nowTime - lastTime > delay){
                // 綁定this指向
                fn.call(this)
                //同步時間
                lastTime = nowTime
            }
            }
       }

2.防抖debounceTail

 2.1)只執行首次

// 防抖 且首次執行
// 採用原理:第一操作觸發,連續操作時,最後一次操作打開任務開關(並非執行任務),任務將在下一次操作時觸發)
function debounceStart(fn, delay, ctx) {
    let immediate = true 
    let movement = null
    return function() {
        let args = arguments
        
        // 開關打開時,執行任務
        if (immediate) {
            fn.apply(ctx, args)
            immediate = false
        }
        // 清空上一次操作
        clearTimeout(movement)
        
        // 任務開關打開
        movement = setTimeout(function() {
            immediate = true
        }, delay)
    }
}

2.2)只執行最後一次

// 防抖 尾部執行
// 採用原理:連續操作時,上次設置的setTimeout被clear掉
function debounceTail(fn, delay, ctx) {
    let movement = null
    return function() {
        let args = arguments
        
        // 清空上一次操作
        clearTimeout(movement)
        
        // delay時間之後,任務執行
        movement = setTimeout(function() {
            fn.apply(ctx, args)
        }, delay)
    }
}

站長推薦

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

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

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

沐鳴總代理_使用scss開發小程序(各種小程序平台通用)

微信小程序的wxss、阿里旗下淘寶、支付寶小程序的acss等等語法很類似原生css,但是在web開發里用慣了動態css語言,再寫回原生css很不習慣,尤其是父子樣式的嵌套寫法非常繁瑣。

因此,我希望能有一個自動化構建方案,能夠簡單地將scss轉換成小程序的樣式語言。

方案1

以前寫微信小程序的依賴庫時用過,使用gulp編譯,將源碼和編譯后的代碼分別放到src和dist兩個目錄。gulp會處理src下面的所有文件,將其中的scss轉換成css,並將其他所有文件原封不動挪到dist下相應位置。

這裏就不詳細說了,代碼參考Wux。

方案2

非常簡單直接,使用Webstorm/IDEA的File Watchers功能實時轉換。

安裝Ruby和sass

確保命令行輸入sass -v能出現版本號,安裝過程略。

安裝File Watchers

到插件市場上搜索並安裝(已安裝則跳過)

添加scss的轉換腳本

現在安裝完插件打開項目會自動彈出scss轉css的嚮導,方便了很多。但還需要做一些修改,配置如下:

首先要將生成文件的後綴名改掉,比如這裏我的淘寶小程序就得是acss。

其次,將Arguments改為:

$FileName$:$FileNameWithoutExtension$.acss --no-cache --sourcemap=none --default-encoding utf-8 --style expanded

如果不加–no-cache,scss文件同目錄下會出現一個.sass-cache目錄。

如果不加–sourcemap=none, scss文件同目錄下會出現一個.map文件。

如果不加–default-encoding utf-8, scss文件如果有中文註釋轉換就會報錯。

style可不加,這裏用的是無縮進和壓縮的風格,反正小程序打包發布時還會壓,這裏保持可讀性。

現在這個scss轉換是單獨作用於項目的,如果新建一個小程序項目,就需要重新添加(不建議設置成global,容易誤傷)。

注意到File Watchers列表的右側操作欄下方有導入導出按鈕,可以將現在配好的設置導出保存,將來新建項目時只要導入一下就行了。

之後還有一個問題,如果我手動將編譯后的css(即wxss或者acss,下略)文件刪除,scss文件不改動的話,就不會重新編譯出css文件。
或者萬一監聽失效或者不夠及時,css還有可能是舊的。
所以還需要一個命令,用來將整個目錄下的scss文件統一轉換,確保沒有遺漏和保持代碼最新。

不過我看了半天sass和sass-convert的文檔,沒有找到一個可用的寫法,能讓命令行遍歷指定目錄下的所有scss文件,將其轉換成css放到源文件所在目錄,並且將後綴名改為wxss或者acss。

所以遍歷這個行為只能交給nodejs來實現,代碼如下:

創建編譯腳本build/scss-convert.js:

var path = require("path")
var fs = require("fs")
const { exec } = require('child_process')

const basePath = path.resolve(__dirname, '../')

function mapDir(dir, callback, finish) {
  fs.readdir(dir, function(err, files) {
    if (err) {
      console.error(err)
      return
    }
    files.forEach((filename, index) => {
      let pathname = path.join(dir, filename)
      fs.stat(pathname, (err, stats) => { // 讀取文件信息
        if (err) {
          console.log('獲取文件stats失敗')
          return
        }
        if (stats.isDirectory()) {
          mapDir(pathname, callback, finish)
        } else if (stats.isFile()) {
          if (!['.scss'].includes(path.extname(pathname))) {
            return
          }
          callback(pathname)
        }
      })
      if (index === files.length - 1) {
        finish && finish()
      }
    })
  })
}

mapDir(
  basePath,
  function (file) {
    const newFileWithoutExt = path.basename(file, '.scss')
    if (newFileWithoutExt.startsWith('_')) {
      return  // 按照scss規則,下劃線開頭的文件不會生成css
    }
    // exec可以讓nodejs執行外部命令
    exec(`sass --no-cache --sourcemap=none --default-encoding utf-8 --style expanded ${file}:${newFileWithoutExt}.acss`, {
      cwd: path.dirname(file) // 不寫這個會導致生成的文件出現在根目錄
    }, (err, stdout, stderr) => {
      if (err) {
        console.log(err)
        return
      }
      console.log(`stdout: ${stdout}`)
    })
  },
  function() {
    // console.log('xxx文件目錄遍歷完了')
  }
)

在package.json里添加一條script:

  "scripts": {
    "scss": "node build/scss-convert",
  },

站長推薦

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

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

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

沐鳴娛樂_理解JS 中相等和全等操作符比較規則

在日常的 js 編碼過程中,可能很難看到相等運算符(=)是如何工作的。特別是當操作數具有不同類型時。這有時會在條件語句中產生一些難以識別的 bug。很容易理解為什麼 0 == 8 是 flase 的或者 ” == false 是 true。但是為什麼{} == true是 false 的就看不出來了。接下將會講這是腫么肥事。

在這之前,先說幾個術語:

  • 操作符(Operator) 表示操作的符號。例如,相等運算符==比較兩個值,三等運算符 === 比較兩個值及其類型,加法運算符+兩個数字和或連接兩個字符串。
  • 操作數(Operand) 是運算的主體,是執行運算的數量。例如,在表達式 0 == {} 中,0 是第一個操作數,{} 是第二個操作數。
  • js 中的基本數據類型(原始類型)有 number,string, boolean,null 和 undefined,symbol。

全等運算符 ===

全等和不全等操作符遵循以下基本規則(IEA規則):

  1. 如果兩個操作數有不同的類型,它們不是嚴格相等的
  2. 如果兩個操作數都為 null,則它們是嚴格相等的
  3. 如果兩個操作數都為 undefined,它們是嚴格相等的
  4. 如果一個或兩個操作數都是 NaN,它們就不是嚴格相等的
  5. 如果兩個操作數都為 true 或都為 false,它們是嚴格相等的
  6. 如果兩個操作數都是 number 類型並且具有相同的值,則它們是嚴格相等的
  7. 如果兩個操作數都是 string 類型並且具有相同的值,則它們是嚴格相等的
  8. 如果兩個操作數都引用相同的對象或函數,則它們是嚴格相等的
  9. 以下所有其他情況下操作數都不是嚴格相等的。

規則很簡單。

值得一提的是,在全等運算中,NaN 與其他任何值相比,結果都是 false。 來看看考慮些例子,這是學習這些規則的好方式。

例 1

1 === "1" // false, 規則 1

操作數是不同的類型(数字和字符串),基於 IEA 規則1,它們是不等的。

例 2

0 === 0 // true, 規則 6

操作數具有相同的類型和相同的值,因此根據IEA規則6,它們是嚴格相等的。

例 3

undefined === undefined // true, 規則 3

兩個操作數都是 undefined 的,應用 IEA 規則3,它們是相等的。

例 4

undefined === null // false, 規則 1

因為操作數是不同的類型,根據IEA規則1,它們並不相同。

例 5

NaN === NaN // false, IEA 規則 5

操作數是相同的類型,但是IEA 規則4 表明任何與 NaN 比較都是不相等的。

例 6

var firstObject = {},
  secondObject = firstObject;
secondObject['name'] = 'Neo';
secondObject === firstObject // true, IEA 規則 8

兩個變量 firstObject 和 secondObject 都是對同一對象的引用,根據 IEA 規則8,它們相等。

例 7

[] === [] //false, IEA 規則 9

字面量 [] 創建了一個新的數組引用。這兩個操作數是相同的類型(對象),但是它們引用不同的對象。根據 IEA 規則 9 ,它們不相等。

對象轉換為原始值的規則

對象到布爾值

對象到布爾值的轉換非常簡單:所有的對象(包括数字和函數)都轉換為 true。對於包裝對象亦是如此:new Boolean(false) 是一個對象而不是原始值,它將轉換為 true。

對象到字符串

對象到字符串 和 對象到数字 的轉換都是通過調用待轉換對象的一個方法來完成的。一個麻煩的事實是,JS 對象有兩個不同的方法來執行轉換,接下來要討論的一些特殊場景更加複雜。值得注意的是,這裏提到的字符串和對象的轉換規則只適用於原生對象(native object)。宿主對象(例如有Web瀏覽器定義的對象)根據各自的算法可以轉換成字符串和数字。

所有的對象繼承了兩個轉換方法。第一個是toString(),它的作用是返回一個反映這個對象的字符串。默認的 toString() 方法並不會返回一個有趣的值:

({x:1,y:2}).toString()  //=>"[object object]"

很多類定義了更多特定版本的toString()方法。例如,數組的 toString() 方法是將每個數組元素轉換為一個字符串,並在元素之間添加逗號后合併成結果字符串。

函數的 toString() 方法返回了這個函數的實現定義。實際上,這裏的實現是通常是將用戶定義的函數轉換為 JS 源代碼字符串。

日期 Date 的 toString() 方法返回了一個可讀的日期和時間字符串。

RegExp 的 toString() 方法將RegExp對象轉換為表示正則表達式直接量的字符串:

來幾個例子:

[1,2,3].toString() //=> "1,2,3"
(function(x){ f(x); }).toString() // => "function(x){ f(x); }"
/\d+/g.toString()   // => "/\d+/g"
new Date(2019,9,16).toString()  //=> "Wed Oct 16 2019 00:00:00 GMT+0800 (中國標準時間)"

另一個轉換對象的函數是 valueOf()。如果存在任意原始值,它就默認將對象轉換為表示它的原始值。對象是複合值,而且大多數對象無法真正表示為一個原始值,因此默認的 valueOf() 方法簡單地返回對象本身,而不是返回一個原始值。數組、函數和正則表達式簡單地繼承了這個方法,調用這些類型的實例的valueOf() 方法只是簡單返回對象本身。日期 Date 的 valueOf() 方法會返回它的一個內部表示:1970年1月1日以來的毫秒數。

new Date(2019,9,16).valueOf() // 1571155200000

通過使用 toString() 和 valueOf() 方法,就可以做到對象到字符串和對象到数字的轉換了。但需要注意的是,在某些特殊的場景中,JS 執行了完全不同的對象到原始值的轉換。

JS 中對象到字符串的轉換經過如下這些步驟,咱們簡稱 OPCA 算法。

  1. 如果方法 valueOf() 存在,則調用它。如果 valueOf() 返回一個原始值,JS 將這個值轉換為字符串(如果本身不是字符串的話),並返回這個字符串結果。
  2. 如果方法 toString() 存在,則調用它。如果 toString() 返回一個原始值,JS 將這個值轉換為字符串(如果本身不是字符串的話),並返回這個字符串結果。需要注意,原始值到字符串的轉換。
  3. 否則,JS 無法從 toString() 或 valueOf() 獲得一個原始值,它將拋出一個 TypeError:不能將對象轉換為原始值 異常

當調用 valueOf() 方法時,大多數原生對象都會返回對象本身。因此 toString() 方法使用得更頻繁。

關於 Date 對象的注意事項:在轉換為原始值時,對象立即使用 toString() 方法轉換為字符串。這樣,規則1就被跳過了。普通的 JS 對象,{}或 new object(),通常被轉換成 “[object Object]”

數組通過將它的元素與“,”分隔符連接轉換為。例如 [1,3,”four”] 被轉換成” 1,3,four”。

相等運算符 ==

相等運算符 “==” 如果兩個操作數不是同一類型,那麼相等運算符會嘗試一些類型轉換,然後進行比較。

相等運算符算法(EEA)

  1. 如果操作數具有相同的類型,請使用上面的 IEA 測試它們是否嚴格相等。 如果它們不嚴格相等,則它們不相等,否則相等。
  2. 如果操作數有不同的類型:
    2.1如果一個操作數為 null 而另一個 undefined,則它們相等
    2.2如果一個值是数字,另一個是字符串,先將字符串轉換為数字,然後使用轉換后的值比較
    2.3如果一個操作數是布爾值,則將 true 轉換為 1,將 false 轉換為 0,然後使用轉換后的值比較
    2.4如果一個操作數是一個對象,而另一個操作數是一個数字或字符串,則使用OPCA將該對象轉換為原原始值,再使用轉換后的值比較
  3. 在以上的其他情況下,操作數都不相等

例 1

1 == true // true

上面的轉換步驟:

  1. 1 == true (使用EEA 規則2.3 將 true 轉換為 1)
  2. 1 == 1(操作數有相同的類型。使用 EEA 規則1 將相等轉換為全等運算進行比較
  3. 1 === 1(兩個操作數都是数字,並且具有相同的值。根據 IEA 規則 6,這是相等的)
  4. true

例 2

'' == 0 // true

上面的轉換步驟:

  1. ” == 0(一個操作數是字符串,另一個操作數是数字,根據EEA規則2.2,” 被轉換為数字 0 )
  2. 0 == 0(操作數類型相同,使用 EEA規則1 將相等轉換為全等運算進行比較)
  3. 0 === 0(操作數類型相同,值相同,所以根據IEA規則6,它是一個恆等式)
  4. true

例 3

null == 0 // false

上面的轉換步驟:

  1. null == 0 (null 是原始類型,0 是 number 類型。根據EEA規則3)
  2. false

例 4

null == undefined // true

上面的轉換步驟:

  1. null == undefined(基於EEA規則2.1,操作數相等)
  2. true

例 5

NaN == NaN // false

上面的轉換步驟:

  1. NaN == NaN(兩個操作數都是数字。根據EEA規則1,將相等轉換為全等運算進行比較)
  2. NaN === NaN(根據IEA規則4,操作數嚴格不相等)
  3. false

例 6

[''] == '' // true

上面的轉換步驟:

  1. [”] == ”([”]是一個數組和 ” 是一個字符串。應用EEA規則2.4並使用OPCA規則2將數組轉換為原始值 ”)
  2. ” == ” (兩個操作數都是字符串,將相等轉換為全等運算進行比較)
  3. ” === ” (兩個操作數類型相同,值相同。使用IEA規則7,它們是相等的)
  4. true

例 7

{} == true // false

上面的轉換步驟:

  1. {} == true(使用EEA規則2.3,將 true 操作數轉換為 1)
  2. {} == 1(第一個操作數是一個對象,因此有必要使用OPCA將其轉換為原始值)
  3. “[object object]”== 1(因為第一個操作數是字符串,第二個操作數是数字,根據 EEA規則2.2 將“[object object]”轉換為数字)
  4. NaN == 1(兩個操作數都是数字,因此使用 EEA規則1 將相等轉換為全等運算進行比較)
  5. NaN === 1(根據 IEA規則4,沒有什麼是與 NaN 相等的,結果是 false)
  6. false

實用技巧

即使在詳細研究了本文中的所有示例、學習了算法之後,你會發現要立即理解複雜的比較還需要時間的積累。

告訴你一些技巧。 將本文添加到書籤中(使用Ctrl + D),下一次看到有趣的情況時,可以根據等式算法編寫逐步的計算。 如果檢查至少 10 個示例,則以後不會有任何問題。

現在就可以試試,如 [0] == 0 的結果和轉化步驟是什麼?

相等運算符==進行類型轉換。因此,可能會產生意想不到的結果,例如 {}== true 是 false( 參見例7)。在大多數情況下,使用全等操作符 === 更安全。

總結

相等和全等運算符號可能是最常用的運算符之一。理解它們是編寫穩定且bug較少的 JS 的步驟之一。

原文:https://dmitripavlutin.com/

站長推薦

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

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

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

沐鳴娛樂_如何利用 策略模式 優化表單驗證

背景

在做移動端項目開發的時候,經常會遇到各種表單驗證,有時候不同的頁面,驗證規則是一樣的 ,如圖

之前項目里的驗證代碼寫的很凌亂,最近剛好遇到這個模塊的功能需要重構,於是對代碼做了大量的優化,記錄一下如何利用策略模式優化表單驗證的

優化前

之前我們的驗證代碼是這樣寫的,把驗證func寫到一個公共模塊裏面,然後每個方法返回一個包含是否通過和message字段的對象

//common.js
export default {
  validateName: function (data, message = '', flag = false) {
    if (!/xxx/.test(data)) {
      message = '驗證不通過提示語'
    } else {
      flag = true
    }
    return {
      message,
      flag
    }
  },
  validatePhone: function (data, message = '') {},
  validateAddress: function (data, message = '') {}
}

然後在業務代碼裏面引用,這種不僅代碼量比較大,而且這種基礎驗證模塊和業務模塊耦合嚴重,我們應該盡量做到將不同模塊和功能代碼分離,提高代碼的可擴展性

優化后

如果驗證不通過,在基礎驗證代碼里立即彈出msg,業務代碼只需要判斷返回的是true或者false就行了

//common.js
export default {
  validateName: function (data, msg = '') {
    if (!/xxx/.test(data)) {
      msg = '驗證不通過提示語'
    }
    msg && alert(msg)
    return !!msg
  },
  validatePhone: function (data, msg = '') {},
  validateAddress: function (data, msg = '') {}
}

這樣業務代碼看上去很簡潔

//index.js
import validate from './common.js'
const keys = Object.keys(validate)
//通過some方法,有1個驗證不通過,就跳出驗證,然後主業務代碼里通過判斷validateResult的值,驗證是否完全通過。
//還有dataKey需要和validateKey有一定的映射規則
const validateResult = keys.some(key => {
  return !validate[key](this.data[dataKey])
})

如果你的驗證方法包含了很多本頁面用不到的其他驗證規則,你只需在業務代碼添加一個白名單字段

const whiteList = ['validatePhone','validateName','validateAddress']

如果你的驗證有先後順序,添加一個優先級字段

const whiteList = [{
    name:'validatePhone',
    level:1,
},{
    name:'validateName',
    level:2,
}]

以上就是我對錶單驗證規則的優化心得,有哪裡需要改進的希望大家可以指點出來,相互交流

原文:https://segmentfault.com/a/1190000021855553

站長推薦

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

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

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

沐鳴:_如何閱讀別人的代碼?

挺多人問過我“如何閱讀已有代碼”這個問題,希望我能有一個好的方法。有些人希望通過閱讀“優質項目”(比如 Linux 內核)得到啟發,改進自己的代碼質量。對於這個問題,我一般都不好回答,因為我很少從閱讀別人的代碼得到提升。每一次閱讀別人的代碼,對我來說都是一種折磨,而且每一次都不是別人的代碼提升了我,而是我提升了別人的代碼。

比起閱讀代碼,我更喜歡看別人的文章或者書。我喜歡他們跟我面對面的交流,用簡單的自然語言或者畫圖解釋他們的思想。有了思想,我自然知道如何把它變成代碼,而且是優雅的代碼。每一次參加學術會議,我都發現自己幾乎無法理解會議上的 talk 或者 paper。我會在 talk 結束之後的喝茶時間走到演講者面前,對他說:“你的 talk 很有意思,你能在三句話之內總結一下你說了什麼嗎?” 這樣交流之後,我忽然就懂了。

如果有同事請我幫他改進代碼,我不會拿起代碼埋頭看,因為我知道看代碼往往是沒用的。我會讓他們先在白板上給我解釋那些代碼是什麼意思。我的同事們都發現,把我講明白是很困難的。因為我的要求非常高,只要有一點不明白,我就會讓他們重新講。還得畫圖,我會讓他們反覆改進畫出來的圖,直到我能一眼看明白為止。如果圖形是 3D 的,我會讓他們給我壓縮成 2D 的,理解了之後再“推廣”到 3D。我無法理解複雜的,高維度的概念,他們必須把它給我變得很簡單。

所以跟我講代碼總是需要費很多時間,但這是值得的,因為我明白了之後,往往能挖出其他人都難以看清楚的要點。給我講解事情,也能提升他們自己的思維和語言能力,幫助他們簡化思想,甚至在忽然間發現改進他們自己代碼的方法。很多時候我根本沒看代碼,通過給我講解,後來他們自己就把代碼給簡化了。節省了我的腦力和視力,他們也得到了提高。

我最近一次看別人的代碼是在 Intel,我們改了 PyTorch 的代碼。那不是一次愉悅的經歷,因為雖然很多人覺得 PyTorch “好用”,它內部的代碼卻是非常晦澀,難以理解的。PyTorch 不是 Intel 自己的東西,所以沒有人可以給我講。修改 PyTorch 代碼,增加新功能的時候,我發現很難從代碼本身看明白應該改哪裡。後來我發現,原因在於 PyTorch 的編譯構架里自動生成了很多代碼,導致你無法理解那些代碼是怎麼來的。

比如他們有好幾個自己設計的文件格式,裏面有一些特殊的文本,決定了如何在編譯時生成代碼。你得理解這些文件裏面的內容在說什麼,而那不是任何已知的語言。這些文本文件被一些 Python 腳本讀進去,吐出來一些奇怪的 C++,CUDA,或者 Python 代碼。這其實是一種 DSL。我已經在之前的文章中解釋過 DSL 帶來的問題。所以要往 PyTorch 裏面加功能,你就得理解這些腳本是如何處理這些文本文件,生成代碼。而這些腳本寫得也比較混亂和草率,所以就是頭痛 2 ,頭痛的平方。

最後我發現,根本沒有辦法完全依靠這些代碼本身來理解它。那麼怎麼解決這個問題呢?幸好,網絡上有 PyTorch 的內部工程師寫了篇 blog ,解釋 PyTorch 如何組織代碼。Blog 的作者 E. Z. Yang 我見過一面,是在一次 PL 學術會議上。他當時在 MIT 讀書,貌似一個挺聰明的小伙子。不過看了這 blog 也只能初步知道它做了什麼,應該碰大概哪些文件,而這些每天都可能變化。

這篇 blog 還提到,某幾個目錄裏面是歷史遺留代碼,如果你不知道那是什麼,那麼請不要碰!看看那幾個目錄,裏面都是一些利用 C 語言的宏處理生成代碼的模板,而它使用 C 語言宏的方式還跟普通的用法不一樣。在我看來,所謂“宏”(macro)和 metaprogramming 本身就是一個巨大的誤區,而 PyTorch 對宏的用法還如此奇怪,自作聰明。

你以為看了這篇 blog 就能理解 PyTorch 代碼了嗎?不,仍然是每天各種碰壁。大量的經驗都來自折騰,碰壁,在黑暗中摸索。多個人同時在進行這些事情,然後分享自己的經驗。討論會內容經常是:“我發現要做這個,得在這個文件里加這個,然後在那個文件里加那個…… 然後好像就行了。” 下次開會又有人說:“我發現不是像你說的那樣,還得改這裏和這裏,而那裡不是關鍵……” 許多的知其然不知其所以然,盲人摸象,因為“所以然”已經被 PyTorch 原來的作者們掩蓋在一堆堆混亂的 DSL 下面了。

所以我從 PyTorch 的代碼裏面學到了什麼呢?什麼都沒有。我只看到各種軟件開發的誤區在反覆上演。如果他們在早期得到我的建議,根本不可能把代碼組織成這種樣子,不可能有這麼多的宏處理,代碼生成,DSL。PyTorch 之類的深度學習框架,本質上是某種簡單編程語言的解釋器,只不過這些語言寫出來的函數可以求導而已。而寫解釋器是我最在行的事情。

那麼我是怎麼成為現在這個樣子的呢?肯定有某種方法,對吧。我的方法很簡單,寫最短最精巧的代碼,從最薄最精悍的書或者課程裏面學,從真正的大師那裡學,而不是大型開源項目。

造就我今天的編程能力和洞察力的,不是幾百萬行的大型項目,而是小到幾行,幾十行之短的練習。不要小看了這些短小的代碼,它們就是編程最精髓的東西。反反覆復琢磨這些短小的代碼,不斷改進和提煉裏面的結構,磨礪自己的思維。逐漸的,你的認識水平就超越了所有這些幾百萬行,讓人頭痛的項目。

很多人都不知道,有一天我用不到一百行 Scheme 代碼就寫出了一個“深度學習框架”,它其實是一個小的編程語言。雖然沒有性能可言,沒有任何 GPU 加速,功能也不完善,但它抓住了 PyTorch 等大型框架的本質——用這個語言寫出來的函數能自動求導。這種洞察力才是最關鍵的東西,只要抓住了關鍵,細節都可以在需要的時候琢磨出來。幾十行代碼反覆琢磨,往往能幫助你看透上百萬行的項目里隱藏的秘密。

所以我如何閱讀別人的代碼呢?Don’t。除非我真的要使用那個項目的代碼,我才會去折騰它。

原文 http://www.yinwang.org/blog-cn/2020/02/05/how-to-read-code

站長推薦

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

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

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

沐鳴娛樂_利用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