你不知道的JavaScript(中卷) 第3、四章

這裏的內容是讀書筆記,僅供本身學習所用,有欠缺的地方歡迎留言提示。


第3章 原生函數
JavaScript的內建函數,也叫原生函數,如String和Number。
經常使用的原生函數有:正則表達式

  • String()
  • Number()
  • Boolean()
  • Array()
  • Object()
  • Function()
  • RegExp()
  • Date()
  • Error()
  • Symbol()

實際上,它們就是內建函數。
原生函數能夠被看成構造函數來使用,但其構造出來的對象可能會和咱們設想的有所出入。數組

let a = new String('abc');
typeof a; // 是"object",不是"string"
a instanceof String; // true
Object.prototype.toString.call(a); // "[object String]"

經過構造函數建立出來的是封裝了基本類型值(如"abc")的封裝對象。
請注意:typeof在這裏返回的是對象類型的子類型。
再次強調,new String("abc")建立的是字符串"abc"的封裝對象,而非基本類型值"abc"。瀏覽器

3.1 內部屬性 [[Class]]
全部typeof返回值爲"object"的對象(如數組)都包含一個內部屬性[[Class]](咱們能夠把它看做一個內部的分類,而非傳統的面向對象意義上的類)。這個屬性沒法直接訪問,通常經過Object.prototype.toString(..)來查看。例如:安全

Object.prototype.toString.call([1, 2, 3]);
// "[object Array]"

多數狀況下,對象的內部[[Class]]屬性和建立該對象的內建原生構造函數相對應,但並不是老是如此。性能優化

Object.prototype.toString.call(null);
// "[Object Null]"
Object.prototype.toString.call(undefined);
// "[Object undefined]"
// 雖然Null()和Undefined()這樣的原生構造函數並不存在,可是內部[[Class]]屬性值仍然是"Null"和"Undefined"。

其餘基本類型值(如字符串、數字和布爾)的狀況有所不一樣,一般稱爲「包裝」。app

Object.prototype.toString.call("abc");
// "[object String]"
Object.prototype.toString.call(42);
// "[object Number]"
Object.prototype.toString.call(true);
// "[object Boolean]"

3.2 封裝對象包裝
封裝對象(object wrapper)扮演着十分重要的角色。因爲基本類型值沒有.length和.toString()這樣的屬性和方法,須要經過封裝對象才能訪問,此時JavaScript會自動爲基本類型值包裝(box或者wrap)一個封裝對象:ide

let a = "abc";
a.length; // 3
a.toUpperCase(); // "ABC"

若是須要常常用到這些字符串屬性和方法,那麼從一開始就建立一個封裝對象也許更爲方便,這樣JavaScript引擎就不用每次都自動建立了。但實際證實這並非一個好辦法,由於瀏覽器已經爲.length這樣的常見狀況作了性能優化,直接使用封裝對象來「提早優化」代碼反而會減低執行效率。
通常狀況下,咱們不須要直接使用封裝對象。最好的辦法是讓JavaScript引擎本身決定何時應該使用封裝對象。
tip: 優先考慮使用基本類型值。函數

封裝對象釋疑
使用封裝對象時有些地方須要特別注意。工具

let a = new Boolean(false);
if(!a) {
    console.log('here'); // 執行不到這裏
}

3.3 拆封
若是想要獲得封裝對象中的基本類型,可使用valueOf()函數:性能

let a = new String('abc');
let b = new Number(42);
let c = new Boolean(true);

a.valueOf(); // "abc"
b.valueOf(); // 42
c.valueOf(); // true

3.4 原生函數做爲構造函數
關於數組(array)、對象(object)、函數(function)和正則表達式,咱們一般喜歡以常量的形式來建立它們。實際上,使用常量和是同構造函數的效果是同樣的(建立的值都是經過封裝對象來包裝)。
如前所述,應該儘可能避免使用構造函數,除非十分必要,由於它們常常會產生意想不到的結果。

3.4.1 Array(..)
Array構造函數只帶一個數字參數的時候,該參數會被做爲數組的預設長度(length),而非只充當數組中的一個元素。
着實非明智之舉:一是容易忘記,二是容易出錯。
更爲關鍵的是,數組並無預設長度這個概念。這樣建立出來的只是一個空數組,只不過它的length屬性被設置成了指定的值。

3.4.2 Object(..)、Function(..)和RegExp(..)
一樣,除非萬不得已,不然儘可能不要使用Object(..)/Function(..)/RegExp(..)。

3.4.3 Date(..)和Error(..)
相較於其餘原生構造函數,Date(..)和Error(..)的用處要大不少,由於沒有對應的常量形式來做爲它們的替代。
建立日期對象必須使用new Date()。Date(..)能夠帶參數,用來指定日期和時間,而不帶參數的話則使用當前的日期和時間。
Date(..)主要用來得到當前的Unix時間戳(從1970年1月1日開始計算,以秒爲單位)。該值能夠經過日期對象中的getTime()來得到。
建立錯誤對象(error object)主要是爲了得到當前運行棧的上下文(大部分JavaScript引擎經過只讀屬性.stack來訪問)。棧上下文信息包括函數調用棧信息和產生錯誤的代碼行號,以便於調式(debug)。
錯誤對象一般與throw一塊兒使用:

function Foo(x) {
    if(!x) {
        throw new Error("x wasn't provided");
    }
    // ..
}

一般所悟對象至少包含一個message屬性,有時也不乏其餘屬性(必須做爲只讀屬性訪問)。

3.4.4 Symbol(..)
ES6中新加入了一個基本數據類型——符號(Symbol)。符號是具備惟一性的特殊值(並不是絕對),用它來命名對象屬性不容易致使重名。

obj[Symbol.iterator] = function() { /*..*/ };

符號並不是對象,而是一種簡單標量基本類型。

3.4.5 原生原型
原生構造函數有本身的.prototype對象,如Array.prototype、String.prototype等。
這些對象包含其對應子類型所特有的行爲特徵。
例如,將字符串值封裝爲字符串對象以後,就能訪問String.prototype中定義的方法。
根據文檔約定,咱們將String.prototype.XYZ簡寫爲String#XYZ,對其餘.prototype也一樣如此。

  • String#indexOf(..)
    在字符串中找到指定子字符串的位置。
  • String#charAt(..)
    得到字符串指定位置上的字符。
  • String#substr(..)、String#substring(..)和String#slice(..)
    得到字符串的指定部分。
  • String#toUpperCase()和String#toLowerCase()
    將字符串轉換爲大寫或小寫
  • String#trim()
    去掉字符串先後的空格,返回新的字符串。

以上方法並不改變原字符串的值,而是返回一個新字符串。
tip: trim能夠用來校驗是否爲空字符串,可是trim只能去掉字符串先後的空格,字符之間夾雜的空格並不餓能去掉。

typeof Function.prototype; // "function"
Function.prototype(); // 空函數!

RegExp.prototype.toString(); // "/(?:)/"——空正則表達式
"abc".match(RegExp.prototype); // [""]

Array.isArray(Array.prototype); // true

Function.prototype是一個函數,RegExp.prototype是一個空的正則表達式,而Array.prototype是一個空數組。這裏,將原型做爲默認值。

tips: 從ES6開始,咱們再也不須要使用vals = vals || ..這樣的方式來設置默認值,由於默認值能夠經過函數聲明中的內置語法來設置。

3.5 小結
JavaScript爲基本數據類型值提供了封裝對象,稱爲原生函數(如String、Number、Boolean等)。它們爲基本數據類型值提供了該子類型所持有的方法和屬性(如:String#trim()和Array#concat())。
對於簡單標量基本類型值,好比"abc",若是要訪問它的length屬性或String.prototype方法,JavaScript引擎會自動對該值進行封裝(即用相應類型的封裝對象來包裝它)來實現對這些屬性和方法的訪問。

第4章 強制類型轉換

4.1 值類型轉換
將值從一種類型轉換爲另外一種類型一般稱爲類型轉換,這是顯式的狀況;隱式的狀況稱爲強制類型轉換。
也能夠這樣來區分:類型轉換髮生在靜態類型語言的編譯階段,而強制類型轉換則發生在動態類型語言的運行時。
然而在JavaScript中一般將它們統稱爲強制類型轉換。
JavaScript中的強制類型轉換老是返回標量基本類型值,如字符串、數字和布爾值,不會返回對象和函數。

4.2 抽象值操做

4.2.1 ToString
toString負責處理非字符串到字符串的強制類型轉換。
tips: 基本類型值的字符串化規則爲:null轉換爲"null",undefined轉換爲"undefined",true轉換爲"true"。數字的字符串化規則遵循通用規則。
若是對象有本身的toString()方法,字符串化時就會調用該方法並使用其返回值。
JSON字符串化
工具函數JSON.stringify(..)在將JSON對象序列化爲字符串時也用到了ToString。
全部安全的JSON值均可以使用JSON.stringify(..)字符串化。安全的JSON值是指可以呈現爲有效JSON格式的值。
undefined、function、symbol和包含循環引用(對象之間相互引用,造成一個無限循環)的對象都不符合JSON結構標準,支持JSON的語言沒法處理它們。
JSON.stringify(..)在對象中遇到undefined、function和symbol時會自動將其忽略,在數組中則會返回null(以保證單元位置不變)。

JSON.stringify(undefined); // undefined
JSON.stringify(function () {}); // undefined
JSON>stringify(
    [1, undefined, function() {}, 4 ]
); // "[1, null, null, 4]"
JSON.stringify(
    { a: 2, b: function() {} }
); // "{"a": 2}"

若是對象中定義了toJSON()防範,JSON字符串化時會首先調用該方法,而後用它的返回值進行序列化。
不少人誤覺得toJSON()返回的是JSON字符串化後的值,其實否則,除非咱們確實想要對字符串進行字符串化(一般不會!)。toJSON()返回的應該是一個適當的值,能夠是任何類型,而後再由JSON.stringify(..)對其進行字符串化。也就是說,toJSON()應該「返回一個可以被字符串化的安全的JSON值」,而不是「返回一個JSON字符串」。
JSON.stringify(..)並非強制類型轉換,涉及ToString強制類型轉換,具體表如今如下兩點。
(1)字符串、數字、布爾值和null的JSON.stringify(..)規則與ToString基本相同。
(2)若是傳遞給JSON.stringify(..)的對象中定義了toJSON()方法,那麼該方法會在字符串化前調用,以便將對象轉換爲安全的JSON值。

4.2.2 ToNumber
ES5定義了抽象操做ToNumber。
tips:其中true轉換爲1,false轉換爲0,undefined轉換爲NaN,null轉換爲0。
ToNumber對字符串的處理基本遵循數字常量的相關規則/語法,處理失敗時返回NaN。
對象(包括數組)會首先被轉換爲相應的基本類型值,若是返回的是非數字的基本類型值,則再遵循以上規則將其強制轉換爲數字。
爲了將值轉換爲相應的基本類型值,抽象操做ToPrimitive會首先檢查該值是否有valueOf()方法;若是有而且返回基本類型值,就使用該值進行強制類型轉換,若是沒有就使用toString()的返回值(若是存在)來進行強制類型轉換。
若是valueOf()和toString()均不返回基本類型值,會產生TypeError錯誤。

let c = [4, 2];
c.toString = function() {
    return this.join(""); // "42"
}
Number(c); // 42
Number(""); // 0
Number([]); // 0
Number(["abc"]); // NaN

4.2.3 ToBoolean
一、假值
JavaScript中的值能夠分爲如下兩類:
(1)能夠被強制類型轉換爲false的值;
(2)其餘(被強制類型轉換爲true的值)。
JavaScript規範具體定義了一小撮能夠被強制類型轉換爲false的值。
tips:如下這些是假值:

  • undefined
  • null
  • false
  • +0、-0和NaN
  • ""

假值的布爾強制類型轉換結果爲false。

二、假值對象

三、真值
真值就是假值列表以外的值。

let a = "false";
let b = "0";
let c = "''";

let aa = [];
let bb = {};
let cc = function() {};

let d = Boolean( a && b && c && aa && bb && cc);
d; // true

以上的值都不在假值列表中,都是真值,不過""除外,由於它是假值列表中惟一的字符串。
也就是說真值列表能夠無限長,沒法一一列舉,因此咱們只能用假值列表做爲參考。

4.3 顯式強制類型轉換

4.3.1 字符串和數字之間的顯式轉換
一、日期顯式轉換爲數字
tips: 建議使用Date.now()來得到當前的時間戳,使用new Date(..).getTime()來得到指定時間的時間戳。

二、奇特的~運算符
按照離散數學來解釋:~返回2的補碼;也就是說~x等同於-(x+1)。

~42; // -(42+1) ==> -43

let a = "Hello World";
if(a.indexOf("lo") >= 0) { // true
    // 找到匹配!
}
if(a.indexOf("lo") != -1) { // true
    // 找到匹配!
}
=0和==-1這樣的寫法不是很好,稱爲「抽象滲漏」,意思是在代碼中莫樓了底層的實現細節,這裏只用-1做爲失敗時的返回值,這些細節應該被屏蔽掉。
if(~a.indexOf("lo")) { // true
    // 找到匹配!
}

4.3.2 顯式解析數字字符串
解析字符串中的數字和將字符串強制類型轉換爲數字的返回結果都是數字。但解析和轉換二者之間仍是有明顯的差異。

let a = "42";
let b = "42px";
Number(a); // 42
parseInt(a); // 42

Number(b); // NaN
parseInt(b); // 42

tips:解析容許字符串中含有非數字字符,解析從左到右的順序,若是遇到非數字字符就中止。而轉換不容許出現非數字字符,不然會失敗並返回NaN。

4.3.3 顯式轉換爲布爾值
布爾值狀況:

  • "" false
  • 0 false
  • null false
  • undefined false

顯式強制類型轉換爲布爾值最經常使用的方法是!!。

4.4 隱式強制類型轉換
隱式強制類型轉換值得是那些隱蔽的強制類型轉換,反作用也不是很明顯。
隱式強制類型轉換的做用是減小冗餘,讓代碼更簡潔。

4.4.1 隱式地簡化
隱式強制類型轉換一樣能夠用來提升代碼可讀性。然而隱式強制類型轉換也會帶來一些負面影響,有時甚至是弊大於利,可是不該該「因噎廢食」。

4.4.2 字符串和數字之間的隱式強制類型轉換
+運算符即能用於數字加法,也能用於字符串拼接。

let a = [1, 2];
let b = [3, 4];
a + b; // "1, 23, 4"

操做數的valueOf()操做沒法獲得簡單基本類型值,因而轉而調用toString()。
tips: a+""能夠轉換爲字符串;a-0能夠轉換爲數字。

4.4.3 布爾值到數字的隱式強制類型轉換
4.4.6 符號的強制類型轉化
ES6容許從符號到字符串的顯式強制類型轉換,然而隱式強制類型轉換會產生錯誤。

let s1 = Symbol("cool");
String(s1); // "Symbol(cool)"

let s2 = Symbol("not cool");
s2 + ""; // TypeError

符號不可以被強制類型轉換爲數字(顯式和隱式都會產生錯誤),但能夠被強制類型轉換爲布爾值(顯式和隱式結果都是true)。

4.5 寬鬆相等和嚴格相等
==容許在相等比較中進行強制類型轉換,而===不容許。

4.5.1 相等比較操做的性能
若是兩個值的類型不一樣,須要考慮有沒有強制類型轉換的必要,有就用==,沒有就用===,不用在意性能。
==和===都會檢查操做數的類型,區別在於操做數類型不一樣時它們的處理方式不一樣。

4.5.2 抽象相等
須要注意的是:

  • NaN不等於NaN
  • +0 等於 -0

對象(包括函數和數組)的寬鬆相等==。兩個對象指向同一個值時即視爲相等,不發生強制類型轉換。
在比較兩個對象的時候,==和===的工做原理是同樣的。

二、其餘類型和布爾類型之間的相等比較
==最容易出錯的一個地方是true和false與其餘類型之間的相等比較。

let a = '42';
let b = true;
a == b; // false

規範:
(1)若是Type(x)是布爾類型,則返回ToNumber(x) == y的結果;
(2)若是Type(y)是布爾類型,則返回x == ToNumber(y)的結果。

三、null和undefined之間的相等比較
規範:
(1)若是x爲null,y爲undefined,則結果爲true;
(2)若是x爲undefined,y爲null,則結果爲true。
在==中null和undefined相等(它們也與其自身相等),除此以外其餘值都不存在這種狀況。

4.5.3 比較少見的狀況

'0' == false;
false == 0;
false == '';
false == [];
'' == 0;
'' == [];
0 == [];

若是兩邊的值中有true或者false,千萬不要使用==;
若是兩邊的值中有[]、""或者0,儘可能不要使用==。

4.6 抽象關係比較
奇奇怪怪的東西???

let a = { b: 42 };
let b = { b: 42 };
a < b; // false
a == b; // false
a > b; // false

a <= b; // true ?!!
a >= b; // true !!!

JavaScript中<=是「不大於」的意思。好比:a <= b就是a>b的反轉。emmmm有意思

相關文章
相關標籤/搜索