成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專(zhuān)欄INFORMATION COLUMN

【JS進(jìn)階】你真的掌握變量和類(lèi)型了嗎

fuyi501 / 832人閱讀

摘要:本文從底層原理到實(shí)際應(yīng)用詳細(xì)介紹了中的變量和類(lèi)型相關(guān)知識(shí)。內(nèi)存空間又被分為兩種,棧內(nèi)存與堆內(nèi)存。一個(gè)值能作為對(duì)象屬性的標(biāo)識(shí)符這是該數(shù)據(jù)類(lèi)型僅有的目的。

導(dǎo)讀

變量和類(lèi)型是學(xué)習(xí)JavaScript最先接觸到的東西,但是往往看起來(lái)最簡(jiǎn)單的東西往往還隱藏著很多你不了解、或者容易犯錯(cuò)的知識(shí),比如下面幾個(gè)問(wèn)題:

JavaScript中的變量在內(nèi)存中的具體存儲(chǔ)形式是什么?

0.1+0.2為什么不等于0.3?發(fā)生小數(shù)計(jì)算錯(cuò)誤的具體原因是什么?

Symbol的特點(diǎn),以及實(shí)際應(yīng)用場(chǎng)景是什么?

[] == ![]、[undefined] == false為什么等于true?代碼中何時(shí)會(huì)發(fā)生隱式類(lèi)型轉(zhuǎn)換?轉(zhuǎn)換的規(guī)則是什么?

如何精確的判斷變量的類(lèi)型?

如果你還不能很好的解答上面的問(wèn)題,那說(shuō)明你還沒(méi)有完全掌握這部分的知識(shí),那么請(qǐng)好好閱讀下面的文章吧。

本文從底層原理到實(shí)際應(yīng)用詳細(xì)介紹了JavaScript中的變量和類(lèi)型相關(guān)知識(shí)。

一、JavaScript數(shù)據(jù)類(lèi)型

ECMAScript標(biāo)準(zhǔn)規(guī)定了7種數(shù)據(jù)類(lèi)型,其把這7種數(shù)據(jù)類(lèi)型又分為兩種:原始類(lèi)型和對(duì)象類(lèi)型。

原始類(lèi)型

Null:只包含一個(gè)值:null

Undefined:只包含一個(gè)值:undefined

Boolean:包含兩個(gè)值:truefalse

Number:整數(shù)或浮點(diǎn)數(shù),還有一些特殊值(-Infinity、+Infinity、NaN

String:一串表示文本值的字符序列

Symbol:一種實(shí)例是唯一且不可改變的數(shù)據(jù)類(lèi)型

(在es10中加入了第七種原始類(lèi)型BigInt,現(xiàn)已被最新Chrome支持)

對(duì)象類(lèi)型

Object:自己分一類(lèi)絲毫不過(guò)分,除了常用的Object,Array、Function等都屬于特殊的對(duì)象

二、為什么區(qū)分原始類(lèi)型和對(duì)象類(lèi)型 2.1 不可變性

上面所提到的原始類(lèi)型,在ECMAScript標(biāo)準(zhǔn)中,它們被定義為primitive values,即原始值,代表值本身是不可被改變的。

以字符串為例,我們?cè)谡{(diào)用操作字符串的方法時(shí),沒(méi)有任何方法是可以直接改變字符串的:

var str = "ConardLi";
str.slice(1);
str.substr(1);
str.trim(1);
str.toLowerCase(1);
str[0] = 1;
console.log(str);  // ConardLi

在上面的代碼中我們對(duì)str調(diào)用了幾個(gè)方法,無(wú)一例外,這些方法都在原字符串的基礎(chǔ)上產(chǎn)生了一個(gè)新字符串,而非直接去改變str,這就印證了字符串的不可變性。

那么,當(dāng)我們繼續(xù)調(diào)用下面的代碼:

str += "6"
console.log(str);  // ConardLi6

你會(huì)發(fā)現(xiàn),str的值被改變了,這不就打臉了字符串的不可變性么?其實(shí)不然,我們從內(nèi)存上來(lái)理解:

JavaScript中,每一個(gè)變量在內(nèi)存中都需要一個(gè)空間來(lái)存儲(chǔ)。

內(nèi)存空間又被分為兩種,棧內(nèi)存與堆內(nèi)存。

棧內(nèi)存:

存儲(chǔ)的值大小固定

空間較小

可以直接操作其保存的變量,運(yùn)行效率高

由系統(tǒng)自動(dòng)分配存儲(chǔ)空間

JavaScript中的原始類(lèi)型的值被直接存儲(chǔ)在棧中,在變量定義時(shí),棧就為其分配好了內(nèi)存空間。

由于棧中的內(nèi)存空間的大小是固定的,那么注定了存儲(chǔ)在棧中的變量就是不可變的。

在上面的代碼中,我們執(zhí)行了str += "6"的操作,實(shí)際上是在棧中又開(kāi)辟了一塊內(nèi)存空間用于存儲(chǔ)"ConardLi6",然后將變量str指向這塊空間,所以這并不違背不可變性的特點(diǎn)。

2.2 引用類(lèi)型

堆內(nèi)存:

存儲(chǔ)的值大小不定,可動(dòng)態(tài)調(diào)整

空間較大,運(yùn)行效率低

無(wú)法直接操作其內(nèi)部存儲(chǔ),使用引用地址讀取

通過(guò)代碼進(jìn)行分配空間

相對(duì)于上面具有不可變性的原始類(lèi)型,我習(xí)慣把對(duì)象稱為引用類(lèi)型,引用類(lèi)型的值實(shí)際存儲(chǔ)在堆內(nèi)存中,它在棧中只存儲(chǔ)了一個(gè)固定長(zhǎng)度的地址,這個(gè)地址指向堆內(nèi)存中的值。

var obj1 = {name:"ConardLi"}
var obj2 = {age:18}
var obj3 = function(){...}
var obj4 = [1,2,3,4,5,6,7,8,9]

由于內(nèi)存是有限的,這些變量不可能一直在內(nèi)存中占用資源,這里推薦下這篇文章JavaScript中的垃圾回收和內(nèi)存泄漏,這里告訴你JavaScript是如何進(jìn)行垃圾回收以及可能會(huì)發(fā)生內(nèi)存泄漏的一些場(chǎng)景。

當(dāng)然,引用類(lèi)型就不再具有不可變性了,我們可以輕易的改變它們:

obj1.name = "ConardLi6";
obj2.age = 19;
obj4.length = 0;
console.log(obj1); //{name:"ConardLi6"}
console.log(obj2); // {age:19}
console.log(obj4); // []

以數(shù)組為例,它的很多方法都可以改變它自身。

pop() 刪除數(shù)組最后一個(gè)元素,如果數(shù)組為空,則不改變數(shù)組,返回undefined,改變?cè)瓟?shù)組,返回被刪除的元素

push()向數(shù)組末尾添加一個(gè)或多個(gè)元素,改變?cè)瓟?shù)組,返回新數(shù)組的長(zhǎng)度

shift()把數(shù)組的第一個(gè)元素刪除,若空數(shù)組,不進(jìn)行任何操作,返回undefined,改變?cè)瓟?shù)組,返回第一個(gè)元素的值

unshift()向數(shù)組的開(kāi)頭添加一個(gè)或多個(gè)元素,改變?cè)瓟?shù)組,返回新數(shù)組的長(zhǎng)度

reverse()顛倒數(shù)組中元素的順序,改變?cè)瓟?shù)組,返回該數(shù)組

sort()對(duì)數(shù)組元素進(jìn)行排序,改變?cè)瓟?shù)組,返回該數(shù)組

splice()從數(shù)組中添加/刪除項(xiàng)目,改變?cè)瓟?shù)組,返回被刪除的元素

下面我們通過(guò)幾個(gè)操作來(lái)對(duì)比一下原始類(lèi)型和引用類(lèi)型的區(qū)別:

2.3 復(fù)制

當(dāng)我們把一個(gè)變量的值復(fù)制到另一個(gè)變量上時(shí),原始類(lèi)型和引用類(lèi)型的表現(xiàn)是不一樣的,先來(lái)看看原始類(lèi)型:

var name = "ConardLi";
var name2 = name;
name2 = "code秘密花園";
console.log(name); // ConardLi;

內(nèi)存中有一個(gè)變量name,值為ConardLi。我們從變量name復(fù)制出一個(gè)變量name2,此時(shí)在內(nèi)存中創(chuàng)建了一個(gè)塊新的空間用于存儲(chǔ)ConardLi,雖然兩者值是相同的,但是兩者指向的內(nèi)存空間完全不同,這兩個(gè)變量參與任何操作都互不影響。

復(fù)制一個(gè)引用類(lèi)型:

var obj = {name:"ConardLi"};
var obj2 = obj;
obj2.name = "code秘密花園";
console.log(obj.name); // code秘密花園

當(dāng)我們復(fù)制引用類(lèi)型的變量時(shí),實(shí)際上復(fù)制的是棧中存儲(chǔ)的地址,所以復(fù)制出來(lái)的obj2實(shí)際上和obj指向的堆中同一個(gè)對(duì)象。因此,我們改變其中任何一個(gè)變量的值,另一個(gè)變量都會(huì)受到影響,這就是為什么會(huì)有深拷貝和淺拷貝的原因。

2.4 比較

當(dāng)我們?cè)趯?duì)兩個(gè)變量進(jìn)行比較時(shí),不同類(lèi)型的變量的表現(xiàn)是不同的:

var name = "ConardLi";
var name2 = "ConardLi";
console.log(name === name2); // true
var obj = {name:"ConardLi"};
var obj2 = {name:"ConardLi"};
console.log(obj === obj2); // false

對(duì)于原始類(lèi)型,比較時(shí)會(huì)直接比較它們的值,如果值相等,即返回true

對(duì)于引用類(lèi)型,比較時(shí)會(huì)比較它們的引用地址,雖然兩個(gè)變量在堆中存儲(chǔ)的對(duì)象具有的屬性值都是相等的,但是它們被存儲(chǔ)在了不同的存儲(chǔ)空間,因此比較值為false。

2.5 值傳遞和引用傳遞

借助下面的例子,我們先來(lái)看一看什么是值傳遞,什么是引用傳遞:

let name = "ConardLi";
function changeValue(name){
  name = "code秘密花園";
}
changeValue(name);
console.log(name);

執(zhí)行上面的代碼,如果最終打印出來(lái)的name"ConardLi",沒(méi)有改變,說(shuō)明函數(shù)參數(shù)傳遞的是變量的值,即值傳遞。如果最終打印的是"code秘密花園",函數(shù)內(nèi)部的操作可以改變傳入的變量,那么說(shuō)明函數(shù)參數(shù)傳遞的是引用,即引用傳遞。

很明顯,上面的執(zhí)行結(jié)果是"ConardLi",即函數(shù)參數(shù)僅僅是被傳入變量復(fù)制給了的一個(gè)局部變量,改變這個(gè)局部變量不會(huì)對(duì)外部變量產(chǎn)生影響。

let obj = {name:"ConardLi"};
function changeValue(obj){
  obj.name = "code秘密花園";
}
changeValue(obj);
console.log(obj.name); // code秘密花園

上面的代碼可能讓你產(chǎn)生疑惑,是不是參數(shù)是引用類(lèi)型就是引用傳遞呢?

首先明確一點(diǎn),ECMAScript中所有的函數(shù)的參數(shù)都是按值傳遞的。

同樣的,當(dāng)函數(shù)參數(shù)是引用類(lèi)型時(shí),我們同樣將參數(shù)復(fù)制了一個(gè)副本到局部變量,只不過(guò)復(fù)制的這個(gè)副本是指向堆內(nèi)存中的地址而已,我們?cè)诤瘮?shù)內(nèi)部對(duì)對(duì)象的屬性進(jìn)行操作,實(shí)際上和外部變量指向堆內(nèi)存中的值相同,但是這并不代表著引用傳遞,下面我們?cè)侔匆粋€(gè)例子:

let obj = {};
function changeValue(obj){
  obj.name = "ConardLi";
  obj = {name:"code秘密花園"};
}
changeValue(obj);
console.log(obj.name); // ConardLi

可見(jiàn),函數(shù)參數(shù)傳遞的并不是變量的引用,而是變量拷貝的副本,當(dāng)變量是原始類(lèi)型時(shí),這個(gè)副本就是值本身,當(dāng)變量是引用類(lèi)型時(shí),這個(gè)副本是指向堆內(nèi)存的地址。所以,再次記?。?/p>

ECMAScript中所有的函數(shù)的參數(shù)都是按值傳遞的。
三、分不清的null和undefined

在原始類(lèi)型中,有兩個(gè)類(lèi)型NullUndefined,他們都有且僅有一個(gè)值,nullundefined,并且他們都代表無(wú)和空,我一般這樣區(qū)分它們:

null

表示被賦值過(guò)的對(duì)象,刻意把一個(gè)對(duì)象賦值為null,故意表示其為空,不應(yīng)有值。

所以對(duì)象的某個(gè)屬性值為null是正常的,null轉(zhuǎn)換為數(shù)值時(shí)值為0。

undefined

表示“缺少值”,即此處應(yīng)有一個(gè)值,但還沒(méi)有定義,

如果一個(gè)對(duì)象的某個(gè)屬性值為undefined,這是不正常的,如obj.name=undefined,我們不應(yīng)該這樣寫(xiě),應(yīng)該直接delete obj.name。

undefined轉(zhuǎn)為數(shù)值時(shí)為NaN(非數(shù)字值的特殊值)

JavaScript是一門(mén)動(dòng)態(tài)類(lèi)型語(yǔ)言,成員除了表示存在的空值外,還有可能根本就不存在(因?yàn)榇娌淮嬖谥辉谶\(yùn)行期才知道),這就是undefined的意義所在。對(duì)于JAVA這種強(qiáng)類(lèi)型語(yǔ)言,如果有"undefined"這種情況,就會(huì)直接編譯失敗,所以在它不需要一個(gè)這樣的類(lèi)型。

四、不太熟的Symbol類(lèi)型

Symbol類(lèi)型是ES6中新加入的一種原始類(lèi)型。

每個(gè)從Symbol()返回的symbol值都是唯一的。一個(gè)symbol值能作為對(duì)象屬性的標(biāo)識(shí)符;這是該數(shù)據(jù)類(lèi)型僅有的目的。

下面來(lái)看看Symbol類(lèi)型具有哪些特性。

4.1 Symbol的特性

1.獨(dú)一無(wú)二

直接使用Symbol()創(chuàng)建新的symbol變量,可選用一個(gè)字符串用于描述。當(dāng)參數(shù)為對(duì)象時(shí),將調(diào)用對(duì)象的toString()方法。

var sym1 = Symbol();  // Symbol() 
var sym2 = Symbol("ConardLi");  // Symbol(ConardLi)
var sym3 = Symbol("ConardLi");  // Symbol(ConardLi)
var sym4 = Symbol({name:"ConardLi"}); // Symbol([object Object])
console.log(sym2 === sym3);  // false

我們用兩個(gè)相同的字符串創(chuàng)建兩個(gè)Symbol變量,它們是不相等的,可見(jiàn)每個(gè)Symbol變量都是獨(dú)一無(wú)二的。

如果我們想創(chuàng)造兩個(gè)相等的Symbol變量,可以使用Symbol.for(key)。

使用給定的key搜索現(xiàn)有的symbol,如果找到則返回該symbol。否則將使用給定的key在全局symbol注冊(cè)表中創(chuàng)建一個(gè)新的symbol。
var sym1 = Symbol.for("ConardLi");
var sym2 = Symbol.for("ConardLi");
console.log(sym1 === sym2); // true

2.原始類(lèi)型

注意是使用Symbol()函數(shù)創(chuàng)建symbol變量,并非使用構(gòu)造函數(shù),使用new操作符會(huì)直接報(bào)錯(cuò)。

new Symbol(); // Uncaught TypeError: Symbol is not a constructor

我們可以使用typeof運(yùn)算符判斷一個(gè)Symbol類(lèi)型:

typeof Symbol() === "symbol"
typeof Symbol("ConardLi") === "symbol"

3.不可枚舉

當(dāng)使用Symbol作為對(duì)象屬性時(shí),可以保證對(duì)象不會(huì)出現(xiàn)重名屬性,調(diào)用for...in不能將其枚舉出來(lái),另外調(diào)用Object.getOwnPropertyNames、Object.keys()也不能獲取Symbol屬性。

可以調(diào)用Object.getOwnPropertySymbols()用于專(zhuān)門(mén)獲取Symbol屬性。
var obj = {
  name:"ConardLi",
  [Symbol("name2")]:"code秘密花園"
}
Object.getOwnPropertyNames(obj); // ["name"]
Object.keys(obj); // ["name"]
for (var i in obj) {
   console.log(i); // name
}
Object.getOwnPropertySymbols(obj) // [Symbol(name)]
4.2 Symbol的應(yīng)用場(chǎng)景

下面是幾個(gè)Symbol在程序中的應(yīng)用場(chǎng)景。

應(yīng)用一:防止XSS

ReactReactElement對(duì)象中,有一個(gè)$$typeof屬性,它是一個(gè)Symbol類(lèi)型的變量:

var REACT_ELEMENT_TYPE =
  (typeof Symbol === "function" && Symbol.for && Symbol.for("react.element")) ||
  0xeac7;

ReactElement.isValidElement函數(shù)用來(lái)判斷一個(gè)React組件是否是有效的,下面是它的具體實(shí)現(xiàn)。

ReactElement.isValidElement = function (object) {
  return typeof object === "object" && object !== null && object.$$typeof === REACT_ELEMENT_TYPE;
};

可見(jiàn)React渲染時(shí)會(huì)把沒(méi)有$$typeof標(biāo)識(shí),以及規(guī)則校驗(yàn)不通過(guò)的組件過(guò)濾掉。

如果你的服務(wù)器有一個(gè)漏洞,允許用戶存儲(chǔ)任意JSON對(duì)象, 而客戶端代碼需要一個(gè)字符串,這可能會(huì)成為一個(gè)問(wèn)題:

// JSON
let expectedTextButGotJSON = {
  type: "div",
  props: {
    dangerouslySetInnerHTML: {
      __html: "/* put your exploit here */"
    },
  },
};
let message = { text: expectedTextButGotJSON };

{message.text}

JSON中不能存儲(chǔ)Symbol類(lèi)型的變量,這就是防止XSS的一種手段。

應(yīng)用二:私有屬性

借助Symbol類(lèi)型的不可枚舉,我們可以在類(lèi)中模擬私有屬性,控制變量讀寫(xiě):

const privateField = Symbol();
class myClass {
  constructor(){
    this[privateField] = "ConardLi";
  }
  getField(){
    return this[privateField];
  }
  setField(val){
    this[privateField] = val;
  }
}

應(yīng)用三:防止屬性污染

在某些情況下,我們可能要為對(duì)象添加一個(gè)屬性,此時(shí)就有可能造成屬性覆蓋,用Symbol作為對(duì)象屬性可以保證永遠(yuǎn)不會(huì)出現(xiàn)同名屬性。

例如下面的場(chǎng)景,我們模擬實(shí)現(xiàn)一個(gè)call方法:

    Function.prototype.myCall = function (context) {
      if (typeof this !== "function") {
        return undefined; // 用于防止 Function.prototype.myCall() 直接調(diào)用
      }
      context = context || window;
      const fn = Symbol();
      context[fn] = this;
      const args = [...arguments].slice(1);
      const result = context[fn](...args);
      delete context[fn];
      return result;
    }

我們需要在某個(gè)對(duì)象上臨時(shí)調(diào)用一個(gè)方法,又不能造成屬性污染,Symbol是一個(gè)很好的選擇。

五、不老實(shí)的Number類(lèi)型

為什么說(shuō)Number類(lèi)型不老實(shí)呢,相信大家都多多少少的在開(kāi)發(fā)中遇到過(guò)小數(shù)計(jì)算不精確的問(wèn)題,比如0.1+0.2!==0.3,下面我們來(lái)追本溯源,看看為什么會(huì)出現(xiàn)這種現(xiàn)象,以及該如何避免。

下面是我實(shí)現(xiàn)的一個(gè)簡(jiǎn)單的函數(shù),用于判斷兩個(gè)小數(shù)進(jìn)行加法運(yùn)算是否精確:

    function judgeFloat(n, m) {
      const binaryN = n.toString(2);
      const binaryM = m.toString(2);
      console.log(`${n}的二進(jìn)制是    ${binaryN}`);
      console.log(`${m}的二進(jìn)制是    ${binaryM}`);
      const MN = m + n;
      const accuracyMN = (m * 100 + n * 100) / 100;
      const binaryMN = MN.toString(2);
      const accuracyBinaryMN = accuracyMN.toString(2);
      console.log(`${n}+${m}的二進(jìn)制是${binaryMN}`);
      console.log(`${accuracyMN}的二進(jìn)制是    ${accuracyBinaryMN}`);
      console.log(`${n}+${m}的二進(jìn)制再轉(zhuǎn)成十進(jìn)制是${to10(binaryMN)}`);
      console.log(`${accuracyMN}的二進(jìn)制是再轉(zhuǎn)成十進(jìn)制是${to10(accuracyBinaryMN)}`);
      console.log(`${n}+${m}在js中計(jì)算是${(to10(binaryMN) === to10(accuracyBinaryMN)) ? "" : "不"}準(zhǔn)確的`);
    }
    function to10(n) {
      const pre = (n.split(".")[0] - 0).toString(2);
      const arr = n.split(".")[1].split("");
      let i = 0;
      let result = 0;
      while (i < arr.length) {
        result += arr[i] * Math.pow(2, -(i + 1));
        i++;
      }
      return result;
    }
    judgeFloat(0.1, 0.2);
    judgeFloat(0.6, 0.7);

5.1 精度丟失

計(jì)算機(jī)中所有的數(shù)據(jù)都是以二進(jìn)制存儲(chǔ)的,所以在計(jì)算時(shí)計(jì)算機(jī)要把數(shù)據(jù)先轉(zhuǎn)換成二進(jìn)制進(jìn)行計(jì)算,然后在把計(jì)算結(jié)果轉(zhuǎn)換成十進(jìn)制

由上面的代碼不難看出,在計(jì)算0.1+0.2時(shí),二進(jìn)制計(jì)算發(fā)生了精度丟失,導(dǎo)致再轉(zhuǎn)換成十進(jìn)制后和預(yù)計(jì)的結(jié)果不符。

5.2 對(duì)結(jié)果的分析—更多的問(wèn)題

0.10.2的二進(jìn)制都是以1100無(wú)限循環(huán)的小數(shù),下面逐個(gè)來(lái)看JS幫我們計(jì)算所得的結(jié)果:

0.1的二進(jìn)制

0.0001100110011001100110011001100110011001100110011001101

0.2的二進(jìn)制

0.001100110011001100110011001100110011001100110011001101

理論上講,由上面的結(jié)果相加應(yīng)該:

0.0100110011001100110011001100110011001100110011001100111

實(shí)際JS計(jì)算得到的0.1+0.2的二進(jìn)制

0.0100110011001100110011001100110011001100110011001101

看到這里你可能會(huì)產(chǎn)生更多的問(wèn)題:

為什么 js計(jì)算出的 0.1的二進(jìn)制 是這么多位而不是更多位???

為什么 js計(jì)算的(0.1+0.2)的二進(jìn)制和我們自己計(jì)算的(0.1+0.2)的二進(jìn)制結(jié)果不一樣呢???

為什么 0.1的二進(jìn)制 + 0.2的二進(jìn)制 != 0.3的二進(jìn)制???

5.3 js對(duì)二進(jìn)制小數(shù)的存儲(chǔ)方式

小數(shù)的二進(jìn)制大多數(shù)都是無(wú)限循環(huán)的,JavaScript是怎么來(lái)存儲(chǔ)他們的呢?

在ECMAScript?語(yǔ)言規(guī)范中可以看到,ECMAScript中的Number類(lèi)型遵循IEEE 754標(biāo)準(zhǔn)。使用64位固定長(zhǎng)度來(lái)表示。

事實(shí)上有很多語(yǔ)言的數(shù)字類(lèi)型都遵循這個(gè)標(biāo)準(zhǔn),例如JAVA,所以很多語(yǔ)言同樣有著上面同樣的問(wèn)題。

所以下次遇到這種問(wèn)題不要上來(lái)就噴JavaScript...

有興趣可以看看下這個(gè)網(wǎng)站http://0.30000000000000004.com/,是的,你沒(méi)看錯(cuò),就是http://0.30000000000000004.com/!??!

5.4 IEEE 754

IEEE754標(biāo)準(zhǔn)包含一組實(shí)數(shù)的二進(jìn)制表示法。它有三部分組成:

符號(hào)位

指數(shù)位

尾數(shù)位

三種精度的浮點(diǎn)數(shù)各個(gè)部分位數(shù)如下:

JavaScript使用的是64位雙精度浮點(diǎn)數(shù)編碼,所以它的符號(hào)位1位,指數(shù)位占11位,尾數(shù)位占52位。

下面我們?cè)诶斫庀率裁词?b>符號(hào)位、指數(shù)位、尾數(shù)位,以0.1為例:

它的二進(jìn)制為:0.0001100110011001100...

為了節(jié)省存儲(chǔ)空間,在計(jì)算機(jī)中它是以科學(xué)計(jì)數(shù)法表示的,也就是

1.100110011001100... X 2-4

如果這里不好理解可以想一下十進(jìn)制的數(shù):

1100的科學(xué)計(jì)數(shù)法為11 X 102

所以:

符號(hào)位就是標(biāo)識(shí)正負(fù)的,1表示負(fù),0表示;

指數(shù)位存儲(chǔ)科學(xué)計(jì)數(shù)法的指數(shù);

尾數(shù)位存儲(chǔ)科學(xué)計(jì)數(shù)法后的有效數(shù)字;

所以我們通??吹降亩M(jìn)制,其實(shí)是計(jì)算機(jī)實(shí)際存儲(chǔ)的尾數(shù)位。

5.5 js中的toString(2)

由于尾數(shù)位只能存儲(chǔ)52個(gè)數(shù)字,這就能解釋toString(2)的執(zhí)行結(jié)果了:

如果計(jì)算機(jī)沒(méi)有存儲(chǔ)空間的限制,那么0.1二進(jìn)制應(yīng)該是:

0.00011001100110011001100110011001100110011001100110011001...

科學(xué)計(jì)數(shù)法尾數(shù)位

1.1001100110011001100110011001100110011001100110011001...

但是由于限制,有效數(shù)字第53位及以后的數(shù)字是不能存儲(chǔ)的,它遵循,如果是1就向前一位進(jìn)1,如果是0就舍棄的原則。

0.1的二進(jìn)制科學(xué)計(jì)數(shù)法第53位是1,所以就有了下面的結(jié)果:

0.0001100110011001100110011001100110011001100110011001101

0.2有著同樣的問(wèn)題,其實(shí)正是由于這樣的存儲(chǔ),在這里有了精度丟失,導(dǎo)致了0.1+0.2!=0.3。

事實(shí)上有著同樣精度問(wèn)題的計(jì)算還有很多,我們無(wú)法把他們都記下來(lái),所以當(dāng)程序中有數(shù)字計(jì)算時(shí),我們最好用工具庫(kù)來(lái)幫助我們解決,下面是兩個(gè)推薦使用的開(kāi)源庫(kù):

number-precision

mathjs/

5.6 JavaScript能表示的最大數(shù)字

由與IEEE 754雙精度64位規(guī)范的限制:

指數(shù)位能表示的最大數(shù)字:1023(十進(jìn)制)

尾數(shù)位能表達(dá)的最大數(shù)字即尾數(shù)位都位1的情況

所以JavaScript能表示的最大數(shù)字即位

1.111...X 21023 這個(gè)結(jié)果轉(zhuǎn)換成十進(jìn)制是1.7976931348623157e+308,這個(gè)結(jié)果即為Number.MAX_VALUE。

5.7 最大安全數(shù)字

JavaScript中Number.MAX_SAFE_INTEGER表示最大安全數(shù)字,計(jì)算結(jié)果是9007199254740991,即在這個(gè)數(shù)范圍內(nèi)不會(huì)出現(xiàn)精度丟失(小數(shù)除外),這個(gè)數(shù)實(shí)際上是1.111...X 252。

我們同樣可以用一些開(kāi)源庫(kù)來(lái)處理大整數(shù):

node-bignum

node-bigint

其實(shí)官方也考慮到了這個(gè)問(wèn)題,bigInt類(lèi)型在es10中被提出,現(xiàn)在Chrome中已經(jīng)可以使用,使用bigInt可以操作超過(guò)最大安全數(shù)字的數(shù)字。

六、還有哪些引用類(lèi)型
ECMAScript中,引用類(lèi)型是一種數(shù)據(jù)結(jié)構(gòu),用于將數(shù)據(jù)和功能組織在一起。

我們通常所說(shuō)的對(duì)象,就是某個(gè)特定引用類(lèi)型的實(shí)例。

ECMAScript關(guān)于類(lèi)型的定義中,只給出了Object類(lèi)型,實(shí)際上,我們平時(shí)使用的很多引用類(lèi)型的變量,并不是由Object構(gòu)造的,但是它們?cè)玩湹慕K點(diǎn)都是Object,這些類(lèi)型都屬于引用類(lèi)型。

Array 數(shù)組

Date 日期

RegExp 正則

Function 函數(shù)

6.1 包裝類(lèi)型

為了便于操作基本類(lèi)型值,ECMAScript還提供了幾個(gè)特殊的引用類(lèi)型,他們是基本類(lèi)型的包裝類(lèi)型:

Boolean

Number

String

注意包裝類(lèi)型和原始類(lèi)型的區(qū)別:

true === new Boolean(true); // false
123 === new Number(123); // false
"ConardLi" === new String("ConardLi"); // false
console.log(typeof new String("ConardLi")); // object
console.log(typeof "ConardLi"); // string
引用類(lèi)型和包裝類(lèi)型的主要區(qū)別就是對(duì)象的生存期,使用new操作符創(chuàng)建的引用類(lèi)型的實(shí)例,在執(zhí)行流離開(kāi)當(dāng)前作用域之前都一直保存在內(nèi)存中,而自基本類(lèi)型則只存在于一行代碼的執(zhí)行瞬間,然后立即被銷(xiāo)毀,這意味著我們不能在運(yùn)行時(shí)為基本類(lèi)型添加屬性和方法。
var name = "ConardLi"
name.color = "red";
console.log(name.color); // undefined
6.2 裝箱和拆箱

裝箱轉(zhuǎn)換:把基本類(lèi)型轉(zhuǎn)換為對(duì)應(yīng)的包裝類(lèi)型

拆箱操作:把引用類(lèi)型轉(zhuǎn)換為基本類(lèi)型

既然原始類(lèi)型不能擴(kuò)展屬性和方法,那么我們是如何使用原始類(lèi)型調(diào)用方法的呢?

每當(dāng)我們操作一個(gè)基礎(chǔ)類(lèi)型時(shí),后臺(tái)就會(huì)自動(dòng)創(chuàng)建一個(gè)包裝類(lèi)型的對(duì)象,從而讓我們能夠調(diào)用一些方法和屬性,例如下面的代碼:

var name = "ConardLi";
var name2 = name.substring(2);

實(shí)際上發(fā)生了以下幾個(gè)過(guò)程:

創(chuàng)建一個(gè)String的包裝類(lèi)型實(shí)例

在實(shí)例上調(diào)用substring方法

銷(xiāo)毀實(shí)例

也就是說(shuō),我們使用基本類(lèi)型調(diào)用方法,就會(huì)自動(dòng)進(jìn)行裝箱和拆箱操作,相同的,我們使用NumberBoolean類(lèi)型時(shí),也會(huì)發(fā)生這個(gè)過(guò)程。

從引用類(lèi)型到基本類(lèi)型的轉(zhuǎn)換,也就是拆箱的過(guò)程中,會(huì)遵循ECMAScript規(guī)范規(guī)定的toPrimitive原則,一般會(huì)調(diào)用引用類(lèi)型的valueOftoString方法,你也可以直接重寫(xiě)toPeimitive方法。一般轉(zhuǎn)換成不同類(lèi)型的值遵循的原則不同,例如:

引用類(lèi)型轉(zhuǎn)換為Number類(lèi)型,先調(diào)用valueOf,再調(diào)用toString

引用類(lèi)型轉(zhuǎn)換為String類(lèi)型,先調(diào)用toString,再調(diào)用valueOf

valueOftoString都不存在,或者沒(méi)有返回基本類(lèi)型,則拋出TypeError異常。

const obj = {
  valueOf: () => { console.log("valueOf"); return 123; },
  toString: () => { console.log("toString"); return "ConardLi"; },
};
console.log(obj - 1);   // valueOf   122
console.log(`${obj}ConardLi`); // toString  ConardLiConardLi

const obj2 = {
  [Symbol.toPrimitive]: () => { console.log("toPrimitive"); return 123; },
};
console.log(obj2 - 1);   // valueOf   122

const obj3 = {
  valueOf: () => { console.log("valueOf"); return {}; },
  toString: () => { console.log("toString"); return {}; },
};
console.log(obj3 - 1);  
// valueOf  
// toString
// TypeError

除了程序中的自動(dòng)拆箱和自動(dòng)裝箱,我們還可以手動(dòng)進(jìn)行拆箱和裝箱操作。我們可以直接調(diào)用包裝類(lèi)型的valueOftoString,實(shí)現(xiàn)拆箱操作:

var name =new Number("123");  
console.log( typeof name.valueOf() ); //number
console.log( typeof name.toString() ); //string
七、類(lèi)型轉(zhuǎn)換

因?yàn)?b>JavaScript是弱類(lèi)型的語(yǔ)言,所以類(lèi)型轉(zhuǎn)換發(fā)生非常頻繁,上面我們說(shuō)的裝箱和拆箱其實(shí)就是一種類(lèi)型轉(zhuǎn)換。

類(lèi)型轉(zhuǎn)換分為兩種,隱式轉(zhuǎn)換即程序自動(dòng)進(jìn)行的類(lèi)型轉(zhuǎn)換,強(qiáng)制轉(zhuǎn)換即我們手動(dòng)進(jìn)行的類(lèi)型轉(zhuǎn)換。

強(qiáng)制轉(zhuǎn)換這里就不再多提及了,下面我們來(lái)看看讓人頭疼的可能發(fā)生隱式類(lèi)型轉(zhuǎn)換的幾個(gè)場(chǎng)景,以及如何轉(zhuǎn)換:

7.1 類(lèi)型轉(zhuǎn)換規(guī)則

如果發(fā)生了隱式轉(zhuǎn)換,那么各種類(lèi)型互轉(zhuǎn)符合下面的規(guī)則:

7.2 if語(yǔ)句和邏輯語(yǔ)句

if語(yǔ)句和邏輯語(yǔ)句中,如果只有單個(gè)變量,會(huì)先將變量轉(zhuǎn)換為Boolean值,只有下面幾種情況會(huì)轉(zhuǎn)換成false,其余被轉(zhuǎn)換成true

null
undefined
""
NaN
0
false
7.3 各種運(yùn)數(shù)學(xué)算符

我們?cè)趯?duì)各種非Number類(lèi)型運(yùn)用數(shù)學(xué)運(yùn)算符(- * /)時(shí),會(huì)先將非Number類(lèi)型轉(zhuǎn)換為Number類(lèi)型;

1 - true // 0
1 - null //  1
1 * undefined //  NaN
1 - {}  //  1
2 * ["5"] //  10

注意+是個(gè)例外,執(zhí)行+操作符時(shí):

1.當(dāng)一側(cè)為String類(lèi)型,被識(shí)別為字符串拼接,并會(huì)優(yōu)先將另一側(cè)轉(zhuǎn)換為字符串類(lèi)型。

2.當(dāng)一側(cè)為Number類(lèi)型,另一側(cè)為原始類(lèi)型,則將原始類(lèi)型轉(zhuǎn)換為Number類(lèi)型。

3.當(dāng)一側(cè)為Number類(lèi)型,另一側(cè)為引用類(lèi)型,將引用類(lèi)型和Number類(lèi)型轉(zhuǎn)換成字符串后拼接。

123 + "123" // 123123   (規(guī)則1)
123 + null  // 123    (規(guī)則2)
123 + true // 124    (規(guī)則2)
123 + {}  // 123[object Object]    (規(guī)則3)
7.4 ==

使用==時(shí),若兩側(cè)類(lèi)型相同,則比較結(jié)果和===相同,否則會(huì)發(fā)生隱式轉(zhuǎn)換,使用==時(shí)發(fā)生的轉(zhuǎn)換可以分為幾種不同的情況(只考慮兩側(cè)類(lèi)型不同):

1.NaN

NaN和其他任何類(lèi)型比較永遠(yuǎn)返回false(包括和他自己)。

NaN == NaN // false

2.Boolean

Boolean和其他任何類(lèi)型比較,Boolean首先被轉(zhuǎn)換為Number類(lèi)型。

true == 1  // true 
true == "2"  // false
true == ["1"]  // true
true == ["2"]  // false
這里注意一個(gè)可能會(huì)弄混的點(diǎn):undefined、nullBoolean比較,雖然undefined、nullfalse都很容易被想象成假值,但是他們比較結(jié)果是false,原因是false首先被轉(zhuǎn)換成0
undefined == false // false
null == false // false

3.String和Number

StringNumber比較,先將String轉(zhuǎn)換為Number類(lèi)型。

123 == "123" // true
"" == 0 // true

4.null和undefined

null == undefined比較結(jié)果是true,除此之外,null、undefined和其他任何結(jié)果的比較值都為false

null == undefined // true
null == "" // false
null == 0 // false
null == false // false
undefined == "" // false
undefined == 0 // false
undefined == false // false

5.原始類(lèi)型和引用類(lèi)型

當(dāng)原始類(lèi)型和引用類(lèi)型做比較時(shí),對(duì)象類(lèi)型會(huì)依照ToPrimitive規(guī)則轉(zhuǎn)換為原始類(lèi)型:

  "[object Object]" == {} // true
  "1,2,3" == [1, 2, 3] // true

來(lái)看看下面這個(gè)比較:

[] == ![] // true

!的優(yōu)先級(jí)高于==![]首先會(huì)被轉(zhuǎn)換為false,然后根據(jù)上面第三點(diǎn),false轉(zhuǎn)換成Number類(lèi)型0,左側(cè)[]轉(zhuǎn)換為0,兩側(cè)比較相等。

[null] == false // true
[undefined] == false // true

根據(jù)數(shù)組的ToPrimitive規(guī)則,數(shù)組元素為nullundefined時(shí),該元素被當(dāng)做空字符串處理,所以[null]、[undefined]都會(huì)被轉(zhuǎn)換為0。

所以,說(shuō)了這么多,推薦使用===來(lái)判斷兩個(gè)值是否相等...

7.5 一道有意思的面試題

一道經(jīng)典的面試題,如何讓?zhuān)?b>a == 1 && a == 2 && a == 3。

根據(jù)上面的拆箱轉(zhuǎn)換,以及==的隱式轉(zhuǎn)換,我們可以輕松寫(xiě)出答案:

const a = {
   value:[3,2,1],
   valueOf: function() {return this.value.pop(); },
} 
八、判斷JavaScript數(shù)據(jù)類(lèi)型的方式 8.1 typeof

適用場(chǎng)景

typeof操作符可以準(zhǔn)確判斷一個(gè)變量是否為下面幾個(gè)原始類(lèi)型:

typeof "ConardLi"  // string
typeof 123  // number
typeof true  // boolean
typeof Symbol()  // symbol
typeof undefined  // undefined

你還可以用它來(lái)判斷函數(shù)類(lèi)型:

typeof function(){}  // function

不適用場(chǎng)景

當(dāng)你用typeof來(lái)判斷引用類(lèi)型時(shí)似乎顯得有些乏力了:

typeof [] // object
typeof {} // object
typeof new Date() // object
typeof /^d*$/; // object

除函數(shù)外所有的引用類(lèi)型都會(huì)被判定為object

另外typeof null === "object"也會(huì)讓人感到頭痛,這是在JavaScript初版就流傳下來(lái)的bug,后面由于修改會(huì)造成大量的兼容問(wèn)題就一直沒(méi)有被修復(fù)...

8.2 instanceof

instanceof操作符可以幫助我們判斷引用類(lèi)型具體是什么類(lèi)型的對(duì)象:

[] instanceof Array // true
new Date() instanceof Date // true
new RegExp() instanceof RegExp // true

我們先來(lái)回顧下原型鏈的幾條規(guī)則:

1.所有引用類(lèi)型都具有對(duì)象特性,即可以自由擴(kuò)展屬性

2.所有引用類(lèi)型都具有一個(gè)__proto__(隱式原型)屬性,是一個(gè)普通對(duì)象

3.所有的函數(shù)都具有prototype(顯式原型)屬性,也是一個(gè)普通對(duì)象

4.所有引用類(lèi)型__proto__值指向它構(gòu)造函數(shù)的prototype

5.當(dāng)試圖得到一個(gè)對(duì)象的屬性時(shí),如果變量本身沒(méi)有這個(gè)屬性,則會(huì)去他的__proto__中去找

[] instanceof Array 實(shí)際上是判斷Foo.prototype是否在[]的原型鏈上。

所以,使用instanceof來(lái)檢測(cè)數(shù)據(jù)類(lèi)型,不會(huì)很準(zhǔn)確,這不是它設(shè)計(jì)的初衷:

[] instanceof Object // true
function(){}  instanceof Object // true

另外,使用instanceof也不能檢測(cè)基本數(shù)據(jù)類(lèi)型,所以instanceof并不是一個(gè)很好的選擇。

8.3 toString

上面我們?cè)诓鹣洳僮髦刑岬搅?b>toString函數(shù),我們可以調(diào)用它實(shí)現(xiàn)從引用類(lèi)型的轉(zhuǎn)換。

每一個(gè)引用類(lèi)型都有toString方法,默認(rèn)情況下,toString()方法被每個(gè)Object對(duì)象繼承。如果此方法在自定義對(duì)象中未被覆蓋,toString() 返回 "[object type]",其中type是對(duì)象的類(lèi)型。
const obj = {};
obj.toString() // [object Object]

注意,上面提到了如果此方法在自定義對(duì)象中未被覆蓋,toString才會(huì)達(dá)到預(yù)想的效果,事實(shí)上,大部分引用類(lèi)型比如Array、Date、RegExp等都重寫(xiě)了toString方法。

我們可以直接調(diào)用Object原型上未被覆蓋的toString()方法,使用call來(lái)改變this指向來(lái)達(dá)到我們想要的效果。

8.4 jquery

我們來(lái)看看jquery源碼中如何進(jìn)行類(lèi)型判斷:

var class2type = {};
jQuery.each( "Boolean Number String Function Array Date RegExp Object Error Symbol".split( " " ),
function( i, name ) {
    class2type[ "[object " + name + "]" ] = name.toLowerCase();
} );

type: function( obj ) {
    if ( obj == null ) {
        return obj + "";
    }
    return typeof obj === "object" || typeof obj === "function" ?
        class2type[Object.prototype.toString.call(obj) ] || "object" :
        typeof obj;
}

isFunction: function( obj ) {
        return jQuery.type(obj) === "function";
}

原始類(lèi)型直接使用typeof,引用類(lèi)型使用Object.prototype.toString.call取得類(lèi)型,借助一個(gè)class2type對(duì)象將字符串多余的代碼過(guò)濾掉,例如[object function]將得到array,然后在后面的類(lèi)型判斷,如isFunction直接可以使用jQuery.type(obj) === "function"這樣的判斷。

參考

http://www.ecma-international...

https://while.dev/articles/ex...

https://github.com/mqyqingfen...

https://juejin.im/post/5bc5c7...

https://juejin.im/post/5bbda2...

《JS高級(jí)程序設(shè)計(jì)》

小結(jié)

希望你閱讀本篇文章后可以達(dá)到以下幾點(diǎn):

了解JavaScript中的變量在內(nèi)存中的具體存儲(chǔ)形式,可對(duì)應(yīng)實(shí)際場(chǎng)景

搞懂小數(shù)計(jì)算不精確的底層原因

了解可能發(fā)生隱式類(lèi)型轉(zhuǎn)換的場(chǎng)景以及轉(zhuǎn)換原則

掌握判斷JavaScript數(shù)據(jù)類(lèi)型的方式和底層原理

文中如有錯(cuò)誤,歡迎在評(píng)論區(qū)指正,如果這篇文章幫助到了你,歡迎點(diǎn)贊和關(guān)注。

想閱讀更多優(yōu)質(zhì)文章、可關(guān)注我的github博客,你的star?、點(diǎn)贊和關(guān)注是我持續(xù)創(chuàng)作的動(dòng)力!

推薦關(guān)注我的微信公眾號(hào)【code秘密花園】,每天推送高質(zhì)量文章,我們一起交流成長(zhǎng)。

關(guān)注公眾號(hào)后回復(fù)【加群】拉你進(jìn)入優(yōu)質(zhì)前端交流群。

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/109908.html

相關(guān)文章

  • 理解JavaScript變量類(lèi)型

    摘要:接著下一個(gè)例子賦予副本新的地址可見(jiàn),函數(shù)參數(shù)傳遞的并不是變量的引用,而是變量拷貝的副本,當(dāng)變量是原始類(lèi)型時(shí),這個(gè)副本就是值本身,當(dāng)變量是引用類(lèi)型是,這個(gè)副本是指向堆內(nèi)存的地址。 轉(zhuǎn)載自ConardLi: 《【JS進(jìn)階】 你真的掌握變量和類(lèi)型了嗎》 公眾號(hào): code秘密花園 1. JavaScript數(shù)據(jù)類(lèi)型 ECMAScript標(biāo)準(zhǔn)規(guī)定了7種數(shù)據(jù)類(lèi)型,這些數(shù)據(jù)類(lèi)型分為原始類(lèi)型和對(duì)象...

    xiaodao 評(píng)論0 收藏0
  • javasscript - 收藏集 - 掘金

    摘要:跨域請(qǐng)求詳解從繁至簡(jiǎn)前端掘金什么是為什么要用是的一種使用模式,可用于解決主流瀏覽器的跨域數(shù)據(jù)訪問(wèn)的問(wèn)題。異步編程入門(mén)道典型的面試題前端掘金在界中,開(kāi)發(fā)人員的需求量一直居高不下。 jsonp 跨域請(qǐng)求詳解——從繁至簡(jiǎn) - 前端 - 掘金什么是jsonp?為什么要用jsonp?JSONP(JSON with Padding)是JSON的一種使用模式,可用于解決主流瀏覽器的跨域數(shù)據(jù)訪問(wèn)的問(wèn)題...

    Rango 評(píng)論0 收藏0
  • 8道經(jīng)典JavaScript面試題解析,真的掌握JavaScript了嗎

    摘要:瀏覽器的主要組成包括有調(diào)用堆棧,事件循環(huán),任務(wù)隊(duì)列和。好了,現(xiàn)在有了前面這些知識(shí),我們可以看一下這道題的講解過(guò)程實(shí)現(xiàn)步驟調(diào)用會(huì)將函數(shù)放入調(diào)用堆棧。由于調(diào)用堆棧是空的,事件循環(huán)將選擇回調(diào)并將其推入調(diào)用堆棧進(jìn)行處理。進(jìn)程再次重復(fù),堆棧不會(huì)溢出。 JavaScript是前端開(kāi)發(fā)中非常重要的一門(mén)語(yǔ)言,瀏覽器是他主要運(yùn)行的地方。JavaScript是一個(gè)非常有意思的語(yǔ)言,但是他有很多一些概念,大...

    taowen 評(píng)論0 收藏0
  • 前端進(jìn)階系列(七):什么是執(zhí)行上下文?什么是調(diào)用棧?

    摘要:什么是中的調(diào)用棧調(diào)用棧就像是程序當(dāng)前執(zhí)行的日志。當(dāng)函數(shù)執(zhí)行結(jié)束時(shí),將從調(diào)用棧中出去。了解全局和局部執(zhí)行上下文是掌握作用域和閉包的關(guān)鍵??偨Y(jié)引擎創(chuàng)建執(zhí)行上下文,全局存儲(chǔ)器和調(diào)用棧。 原文作者:Valentino 原文鏈接:https://www.valentinog.com/blog/js-execution-context-call-stack 什么是Javascript中的執(zhí)行上下文...

    leone 評(píng)論0 收藏0
  • [ JS 進(jìn)階 ] 如何改進(jìn)代碼性能 (3)

    摘要:這樣就改進(jìn)了代碼的性能,看代碼將保存在局部變量中所以啊,我們?cè)陂_(kāi)發(fā)中,如果在函數(shù)中會(huì)經(jīng)常用到全局變量,把它保存在局部變量中避免使用語(yǔ)句用語(yǔ)句延長(zhǎng)了作用域,查找變量同樣費(fèi)時(shí)間,這個(gè)我們一般不會(huì)用到,所以不展開(kāi)了。 本來(lái)在那片編寫(xiě)可維護(hù)性代碼文章后就要總結(jié)這篇代碼性能文章的,耽擱了幾天,本來(lái)也是決定每天都要更新一篇文章的,因?yàn)橐郧扒废绿鄸|西沒(méi)總結(jié),學(xué)過(guò)的東西沒(méi)去總結(jié)真的很快就忘記了...

    young.li 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<