沐鳴登錄_JavaScript數據類型轉換

前言

JavaScript是一門動態語言,所謂的動態語言可以暫時理解為在語言中的一切內容都是不確定的。比如一個變量,這一時刻是個整型,下一時刻可能會變成字符串了。雖然變量的數據類型是不確定的,但是各種運算符對數據類型是有要求的。如果運算符發現,運算子的類型與預期不符,就會自動轉換類型。

本文主要介紹數據類型強制轉換和自動轉換,自動轉換是基於強制轉換之上。強制轉換主要指使用Number、String和Boolean三個函數,手動將各種類型的值,分佈轉換成数字、字符串或者布爾值

一、強制轉換

1、其他的數據類型轉換為String

方式一:toString()方法

調用被轉換數據類型的toString()方法,該方法不會影響到原變量,它會將轉換的結果返回,但是注意:null和undefined這兩個值沒有toString,如果調用他們的方法,會報錯

var a = 123
a.toString()//"123"
var b = null;
b.toString()//"報錯"
var c = undefined
c.toString()//"報錯"

採用 Number 類型的 toString() 方法的基模式,可以用不同的基輸出数字,例如二進制的基是 2,八進制的基是 8,十六進制的基是 16

var iNum = 10;
alert(iNum.toString(2));        //輸出 "1010"
alert(iNum.toString(8));        //輸出 "12"
alert(iNum.toString(16));       //輸出 "A"

方式二:String()函數

使用String()函數做強制類型轉換時,對於Number和Boolean實際上就是調用的toString()方法,
但是對於null和undefined,就不會調用toString()方法,它會將null直接轉換為”null”,將undefined 直接轉換為”undefined”

var a = null
String(a)//"null"
var b = undefined
String(b)//"undefined"

String方法的參數如果是對象,返回一個類型字符串;如果是數組,返回該數組的字符串形式。

String({a: 1}) // "[object Object]"
String([1, 2, 3]) // "1,2,3"

2、其他的數據類型轉換為Number

方式一:使用Number()函數

下面分成兩種情況討論,一種是參數是原始類型的值,另一種是參數是對象

(1)原始類型值

①字符串轉数字

Ⅰ 如果是純数字的字符串,則直接將其轉換為数字

Ⅱ 如果字符串中有非数字的內容,則轉換為NaN

Ⅲ 如果字符串是一個空串或者是一個全是空格的字符串,則轉換為0

Number('324') // 324
Number('324abc') // NaN
Number('') // 0

②布爾值轉数字:true轉成1,false轉成0

Number(true) // 1
Number(false) // 0

③undefined轉数字:轉成NaN

Number(undefined) // NaN

④null轉数字:轉成0

Number(null) // 0

⑤Number() 接受數值作為參數,此時它既能識別負的十六進制,也能識別0開頭的八進制,返回值永遠是十進制值

Number(3.15);    //3.15
Number(023);     //19
Number(0x12);    //18
Number(-0x12);   //-18

(2)對象

簡單的規則是,Number方法的參數是對象時,將返回NaN,除非是包含單個數值的數組。

Number({a: 1}) // NaN
Number([1, 2, 3]) // NaN
Number([5]) // 5

方式二:parseInt() & parseFloat()

這種方式專門用來對付字符串,parseInt()一個字符串轉換為一個整數,可以將一個字符串中的有效的整數內容取出來,然後轉換為Number。parseFloat()把一個字符串轉換為一個浮點數。parseFloat()作用和parseInt()類似,不同的是它可以獲得有效的小數。

console.log(parseInt('.21'));        //NaN
console.log(parseInt("10.3"));        //10
console.log(parseFloat('.21'));      //0.21
console.log(parseFloat('.d1'));       //NaN
console.log(parseFloat("10.11.33"));  //10.11
console.log(parseFloat("4.3years"));  //4.3
console.log(parseFloat("He40.3"));    //NaN

parseInt()在沒有第二個參數時默認以十進制轉換數值,有第二個參數時,以第二個參數為基數轉換數值,如果基數有誤返回NaN

console.log(parseInt("13"));          //13
console.log(parseInt("11",2));        //3
console.log(parseInt("17",8));        //15
console.log(parseInt("1f",16));       //31

兩者的區別:Number函數將字符串轉為數值,要比parseInt函數嚴格很多。基本上,只要有一個字符無法轉成數值,整個字符串就會被轉為NaN。

parseInt('42 cats') // 42
Number('42 cats') // NaN

上面代碼中,parseInt逐個解析字符,而Number函數整體轉換字符串的類型。
另外,對空字符串的處理也不一樣

Number("   ");     //0    
parseInt("   ");   //NaN

3、其他的數據類型轉換為Boolean

它的轉換規則相對簡單:只有空字符串(“”)、null、undefined、+0、-0 和 NaN 轉為布爾型是 false,其他的都是 true,空數組、空對象轉換為布爾類型也是 true,甚至連false對應的布爾對象new Boolean(false)也是true

Boolean(undefined) // false
Boolean(null) // false
Boolean(0) // false
Boolean(NaN) // false
Boolean('') // false
Boolean({}) // true
Boolean([]) // true
Boolean(new Boolean(false)) // true

二、自動轉換

遇到以下三種情況時,JavaScript 會自動轉換數據類型,即轉換是自動完成的,用戶不可見。

1.自動轉換為布爾值

JavaScript 遇到預期為布爾值的地方(比如if語句的條件部分),就會將非布爾值的參數自動轉換為布爾值。系統內部會自動調用Boolean函數。

if ('abc') {
  console.log('hello')
}  // "hello"

2.自動轉換為數值

算數運算符(+ – * /)跟非Number類型的值進行運算時,會將這些值轉換為Number,然後在運算,除了字符串的加法運算

true + 1 // 2
2 + null // 2
undefined + 1 // NaN
2 + NaN // NaN 任何值和NaN做運算都得NaN
'5' - '2' // 3
'5' * '2' // 10
true - 1  // 0
'1' - 1   // 0
'5' * []    // 0
false / '5' // 0
'abc' - 1   // NaN

一元運算符也會把運算子轉成數值。

+'abc' // NaN
-'abc' // NaN
+true // 1
-false // 0

3.自動轉換為字符串

字符串的自動轉換,主要發生在字符串的加法運算時。當一個值為字符串,另一個值為非字符串,則後者轉為字符串。

'5' + 1 // '51'
'5' + true // "5true"
'5' + false // "5false"
'5' + {} // "5[object Object]"
'5' + [] // "5"
'5' + function (){} // "5function (){}"
'5' + undefined // "5undefined"
'5' + null // "5null"

三、總結

1. 強制轉換的各種情況

2. 自動轉換的的各種情況

只有空字符串(“”)、null、undefined、+0、-0 和 NaN 轉為布爾型是 false,其他的都是 true

除了加法運算符(+)有可能把運算子轉為字符串,其他運算符都會把運算子自動轉成數值。一元運算符也會把運算子轉成數值。

字符串的自動轉換,主要發生在字符串的加法運算時。

站長推薦

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

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

沐鳴娛樂業務:_ES12(2021)新特性: replaceAll、Promise.any、WeakRefs、数字分隔符、邏輯運算符和賦值表達式

2021年3月13日,ES2021 候選提案發布了其最終功能集的版本。如果它能夠在今年6月的ECMA 大會上通過,就會成為官方的標準!

這個候選提案提及到ECMAScript新特性如下所示:

String.prototype.replaceAll()

Promise.any

邏輯運算符和賦值表達式

數值分隔符

WeakRef and Finalizers

這些新的特性已經進入第四階段且已添加到谷歌 Chrome V8 引擎中。

1. replaceAll

返回一個全新的字符串,所有符合匹配規則的字符都將被替換掉

const str = 'hello world';
str.replaceAll('l', ''); // "heo word"

2. Promise.any

Promise.any() 接收一個Promise可迭代對象,只要其中的一個 promise 成功,就返回那個已經成功的 promise 。如果可迭代對象中沒有一個 promise 成功(即所有的 promises 都失敗/拒絕),就返回一個失敗的 promise

const promise1 = new Promise((resolve, reject) => reject('我是失敗的Promise_1'));
const promise2 = new Promise((resolve, reject) => reject('我是失敗的Promise_2'));
const promiseList = [promise1, promise2];
Promise.any(promiseList)
.then(values=>{
  console.log(values);
})
.catch(e=>{
  console.log(e);
});

3. WeakRefs

ES2021 了新的類 WeakRefs。允許創建對象的弱引用。這樣就能夠在跟蹤現有對象時不會阻止對其進行垃圾回收。對於緩存和對象映射非常有用。

必須用 new關鍵字創建新的 WeakRef,並把某些對象作為參數放入括號中。當你想讀取引用(被引用的對象)時,可以通過在弱引用上調用 deref() 來實現。

const myWeakRef = new WeakRef({
name: '星野',
year: '25'
})

myWeakRef.deref()
// => { name: '星野', year: '25' }

myWeakRef.deref().name
// => '星野'

與 WeakRef 緊密相連的還有另一個功能,名為 finalizers 或 FinalizationRegistry。這個功能允許你註冊一個回調函數,這個回調函數將會在對象被垃圾回收時調用。

// 創建 FinalizationRegistry:
const reg = new FinalizationRegistry((val) => {
console.log(val)
})

(() => {
// 創建新對象:
const obj = {}

//為 “obj” 對象註冊 finalizer:
//第一個參數:要為其註冊 finalizer 的對象。
//第二個參數:上面定義的回調函數的值。
reg.register(obj, 'obj has been garbage-collected.')
})()
// 當 "obj" 被垃圾回收時輸出:
// 'obj has be

4. 邏輯運算符和賦值表達式

邏輯運算符和賦值表達式,新特性結合了邏輯運算符(&&,||,??)和賦值表達式而JavaScript已存在的 複合賦值運算符有:

a ||= b
//等價於
a = a || (a = b)

a &&= b
//等價於
a = a && (a = b)

a ??= b
//等價於
a = a ?? (a = b)

5. 数字分隔符

数字分隔符,可以在数字之間創建可視化分隔符,通過_下劃線來分割数字,使数字更具可讀性

const money = 1_000_000_000;
//等價於
const money = 1000000000;

1_000_000_000 === 1000000000; // true

站長推薦

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

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

沐鳴登錄網站_Javascript正則中的命名捕獲分組

假設你在一段陌生的代碼中看到這樣一個函數: 

function toLocalDate(date) {
  return date.replace(/(\d{2})-(\d{2})-(\d{4})/, "$2-$1-$3")
}

單看這個函數你能知道它是想把“日-月-年”替換成“月-日-年”,還是反過來?匿名捕獲分組沒法做到這一點,那就該命名捕獲分組上場了:

function toLocalDate(date){
  return date.replace(/(?<month>\d{2})-(?<day>\d{2})-(?<year>\d{4})/, "$<day>-$<month>-$<year>")
}

俗話說的好,“一個好的變量名賽過一行註釋”,命名捕獲分組很大的一個作用就是它能起到註釋的作用。

另外,命名捕獲分組還有一個好處,那就是假如你在修改一個正則時,在已有分組的左邊引入了新的分組,那麼你還得記得更新已有的反向引用的数字。比如將 (foo)\1 改成了 (bar)(foo)\1,那你得把原來的 \1 改成 \2,replace() 方法的第二個參數里的 $1 也同樣得改,用命名分組不會有這個問題。

語法

命名捕獲分組自身的語法是 (?<name>…),比普通的分組多了一個 ?<name> 字樣,其中 name 的起法就和你平時起變量名一樣即可(不過在這裏關鍵字也可用)。

反向引用一個命名分組的語法是 \k<name>,注意命名分組同樣可以通過数字索引來反向引用,比如:

/(?<foo>a)\k<foo>\1/.test("aaa") // true

在 replace() 方法的替換字符串中反向引用是用 $<name>:

"abc".replace(/(?<foo>a)/, "$<foo>-") // "a-bc",同樣 $1 仍然可用

總結一下就是,和命名分組相關的有三種語法,分別是 ?<name>、\k<name>、$<name>,相同點是都用尖括號包裹着分組名。

在 API 中的使用

在 exec() 和 match() 中的使用:

const groups = "04-25-2017".match(/(?<month>\d{2})-(?<day>\d{2})-(?<year>\d{4})/).groups // {month: "04", day: "25", year: "2017"}

const {day, month, year} = groups

exec() 和 match() 方法返回的匹配結果數組上多了一個 groups 屬性,裏面存放着每個命名分組的名稱以及它們匹配到的值,利用 ES6 的解構語法,可以方便的提取出想要的字段。注意這個 groups 屬性只有在當前正則里至少存在一個命名分組的前提下才會存在,比如:

/(\d{2})-(\d{2})-(\d{4})/.exec("04-25-2017").groups // undefined,因為沒有命名分組

在 replace(/…/, replacement) 中的使用:

replacement 是字符串的情況上面已經舉過例子了,這裏主要講它是函數的情況:

"04-25-2017".replace(/(?<month>\d{2})-(?<day>\d{2})-(?<year>\d{4})/, (...args) => {
  const groups = args.slice(-1)[0]
  const {day, month, year} = groups
  return `${day}-${month}-${year}`
}) // "25-04-2017"

也就是說,在實參列表的最末尾,多傳了一個 groups 對象。同樣,如果正則里沒有命名分組,這個參數不會存在。

異常情況

分組名不能有重複項:

/(?<foo>a)(?<foo>b)/ // SyntaxError: Duplicate capture group name

反向引用一個不存在的分組名:

/\k<foo>/u // SyntaxError: Invalid named capture referenced

/\k<foo>/.test("k<foo>") // true, 非 Unicode 下為了向後兼容,k 前面的 \ 會被丟棄

在 reaplce() 方法的替換字符串中引用一個不存在的分組:

"abc".replace(/(?<foo>.*)/, "$<bar>") // SyntaxError: Invalid replacement string

"abc".replace(/(.*)/, "$<bar>") // "$<bar>",不包含命名分組時會向後兼容

總結

V8 目前已經完全實現了命名捕獲分組的提案 https://tc39.github.io/proposal-regexp-named-groups/。

命名分組雖然帶來了一些好處,但我個人覺得,正則越長越難讀懂,尤其增加的長度是一堆小括號和尖括號。在可讀性上,命名分組也許會起到反作用,尤其對正則苦手來說。 

站長推薦

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

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

沐鳴註冊平台官網_ES9(2018)新特性:異步迭代、Promise.finally、Rest/Spread等

ES9是ECMA協會在2018年6月發行的一個版本,因為是ECMAScript的第九個版本,所以也稱為ES9。本篇文章介紹ES2018(ES9)的新特性,來看看怎麼使用它們。

ES9新特性(2018)

異步迭代

Promise.finally()

Rest/Spread 屬性

正則表達式命名捕獲組(Regular Expression Named Capture Groups)

正則表達式反向斷言(lookbehind)

正則表達式dotAll模式

正則表達式 Unicode 轉義

非轉義序列的模板字符串

1. 異步迭代

await可以和for…of循環一起使用,以串行的方式運行異步操作

async function process(array) {
for await (let i of array) {
// doSomething(i);
}
}

2.Promise.finally()

一個Promise調用鏈要麼成功到達最後一個.then(),要麼失敗觸發.catch()。在某些情況下,你想要在無論Promise運行成功還是失敗,運行相同的代碼,例如清除,刪除對話,關閉數據庫連接等。

.finally()允許你指定最終的邏輯:

function doSomething() {
  doSomething1()
  .then(doSomething2)
  .then(doSomething3)
  .catch(err => {
    console.log(err);
  })
  .finally(() => {
    // finish here!
  });
}

3. Rest/Spread 屬性

ES2015引入了Rest參數和擴展運算符。三個點(…)僅用於數組。Rest參數語法允許我們將一個不定數量的參數表示為一個數組。

restParam(1, 2, 3, 4, 5);

function restParam(p1, p2, ...p3) {
  // p1 = 1
  // p2 = 2
  // p3 = [3, 4, 5]
}

展開操作符以相反的方式工作,將數組轉換成可傳遞給函數的單獨參數。例如Math.max()返回給定数字中的最大值:

const values = [99, 100, -1, 48, 16];
console.log( Math.max(...values) ); // 100

ES2018為對象解構提供了和數組一樣的Rest參數()和展開操作符,一個簡單的例子:

const myObject = {
  a: 1,
  b: 2,
  c: 3
};

const { a, ...x } = myObject;
// a = 1
// x = { b: 2, c: 3 }

或者你可以使用它給函數傳遞參數:

restParam({
  a: 1,
  b: 2,
  c: 3
});

function restParam({ a, ...x }) {
  // a = 1
  // x = { b: 2, c: 3 }
}

跟數組一樣,Rest參數只能在聲明的結尾處使用。此外,它只適用於每個對象的頂層,如果對象中嵌套對象則無法適用。

擴展運算符可以在其他對象內使用,例如:

const obj1 = { a: 1, b: 2, c: 3 };
const obj2 = { ...obj1, z: 26 };
// obj2 is { a: 1, b: 2, c: 3, z: 26 }

可以使用擴展運算符拷貝一個對象,像是這樣obj2 = {…obj1},但是 這隻是一個對象的淺拷貝。另外,如果一個對象A的屬性是對象B,那麼在克隆后的對象cloneB中,該屬性指向對象B。

4. 正則表達式命名捕獲組

ECMAScript 2018將命名捕獲組引入JavaScript正則表達式。

JavaScript正則表達式可以返回一個匹配的對象——一個包含匹配字符串的類數組,例如:以YYYY-MM-DD的格式解析日期:

const   reDate = /([0-9]{4})-([0-9]{2})-([0-9]{2})/,   
        match  = reDate.exec('2018-04-30'),   
        year   = match[1], // 2018   
        month  = match[2], // 04   
        day    = match[3]; // 30

這樣的代碼很難讀懂,並且改變正則表達式的結構有可能改變匹配對象的索引。

ES2018允許命名捕獲組使用符號?<name>,在打開捕獲括號(后立即命名,示例如下:

const   reDate = /(?<year>[0-9]{4})-(?<month>[0-9]{2})-(?<day>[0-9]{2})/,   
        match  = reDate.exec('2018-04-30'),   
        year   = match.groups.year,  // 2018   
        month  = match.groups.month, // 04   
        day    = match.groups.day;   // 30

任何匹配失敗的命名組都將返回undefined。

命名捕獲也可以使用在replace()方法中。例如將日期轉換為美國的 MM-DD-YYYY 格式:

const   reDate = /(?<year>[0-9]{4})-(?<month>[0-9]{2})-(?<day>[0-9]{2})/, 
        d = '2018-04-30', 
        usDate = d.replace(reDate, '$<month>-$<day>-$<year>');

5.正則表達式反向斷言

目前JavaScript在正則表達式中支持先行斷言(lookahead)。這意味着匹配會發生,但不會有任何捕獲,並且斷言沒有包含在整個匹配字段中。例如從價格中捕獲貨幣符號:

const   reLookahead = /\D(?=\d+)/,   
        match = reLookahead.exec('$123.89');  
console.log( match[0] ); // $

ES2018引入以相同方式工作但是匹配前面的反向斷言(lookbehind),這樣我就可以忽略貨幣符號,單純的捕獲價格的数字:

const   reLookbehind = /(?<=\D)\d+/,   
        match = reLookbehind.exec('$123.89');  
console.log( match[0] ); // 123.89

以上是 肯定反向斷言,非数字\D必須存在。同樣的,還存在 否定反向斷言,表示一個值必須不存在,例如:

const   reLookbehindNeg = /(?<!\D)\d+/,   
        match  = reLookbehind.exec('$123.89');  
console.log( match[0] ); // null

6.正則表達式dotAll模式

正則表達式中點.匹配除回車外的任何單字符,標記s改變這種行為,允許行終止符的出現,例如:

/hello.world/.test('hello\nworld');  // false 
/hello.world/s.test('hello\nworld'); // true

7.正則表達式 Unicode 轉義

到目前為止,在正則表達式中本地訪問 Unicode 字符屬性是不被允許的。ES2018添加了 Unicode 屬性轉義——形式為\p{…}和\P{…},在正則表達式中使用標記 u (unicode) 設置,在\p塊兒內,可以以鍵值對的方式設置需要匹配的屬性而非具體內容。例如:

const reGreekSymbol = /\p{Script=Greek}/u; 
reGreekSymbol.test('π'); // true

此特性可以避免使用特定 Unicode 區間來進行內容類型判斷,提升可讀性和可維護性。

8.非轉義序列的模板字符串

之前,\u開始一個 unicode 轉義,\x開始一個十六進制轉義,\後跟一個数字開始一個八進制轉義。這使得創建特定的字符串變得不可能,例如Windows文件路徑 C:\uuu\xxx\111。

站長推薦

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

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

沐鳴登錄_ES8(2017)新特性:async/await、Object.values、Object.entries、String padding等

ES8 是 ECMA-262 標準第 8 版的簡稱,從 ES6 開始每年發布一個版本,以年份作為名稱,因此又稱 ECMAScript 2017,簡稱 ES2017。

ES8新特性(2017)

與 ES6 相比,ES8 是 JavaScript 的一個小版本,但它仍然引入了非常有用的功能:

async/await

Object.values()

Object.entries()

String padding: padStart()和padEnd(),填充字符串達到當前長度

函數參數列表結尾允許逗號

Object.getOwnPropertyDescriptors()

ShareArrayBuffer和Atomics對象,用於從共享內存位置讀取和寫入

1.async/await

ES2018引入異步迭代器(asynchronous iterators),這就像常規迭代器,除了next()方法返回一個Promise。因此await可以和for…of循環一起使用,以串行的方式(即使異步按一定順序執行)運行異步操作。例如:

async function process(array) {
  for await (let i of array) {
    doSomething(i);
  }
}

異步終極解決方案。

2.Object.values()

Object.values()是一個與Object.keys()類似的新函數,但返回的是Object自身屬性的所有值,不包括繼承的值。

假設我們要遍歷如下對象obj的所有值:

const obj = {a: 1, b: 2, c: 3};

不使用Object.values() :ES7

const vals=Object.keys(obj).map(key=>obj[key]);
console.log(vals);//[1, 2, 3]

使用Object.values() :ES8

const values=Object.values(obj1);
console.log(values);//[1, 2, 3]

從上述代碼中可以看出Object.values()為我們省去了遍歷key,並根據這些key獲取value的步驟。

3.Object.entries()

Object.entries()函數返回一個給定對象自身可枚舉屬性的鍵值對的數組。

接下來我們來遍歷上文中的obj對象的所有屬性的key和value:

不使用Object.entries() :ES7

Object.keys(obj).forEach(key=>{
    console.log('key:'+key+' value:'+obj[key]);
})
//key:a value:1
//key:b value:2
//key:c value:3

使用Object.entries() :ES8

for(let [key,value] of Object.entries(obj1)){
    console.log(`key: ${key} value:${value}`)
}
//key:a value:1
//key:b value:2
//key:c value:3

4.String padding

在ES8中String新增了兩個實例函數String.prototype.padStart和String.prototype.padEnd,允許將空字符串或其他字符串添加到原始字符串的開頭或結尾。

String.padStart(targetLength,[padString])

targetLength:當前字符串需要填充到的目標長度。如果這個數值小於當前字符串的長度,則返回當前字符串本身。

padString:(可選)填充字符串。如果字符串太長,使填充后的字符串長度超過了目標長度,則只保留最左側的部分,其他部分會被截斷,此參數的缺省值為 ” “。

console.log('0.0'.padStart(4,'10')) //10.0
console.log('0.0'.padStart(20))// 0.00    

String.padEnd(targetLength,padString])  參數含義同padStart()

console.log('0.0'.padStart(4,'10')) //10.0
console.log('0.0'.padStart(20))// 0.00    

5.函數參數列表結尾允許逗號

主要作用是方便使用git進行多人協作開發時修改同一個函數減少不必要的行變更。你可以直接新加一行而不必給上一行再補充一個逗號。

//函數定義
function clownsEverywhere(
  param1,
  param2,
) { }
//函數調用
clownsEverywhere(
  'foo',
  'bar',
);

6.Object.getOwnPropertyDescriptors()

Object.getOwnPropertyDescriptors()函數用來獲取一個對象的所有自身屬性的描述符,如果沒有任何自身屬性,則返回空對象。

函數原型:Object.getOwnPropertyDescriptors(obj)

返回obj對象的所有自身屬性的描述符,如果沒有任何自身屬性,則返回空對象。

var obj={
    name:"jack",
    age:23,
}
console.log(Object.getOwnPropertyDescriptors(obj))

7.SharedArrayBuffer對象

SharedArrayBuffer 對象用來表示一個通用的,固定長度的原始二進制數據緩衝區,類似於 ArrayBuffer 對象,它們都可以用來在共享內存(shared memory)上創建視圖。與 ArrayBuffer 不同的是,SharedArrayBuffer 不能被分離。

/**
*
* @param {*} length 所創建的數組緩衝區的大小,以字節(byte)為單位。
* @returns {SharedArrayBuffer} 一個大小指定的新 SharedArrayBuffer 對象。其內容被初始化為 0。
*/
new SharedArrayBuffer(10)

8.Atomics對象(這裏不做詳細說明)

Atomics 對象提供了一組靜態方法用來對 SharedArrayBuffer 對象進行原子操作。

站長推薦

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

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

沐鳴開戶_ES6(2015)新特性:類、模塊化、箭頭、函數參數默認值、模板字符串

ES6帶來了特別多的新特性,新方法。對我們前端工作人員的編碼習慣和編碼模式都帶來了翻天覆地的變化。

ES6是ECMA Script的第六個大版本,從ES2015開始,ECMA Script不再使用版本號作為更新標誌,而是改成年份來標記,但是大家還是願意用ES6來稱呼ES2015,甚至很多文章對後期更新的版本更新內容也一概用ES6來進行指代,比如異步終極解決方案async await 其實是ES2017的新增內容,但是很多科普文章還是將其稱為ES6的新特性,所以大家一定要分清楚哦,看到ES6要知道他是單指ES2015還是泛指ES2015之後所有的新內容~

1. 類(class)

ES6引入了Class(類)這個概念,作為對象的模板,通過class關鍵字,可以定義類。基本上,ES6的class可以看作只是一個語法糖,它的絕大部分功能,ES5都可以做到,新的class寫法只是讓對象原型的寫法更加清晰、更像面向對象編程的語法而已。上面的代碼用ES6的“類”改寫,就是下面這樣。

class Man {
  constructor(name) {
    this.name = 'fly63前端';
  }
  console() {
    console.log(this.name);
  }
}
const man = new Man('fly63前端');
man.console(); // fly63前端

2. 模塊化(ES Module)

ES6模塊在編譯時就能確定模塊的依賴關係,以及輸入和輸出的變量。這種加載稱為“編譯時加載”或者靜態加載,效率更高,使靜態分析成為可能,比如引入宏和類型檢驗功能。這也導致了沒法引用ES6模塊本身,因為它不是對象。
ES6模塊自動採用嚴格模式。尤其需要注意this的限制,頂層的this指向undefined。
ES6模塊通過export命令規定模塊的對外接口,通過import命令輸入其它模塊提供的功能。

// 模塊 A 導出一個方法
export const sub = (a, b) => a + b;
// 模塊 B 導入使用
import { sub } from './A';
console.log(sub(1, 2)); // 3

3. 箭頭(Arrow)函數

ES6可以使用“箭頭”(=>)定義函數,注意是函數,不要使用這種方式定義類(構造器)。

const func = (a, b) => a + b;
func(1, 2); // 3

4. 函數參數默認值

在ES6如果函數參數沒有值或未定義的,默認函數參數允許將初始值初始化為默認值。

function foo(age = 25,){ // ...}

5. 模板字符串

模板字面量是允許嵌入表達式的字符串字面量。你可以使用多行字符串和字符串插值功能。它們在ES2015規範的先前版本中被稱為“模板字符串”。

const name = 'fly63前端';
const str = `Your name is ${name}`;

6. 解構賦值

解構賦值是對賦值運算符的擴展。

他是一種針對數組或者對象進行模式匹配,然後對其中的變量進行賦值。

在代碼書寫上簡潔且易讀,語義更加清晰明了;也方便了複雜對象中數據字段獲取。

let a = 1, b= 2;
[a, b] = [b, a]; // a 2  b 1

7. 延展操作符

延展操作符 = …可以在函數調用/數組構造時,將數組表達式或者string在語法層面展開,還可以在構造對象時,將對象表達式按key-value的方式展開。

let a = [...'hello world']; // ["h", "e", "l", "l", "o", " ", "w", "o", "r", "l", "d"]

8. 對象屬性簡寫

在ES6中允許我們在設置一個對象的屬性的時候不指定屬性名。對象中直接寫變量,非常簡潔。

const name='fly63前端',
const obj = { name };

9. Promise

Promise 是異步編程的一種解決方案,其實是一個構造函數,自己身上有all、reject、resolve這幾個方法,原型上有then、catch等方法。

Promise.resolve().then(() => { console.log(2); });
console.log(1);
// 先打印 1 ,再打印 2

10. let和const

ES2015(ES6) 新增加了兩個重要的 JavaScript 關鍵字: let 和 const。 let 聲明的變量只在 let 命令所在的代碼塊內有效。

const 聲明一個只讀的常量,一旦聲明,常量的值就不能改變。

let name = 'fly63';
const arr = [];

站長推薦

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

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

沐鳴開戶_【譯】React團隊的技術準則

本文翻譯自react團隊核心成員Dan Abramov的技術博客。地址:https://overreacted.io/

本文首發於公眾號:符合預期的CoyPan

我react團隊工作的這段時間,很幸運能夠看見 Jordan、Sebastian、Sophie 和其他團隊成員是如何解決問題的。在本文中,我會把從他們身上學到的,濃縮為一篇較高層次的技術準則。這些準則未必詳細。它們都是我對React團隊的觀察和整理 —— 其他團隊成員或許有其他的觀點。

UI優先於API

當我們把抽象概念大規模用於實踐時,難免會有怪異之處。這些怪異之處是如何在用戶界面上呈現的呢?你能看出一個應用中包含了哪些特定的抽象概念么?

抽象概念對用戶體驗有直接的影響 —— 它能創造好的、延續性的的體驗或者限制某些東西。這也是為什麼我們在設計API時,並不會從抽象本身開始。相反地,我們會從用戶體驗開始,然後再回到抽象概念。

有時候當我們回到抽象概念時,會發現必須更改整個方法,才能達到正確的用戶體驗。如果我們先從API下手,就無法察覺到這點。所以,我們將UI放在API之前考慮。

吸收複雜性

簡化React內部的實現並不是我們的目標。如果產品開發者可以使用React寫出更易於理解、易於修改的代碼,我們樂於把React的內部實現變得複雜。

我們想把產品的開發變得更加職責分明,易於合作。這意味着我們必須把負責的部分封裝在React的內部。React不能被切分為小規模、耦合鬆散的模塊,因為這樣便無法工作。React的使命是成為協調者的角色。

通過提升抽象層級,使得產品開發者更加有力。產品開發會從React的可預測的完備系統中受益。這意味着我們推出的每一個新功能,都必須兼容已經存在的功能。設計和實現React的新功能十分困難。這也是我們核心功能並沒有收到太多開源貢獻的原因。

我們吸收了複雜的部分,防止他們污染產品的代碼。

從Hacks到Idioms

每一個Api都有一些局限性。有時,這些限制會妨礙我們打造良好的使用者體驗。為此,我們提供了一些後路(escape hatches)以供需要時使用。

Hacks並不是長久之計,因為他們很脆弱。開發者必須決定他們是否維護,支持這些Hack,或者移除hack而犧牲用戶體驗。通常大多數人會犧牲用戶體驗,不然這些hack也會有可能阻礙用戶的優化。

我們需要讓產品開發者使用這個後路,並觀察在他們實踐中都是如何使用的。我們的目標是提供這類實現一個常用的解決方法(idiomatic solution),目的是達成更好的用戶體驗。有時候,一個解決方案,會花費我們數年的時間。我們更傾向於有彈性的hack來確立完整的習慣用法(a poor idiom)。

實現局部開發

你無法在代碼編輯器做太多的事情。你可以增加幾行,移除幾行。或者複製粘貼。但許多抽象概念讓這些基本操作變得困難。

比如,MVC框架讓刪除一些render的操作變得不可靠。這是因為及即使你刪除了childern的方法,parent仍有可能執行它。相比之下,React的優勢在於:你通常能安全的刪除某些render tree內的代碼。

在設計API時,我們會假設使用它的人只熟悉他們會用到的局部代碼的相關知識。如果預期發生的影響只發生在這局部的代碼中,我們將會避免意料之外的結果。例如,我們通常假設新增代碼時安全的。在移除和修改代碼時,應該清楚指出這些改動會連帶影響、應該被考慮到的部分。我們不應該假設改動單一文件需要對整個代碼都了解。

如果某一項改動不安全,我們希望開發者能夠儘早發現這個改動所帶來的的影響。雖然可以使用警告、類型檢查和開發者工具來幫忙,但它們都受限於API的設計。如果API不夠局部性,局部開發就不可能實現。例如,findDOMNode就不是一個好的API,因為它需要全面的了解。

漸進的複雜度

有些框架會選擇在開發的路上分出岔路,提供兩種路線:簡單的方法或強大、完整的方法。簡單的方法容易學習,但你終究會走到他的極限。這個時候,你必須推倒重來,重新使用另外一套方法來實現。

我們認為實現一個複雜的東西,和實現一個簡單的東西,在結構上沒有太大的差別。我們並不會簡單的狀態提供簡單的寫法,因為這樣會使開發中出現岔路。如果我們認為開發者在開發過程中想要完整的開發工具,我們願意犧牲低門檻來達成這件事。

有時,【簡單】和【強大】代表兩種不同的框架,那麼你扔需要換框架重寫,最好能避免這種事。以React為例,增加服務端的render這類的優化會需要付出額外的努力,但你不需要完全的重寫。

控制損害

從上到下的解決方式很重要,例如代碼評估。然後長時間下來,我們的標準會下降,功能會在dead line前完成,也有可能不繼續維護產品。我們無法期待所有人都遵守規則,身為協調者的React必須控制損害。

如果有些UI相關的代碼很慢,我們需要想盡一切辦法,避免它拖慢載入時間,避免它影響其他的UI表現。最理想的狀況是,開發者只會為了他們使用到的功能付出開發成本,而產品使用者只需要載入他們會用到的UI。Concurrent Mode ,包括 Time Slicing 和 Selective Hydration ,可以以不同的方式達到理想狀態。

由於代碼庫本身的性能相對穩定,而應用的代碼沒有底線。因此我們傾向於在應用代碼中去控制損害,而不是去修正代碼庫內的代碼。

相信理論

有時我們會知道某些做法是死路一條。也許它現在可以運作,但可以想象它的局限。本質上無法依靠它來實現想要的用戶體驗。一旦有機會,我們會立刻從這種情況中抽身。

我們不想卡在這裏。如果某種做法在理論上更站得住腳,就算畫上好幾年,我們也願意在上面投入精力。在達成目標的過程中,會遇到許多障礙和務實的妥協。但我們詳細,若持續的客服這些困難,理論終究會獲勝。

你們團隊的準則是什麼

以上是我觀察到的React團隊在工作時的基本原則,但我可能漏了很多。我也還沒提到React如何推出API,團隊如何溝通未來的改動方向等等。或許下次可以再來談談這些。

站長推薦

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

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

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

沐鳴平台註冊登錄_Vue的基礎指令

v-bind綁定標籤屬性
通過添加v-bind:標籤屬性來控制標籤的屬性,設置后自動查詢vue裏面的數據,通常簡寫成:標籤屬性,也可以傳入對象,通過對象的值來控制是否為真

<body>
    <div id="app">
        <a v-bind:href="BaiDuUrl">{{txt}}</a>
        <p :class="{txtPink:isPink}">新年新氣象</p>
    </div>
</body>
<script>
        let vm = new vue({
            el:"#app",
            data:{
                BaiDuUrl:"https://www.baidu.com/",
                txt:"百度",
                isPink:true
            }
        })
 </script>
</html>
<style>
.txtPink{
    color: pink;
}
</style>


v-on監聽事件
v-on指令用於綁定事件監聽器,通常簡寫成@事件類型(click,mouseenter,mouseleave…)=”表達式”,表達式可以是方法名(方法需要寫在methods對象裏面,方法裏面可以接收事件對象),邏輯等.

<body>
    <div id="app">
        <p>{{num}}</p>
        <button v-on:click="num+=2">加2</button>
        <button @click="numCut">減2</button>
    </div>
</body>
<script>
        let vm = new Vue({
            el:"#app",
            data:{
                num:20
            },
            methods: {
                numCut(e){
                    this.num -= 2 ;
                    console.log(e)
                }
            },
        })
 </script>

v-on可以添加事件修飾符進行事件的擴展

.stop
.prevent
.capture
.self
.once

<!-- 阻止單擊事件冒泡 -->
<a v-on:click.stop="doThis"></a>
<!-- 提交事件不再重載頁面 -->
<form v-on:submit.prevent="onSubmit"></form>
<!-- 修飾符可以串聯 -->
<a v-on:click.stop.prevent="doThat"></a>
<!-- 只有修飾符 -->
<form v-on:submit.prevent></form>
<!-- 添加事件偵聽器時使用事件捕獲模式 -->
<div v-on:click.capture="doThis">...</div>
<!-- 只當事件在該元素本身(比如不是子元素)觸發時觸發回調 -->
<div v-on:click.self="doThat">...</div>
<!-- 點擊事件將只會觸發一次 -->
<a v-on:click.once="doThis"></a>


v-if和v-show指令控制显示/隱藏
v-show是對樣式層面的控制,v-if是對dom節點的控制,並且可以設置v-else if,v-else進行擴展

<body>
    <div id="app">
        <h1 v-show="isShow">標題</h1>
        <p v-if="isShow">第一段文字</p>
        <p v-else-if="num==1">第二段文字</p>
        <p v-else>第三段文字</p>
        <button @click="isShow=!isShow">显示/隱藏</button>
    </div>
</body>
<script>
        let vm = new Vue({
            el:"#app",
            data:{
                isShow:true,
                num:0,
            },
        })
 </script>


v-for指令進行循環
當需要循環Vue裏面的數據渲染到頁面時可以使用v-for指令,使用 (item,index) in list的語法,通常後面設置:key=”唯一值”,用來高效的更新DOM.如果傳入對象可以使用(item,key,index) in obj,取到對象的鍵值

<body>
    <div id="app">
        <ul>
            <li v-for="(item,index) in lists" :key="item">第{{index+1}}個:{{item}}</li>
        </ul>
        <p v-for="(item,key,index) in someOne" :key="key">第{{index+1}},{{key}}是{{item}}</p>
    </div>
</body>
<script>
        let vm = new Vue({
            el:"#app",
            data:{
                lists:['段譽','王語嫣','虛竹','喬峰'],
                someOne:{
                    '早上':'吃早餐',
                    '中午':'吃午飯',
                    '晚上':"吃晚飯"
                }
            },
        })
 </script>
 <!-- 頁面數據
 第1個:段譽
第2個:王語嫣
第3個:虛竹
第4個:喬峰
第1,早上是吃早餐

第2,中午是吃午飯

第3,晚上是吃完飯
  -->


v-model指令
v-model的值是,vue接收表單元素的值的變量名,可以實現雙向數據綁定,通過改變v-model的txt值,使綁定txt值的元素髮生改變.
v-model限制在input、select、textarea、components中使用,修飾符.lazy(取代 input 監聽 change 事件)、.number(輸入字符串轉為有效的数字)、.trim(輸入首尾空格過濾)
v-model 本質上包含了兩個操作:

  1. v-bind 綁定input元素的value屬性
  2. v-on 指令綁定input元素的input事件,使用data中的數據更新為input元素的value
<body>
    <div id="app">
        <p >{{txt}}</p>
        <input type="text" v-model="txt">
    </div>
</body>
<script>
        let vm = new Vue({
            el:"#app",
            data:{
                txt:'默認文字'
            },
        })
 </script>


站長推薦

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

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

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

沐鳴平台網址_React useEffect的源碼解讀

前言

對源碼的解讀有利於搞清楚Hooks到底做了什麼,如果您覺得useEffect很“魔法”,這篇文章也許對您有些幫助。

本篇博客篇幅有限,只看useEffect,力求簡單明了,帶您到react Hooks的深處看看

按圖索驥找到Hook相關源碼(可以直接跳)

首先我們從Github上得到react的源碼,然後可以在packages中找到react文件夾,其中的index.js就是我們的入口。

代碼很簡單,就兩行:

const React = require('./src/React');

module.exports = React.default || React;

所以接下來我們去看看 ‘react/src/React’,代碼有點多,我們簡化一下:

import ReactVersion from 'shared/ReactVersion';

// ...

import {
  useEffect,
} from './ReactHooks';

const React = {
  useEffect
};

//...
export default React;

很好,現在我們至少知道為什麼Hooks的引用方式是:

import {useEffect} from ‘react’

接下來我們繼續看看 ‘react/src/ReactHooks’。

ReactHooks文件(可以直接跳)

之前說好了只看useEffect,所以同樣需要簡化一下代碼。

並且考慮到有人對TypeScript語法不熟悉,還去掉了TypeScript語法,之後的簡化代碼也會如此。

現在我們看下簡化后的代碼:

import invariant from 'shared/invariant';

import ReactCurrentDispatcher from './ReactCurrentDispatcher';

function resolveDispatcher() {
  const dispatcher = ReactCurrentDispatcher.current;
  // React版本不對或者Hook使用有誤什麼的就報錯
  // ...
  return dispatcher;
}

export function useEffect(create,inputs) {
  const dispatcher = resolveDispatcher();
  return dispatcher.useEffect(create, inputs);
}

這裏可以看到,我們的useEffect實際上是ReactCurrentDispatcher.current.useEffect。

ReactCurrentDispatcher文件(可以直接跳)

看一下ReactCurrentDispatcher文件,這裏沒有簡化:

import type {Dispatcher} from 'react-reconciler/src/ReactFiberHooks';

const ReactCurrentDispatcher = {
  current: (null: null | Dispatcher),
};

export default ReactCurrentDispatcher;

發現他的current的類型是null或者Dispatcher,所以這裏我們很簡單就能猜到,這個東西的源碼在 ‘react-reconciler/src/ReactFiberHooks’。

ReactFiberHooks文件

幾千行代碼,頭大。但是莫慌,咱們又不是來寫react的,看看原理而已。

我們之前已經知道useState實際上是ReactCurrentDispatcher.current.useState。

很明顯ReactCurrentDispatcher.current不管是什麼東西單獨列出來,我們只需要知道誰賦值給他就行了。

精簡代碼,去掉用__DEV__區分的開發代碼之後,我們發現整個文件給ReactCurrentDispatcher.current賦值的沒幾個。

而唯一一個與異常判斷無關的是renderWithHooks函數中的這一塊代碼:

export function renderWithHooks(
  current,
  workInProgress,
  Component,
  props,
  secondArg,
  nextRenderExpirationTime
){
  ReactCurrentDispatcher.current =
      current === null || current.memoizedState === null
        ? HooksDispatcherOnMount
        : HooksDispatcherOnUpdate;
  let children = Component(props, secondArg);
  return children;
}

我們不知道這段代碼是幹嘛的,但是他肯定是渲染組件時用的。

而這裏很顯然ReactCurrentDispatcher.current的值就只能是HooksDispatcherOnMount和HooksDispatcherOnUpdate。

很明顯這兩個一個用於加載時,一個用於更新時。

然後我們們搜一下相關代碼:

const HooksDispatcherOnMount = {
  useEffect: mountEffect
};

const HooksDispatcherOnUpdate = {
  useEffect: updateEffect
};

也就是說,組件加載時,useEffect會調用mountEffect,組件更新時會調用updateEffect。

讓我們繼續看看這兩個函數:

function mountEffect(create, deps) {
  return mountEffectImpl(
    UpdateEffect | PassiveEffect,
    UnmountPassive | MountPassive,
    create,
    deps,
  );
}

function updateEffect(create, deps) {
  return updateEffectImpl(
    UpdateEffect | PassiveEffect,
    UnmountPassive | MountPassive,
    create,
    deps,
  );
}

這裏的UpdateEffect和PassiveEffect是二進制常數,用位運算的方式操作。

先不用知道具體意義,知道是個常量即可。

接下來我們看看具體的mountEffectImpl:

function mountEffectImpl(fiberEffectTag, hookEffectTag, create, deps){
  const hook = mountWorkInProgressHook();
  // useEffect不傳依賴,那麼就為null
  const nextDeps = deps === undefined ? null : deps;
  currentlyRenderingFiber.effectTag |= fiberEffectTag;
  // 鏈表尾部hook對象的memoizedState為pushEffect的返回值
  hook.memoizedState = pushEffect(hookEffectTag, create, undefined, nextDeps);
}

我們看到第一行代碼調用mountWorkInProgressHook新建了一個hook對象,讓我們看看mountWorkInProgressHook:

function mountWorkInProgressHook() {
  const hook = {
    memoizedState: null,

    baseState: null,
    baseQueue: null,
    queue: null,

    next: null,
  };

  if (workInProgressHook === null) {
    // This is the first hook in the list
    currentlyRenderingFiber.memoizedState = workInProgressHook = hook;
  } else {
    // Append to the end of the list
    workInProgressHook = workInProgressHook.next = hook;
  }
  return workInProgressHook;
}

很明顯這裡有個鏈表結構workInProgressHook,如果workInProgressHook鏈表為null就將新建的hook對象賦值給它,如果不為null,那麼就加在鏈表尾部。

這裡有必要講解一下:

Hooks作為一個鏈表存儲在fiber的memoizedState中。
currentHook 是當前fiber的鏈表。
workInProgressHook 是即將被加入到 work-in-progress fiber的鏈表。

然後我們再看看pushEffect:

function pushEffect(tag, create, destroy, deps) {
  // 新建一個effect,很明顯又是個鏈表結構
  const effect = {
    tag,
    create,
    destroy,
    deps,
    // Circular
    next: null,
  };
  // 從currentlyRenderingFiber.updateQueue獲取組件更新隊列
  let componentUpdateQueue= currentlyRenderingFiber.updateQueue;
  // 判斷組件更新隊列是否為空,每次在調用renderWithHooks都會將這個componentUpdateQueue置為null
  // 這樣的話每次update這個組件時,就會創建一個新的effect鏈表
  if (componentUpdateQueue === null) {
    // 為空就創建一個組件更新隊列
    componentUpdateQueue = createFunctionComponentUpdateQueue();
    // 並賦值給currentlyRenderingFiber.updateQueue
    currentlyRenderingFiber.updateQueue = componentUpdateQueue;
    // 組件更新隊列最新的effect為我們新建的effect
    componentUpdateQueue.lastEffect = effect.next = effect;
  } else {
    // 如果組件更新隊列已經存在,獲取它最新的Effect
    const lastEffect = componentUpdateQueue.lastEffect;
    if (lastEffect === null) {
      // 如果最新的Effect為null,那麼組件更新隊列最新的Effect為我們新建的effect
      componentUpdateQueue.lastEffect = effect.next = effect;
    } else {
      // 否則將我們的effect加入到鏈表結構中最末尾,然後他的next為鏈表結構的第一個effect
      // 這裏的effect鏈表是個閉環
      const firstEffect = lastEffect.next;
      lastEffect.next = effect;
      effect.next = firstEffect;
      componentUpdateQueue.lastEffect = effect;
    }
  }
  return effect;
}

我們再看看更新時調用的updateEffectImpl:

function updateEffectImpl(fiberEffectTag, hookEffectTag, create, deps) {
  //  這裏 updateWorkInProgressHook 
  //  workInProgressHook = workInProgressHook.next;
  //  currentHook = currentHook.next;
  const hook = updateWorkInProgressHook();
  const nextDeps = deps === undefined ? null : deps;
  let destroy = undefined;


  if (currentHook !== null) {
    const prevEffect = currentHook.memoizedState;
    destroy = prevEffect.destroy;
    if (nextDeps !== null) {
      const prevDeps = prevEffect.deps;
      // 對比兩個依賴數組的各個值之間是否有變動,如果沒變動,那麼就設置標誌位為NoHookEffect
      if (areHookInputsEqual(nextDeps, prevDeps)) {
        pushEffect(NoHookEffect, create, destroy, nextDeps);
        return;
      }
    }
  }

  currentlyRenderingFiber.effectTag |= fiberEffectTag;

  hook.memoizedState = pushEffect(hookEffectTag, create, destroy, nextDeps);
}

我們可以看到updateEffectImpl和mountEffectImpl很像,最重要的是我們得兩個函數串起來,看看他們到底實現了一個什麼。

Hook相關數據結構簡圖

這裏我自己畫了一張圖,利於理解:

這張圖的結構是一個組件某一時刻的結構。

圖中黃色為Fiber節點,綠色為Hook節點,藍色為Effect節點。

Fiber節點,其實就是我們的虛DOM節點,react會生成一個Fiber節點樹,每個組件在Fiber樹上都有對應的Fiber節點。

其中currentlyRenderingFiber表示我們正在進行渲染的節點,它來自於workInProgress,current表示已經渲染的節點。

組件加載時,會執行各個useEffect,然後就會建立一個Hook鏈表,而workInProgress的memoizedState字段就指向了Hook鏈表的尾部Hook節點。

而構建每個Hook節點時,會同時構造一個Effect節點,同樣,Hook節點的memoizedState字段就指向了對應的Effect節點。

而每個Effect節點又會連接起來形成一個鏈表,然後workInProgress的updateQueue字段指向了Effect鏈表的尾部Effect節點。

組件更新時,會依次對比currentHook指向的Effect的依賴數組與新的依賴數組的不同,如果一樣,就設置Effect節點的effectTag為NoHookEffect。

但是無論依賴數組中的值是否改變,都會新構造一個Effect節點,作為Hook節點的memoizedState字段的值。

然後在準備渲染時,會去直接找到Fiber節點的updateQueue的lastEffect,也就是直接指向Effect鏈表的尾部Effect節點。

因為effect鏈表是閉環的,這裏通過lastEffect的next找到第一個Effect。

然後循環遍歷effect鏈表,當effectTag為NoHookEffect則不做操作,否則會去先執行effect的destroy操作,然後再執行create操作。

對,你沒看錯,總結起來就是每次更新后,只要依賴項改變,那麼就會執行useEffect的卸載函數,再執行第一個參數create函數。

這一部分代碼比較遠:

function commitHookEffectList(
  unmountTag,
  mountTag,
  finishedWork,
) {
  const updateQueue = finishedWork.updateQueue;
  let lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
  if (lastEffect !== null) {
    const firstEffect = lastEffect.next;
    let effect = firstEffect;
    do {
      if ((effect.tag & unmountTag) !== NoHookEffect) {
        // Unmount
        const destroy = effect.destroy;
        effect.destroy = undefined;
        if (destroy !== undefined) {
          destroy();
        }
      }
      if ((effect.tag & mountTag) !== NoHookEffect) {
        // Mount
        const create = effect.create;
        effect.destroy = create();
      }
      effect = effect.next;
    } while (effect !== firstEffect);
  }
}

這裏的位運算大家可能有點看不懂,因為NoHookEffect的值是0,所以只要effect.tag被設置為NoHookEffect,那麼

effect.tag & unmountTag

就必然為NoHookEffect。

我們還記得,我們之前的玩法,依賴數組各個值不變時,就設置Effect節點的effectTag為NoHookEffect。

此時是絕對不會執行先destroy Effect節點,再執行Effect函數create的操作。

而如果effect.tag的值不為NoHookEffect,那也得需要effect.tag與unmountTag至少有一個位相同才能執行destroy。

讓我們看看之前無論是mountEffectImpl還是updateEffectImpl都默認傳的是:UnmountPassive | MountPassive,也就是說effect.tag為UnmountPassive | MountPassive。

而很明顯這個設計的目的在於,當mountTag為MountPassive時執行create函數,而unmountTag為UnmountPassive時創建執行destroy函數。

而只有下面這個地方會做這個Passive操作:

export function commitPassiveHookEffects(finishedWork: Fiber): void {
  if ((finishedWork.effectTag & Passive) !== NoEffect) {
    switch (finishedWork.tag) {
      case FunctionComponent:
      case ForwardRef:
      case SimpleMemoComponent:
      case Chunk: {
        commitHookEffectList(UnmountPassive, NoHookEffect, finishedWork);
        commitHookEffectList(NoHookEffect, MountPassive, finishedWork);
        break;
      }
      default:
        break;
    }
  }
}

這裏的意思很明顯,先遍歷一遍effect鏈表,每個依賴項變了的hook都destroy一下,然後再遍歷一遍effect鏈表,每個依賴項變了的,都執行create函數一下。

也就是說每次都會按照useEffect的調用順序,先執行所有useEffect的卸載函數,再執行所有useEffect的create函數。

而commitPassiveHookEffects又是只有flushPassiveEffects這個函數最終能調用到。

而每次 React 在檢測到數據變化時,flushPassiveEffects就會執行。

不論是props還是state的變化都會如此。

所以如果您真的有需要去模擬一個像之前的componentDidMount和componentWillUnmount的生命周期,那麼最好用上一個單獨的Effect:

useEffect(()=>{
  // 加載時的邏輯
  return ()=>{
    // 卸載時的邏輯
  }
},[])

這裏用[]作為依賴數組,是因為這樣依賴就不會變動,也就是只在加載時執行一次加載邏輯,卸載時執行一次卸載邏輯。

不加依賴數組時,那麼每次渲染都會執行一次加載和卸載。

總結

希望我的這篇文章讓您有一些收穫。

這裏主要想說一下自己在源碼閱讀中的感想。

讀這部分的源碼,其實更像是在盲人摸象。

為了追求效率,需要避開一些比較複雜的東西,比如我們提到的Fiber節點樹,又比如其實useEffect替換effect和具體執行effect並不是同步的。

否則解讀很多東西要花很多時間,怕是真的要寫一個系列了。

源碼閱讀略顯艱澀,還好關鍵的地方都是有註釋的,在閱讀的過程中也能收穫不少東西。

我並不是react的開發者,如果在解讀的過程中有什麼疏漏和誤解,還望諸位批評指正。

作者:韓子盧 

出處:https://www.cnblogs.com/vvjiang/ 

站長推薦

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

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

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

沐鳴註冊網站_CSS實現一個粒子動效的按鈕

原文鏈接:https://github.com/XboxYan/notes/issues/16  

按鈕(button)可能是網頁中最常見的組件之一了,大部分都平淡無奇,如果你碰到的是一個這樣的按鈕,會不會忍不住多點幾次呢?

通常這類效果第一反應可能就是藉助canvas了,比如下面這個案例(建議去codepen原鏈接訪問,segmentfault內置的預覽js會加載失敗)

效果就更加震撼了,當然canvas實現也有一定的門檻,而且實際使用起來也略微麻煩(所有js實現的通病),這裏嘗試一下css的實現方式。

生成粒子

拋開js方案,還有html和css實現方式。html就不用說了,直接寫上大量的標籤

<button>
    button
    <i></i>
    <i></i>
    <i></i>
    <i></i>
    <i></i>
    ...
</button>

一般情況下我不是很喜歡這種方式,標籤太多,結構不美觀,而且有可能對現有的頁面造成其他影響(很多情況下並不方便修改原始HTML結構)

那麼來看看CSS實現方式,主要也是兩種方式,其實就是想一下有哪些屬性可以無限疊加,一個是box-shadow,還有一個是background-image(CSS3支持無限疊加)。

1.box-shadow

我們先看看box-shadow方式,為了避免使用額外標籤,這裏採用偽元素生成。

.button::before{
  position: absolute;
  content: '';
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background-color: #ff0081;
  box-shadow: 10px 10px #ff0081,15px 0px 0 2px #ff0081,20px 15px 0 3px #ff0081,...;/*無限疊加*/
}

效果還是有的,主要就是多花點時間來調試,這裏主要根據偏移量和擴展來決定粒子的位置和大小。

不過這裏的偏移量只能是px單位,無法很好的自適應按鈕的大小,所以這裏採用第二種方式來實現

2.background-image

CSS3中background-image是可以無限疊加的,類似於

.myclass {
  background: background1, background2, /*...*/ backgroundN;
}

這裏我們可以採用徑向漸變radial-gradient來實現多個小圓點。

.button::before{
  position: absolute;
  content: '';
  left: -2em;
  right: -2em;
  top: -2em;
  bottom: -2em;
  pointer-events: none;
  background-repeat: no-repeat;
  background-image: radial-gradient(circle, #ff0081 20%, transparent 0), 
  radial-gradient(circle, #ff0081 20%, transparent 0),
  radial-gradient(circle, #ff0081 20%, transparent 0), 
  radial-gradient(circle, #ff0081 20%, transparent 0), 
  ...;
  background-size: 10% 10%, 20% 20%, 15% 15%,...;
  background-position: 18% 40%, 20% 31%, 30% 30%,...;
}

這裏主要通過background-size和background-position來控制原點的尺寸與位置,看着好像挺複雜,其實只要background-size和background-position與background-image位置一一對應就行了。實際開發中可能有點難調試,可以直接在控制台中通過鍵盤上下左右鍵微調實時預覽效果(可以考慮做一個可視化工具)。

這樣就做出了一個簡單的粒子效果。

動起來

雖然background-image不支持CSS動畫,但是另外兩個background-size和background-position支持呀,所以,CSS transition和CSS animation都可以用起來。

動畫效果很簡單,就是粒子從中心往外擴散,並且逐漸消失的過程。

transition

我們先看看:hover交互

.button::before{
  transition:.75s background-position ease-in-out,75s background-size ease-in-out;
}
.button:hover::before{
  background-position: 5% 44%, -5% 20%, 7% 5%...;
  background-size: 0% 0%;
}

當然直接這樣設置肯定是不理想,鼠標離開時會收縮回去,效果如下

我們需要是鼠標離開時不收縮回去,如何實現呢?

很簡單,把transition設置在:hover下就可以了,表示只有當鼠標經過時才有過渡,離開時沒有

.button:hover::before{
  background-position: 5% 44%, -5% 20%, 7% 5%...;
  background-size: 0% 0%;
  transition:.75s background-position ease-in-out,75s background-size ease-in-out;
}

這樣是不是感覺稍微好些了呢?點擊這裏查看。

如果我們想做成點擊的時候出現粒子動畫該怎麼做呢?這裏就需要藉助:active偽類了。

如果我們按照:hover邏輯,那麼

.button:active::before{
  background-position: 5% 44%, -5% 20%, 7% 5%...;
  background-size: 0% 0%;
  transition:.75s background-position ease-in-out,75s background-size ease-in-out;
}

很遺憾,只有當只有按住不動的時候才能觸發,一旦鼠標抬起就沒有了,這個時候我們就需要換個角度了。可以這麼想象一下,默認就是發散的,然後點擊的時候聚攏,抬起的時候就會有還原成之前的發散狀態,同時,在點擊的時候需要取消掉過渡效果,如下

.button::before {
    /*...*/
    background-position: 5% 44%...;/*擴散的狀態*/
    background-size: 0% 0%;
    transition: background-position .5s ease-in-out, background-size .75s ease-in-out;
}

.button:active::before {
  transition:0s;/**注意取消掉過渡**/
  background-size: 10% 10%, 20% 20%...;
  background-position: 18% 40%, 20% 31%,...;
}

你可以查看這個demo

為什麼在:active需要transition:0s呢,你可以試下不添加的效果就明白了,如下

animation

animation和transition實現原理比較類似,優點是可以做出更加精細的動畫,這裏就拿:active方式來說吧。

.button::before{
  /*...*/
  animation: bubbles ease-in-out .75s forwards;
}
.button:active::before {
  animation: none; /*這裏注意取消動畫*/
  background-size: 0;
}
@keyframes bubbles {
  0% {
    background-position: 18% 40%, ...;
  }
  50% {
    background-position: 10% 44%, ...;
  }
  100% {
    background-position: 5% 44%, ...;
    background-size: 0% 0%;
  }
}

可以在這裏查看源碼。

唯一的不足可能是初始化動畫會自執行一次。

小結

上面介紹了純CSS實現一個粒子動效的按鈕,優點很明顯,複製一下CSS直接扔到項目里就能用,管他什麼原生項目還是react項目,也無需綁定什麼事件,也無需額外的邏輯處理,增強現有體驗。試想一下,如果這是一個‘購買’按鈕,會不會觸發你多購買幾次呢,反正我肯定是會被吸引住了,哈~
缺點也還是有的,比如上面的定位,密密麻麻都是工作量啊,建議這些功能在項目整體完成之後再細細打磨,也可以試着做一些可視化工具來減輕工作量,完。

站長推薦

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

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

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