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

前言

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

  在網上看了一個體系,可做爲大體的學習檢閱本身的途徑,按照清單上的知識檢測本身還有哪些不足和提高,最後造成本身的知識體系。在工做、學習甚至面試時,能夠快速定位到知識點。html

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

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

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

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

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

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

須要注意的是,git

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

二、symbol是ES6引入的一種新的原始數據,表示獨一無二且不可改變的值。經過 Symbol 函數調用生成,因爲生成的 symbol 值爲原始類型,因此 Symbol 函數不能使用 new 調用;github

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

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

這個問題目前對我來講,不可以理解究竟是想問什麼,還有問題,看到一篇這個文章,轉載《從chrome源碼看js object的實現》:https://www.rrfed.com/2017/04/04/chrome-object/chrome

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

(暫未學習總結)

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

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

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

內存的堆棧:

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

1)棧內存:

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

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

 2)堆內存:

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

數據結構的堆棧:

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

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

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

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

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隱式裝箱》 https://sinaad.github.io/xfe/2016/04/15/ToPrimitive/
                   《[譯]JavaScript在中,{} + {}等於多少?》 https://www.cnblogs.com/ziyunfei/archive/2012/09/15/2685885.html 
                     原文《javaScript在中,what is {} + {} in javascrupt    ?》  https://2ality.com/2012/01/object-plus-object.html

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

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}

7、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。

8、至少能夠說出三種判斷 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

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

(暫未整理)

10、出現小數精度丟失的緣由、 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()則是用來判斷一個整數是否落在這個範圍以內。

相關文章
相關標籤/搜索