你真的掌握變量和類型了嗎(一)數(shù)據(jù)類型

導(dǎo)讀

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

  • JavaScript中的變量在內(nèi)存中的具體存儲(chǔ)形式是什么推掸?
  • 0.1+0.2為什么不等于0.3?發(fā)生小數(shù)計(jì)算錯(cuò)誤的具體原因是什么屯换?
  • Symbol的特點(diǎn),以及實(shí)際應(yīng)用場景是什么挽牢?
  • [] == ![]谱煤、[undefined] == false為什么等于true?代碼中何時(shí)會(huì)發(fā)生隱式類型轉(zhuǎn)換?轉(zhuǎn)換的規(guī)則是什么禽拔?
  • 如何精確的判斷變量的類型刘离?

如果你還不能很好的解答上面的問題,那說明你還沒有完全掌握這部分的知識(shí)睹栖,那么請(qǐng)好好閱讀下面的文章吧硫惕。

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

一野来、JavaScript數(shù)據(jù)類型

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

原始類型

  • Null:只包含一個(gè)值:null
  • Undefined:只包含一個(gè)值:undefined
  • Boolean:包含兩個(gè)值:truefalse
  • Number:整數(shù)或浮點(diǎn)數(shù),還有一些特殊值(-Infinity豁辉、+Infinity令野、NaN
  • String:一串表示文本值的字符序列
  • Symbol:一種實(shí)例是唯一且不可改變的數(shù)據(jù)類型

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

對(duì)象類型

  • Object:自己分一類絲毫不過分徽级,除了常用的Object气破,ArrayFunction等都屬于特殊的對(duì)象

二餐抢、為什么區(qū)分原始類型和對(duì)象類型

2.1 不可變性

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

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

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è)方法,無一例外,這些方法都在原字符串的基礎(chǔ)上產(chǎn)生了一個(gè)新字符串,而非直接去改變str鸳碧,這就印證了字符串的不可變性妇穴。

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

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

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

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

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

棧內(nèi)存:

  • 存儲(chǔ)的值大小固定
  • 空間較小
  • 可以直接操作其保存的變量,運(yùn)行效率高
  • 由系統(tǒng)自動(dòng)分配存儲(chǔ)空間

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

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

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

2.2 引用類型

堆內(nèi)存:

  • 存儲(chǔ)的值大小不定承匣,可動(dòng)態(tài)調(diào)整
  • 空間較大蓖乘,運(yùn)行效率低
  • 無法直接操作其內(nèi)部存儲(chǔ),使用引用地址讀取
  • 通過代碼進(jìn)行分配空間

相對(duì)于上面具有不可變性的原始類型悄雅,我習(xí)慣把對(duì)象稱為引用類型驱敲,引用類型的值實(shí)際存儲(chǔ)在堆內(nèi)存中,它在棧中只存儲(chǔ)了一個(gè)固定長度的地址宽闲,這個(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)存泄漏的一些場景。

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

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ù)組的長度
  • shift()把數(shù)組的第一個(gè)元素刪除,若空數(shù)組或听,不進(jìn)行任何操作探孝,返回undefined,改變?cè)瓟?shù)組,返回第一個(gè)元素的值
  • unshift()向數(shù)組的開頭添加一個(gè)或多個(gè)元素誉裆,改變?cè)瓟?shù)組顿颅,返回新數(shù)組的長度
  • reverse()顛倒數(shù)組中元素的順序,改變?cè)瓟?shù)組足丢,返回該數(shù)組
  • sort()對(duì)數(shù)組元素進(jìn)行排序元镀,改變?cè)瓟?shù)組,返回該數(shù)組
  • splice()從數(shù)組中添加/刪除項(xiàng)目霎桅,改變?cè)瓟?shù)組栖疑,返回被刪除的元素

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

2.3 復(fù)制

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

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è)引用類型:

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

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

2.4 比較

當(dāng)我們?cè)趯?duì)兩個(gè)變量進(jìn)行比較時(shí),不同類型的變量的表現(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ì)于原始類型戈盈,比較時(shí)會(huì)直接比較它們的值奠衔,如果值相等,即返回true塘娶。

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

2.5 值傳遞和引用傳遞

借助下面的例子难捌,我們先來看一看什么是值傳遞,什么是引用傳遞:

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

執(zhí)行上面的代碼鸦难,如果最終打印出來的name'ConardLi'根吁,沒有改變,說明函數(shù)參數(shù)傳遞的是變量的值合蔽,即值傳遞击敌。如果最終打印的是'code秘密花園',函數(shù)內(nèi)部的操作可以改變傳入的變量拴事,那么說明函數(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ù)是引用類型就是引用傳遞呢?

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

同樣的等太,當(dāng)函數(shù)參數(shù)是引用類型時(shí),我們同樣將參數(shù)復(fù)制了一個(gè)副本到局部變量蛮放,只不過復(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

可見,函數(shù)參數(shù)傳遞的并不是變量的引用徘六,而是變量拷貝的副本内边,當(dāng)變量是原始類型時(shí),這個(gè)副本就是值本身待锈,當(dāng)變量是引用類型時(shí)漠其,這個(gè)副本是指向堆內(nèi)存的地址。所以竿音,再次記缀褪骸:

ECMAScript中所有的函數(shù)的參數(shù)都是按值傳遞的。

三春瞬、分不清的null和undefined

在原始類型中柴信,有兩個(gè)類型NullUndefined,他們都有且僅有一個(gè)值宽气,nullundefined随常,并且他們都代表無和空,我一般這樣區(qū)分它們:

null

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

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

undefined

表示“缺少值”燃逻,即此處應(yīng)有一個(gè)值序目,但還沒有定義,

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

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

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

四蛮浑、不太熟的Symbol類型

Symbol類型是ES6中新加入的一種原始類型唠叛。

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

下面來看看Symbol類型具有哪些特性。

4.1 Symbol的特性

1.獨(dú)一無二

直接使用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變量挽荡,它們是不相等的,可見每個(gè)Symbol變量都是獨(dú)一無二的即供。

如果我們想創(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.原始類型

注意是使用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類型:

typeof Symbol() === 'symbol'
typeof Symbol('ConardLi') === 'symbol'

3.不可枚舉

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

可以調(diào)用Object.getOwnPropertySymbols()用于專門獲取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)用場景

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

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

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

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

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

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

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

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

// JSON
let expectedTextButGotJSON = {
  type: 'div',
  props: {
    dangerouslySetInnerHTML: {
      __html: '/* put your exploit here */'
    },
  },
};
let message = { text: expectedTextButGotJSON };
<p>
  {message.text}
</p>

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

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

借助Symbol類型的不可枚舉轰坊,我們可以在類中模擬私有屬性铸董,控制變量讀寫:

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)同名屬性。

例如下面的場景颤芬,我們模擬實(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類型

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

下面是我實(shí)現(xiàn)的一個(gè)簡單的函數(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é)果的分析—更多的問題

0.10.2的二進(jìn)制都是以1100無限循環(huán)的小數(shù)晴叨,下面逐個(gè)來看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)生更多的問題:

為什么 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ù)都是無限循環(huán)的,JavaScript是怎么來存儲(chǔ)他們的呢哈雏?

ECMAScript?語言規(guī)范中可以看到势似,ECMAScript中的Number類型遵循IEEE 754標(biāo)準(zhǔn)。使用64位固定長度來表示僧著。

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

所以下次遇到這種問題不要上來就噴JavaScript...

有興趣可以看看下這個(gè)網(wǎng)站http://0.30000000000000004.com/盹愚,是的栅迄,你沒看錯(cuò),就是http://0.30000000000000004.com/=耘隆R阌摺!

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è)诶斫庀率裁词?code>符號(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ù)字;

所以我們通持值看到的二進(jìn)制契耿,其實(shí)是計(jì)算機(jī)實(shí)際存儲(chǔ)的尾數(shù)位。

5.5 js中的toString(2)

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

如果計(jì)算機(jī)沒有存儲(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有著同樣的問題,其實(shí)正是由于這樣的存儲(chǔ)泼疑,在這里有了精度丟失德绿,導(dǎo)致了0.1+0.2!=0.3

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

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翻翩。

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

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

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


歡迎大家到公眾號(hào): you的日常 閱讀服傍,體驗(yàn)更好哦暇昂。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市伴嗡,隨后出現(xiàn)的幾起案子急波,更是在濱河造成了極大的恐慌,老刑警劉巖瘪校,帶你破解...
    沈念sama閱讀 211,290評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件澄暮,死亡現(xiàn)場離奇詭異,居然都是意外死亡阱扬,警方通過查閱死者的電腦和手機(jī)泣懊,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來麻惶,“玉大人馍刮,你說我怎么就攤上這事∏蕴#” “怎么了卡啰?”我有些...
    開封第一講書人閱讀 156,872評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵静稻,是天一觀的道長。 經(jīng)常有香客問我匈辱,道長振湾,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,415評(píng)論 1 283
  • 正文 為了忘掉前任亡脸,我火速辦了婚禮押搪,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘浅碾。我一直安慰自己大州,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,453評(píng)論 6 385
  • 文/花漫 我一把揭開白布垂谢。 她就那樣靜靜地躺著摧茴,像睡著了一般。 火紅的嫁衣襯著肌膚如雪埂陆。 梳的紋絲不亂的頭發(fā)上苛白,一...
    開封第一講書人閱讀 49,784評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音焚虱,去河邊找鬼购裙。 笑死,一個(gè)胖子當(dāng)著我的面吹牛鹃栽,可吹牛的內(nèi)容都是我干的躏率。 我是一名探鬼主播,決...
    沈念sama閱讀 38,927評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼民鼓,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼薇芝!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起丰嘉,我...
    開封第一講書人閱讀 37,691評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤夯到,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后饮亏,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體耍贾,經(jīng)...
    沈念sama閱讀 44,137評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,472評(píng)論 2 326
  • 正文 我和宋清朗相戀三年路幸,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了荐开。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,622評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡简肴,死狀恐怖晃听,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤能扒,帶...
    沈念sama閱讀 34,289評(píng)論 4 329
  • 正文 年R本政府宣布佣渴,位于F島的核電站,受9級(jí)特大地震影響赫粥,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜予借,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,887評(píng)論 3 312
  • 文/蒙蒙 一越平、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧灵迫,春花似錦秦叛、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至狞换,卻和暖如春避咆,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背修噪。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來泰國打工查库, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人黄琼。 一個(gè)月前我還...
    沈念sama閱讀 46,316評(píng)論 2 360
  • 正文 我出身青樓樊销,卻偏偏與公主長得像,于是被迫代替她去往敵國和親脏款。 傳聞我的和親對(duì)象是個(gè)殘疾皇子围苫,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,490評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容

  • 前端工程師吃飯的家伙,深度撤师、廣度一樣都不能差剂府。 一、JavaScript 規(guī)定了幾種語言類型 7 種基本數(shù)據(jù)類型:...
    旭哥_閱讀 727評(píng)論 2 1
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對(duì)...
    cosWriter閱讀 11,092評(píng)論 1 32
  • 今天下午新鄉(xiāng)市人民檢察院的檢察官們來朱莊村給我們@幫的小朋友們普及法律知識(shí)了剃盾。檢察官們還給小朋友們帶了書包做禮物周循,...
    咧咧0622閱讀 234評(píng)論 0 3
  • 不知是不是上了歲數(shù),時(shí)常做夢(mèng)万俗,各種各樣的夢(mèng)湾笛,尤以夢(mèng)到小時(shí)候居多,人到中年回憶多闰歪,大概是這樣的吧嚎研! ...
    紀(jì)娟閱讀 142評(píng)論 0 0
  • 開到荼靡花事了临扮,花季未了余情未了 記錢塘 论矾,朝載酒,夜藏鉤杆勇。青衫斷腸司馬贪壳,消減舊風(fēng)流。三百六橋春色蚜退,二十四番花...
    青梅煮酒f閱讀 1,003評(píng)論 0 0