9個常見的JavaScript問題

Image of coding

JavaScript 被認爲是對初學者很友好的一門語言。部分緣由在於她在互聯網上的普遍使用,還有一部分在於她的一些功能使得編寫不完美的代碼仍然能夠運行(她不像許多其餘語言那樣嚴格,你沒必要再受分號或者內存管理的折磨)。javascript

本文中,咱們將介紹一些有趣的 JavaScript 問題。

1. 爲何 Math.max()小於 Math.min()?

Math.max() > Math.min()返回 false 的事實聽起來是荒唐的,但它確實有點意思。java

若是沒有給出參數,則 Math.min( ) 返回 infinity 而 Math.max( ) 返回 -infinity。這只是 max( ) 和 min( ) 方法規範的一部分,但在這背後有很好的邏輯。要了解緣由,請查看一下代碼:編程

Math.min(1// 1
Math.min(1,infinity)
// 1
Math.min(1,-infinity)
// - 無窮大
複製代碼

若是 -infinity被認爲是 Math.min( )的默認參數 ,那麼每一個結果都是infinity,這是毫無心義的!若是默認參數是infinity,則添加任何其餘參數將返回該數字——這纔是咱們想要的結果。數組


2. 爲何 0.1 + 0.2 === 0.3 返回 false?

簡而言之,這與 JavaScript 在二進制文件中存儲浮點數的準確程度有關。若是您在 Google Chrome 控制檯中輸入如下公式,您將得到:數據結構

0.1 + 0.2
// 0.30000000000000004
0.1 + 0.2  -  0.2
// 0.10000000000000003
0.1 + 0.7
// 0.7999999999999999 ```
複製代碼

若是您在不須要高精度的狀況下執行簡單的方程式,這不太可能致使問題。可是若是你須要測試相等性,它甚至能夠在簡單的應用程序中引發麻煩。有一些解決方案。dom

固定小數點

例如,若是你知道所需的最大精度(例如,若是你正在處理貨幣),則可使用整數類型來存儲該值。所以$4.99 ,您能夠存儲 499 並執行任何方程式。而後,您可使用result = (value / 100).toFixed(2) 返回字符串的表達式將結果顯示給最終用戶。編程語言

二進制編碼的小數

若是精度很是重要,另外一種選擇是使用二進制編碼的十進制(BCD)格式,您可使用此BCD庫在 JavaScript 中訪問該格式。每一個十進制值分別存儲在一個字節(8 位)中。這是低效的,由於一個字節能夠存儲 16 個單獨的值,而且該系統僅使用值 0-9。可是,若是精度對你的應用很重要,那麼可能值得權衡。函數式編程


3. 爲何 018 減 017 等於 3 ?

018-017返回的結果3是默認類型轉換的結果。在這種狀況下,咱們談論的是八進制數。函數

八進制數簡介

你知道在計算中使用二進制(base-2)和十六進制(base-16)數字系統,可是八進制(base-8)在計算機歷史中也佔有突出地位:在 20 世紀 50 年代後期和 20 世紀 60 年代,它被用來縮寫二進制。在高昂的製造系統中削減材料成本!測試

以後不久就出現了十六進制(base-8)。

八進制數還有用嗎?

在現代編程語言中,八進制有用嗎?對於某些用例,Octal 比十六進制有優點,由於它不須要任何非數字數字(使用 0-7 而不是 0-F)。

一個常見用途是 Unix 系統的文件權限,其中有八個權限變體:

4 2 1 0 - - - 無權限 1 - - x 僅執行 2 - x - 僅寫 3 - xx 寫入並執行 4 x - - 僅讀取 5 x - x 讀取並執行 6 xx - 讀取和寫入 7 xxx 讀取,寫和執行

出於相似的緣由,它也用於數字顯示器。

回到問題

在 JavaScript 中,前綴 0 會將任何數字轉換爲八進制。可是,8 不會在八進制中使用,而且任何包含 an 的數字 8 都將以靜默方式轉換爲常規十進制數。

所以,018 — 017 實際上至關於十進制表達式 18 — 15 ,由於它 017 是八進制可是 018 十進制。


4. 函數表達式與函數聲明的不一樣之處是什麼?

函數聲明使用關鍵字function ,後跟函數的名稱。相反,函數表達式以函數名稱和賦值運算符開頭varlet或者const後跟函數名稱= 。這裏有些例子:

// Function Declaration
function sum(x, y) {
 return x + y
}
// Function Expression: ES5
var sum = function(x, y) {
 return x + y
}
//函數表達式:ES6 +
const sum =(x,y)=>{ return x + y }
複製代碼

在使用中,關鍵的區別在於函數聲明被提高,而函數表達式則沒有。這意味着 JavaScript 解釋器將函數聲明移動到其做用域的頂部,所以您能夠定義函數聲明並在代碼中的任何位置調用它。相比之下,您只能以線性順序調用函數表達式:您必須在調用它以前定義它。

現在,許多開發人員更喜歡函數表達式,這有幾個緣由:

  • 首先,函數表達式強制實施更可預測的結構化代碼庫。固然,結構化代碼庫也可使用聲明; 它只是聲明容許您更容易地擺脫凌亂的代碼。
  • 其次,咱們可使用 ES6 語法函數表達式:這個通常比較簡潔,let 並 const 在一個變量是否能夠從新分配提供更多的控制與否,咱們將在接下來的問題中看到的。

5. var,let,和 const 有什麼區別?

var 是初版 JavaScript 中的變量聲明關鍵字。但它的缺點致使在 ES6 中採用了兩個新的關鍵詞:let 和 const 。

i)分配

最基本的區別是,let 並 var 同時能夠從新分配 const 不能。這是 const 不須要更改的變量的最佳選擇,它能夠防止意外從新分配等錯誤。注意,const 它容許變量變異,這意味着若是它表示一個數組或一個對象,它們能夠改變。您沒法從新分配變量自己。

雙方 let 並 var 能夠從新分配,而是-由於如下幾點應明確-  let 超過了顯著的優點 var ,使得它在大多數更好的選擇,如不及時救治變量須要改變的全部狀況。

ii)變量提高

相似於函數聲明和表達式之間的區別(如上所述),使用聲明的變量 var 老是被提高到它們各自範圍的頂部,而變量聲明使用 const 和 let 被提高,可是若是你在聲明以前嘗試訪問它們,那麼你將會出現「暫時死區」錯誤。這是有用的行爲,由於 var 可能更容易出錯,例如意外從新分配。請看如下示例:

var x =「global scope」;
function foo(){
  var x =「functional scope」
  的console.log(X)
}
FOO() //「functional scope」
console.log(x) //「global scope」
複製代碼

在這裏,結果 foo()和 console.log(x)咱們指望的同樣。可是,若是咱們放下第二個 var 呢?

var x = "global scope"

function foo() {
  x = "functional scope"
  console.log(x)
}

foo(); // "functional scope"
console.log(x) // "functional scope"
複製代碼

儘管在函數內定義,但 x = "functional scope"已覆蓋全局變量。咱們須要重複關鍵字 var 以指定第二個變量 x 的範圍僅限於 foo() 。

iii)做用域

雖然 var 是函數做用域的,let 而且 const 是塊做用域的:一般,塊是花括號內的任何代碼{} ,包括函數,條件語句和循環。爲了說明差別,請查看如下代碼:

var a = 0
let b = 0
const c = 0

if (true) {
  var a = 1
  let b = 1
  const c = 1
}

console.log(a) // 1
console.log(b) // 0
console.log(c) // 0
複製代碼

在咱們的條件塊,全局做用域 var a 已經被從新定義,但全局範圍的 let b 並 const c 沒有。通常而言,確保本地分配保持在本地將使代碼更清晰,錯誤更少。


6. 若是你指定不帶關鍵字的變量會怎樣?

若是您在不使用關鍵字的狀況下定義變量會怎樣?從技術上講,若是x尚未定義,那麼x = 1就是簡寫window.x = 1 。這是內存泄漏的常見緣由。

爲了不這種狀況,你可使用嚴格模式(ES5 中引入)。經過在文檔頂部或特定函數中編寫use strict。以後,當你嘗試聲明沒有關鍵字的變量時,你將收到錯誤:Uncaught SyntaxError: Unexpected indentifier


7. 面向對象編程(OOP)和函數式編程(FP)之間有什麼區別?

JavaScript 是一種多範式語言,意味着它支持多種不一樣的編程風格,包括事件驅動,功能和麪向對象。

有許多不一樣的編程範式,但在當代計算中,兩種最流行的樣式是函數式編程(FP)和麪向對象編程(OOP) - 而 JavaScript 能夠同時執行這兩種操做。

面向對象編程(OOP)

OOP 基於「對象」的概念。這些是包含數據字段的數據結構 - 在 JavaScript 中稱爲「屬性」 - 和「方法」。

一些 JavaScript 的內置對象包括Math(用於方法例如randommaxsin), JSON(用於解析 JSON 數據),和原始數據類型,如StringArrayNumberBoolean

不管什麼時候依賴內置的方法,原型或類,實際上你都在使用面向對象的編程。

函數式編程(FP)

FP 基於「純函數」的概念,它避免了共享狀態,可變數據和反作用。這可能看起來像不少術語,但你可能已經在代碼中建立了許多純函數。

給定相同的輸入,純函數老是返回相同的輸出。它沒有反作用:除了返回結果以外,這些都是任何東西,例如記錄到控制檯或修改外部變量。

至於共享狀態,這裏有一個簡單的例子,即狀態能夠改變函數的輸出,即便輸入是相同的。讓咱們設置一個具備兩個函數的場景:一個用於將數字加 5,另外一個用於乘以 5。

const num = {
  val:1
}
const add5 =()=> num.val + = 5
const multiply5 =()=> num.val * = 5
const multiply5 =()=> num.val * = 5
複製代碼

若是咱們 add5 先調用第一個multiply5,那麼整體結果是30 。可是若是咱們以相反的方式調用函數並記錄結果,咱們會獲得一些不一樣的值:10

這違背了函數式編程的原理,由於函數的結果根據上下文而不一樣。咱們能夠從新編寫上面的代碼,以便結果可預測:

const num = {
  val:1
}
const add5 =()=> Object.assign({},num,{val:num.val + 5})
const multiply5 =()=> Object.assign({},num,{val:num.val * 5}
複製代碼

如今,num.val的值老是1 ,無關上下文,add5(num)或者multiply5(num)老是會產生相同的結果。

8. 命令式編程和聲明式編程之間有什麼區別?

咱們還能夠根據「命令」和「聲明」編程之間的區別來考慮 OOP 和 FP 之間的區別。

這些是描述多種不一樣編程範例之間共享特徵的總稱。FP 是聲明性編程的一個例子,而 OOP 是命令式編程的一個例子。

從基本的意義上講,命令式編程關注的是你如何作某事。它闡述了最重要的方式,步驟,特色是forwhile循環,ifswitch表達式等等。

const sumArray = array => {
  let result = 0
  for (let i = 0; i < array.length; i++) {
    result += array[i]
  }
  return result
}
複製代碼

與此相反,聲明式編程關注的是要作什麼,它抽象掉了如何依靠表達式。這一般會致使更簡潔的代碼,可是在規模上,調試會變得更加困難,由於它的透明度要低得多。

如下是sumArray()函數的簡寫:

const sumArray = array => { return array.reduce((x, y) => x + y) }
複製代碼

9. 什麼是基於原型的繼承?

最後,咱們來了解基於原型的繼承。有幾種不一樣風格的面向對象編程,JavaScript 使用的是基於 Prototype 的繼承。該系統容許經過使用充當「原型」的現有對象來重複行爲。

即便原型的想法對你來講是新的,你也會經過使用內置方法遇到原型系統。例如,使用的功能來操做像mapreducesplice等等基於Array.prototype的對象的全部方法。事實上,一個數組的每一個實例(定義使用方括號[],或使用 new Array())繼承 Array.prototype ,這就是爲何相似的方法 map ,reduce 並 splice 默承認用。

一樣,幾乎全部其餘內置對象,如stringsbooleans:只有少部分,如InfinityNaNnullundefined沒有屬性或方法。

在原型鏈的末端,咱們發現Object.prototype ,幾乎每個對象在JavaScript中是一個 Object.prototype:Array.prototype 和 String.prototype 的實例,它們都繼承了 Object.prototype 的屬性和方法 。

要使用原型語法向對象添加屬性和方法,只需將對象做爲函數調用,而後使用 prototype 關鍵字添加屬性和方法:

function Person() {}
Person.prototype.forename = "John"
Person.prototype.surname = "Smith"
複製代碼

咱們應該覆蓋仍是擴展原型的行爲?

能夠像咱們建立和擴展咱們本身的原型同樣改變內置原型的行爲,可是大多數開發人員(以及大多數公司)會建議不要這樣作。

若是您但願多個對象共享相同的行爲,你始終能夠建立一個自定義對象(或定義你本身的「類」或「子類」),該對象繼承自內置原型而不對原型自己進行任何更改。若是您打算與其餘開發人員合做,他們對JavaScript的默認行爲有必定的指望,編輯此默認行爲很容易致使錯誤。

然而,值得注意的是,並不是全部人都清冽反對這種對內置原型的擴展。例如,請參閱JavaScript 的建立者 Brendan Eich 撰寫的這篇文章。在這篇文章中(從 2005 年開始),Eich 建議原型系統其實是部分構建的 - 以使擴展成爲可能!

相關文章
相關標籤/搜索