YDKJS-類型與文法

這一冊回答這樣兩個問題:面試

類型和值真正的工做方式 強制轉換的角落和縫隙、強制轉換值得花時間學習&合理的地方正則表達式

主要內容:回答問題

談談類型和值真正的工做方式

JavaScript中定義的7種內建類型?

首先要說明的是,變量沒有類型,變量持有有類型的值。 其中內建類型有:nullundefinedstringnumberbooleanobjectsymbol(ES6新加); 使用typeof能夠準確測出除null外的6個,還有functiontyepof demo;若是要測試null算法

//惟一一個typeof是object可是是fasly的值
var a = null;

(!a && typeof a === "object"); // true
複製代碼

Array、String、Number和特殊值認識補充

類Array是什麼?有例子嗎?如何轉換爲Array?

類Array指長得很像Array可是沒有連接到Array.prototype的數據結構,好比DOM查詢操做返回的DOM元素列表(連接到NodeList.prototype)、函數內參數值暴露的arguments對象(連接到Object.prototype,在ES6被廢棄)。 如何轉換有兩種方式:數組

  • Array.prototype.slice.call(***)
  • Array.from(***)

對String和Array有膚淺的類似性這樣的說法怎麼看?

膚淺的類似性體如今它們都有一個length屬性,一個indexOf方法和一個concat方法:demo瀏覽器

說是膚淺說明二者更大的是區別。String是不可變的,String.prototype上掛載的方法操做字符串,不是原地修改內容,而是建立返回一個新的字符串;Array是至關可變的,儘管Array.prototype上掛載的方法有建立返回一個新數組的非變化方法,也有原地修改內容的變化方法。安全

對於String操做,有些Array方法是有用的,咱們能夠對string"借用"非變化的array方法:demo;也有著名的反轉字符串的例子是作了String=>Array=>String的轉換:demo。固然若是頻繁借用Array方法,那爲何最開始不直接設爲Array類型。性能優化

0.1+0.2===0.3爲何是false?安全整數是什麼?

第一個問題,0.1和0.2在js中的二進制形式表達是不精確的,相加結果不是0.3,而是接近0.3的一個值,因此是false。爲了靠譜地作數值的比較,引入一個容差/機械極小值,在ES6中這個值是Number.EPSILON,只要差值的絕對值小於容差,認爲是數值相等。bash

第二個問題,JavaScript能表示的最大最小值是Number.MAX_VALUENumber.MIN_VALUE,可是對於整數而言有一個安全範圍,在這個安全範圍內,整數是無誤表示,這個範圍是Number.MIN_SAFE_INTEGERNumber.MAX_SAFE_INTEGER數據結構

NaN是什麼?如何斷定一個值是NaN?

使用不一樣爲number的值作操做等到的就是一個NaN,它是一個哨兵值,能夠理解爲不合法的數字/失敗的數字,試着進行數學操做可是失敗了,這就是失敗的number結果。函數

NaN值的斷定不能直接和NaN比較,由於NaN是惟一一個本身不等於本身的。ES6給了一個Number.isNaN(..),如要要填補的話,類型是數字+本身不等於本身兩個條件。

ES6提供了Object.is(..),用來測試兩個值的絕對等價性,沒有任何例外:demoObject.is(..)的定位是這樣的:

Object.is(..) 可能不該當用於那些 ===== 已知 安全 的狀況(見第四章「強制轉換」),由於這些操做符可能高效得多,而且更慣用/常見。Object.is(..) 很大程度上是爲這些特殊的等價狀況準備的。

談談原生類型構造器/內建函數

內部[[Class]]是什麼?

typeof結果是object的值被額外地打上一個內部標籤屬性,就是[[Class]]。這個屬性不能直接訪問,但能夠間件經過Object.prototype.toString.call(..)訪問。

好比:demo ,注意簡單基本類型stringnumberboolean發生封箱行爲;好比結果[Object Array]後面的Array反應的是原生類型構造器/內建函數這個信息;

特殊的是nullundefined也能工做,結果是[Object Null] [Object Undefined],雖然沒有NullUndefined

Object.prototype.toString.call(..)也被用來測值類型,比typeof更完善。

談談封箱與開箱?自動封箱有什麼優點?

封箱與開箱是兩個相反的過程,封箱將基本類型值包裝進對象,開箱從對象中取出基本類型值。封箱有手動封箱(好比new String("aaa"))和自動封箱兩種,在基本類型值上訪問對象的屬性和方法就會發生自動封箱,好比var a = "zzz";a.length。自動封箱,即讓封箱在須要的地方隱含發生,這是推薦的,一個是方便另外一個是瀏覽器對於.length等常見的狀況有性能優化。開箱也分手動開箱(valueOf)和自動開箱兩種,當以一種查詢基本類型值的方式使用對象包裝時,自動開箱發生,即開箱隱含發生,給個demo

這麼多的原生構造器...表個態?

ArrayObjectFunctionRegExp

這四種的話使用字面形式建立值幾乎是更好的選擇。

(使用Array構造器的一個用途:快速建立undefined * n的數組

(使用Object構造器,得一個一個去添加屬性,麻煩!)

Function構造器用於動態定義函數,這篇文章裏有一個demo,可是通常也挺少用的)

使用RegExp有一個用途是字面形式沒有的——用於建立動態的正則表達式

DateError

這兩種的話沒有字面形式,只能使用原生類型構造器。

使用Date構造器給一個demo,獲取當前時間時間戳比較經常使用

Error手動使用比較少,通常是自動被拋出,可是有些像messagetypestack的屬性可能會有用

Symbol

使用Symbol(...)構造器生成的數據是簡單的基本標量(注意不是object),Symbol主要爲特殊的ES6結構的內建行爲設計。能夠自定義,也有預約義的Symbol值。(這裏簡單帶過了,之後實際遇到了再研究)

談談強制轉換的角落和縫隙、強制轉換值得花時間學習&合理的地

抽象操做ToStringToNumberToBoolean分別是?和強制轉換的關係?

強制轉換最後都是轉爲booleanstringnumber這樣的基本標量,而抽象操做ToStringToNumberToBoolean規定了這樣的轉換。

首先是ToString,轉換爲string,給個demo,注意ToString能夠被明確地調用,也能夠在一個須要String的上下文環境中自動地調用;另外想補充一下JSON.stringify(..)字符串化(自己和ToString的抽象操做沒有關係,只要是對簡單類型的字符串化行爲比較像),JSON字符串化分安全值和非法值,非法值主要是由於不能移植到其餘消費JSON值的語言,非法值包括undefinedfunctionsymbol和帶有循環引用的objectobject能夠定義一個toJSON方法,該方法在JSON.stringify(..)調用時首先被調用,主要用途是將JSON非法值轉換爲安全值,另外JSON.stringify(..)也提供第二個參數是替換器——過濾object屬性、第三個參數填充符。

而後說說ToNumber,轉換爲number,給個demo,注意object/array首先是ToPrimitive抽象操做。

最後是ToBoolean,轉換爲boolean,這個比較簡單,falsy列表中的轉換爲falsenullundefined""+0-0NaNfalse

明確地String<-->Number

最明確的固然是不帶newString(..)Number(..);另外x.toString()也被認爲是明確轉爲字符串,儘管背後隱含地發生了封箱;一元操做符+也被認爲是明確轉爲數字,可是若是狀況是多個+/-連着,是增長困惑的。

String-->Number彷佛有點關聯的是解析數字字符串的行爲(好比parseIntparseFloat),解析數字字符串和類型轉換的一個最大區別是,解析數字字符串是容忍錯誤的,好比parseInt("42px")結果是"42",而類型轉換是零容忍的。關於parseInt有個網紅面試題

明確地 * --> Boolean

明確地轉爲布爾值類型有兩種方式:不帶newBoolean(a)和兩個否認符號!!aBoolean(a)很是明確,可是不常見,!!a更加明確。

有用的隱含強制轉換

隱含地 Strings <--> Numbers

Numbers --> Strings

兩個操做數的+操做符是這樣工做的:若是兩個操做數之一是一個string(或者object/array經過ToPrimitive抽象操做成爲一個string),作string鏈接;其餘狀況作數字加法。使用在隱含地Numbers轉換爲Strings上面是,number + ""

Strings --> Numbers

這裏使用-操做符,-操做符僅爲數字操做定義,因此兩個操做數都會被轉爲number,即發生ToNumber抽象操做。使用在隱含地Strings轉換爲Numbers上面是,string - 0

隱含地 Booleans --> Numbers

兩個操做數的+操做符在沒有string的狀況是作數字加法,也適用這裏,好比true+true===2 ,這裏就隱含地發生了Booleans轉換爲Numbers。這個點有個有意思的用途,好比有3個條件,在僅一個爲true狀況作後續操做:

const cond1 = ....;
const cond2 = ....;
const cond3 = ....;

if((cond1&&!cond2&&!cond3)||(!cond1&&cond2&&!cond3)||(!cond1&&!cond2&&cond3)){
    //後續操做...
}
複製代碼

這裏的boolean邏輯就很複雜,但可以使用前面的方法用數字加法作簡化:

if((!!cond1+!!cond2+!!cond3)===1){//後續操做...}
複製代碼

隱含地 * --> Booleans

有些表達式操做會作一個隱含地強制轉換爲boolean值:

  1. if(..)
  2. for(;;)第二個子句
  3. while(..)do{..}while(..)
  4. ?:三元表達式的第一個子句
  5. ||&&

其中||&&被稱爲邏輯或和邏輯與,但也能夠認爲是操做數選擇器操做符,由於:

a || b; //大體至關於 a?a:b
a && b; //大體至關於 a?b:a
複製代碼

另外JS壓縮器會把if(a){foo();}轉換爲a&&foo(),由於代碼更短,可是不多有人這麼作(我有這麼作過...)

怎麼選擇寬鬆等價與嚴格等價?寬鬆等價的抽象等價性比較算法能夠說一說嗎?

對待寬鬆等價與嚴格等價的態度

比較同類型的值時,它們算法是相同的,作的工做也基本是一致的,因此這裏是隨意的;在比較不一樣類型的值,寬鬆等價容許作強制轉換,若是想要強制轉換用寬鬆等價,不然用嚴格等價。 寬鬆不等價與嚴格不等價是在前面兩個的基礎上取反。

寬鬆等價的「抽象等價性比較算法」

同類型的值是簡單天然地比較;

不一樣類型的值:

  • 比較:stringnumber
  1. 若是Type(x)是Number而Type(y)是String, 返回比較x == ToNumber(y)的結果。

  2. 若是Type(x)是String而Type(y)是Number, 返回比較ToNumber(x) == y的結果。

  • 比較:任何東西與boolean
  1. 若是Type(x)是Boolean, 返回比較 ToNumber(x) == y 的結果。

  2. 若是Type(y)是Boolean, 返回比較 x == ToNumber(y) 的結果。

這裏掛個demo,提醒千萬不要==true或者==false

  • 比較:nullundefined
  1. 若是x是null而y是undefined,返回true。

  2. 若是x是undefined而y是null,返回true。

nullundefined是互相等價的,而不等價於其餘值(惟一寬鬆等價)。這裏給一個僞代碼:

var a = doSomething();

if (a == null) {
	// ..
}
複製代碼

不管anull仍是undefined,檢查都是經過的,而不須要再去作a===undefined||a===null,更加簡潔。

  • 比較:object與非object
  1. 若是Type(x)是一個String或者Number而Type(y)是一個Object, 返回比較 x == ToPrimitive(y) 的結果。
  2. 若是Type(x)是一個Object而Type(y)是String或者Number, 返回比較 ToPrimitive(x) == y 的結果

寬鬆等價的壞列表?

寬鬆等價的壞列表指的是比較難理解的比較狀況,發生的也是隱含強制轉換,demo

爲了不壞列表,給出了能夠遵循的規則:

  1. 若是比較的任意一邊可能出現true或者false值,那麼就永遠,永遠不要使用==
  2. 若是比較的任意一邊可能出現[]"",或0這些值,那麼認真地考慮不使用==

在這些狀況,使用=====

a<b時發生了什麼?說說抽象關係比較算法

大體流程:

  1. 若是ab的值有一個object類型,首先是ToPrimitive抽象操做處理,使得比較雙方都是基礎類型值
  2. 若是這時候兩個比較對象都是string類型,按照簡單的字典順序比較(數字在字母前面)
  3. 若是不知足2,兩個比較對象作ToNumber抽象操做處理,再進行數字比較

demo-兩個對象的比較

在這個例子中a<ba==ba>b結果都是false,奇怪的是a>=ba<=b都是true。由於大於等於、小於等於更貼切的翻譯是不小於、不大於,對比較操做取反。

比較沒有像等價那樣的「嚴格的關係型比較」,因此若是不容許隱含強制轉換髮生,在比較以前作好類型的強制轉換

主要內容以外

undefined和undeclared概念

給個代碼片斷就知道這二者的區別:在這裏a就是undefined,b就是undeclared。

var a;

a; // undefined
b; // ReferenceError: b is not defined
複製代碼

typeof 在對待 undeclared的變量時不會報錯,這是一種安全防衛行爲。它有什麼用呢?

  1. 假設debug.js文件中有一個DEBUG的全局變量,只有在開發環境下導入全局,生產環境不會,在其餘的程序代碼中可能會去檢查這個DEBUG變量:demo
  2. 再好比想象一個你想要其餘人複製-粘貼到他們程序中或模塊中的工具函數,在它裏面你想要檢查包含它的程序是否已經定義了一個特定的變量:demo

第二個實際case沒有遇到過,另外也在連接下面掛出了依賴注入的解決方式。

獲取當前時間戳有哪些方法?

除了以前提到的使用Date構造器的兩種方法(new Date().getTime()Date.now())之外,一元操做符+也能夠:+new Date/+new Date(),可是使用一元操做符+轉換的方式不夠明確,因此不是很推薦。

位操做符~發生了什麼?有什麼用途?

位操做符|會發生ToInt32(轉換爲32位整數),像0|x通常被用於轉換爲32位整數。位操做符~首先第一步也是ToInt32的轉換,其次是按位取反,給個demo。用途有兩個:

  1. 使用arr#indexOfarr#findIndexstr#indexOf時若是找不到結果返回-1-1是一個錯誤狀況的哨兵值,使用>=-1/==-1這樣的判斷,是一種「抽象泄露」,而若是使用~x //0當x=-1這樣的判斷更加優雅
  2. 截斷比特位,~~截斷數字的小數部分,固然是ToInt32的結果

操做符優先級(更緊密的綁定)和結合性

例如分析下面表達式的執行結果:

a && b || c ? c || b ? a : c && b : a
複製代碼

工做分兩步,首先是肯定更緊密的綁定,由於&&優先級比||高,而||優先級比? :高。因此更緊密的綁定使得上述表達式等價於:

((a && b) || c) ? (c || b) ? a : (c && b) : a
複製代碼

第二步是在同等優先級的操做符狀況下,是從左發生分組(左結合)仍是從右發生分組(右結合),好比?:是右結合的,因此a?b:c?d:e是等價於a?b:(c?d:e),而不是(a?b:c)?d:e,因此上述表達式等價於:

((a && b) || c) ? ((c || b) ? a : (c && b)) : a
複製代碼

掘金上有看到問true || true && fn()表達式中,fn會不會執行。 按照以前更緊密的綁定分析,等價於表達式true||(true&&fn()),可是執行順序依然是從左往右,由於||的短接,因此在第一個選擇數爲true的狀況,不會去執行第二個選擇數,也就不會執行fn函數。

涉及TDZ(時間死區)的兩種狀況

  1. letconst聲明的變量是在TDZ中的,因此必須得在聲明以後使用,而不會發生提高;
  2. 參數默認值
var b = 3;

function foo( a = 42, b = a + b + 5 ) {
	// ..
}
複製代碼

在賦值中的b引用將在參數b的TDZ中發生(不會被拉到外面的b引用),因此它會拋出一個錯誤。然而,賦值中的a是沒有問題的,由於那時參數a的TDZ已通過去了。

switch每一個case是嚴格等價仍是寬鬆等價?

答案是嚴格等價,可是寬鬆等價是能夠模擬的:

var a = "42";

switch (true) {
	case a == 10:
		console.log( "10 or '10'" );
		break;
	case a == 42:
		console.log( "42 or '42'" );
		break;
	default:
		// 永遠不會運行到這裏
}

複製代碼
相關文章
相關標籤/搜索