JS面試題-<變量和類型>-JavaScript的數據類型

前言

  整理以前的面試題,發現問js數據類型的頻率挺高的,回憶當初自己的答案,就是簡簡單單的把幾個類型名稱羅列了出來,便沒有了任何下文。其實這一個知識點下可以牽涉發散出很多的知識點,如果一個面試者只是羅列的那些名詞出來,可能面試官都不願意繼續問下去了,這該算是js基礎的基礎了。如果這個問題沒有很好的回答,其他問題仍舊沒有突出的亮點,很可能就過不了。

  在網上看了一個體系,可作為大致的學習檢閱自己的途徑,按照清單上的知識檢測自己還有哪些不足和提升,最後形成自己的知識體系。在工作、學習甚至面試時,可以快速定位到知識點。

1. JavaScript規定了幾種語言類型 2. JavaScript對象的底層數據結構是什麼 3. Symbol類型在實際開發中的應用、可手動實現一個簡單的 Symbol 4. JavaScript中的變量在內存中的具體存儲形式 5. 基本類型對應的內置對象,以及他們之間的裝箱拆箱操作 6. 理解值類型和引用類型 7. null和 undefined的區別 8. 至少可以說出三種判斷 JavaScript數據類型的方式,以及他們的優缺點,如何準確的判斷數組類型 9. 可能發生隱式類型轉換的場景以及轉換原則,應如何避免或巧妙應用 10. 出現小數精度丟失的原因, JavaScript可以存儲的最大数字、最大安全数字, JavaScript處理大数字的方法、避免精度丟失的方法

 

一、JavaScript規定了幾種語言類型

  問:講一下js的數據類型?

  答:js的數據類型分為簡單數據類型和複雜數據類型;

簡單數據類型有六種,分別是String(字符串)、Number(数字)、Null(空)、undefined(未定義)、boolean(布爾值)、symbol(符號),表示不能再繼續分下去的類型,在內存中以固定的大小存儲在棧中,按值訪問;

複雜數據類型是指對象,這裡有常見的array、function、object等,本質上是一組無序的鍵值對組成。它的值大小不固定,所以保存在堆中,但在棧中會存儲有指向其堆內存的地址,按引用來訪問。js不允許直接訪問內存中的位置,也就是說不能直接操作對象的內存空間。也就是說,當我們想要訪問應用類型的值的時候,需要先從棧中獲得對象的地址指針,然後通過地址指針找到其在堆中的數據。

需要注意的是,

1、簡單數據類型中的boolean、number、string不是由內置函數new出來的,儘管他們有對應的引用類型;

2、symbol是ES6引入的一種新的原始數據,表示獨一無二且不可改變的值。通過 Symbol 函數調用生成,由於生成的 symbol 值為原始類型,所以 Symbol 函數不能使用 new 調用;

3、將一個變量賦值給另一個變量時,基礎類型複製的是值,賦值完成兩個變量在沒有任何關係;而對象類型的複製的是地址,修改一個變量另一個變量也會跟着一起變化。(如何解決這個問題?關於深拷貝and淺拷貝)

二、JavaScript對象的底層數據結構是什麼

這個問題目前對我來說,不能夠理解到底是想問什麼,還有問題,看到一篇這個文章,轉載《從chrome源碼看js object的實現》:

三、Symbol類型在實際開發中的應用、手動實現一個簡單的 Symbol

(暫未學習總結)

四、JavaScript中的變量在內存中的具體存儲形式

  js的數據類型分為簡單數據類型和複雜數據類型;在內存中,簡單數據類型以固定的大小存儲在棧中;複雜數據類型存儲在堆中,且大小不固定,同時在棧中會存儲其指向堆地址的指針。

因為這裏問的是內存中的存儲形式,所以我一直注意的是內存中堆棧,後來忽然看到一篇文章寫了數據結構中的堆和棧就有一點懵,先簡單記錄一下相關知識點。

內存的堆棧:

  是一種物理結構,用於存放不同數據的內存空間,分為棧區和堆區。

1)棧內存:

  棧(stack)是向低地址擴展的數據結構,是一塊連續的內存區域;一般來說其大小是系統預先規定好的,存儲大小已知的變量(函數的參數值、局部變量的值等)。由操作系統自動申請分配並釋放(回收)空間,無需程序員控制,這樣的好處是內存可以及時得到回收。但棧的大小有限,如果申請的空間超過棧的剩餘空間,就會提示棧溢出(一般無窮次的遞歸調用或大量的內存分配會引起棧溢出)。

在分配內存的時候類似於數據結構中的棧,先進后出的原則,即從棧低向棧頂,依次存儲。棧是向下增長,即從高地址到低地址分配內存,且內存區域連續、每個單元的大小相同。如下圖:

 2)堆內存:

  在現代程序中,在編譯時刻不能決定大小的對象將被分配在堆區。一般由程序員分配釋放,例如;c++和Java語言都為程序員提供了new(或malloc()),該語句創建的對象(或指向對象的指針),然後使用delete(或free())語句釋放。如果程序員不主動釋放,程序結束時由OS回收。 在內存分配的時候方式類似於鏈表,堆是向上增長,即從低地址到高地址擴展,是不連續的內存區域。如下圖:

數據結構的堆棧:

  是一種抽象的數據存儲結構,

棧:一種連續存儲的數據結構,特點是存儲的數據先進后出,只能在棧頂一端對數據項進行插入和刪除。

堆:是一棵完全二叉樹結構(知識點未掌握)

 五、基本類型對應的內置對象,以及他們之間的裝箱拆箱操作

1)基本包裝類型

 

  問:有了基本類型為什麼還要包裝類型?

  答:為了便於操作基本類型值,ECMAScript提供了3個特殊的引用類型:Boolean、Number和String, 每當讀取一個基本類型值的時候,後台會創建一個對應的基本包裝類型的對象,從而能夠調用一些方法來操作這些基本類型。每個包裝類型都映射到同名的基本類型。

2)裝箱和拆箱

    裝箱就是把基本類型轉換為對應的內置對象,這裏可分為隱式和顯式裝箱。     拆箱就是與裝箱相反,把對象轉變為基本類型的值。        Ⅰ 隱式裝箱 在讀取模式下,訪問基本類型值(即讀取基本類型的值),就會創建基本類型所對應的基本包裝類型的一個對象,從而讓我們能夠調用一些方法來操作這些數據。這個基本包裝類型是臨時的,操作基本類型值的語句一經執行完畢,就會立即銷毀新創建的包裝對象。

var s1 = “stringtext”; var s2 = s1.substring(2);

如上面的例子,第一行變量s1是一個基本類型的字符串,第二行調用了s1的substring()方法,並將結果保存在了s2中。 基本類型值不是對象,從邏輯上講它們不應該有方法。其實這裏就包含了隱式裝箱,後台自動完成了一系列的處理。當第二行代碼訪問s1時,訪問過程處於一種讀取模式,即從內存中讀取這個字符串的值。 在讀取字符串時,後台會完成一下處理。

(1)創建String類型的一個實例      =>  var s1 = new String(“stringtext”); (2)在實例上調用指定的方法        =>  var s2 = s1.substring(2); (3)摧毀這個實例                 =>  s1 = null;

注:①上面s1 = null;這種做法叫做解除引用,一旦有數據不再有用,通過設置其值為null來是釋放其引用;一般適用於大多數全局變量和全局對象的屬性。   ②
引用類型和基本包裝類型的主要區別:就是對象的生存期。使用new操作符創建的引用類型的實例,在執行流離開當前作用域之前都一直保存在內存中。而自動創建的基本包裝類型的對象,則只存在於一行代碼的執行瞬間,然後立即被銷毀,因此我們不能在運行時為基本類型值添加屬性和方法。

var s1 = “stringtext”; s1.color = “red”; //在這一句話執行完的瞬間,第二行創建的String就已經被銷毀了。 console.log(s1.color);//執行這一行代碼時又創建了自己的String對象,而該對象沒有color屬性。 //undefine

   Ⅱ 顯式裝箱 通過New調用Boolean、Number、String來創建基本包裝類型的對象。不過,不建議顯式地創建基本包裝類型的對象,儘管它們操作基本類型值的能力相當重要,每個基本包裝類型都提供了操作相應值的便捷方法。 Object構造函數會像工廠方法一樣,根據傳入值的類型返回相應基本包裝類型的實例。

var obj = new Object(“stringtext”); console.log(obj instanceof String); //true

    Ⅲ 拆箱 把對象轉變為基本類型的值,在拆箱的過程調用了JavaScript引擎內部的抽象操作,ToPrimitive(轉換為原始值),對原始值不發生轉換處理,只針對引用類型。 JavaScript引擎內部的抽象操作ToPrimitive()是這樣定義的,
 ToPrimitive(input [, PreferredType])
該操作接受兩個參數,第一個參數是要轉變的值,第二個是PreferredType為可選參數,只接受Number或String,作用是設置想要轉換原值時的轉換偏好。最後使input轉換成原始值。
如果PreferredType被標誌為Number,則會進行下面的操作來轉換input。 ①如果輸入的是一個原始值,則直接返回它; ②否則,如果輸入的值是一個對象,則調用該對象的valueOf()方法,如果valueOf()方法的返回值是一個原始值,則返回這個原始值; ③否則,調用這個對象的toString()方法,如果toString方法的返回值是一個原始值,則返回這個原始值; ④否則,拋出TypeError異常; 如果PreferredType被標誌為String,則轉換操作的第二步和第三步的順序會調換。即 ①如果輸入的是一個原始值,則直接返回它; ②否則,如果輸入的值是一個對象,則調用該對象的 toString()方法,如果toString()方法的返回值是一個原始值,則返回這個原始值; ③否則,調用這個對象的valueOf()方法,如果valueOf()方法的返回值是一個原始值,則返回這個原始值; ④否則,拋出TypeError異常; 如果沒有PreferredType的值會按照這樣的規則來自動設置: Date類型的對象會被設置為String,其他類型的值被設置為Number  

inputTpye result
Null 不轉換,直接返回
Undefined 不轉換,直接返回
Number 不轉換,直接返回
Boolean 不轉換,直接返回
String 不轉換,直接返回
Symbol 不轉換,直接返回
Object 按照下列步驟進行轉換

  參考文章: 《js隱式裝箱》                   《[譯]JavaScript在中,{} + {}等於多少?》                      原文《javaScript在中,what is {} + {} in javascrupt    ?》 

六、理解值類型和引用類型

js包含兩種數據類型,基本數據類型和複雜數據類型,而其對應的值基本類型的值指的是簡單的數據段,引用類型指的是那些可能有多個值構成的對象。可以從三個方面來理解:動態的屬性、複製變量的值、傳遞參數

1)、動態的屬性

定義基本類型值和引用類型值的方式類似,即創建一個變量併為該變量賦值。兩者的區別在於,對於引用類型的值,我們可以為其添加屬性和方法,也可以改變和刪除其屬性和方法;對於基本類型的值,我們不能為其動態地添加屬性。

var person = new Object(); //創建一個對象並將其保存在變量person中 person.name = “Song”; //為該對象添加一個名為name的屬性,並賦值為Song console.log(person.name); //訪問name這個屬性 //Song

2)、複製變量的值

在從一個變量向另一個變量複製基本類型值和引用類型值時,兩則也是不同的,這主要是由於基本類型和引用類型在內存中存儲不同導致的。

  Ⅰ基本類型的值 基本類型的值是存在棧中,存儲的即是基本類型的值;如果從一個變量向另一個變量複製的時候,就會重新創建一個變量的新值然後將其複製到為新變量分配的位置上,此時兩個變量各自擁有屬於自己的獨立的內存空間,因此兩者可以參与任何操作而不會相互影響。

var a = 1; var b = a; b = 2; console.log(a);//1 console.log(b);//2

 內存變化大致如下:

  Ⅱ複製引用類型的值

引用類型的值存儲在堆中,同時在棧中會有相應的堆地址(指針),指向其在堆的位置。此時如果我們要複製一個引用類型時,複製的不是堆內存中的值,而是將棧內存中的地址複製過去,複製操作結束后,兩個對象實際上都指向堆中的同一個地方。因此改變其中一個對象(堆中的值改變),那麼會影響到另一個對象。

 

var obj1 = {     name:”Song” }; var obj2 = obj1; obj2.name = “D”; //改變obj2的name屬性的值,則將obj1的也改變了。 console.log(obj1.name); // D 

 

注:關於深拷貝和淺拷貝

3)、傳遞參數

 ECMAScript中所有函數的參數都是按值傳遞的,無論在向參數傳遞的是基本類型還是引用類型。(我的理解:正因為是按值傳遞的,所以我們才可以利用此來完成深拷貝)

有一道關於證明引用類型是按值傳遞還是按引用傳遞的題目如下:

function test(person){
person.age = 26;
person = {
  name:'yyy',
  age:30
}
return person
}
const p1 = {
  name:'yck',
  age:25
};
const p2 = test(p1);
console.log(p1);
console.log(p2);

首先當我們從一個變量向另一個變量複製引用類型的值時,這個值是存儲在棧中的指針地址,複製操作結束后,兩個變量引用的是同一個對象,改變其中一個變量,就會影響另一個變量。

而在向參數傳遞引用類型的值時,同樣是把內存中的地址複製給一個局部變量,所以在上述代碼中,將p1的內存地址指針複製給了局部變量person,兩者引用的是同一個對象,這個時候在函數中改變變量,就會影響到外部。

接下來相當於從新開闢了一個內存空間,然後將此內存空間的地址賦給person,可以理解為將剛才指向p1的指針地址給覆蓋了,所以改變了person的指向,當該函數結束后便釋放此內存。

(此圖作為自己的理解,不代表實際,很有可能實際並不是這樣操作的。)

所以在person.age = 26;這句話執行后把p1內存里的值改變了,打印出來p1是{name: “yck”, age: 26}  p2是{name: “yyy”, age: 30}

而我理解的如果按引用傳遞,則相當於person的指向是和p1也一樣,所以後續只要是對person進行了操作,都會直接影響p1。

因此在這種情況下,打印出來p1和p2都是{name: “yyy”, age: 30}

七、null和 undefined的區別

1)、null類型    

        《高程》上解釋:null值表示一個空對象指針,所以這也是使用typeof操作符檢測null值時會返回”object”的原因。

var car = null; console.log(typeof car); //object

             一般來說,我們要保存對象的變量在還沒有真正保存對象之前可以賦值初始化為null,其他的基礎類型在未賦值前默認為undefined,這樣一來我們直接檢查變量是否為null可以知道相應的變量是否已經保存了一個對象的引用。 即如果定義的變量準備在將來保存為對象,那麼我們將該變量初始化為null,而不是undefined。 2)、undefined類型         在使用var申明變量但未對其初始化時,這個變量的值就是undefined。

var s; console.log(s == undefined); //true

3)、null和undefined的區別     一般來說undefined是派生自null的值,因此null == undefined  是為true,因為它們是類似的值;如果用全等於(===),null ===undefined會返回false ,因為它們是不同類型的值。以此我們可以區分null和undefined。

八、至少可以說出三種判斷 JavaScript數據類型的方式,以及他們的優缺點,如何準確的判斷數組類型

  問:判斷js數據類型有哪幾種方式,分別有什麼優缺點?怎麼樣判斷一個值是數組類型還是對象?(或者typeof能不能正確判斷類型)

  答:一般來說有5種常用的方法,分別是typeof、instanceof、Object.prototype.toString()、constructor、jquery的type();

1)對於typeof來說,在檢測基本數據類型時十分得力,對於基本類型,除了null都可以返回正確類型,對於對象來說,除了function都返回object。

基本類型

  typeof “somestring”   // ‘string’
  typeof true        // ‘boolean’
  typeof 10          // ‘number’
  typeof Symbol()    // ‘symbol’
  typeof null        // ‘object’ 無法判定是否為 null
  typeof undefined   // ‘undefined’

複雜類型

  typeof {}           // ‘object’
  typeof []           // ‘object’  如果需要判斷數組類型,則不能使用這樣方式
  typeof(() => {})    // ‘function’

 

注:怎麼使用複合條件來檢測null值的類型?

var a = null;

(!a && typeof a === “object”);     // true

 

2)對於instanceof來說,可以來判斷已知對象的類型,如果使用instanceof來判斷基本類型,則始終返回false。

其原理是測試構造函數的prototype是否出現在被檢測對象的原型鏈上;所有的複雜類型的值都是object的實例,在檢測一個引用類型值和Object構造函數時,instanceof操作符始終返回true。

[] instanceof Array         //true  -》 無法優雅的判斷一個值到底屬於數組還是普通對象 ({}) instanceof Object         //true (()=>{}) instanceof Function         //true

而且在《高程》上還看到說一個問題,如果不是單一的全局執行環境,比如網頁中包含多個框架,那麼實際上存在兩個以上不同的全局執行環境,從而存在兩個以上不同版本的Array構造函數,如果從一個框架向另外一個框架傳入數組,那麼傳入的數據與在第二個框架中原生創建的數組分別具有各自不同的構造函數。eg:例如index頁面傳入一個arr變量給iframe去處理,則即使arr instanceof Array還是返回false,因為兩個引用的Array類型不是同一個。並且constructor可以重寫所以不能確保萬無一失。

對於數組來說,相當於new Array()出的一個實例,所以arr.proto === Array.prototype;又因為Array是Object的子對象,所以Array.prototype.proto === Object.prototype。因此Object構造函數在arr的原型鏈上,便無法判斷一個值到底屬於數組還是普通對象。

注:判斷變量是否為數組的方法

3)通用但比較繁瑣的方法Object.prototype.toString() 

該方法本質是利用Object.prototype.toString()方法得到對象內部屬性[[Class]],傳入基本類型也能夠判斷出結果是因為對其值做了包裝。

Object.prototype.toString.call({})  ===  ‘[object Object]’      ——-> true;
Object.prototype.toString.call([])   ===  ‘[object Array]’  ——-> true;
Object.prototype.toString.call(() => {})  ===  ‘[object Function]’  ——-> true;
Object.prototype.toString.call(‘somestring’)  ===  ‘[object String]’  ——-> true;
Object.prototype.toString.call(1)  ===  ‘[object Number]’  ——-> true;
Object.prototype.toString.call(true)  ===  ‘[object Boolean]’  ——-> true;
Object.prototype.toString.call(Symbol()) ===  ‘[object Symbol]’  ——-> true;
Object.prototype.toString.call(null)   ===  ‘[object Null]’  ——-> true;
Object.prototype.toString.call(undefined)  === ‘[object Undefined]’  ——-> true;

Object.prototype.toString.call(new Date())   ===  ‘[object Date]’  ——-> true;
Object.prototype.toString.call(Math)  === ‘[object Math]’  ——-> true;
Object.prototype.toString.call(new Set())  ===  ‘[object Set]’  ——-> true;
Object.prototype.toString.call(new WeakSet())  ===  ‘[object WeakSet]’  ——-> true;
Object.prototype.toString.call(new Map())  ===  ‘[object Map]’  ——-> true;
Object.prototype.toString.call(new WeakMap())  ===  ‘[object WeakMap]’  ——-> true;

4)根據對象的constructor判斷

 

[].constructor === Array     ——–> true var d = new Date(); d.constructor === Date     ———> true (()=>{}).constructor === Function   ——-> true 注意: constructor 在類繼承時會出錯 eg: function A(){}; function B(){}; A.prototype = new B(); //A繼承自B var aobj = new A(); aobj.constructor === B  ——–> true; aobj.constructor === A   ——–> false; 而instanceof方法不會出現該問題,對象直接繼承和間接繼承的都會報true:

5)jquery的type()

如果對象是undefined或null,則返回相應的“undefined”或“null”, jQuery.type( undefined ) === “undefined” jQuery.type() === “undefined” jQuery.type( null ) === “null” 如果對象有一個內部的[[Class]]和一個瀏覽器的內置對象的 [[Class]] 相同,我們返回相應的 [[Class]] 名字。  jQuery.type( true ) === “boolean” jQuery.type( 3 ) === “number” jQuery.type( “test” ) === “string” jQuery.type( function(){} ) === “function” jQuery.type( [] ) === “array” jQuery.type( new Date() ) === “date” jQuery.type( new Error() ) === “error” // as of jQuery 1.9 jQuery.type( /test/ ) === “regexp”

6)如何判斷一個數組?

var  a = [];

a.instanceof  Array;  ——–> true

a.constructor === Array  ——–> true

Object.prototype.toString.call(a)   ===  ‘[object Array]’  ——–> true

Array.isArray([]);      ——–> true

九、可能發生隱式類型轉換的場景以及轉換原則,應如何避免或巧妙應用

(暫未整理)

十、出現小數精度丟失的原因、 JavaScript可以存儲的最大数字以及最大安全数字、JavaScript處理大数字的方法、避免精度丟失的方法

  問:0.1+0.2 === 0.3 為什麼是false?

  答:在ECMAScript數據類型中的Number類型是使用IEEE754格式來表示的整數和浮點數值,所謂浮點數值就是該數值必須包含一個小數點,並且小數點後面必須至少有一位数字。而在使用基於IEEE754數值的浮點運算時出現參數舍入的誤差問題,即出現小數精度丟失,無法測試特定的浮點數值。

  ①在進行0.1+0.2的時候首先要將其轉換成二進制。

  0.1 => 0.0001 1001 1001 1001…(無限循環)   0.2 => 0.0011 0011 0011 0011…(無限循環)   ②由於 JavaScript 採用 IEEE 754 標準,數值存儲為64位雙精度格式,數值精度最多可以達到 53 個二進制位(1 個隱藏位與 52 個有效位)。如果數值的精度超過這個限度,第54位及後面的位就會被丟棄,所以在相加的時候會因為小數位的限制而將二進制数字截斷。   0.0001 1001 1001 1001…+0.0011 0011 0011 0011… = 0.0100110011001100110011001100110011001100110011001100   ③再轉換成十進制就成了0.30000000000000004,而非我們期望的0.3

在《js權威指南》中有指出:

Javascript採用了IEEE-745浮點數表示法(幾乎所有的編程語言都採用),這是一種二進製表示法,可以精確地表示分數,比如1/2,1/8,1/1024。遺憾的是,我們常用的分數(特別是在金融的計算方面)都是十進制分數1/10,1/100等。二進制浮點數表示法並不能精確的表示類似0.1這樣 的簡單的数字,上訴代碼的中的x和y的值非常接近最終的正確值,這種計算結果可以勝任大多數的計算任務:這個問題也只有在比較兩個值是否相等時才會出現。 這個問題並不是只在javascript中才會出現,在任何使用二進制浮點數的編程語言中都會出現這個問題。 所以說,精度丟失並不是語言的問題,而是浮點數存儲本身固有的缺陷。只不過在 C++/C#/Java 這些語言中已經封裝好了方法來避免精度的問題,而 JavaScript 是一門弱類型的語言,從設計思想上就沒有對浮點數有個嚴格的數據類型,所以精度誤差的問題就顯得格外突出。   javascript的未來版本或許會支持十進制数字類型以避免這些舍入問題,在這之前,你更願意使用大整數進行重要的金融計算,例如,要使用整數‘分’而不是使用小數‘元’進行貨比單位的運算。

   問:怎麼避免精度丟失?

   答:一般常用的有四個方法,第一個是設置一個“能夠接受的誤差範圍”,在這個範圍內,可認為沒有誤差;第二個是使用三方的類庫math.js;第三是使用toFixed()方法;第四是封裝一個計算類(加、減、乘、除)。   ①ES6在Number對象上面,新增了一個極小的常量Number.EPSILON,它表示1與大於1的最小浮點數之間的差,它是實際上是javascript能夠表示的最小精度(可以接受的最小誤差範圍),誤差如果小於這個值,就可以認為已經沒有意義了,即不存在誤差。

Number.EPSILON === Math.pow(2, -52) // true      說明這個值Number.EPSILON是等於 2 的 -52 次方

  寫一個誤差檢測函數,來判斷0.1 + 0.2 === 0.3   設置誤差範圍為 2 的-50 次方(即
Number.EPSILON * Math.pow(2, 2)),即如果兩個浮點數的差小於這個值,我們就認為這兩個浮點數相等。

  function withinErrorMargin (left, right) {  
         return Math.abs(left - right) < Number.EPSILON * Math.pow(2, 2);
  }
    withinErrorMargin(0.1 + 0.2, 0.3)    //true

  ②math.js是一個廣泛應用於JavaScript 和 Node.js的數學庫,它的特點是靈活表達式解析器,支持符號計算,內置大量函數與常量,並提供集成解決方案來處理不同的數據類型,如数字,大数字,複數,分數,單位和矩陣。

  ③toFixed()方法   定義:toFixed() 方法可把 Number 四舍五入為指定小數位數的数字。   用法:NumberObject.toFixed(num) 其中num是必須的,規定小數的位數,是 0 ~ 20 之間的值,包括 0 和 20,有些實現可以支持更大的數值範圍。如果省略了該參數,將用 0 代替。 然而實際上,並不是完美的,可能你開發時候測試的幾個實例恰巧都是你想要的結果,可能在實際上線后遇到大量的數據后發現出問題了,不能正確的計算。一般是在遇到最後一位是5的時候,就不是’四舍五入”,eg:2.55.toFixed(1)   //  2.5,而我們齊期望的是2.56。 我有查這個產生誤差的原因,有人說是“銀行家的舍入規則”,即四舍六入五考慮,這裏“四”是指≤4 時捨去,”六”是指≥6時進上。”五”指的是根據5後面的数字來定,當5後有數時,舍5入1;當5后無有效数字時,需要分兩種情況來講:5前為奇數,舍5入1;5前為偶數,舍5不進(0是偶數)。但在某些情況下也是不成立。

2.65.toFixed(1)  //2.6    結果正確     2.45.toFixed(1)  //2.5    希望得到的結果是2.4 2.35.toFixed(1)   //2.4    結果正確

由於無法解決這種問題,所以看到有一些是以項目需求為準重寫符合要求的函數,在Math.round(x)來擴展解決toFixed()四舍五入不精確的問題。  原本round(x) 方法可把一個数字四舍五入為最接近的整數,其中x是必須的且必須是数字。雖然解決了四舍五入的問題,但卻沒有直接解決保留小數點后多少位的問題,因而需要重寫符合需求的函數。

function RoundNum(n, m){ //n表示需要四舍五入的數,m表示需要保留的小數位數
var newNum = Math.round(n * Math.pow(10, m)) / Math.pow(10, m) ;
//首先將要保留的小數位數的小數部分轉成整數部分,利用冪函數將n乘以10的m次方
//然後利用Math.round()方法進行四舍五入處理
//最後再除以10的m次方還原小數部分
//注:此時還未能將所有数字正確轉換。例如將1.0001保留3位小數我們想要的結果是1.000,而此時newNum裏面的值是1
//所以還需要處理此種特殊情況,即保留的小數位上全0
var newSNum = newNum.toString();
//這一步將剛才進行處理過的數轉換成字符串
var rs = newSNum.indexOf('.'); //利用indexOf查找字符串中是否有.,它返回某個指定的字符串值在字符串中首次出現的位置,不存在則返回-1
if (rs < 0) {
rs = newSNum.length;
newSNum += '.';
}
while (newSNum.length <= rs + m) { //在末尾加0
newSNum += '0';
}
return newSNum;
}

console.log(RoundNum(1.0005, 3)); //得到1.001

  ④封裝一個計算類(加、減、乘、除)

(暫未實際寫過)

  問:JavaScript可以存儲的最大数字以及最大安全数字

  答:最大数字是Number.MAX_VALUE、最大安全数字是Number.MAX_SAFE_INTEGER。Number.MAX_VALUE大於Number.MAX_SAFE_INTEGER,我的理解是js可以精確表示最大安全数字以內的數,超過了最大安全数字但沒超過最大数字可以表示,但不精確,如果超過了最大数字,則這個數值會自動轉換成特殊的Infinity值。

由於內存的限制,ECMAScript並不能保存世界上所有的數值,ECMAScript能夠表示的最小數值是Number.MIN_VALUE,能夠表示的最大數值是Number.MAX_VALUE。超過數值是正值,則被轉成Infinity(正無窮),如果是負值則被轉成-Infinity(負無窮)。如果在某次返回了正或負的Infinity值,那麼該值將無法繼續參与下一次的計算,所以我們需要確定一個數值是不是有窮的,即是不是位於最小和最大的數值之間,可以使用isFinite()函數,如果該函數參數在最小和最大數值之間時會返回true。注意,如果參數類型不是數值,Number.isFinite一律返回false

JavaScript 能夠準確表示的整數範圍在-2^532^53之間(不含兩個端點),超過這個範圍,無法精確表示這個值。ES6 引入了Number.MAX_SAFE_INTEGERNumber.MIN_SAFE_INTEGER這兩個常量,用來表示這個範圍的上下限。Number.isSafeInteger()則是用來判斷一個整數是否落在這個範圍之內。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

新北清潔公司,居家、辦公、裝潢細清專業服務