iT邦幫忙

第 12 屆 iT 邦幫忙鐵人賽

DAY 7
0
Modern Web

【這些年我似是非懂的 Javascript】系列 第 7

【這些年我似是非懂的 Javascript】Day 7 - Natives 原生功能

常用的原生功能我相信各位讀者多少都看過
以下列舉幾個常見常用的。

  • String()
  • Number()
  • Boolean()
  • Array()
  • Object()
  • Function(
  • RegExp()
  • Date()
  • Error()
  • Symbol() // ES6 新增的~

與你想像中的建構器不太一樣

我們時常在其他語言中會看到類似這樣的作法

const str = new String("Robin is handsome")

console.log(str.toString()); // "Robin is handsome"

看起來也非常合理,但是實際上在JS中可能有一些行為會與你想像的不同。

const str = new String("test yoyo")
typeof str // ?

各位猜看看這的型態是什麼?
答案是...

String

...嗎? 看起來非常合理!

但是實際上卻是...

Object

主要原因是他建立了一個字串包裹器的物件而不是單純建立 "test yoyo" 這個字串的基值。

Internal [[Class]]

你知道嗎...
typeof 是 "object" 的那些值內部額外會有一個特性,叫做 [[Class]]
我知道你內心想的是什麼!
但是不是你想的那種 Class
你把它想成是一種內部的分類,
這個特性很特別他無法被直接使用,
但是你可以使用 Object.prototype.toString(..) 方法來呼叫他。

Object.prototype.toString.call([6,6,6]); // "[object Array]"

Object.prototype.toString.call(/regex-literal/i); // "[object RegExp]"

undefinednull 也是

Object.prototype.toString.call(undefined); // "[object Undefined]"

Object.prototype.toString.call(null); // "[object Null]"

stringnumberboolean 並不是這樣,他們會由封裝用的包裹器涉入。
接著我們就來講封裝用的包裹器

封裝用的包裹器

還記得之前說的各種型態的小精靈嗎?
如果基型值沒有特性和方法想要存取類似 .length 或是 .toString() 需要包裹基型值的物件包裹器,那我該怎麼做?
答案是...
什麼都不用做!
JS 會自動封裝基本型別的值,
該注意的是你不應該藉由直接使用物件形式試著預先最佳化

簡單來說就是你什麼都不該做,因為這樣做只會讓你的程式碼執行得更慢。
舉例來說,
不要做 new String("foo") 或是 new Number(18) 之類的動作,
直接優先使用 "foo" 或是 18 字面形式的基型值就可以了。

如果我就是要做怎麼辦?
也不是不行但是可能會遇到一些雷,
例如以下範例

const a = new Boolean(false);

if(!fasle) console.log("TEST); // 不會執行。

... 為什麼?
因為你建立的是一個"物件包裹器",物件本身是 object,他又是 truthy,所以產生的行為就會跟你原本建立的 false,產生完全不一樣的結果。

解封裝

封印解除!

如果有一個物件包裹器想要拿裡面的值該怎麼做?
可以直接使用 valueOf() 這個方法。

const a = new String("test");
const b = new String(123);
const c = new String(true);

a.valueOf(); // "test"
b.valueOf(); // 123
c.valueOf(); // true

解封裝也有隱含發生的可能,例如以下範例

const a = new String("test");
const b = a + "test" // 直接拿取 a 解封後的基型值

typeof a // "object"
typeof b // "string"

作為建構器的 Natives

objectarrayfunction正規表達式 這些值比較常看到被使用的方式就是直接用字面的方式如下範例

const a = [];
const b = {};
const c = function(){..};
const d = /^a.b*c/g;

接下來要分享如果使用建構器的方式可能會遇到的問題。

Array

Array 建構器前面不用使用 new ,因為有加和沒加的結果是相同的。

const a = new Array(1,2,3);
const b = new Array(1,2,3);
const c = [1,2,3]

console.log(a); // [1,2,3]
console.log(b); // [1,2,3]
console.log(c); // [1,2,3]

當只有一個 number 參數帶入 Array 的建構器時,他不會把它當作陣列的內容,他會把他當一個長度來"預先設定陣列大小"。
可以觀察一下底下範例的差異。

const a = new Array(5);
const b = [ undefined, undefined, undefined, undefined, undefined];
const c = [];
c.length = 5;

console.log(a); // [ <5 empty items> ]
console.log(b); // [ undefined, undefined, undefined, undefined, undefined ]
console.log(c); // [ <5 empty items> ]

但是還記得我昨天在 這篇所論述的嗎?

JS 沒有預設大小這種事存在

也就等於是你有可能會插了空插槽,有可能會造成你創造出稀疏陣列,就是很莫名其妙的資料結構 xD

上面的範例是在 Chrome 所產生的,
然而在 Firefox 所顯示的卻不太一樣。

const a = new Array(5);

console.log(a); // [ <5 empty slots> ]

書上寫道原本更慘xD
原本在 FireFox所顯示的會長這樣

const a = new Array(5);

console.log(a); // [ , , , , , ]

在最後放一個額外的 , 是因為在 ES5 中的串列的尾隨逗號是被允許的所以最後一個會被忽略。
雖然從上面的 [ , , , , , ] 改為 [ <5 empty slots> ] 是一個改進沒錯...

But 事情永遠都不是那麼如人所願...
剛剛看 ab 的長的雖然不一樣但是顯示的行為相同,
但是他某些情況行為相同,但有時候又不同。

const a = new Array(5);
const b = [ undefined, undefined, undefined, undefined, undefined];

a.join('-');
b.join('-');

a.map(function(v,i){return i} ); // [ <5 empty items> ]
b.map(function(v,i){return i} ); // [ 0, 1, 2, 3, 4 ]

a.map 實際會失敗的原因簡單來說是那些插槽其實不是真的存在,就是他真的是"空插槽",不是真的插入 undefined

如果真的想要建置帶有實質的 undefined 值的陣列除了像我剛剛上面手動打之外你還可以使用 apply

const a = Array.apply(null, {length:5});

Anyway...
不要這樣做xDDD

Object 、 Function 、 RegExp

這三種也盡量不要使用建構器,請也直接使用字面的形式。

我記得如果你在你的專案有使用 ESLint 他也會跟你說母湯安餒。

正規表示法直接選用的理由除了語法簡單外,還有因為效能問題,JS 引擎會在程式碼執行前預先編譯和快取。

但是如果說正規表達式有動態的需求就可以譬如以下範例

const name = 'robin';

const namePattern = new RegExp("\\b(?:" + name + ")+\\b", "ig");

const matches = text.match(namePattern);

Date 和 Error

Date

這兩個的原生建構器比較常見,因為他們都沒有字面形式可以用 QQ
要建立日期物件值時可以用new Date(),如果不使用new 拿到的值則會是字串形式的日期~

Error

Error 這個建構器不管有沒有 new 行為都相同,通常會藉由 throw 運算子來使用這樣的錯誤物件如範例

function foo(){
    if(!x){
        throw new Error("x wasn't provided")
    }
}

Symbol

他跟 DateError 一樣沒有字面的形式,然後你不能使用 new ,因為他會直接噴錯xD

原生的原型 Native Prototypes

原生的建構器都自己的 .prototype 物件,以 String 來說

String#indexOf(..)
String#chartAt(..)
String#substr(..)
String#toUpperCase(..)
String#trim(..)

如果是一般使用直接使用的話會藉由原型委派的機制讓他可以使用這些方法。


以上是我今天分享的內容
感覺論述的越來越抽象xDD
但是某些讀著讀著就是各種
哦哦哦哦哦哦!!!

感謝你的收看
我們明天見


參考來源:

你所不知道的 JS|導讀,型別與文法 (You Don't Know JS: Up & Going)


上一篇
【這些年我似是非懂的 Javascript】Day 6 - 值
下一篇
【這些年我似是非懂的 Javascript】Day 8 - 魔幻邪惡的強制轉型 #第一章 # 心情轉折
系列文
【這些年我似是非懂的 Javascript】34

尚未有邦友留言

立即登入留言