原生函數----《你不知道的js》

原生函數能夠被當作構造函數來使用,但經過構造函數建立出來的是封裝了基本類型的封裝對象正則表達式

var a = new String("abc");
typeof a; // "object"
a instanceof String; // true
Obejct.prototype.toString.call(a); // "[object String]"
複製代碼

能夠這樣來查看封裝對象:chrome

console.log(a);
複製代碼

因爲不一樣的瀏覽器在開發控制檯中顯示對象的方式不一樣(對象序列化),因此結果也不一樣。數組

chrome 顯示:String{0: "a", 1: "b", 2: "c", length: 3, [[PrimitiveValue]]: "abc"};老版本:String{0: "a", 1: "b", 2: "c"}。最新版本的firefox: String["a","b","c"];老版本:"abc",而且能夠點擊打開對象查看器。輸出結果隨瀏覽器變換而變化。瀏覽器

注:new String("abc")建立的是字符串"abc"的封裝對象,而非基本類型值"abc"。bash

1、內部屬性[[Class]]

全部typeof返回值爲「object」對象(如數組)都包含一個內部屬性[[Class]]。這個屬性沒法直接訪問,通常經過Object.prototype.toString(..)來查看app

Object.prototype.toString.call([1, 2, 3]); // "[object Array]"
Object.prototype.toString.call(/regex-literal/i); // "[object RegExp]"
複製代碼

上例中,數組的內部[[Class]]屬性值是"Array",正則表達式的值是"RegExp"。多數狀況下,對象的內部[[Class]]屬性和建立該對象的內建原生構造函數相對應,但並不是老是如此。ide

Object.prototype.toString.call(null); // "[object Null]"
Object.prototype.toString.call(undefined); // "[object Undefined]"
複製代碼

雖然Null()和Undefined()這樣的原生構造函數並不存在,但內部[[Class]]屬性值仍然是"Null"和"Undefined"。函數

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

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

上例中基本類型值被各自的封裝對象自動包裝,因此他們內部[[Class]]屬性值分別爲"String"、"Number"、"Boolean"spa

2、封裝對象包裝

因爲基本類型值沒有.length和.toString()這樣的屬性和方法,須要經過封裝對象才能訪問,此時js會自動爲基本類型值包裝(box或wrap)一個封裝對象:

var a = "abc";
a.length; // 3
a.toUpperCase(); // "ABC"
複製代碼
  • 封裝對象釋疑 使用封裝對象時有些地方須要特別注意
var a = new Boolean(false);
if(!a) {
    console.log("Oops"); // 執行不到這裏
}
複製代碼

咱們爲false建立了一個封裝對象,然而該對象是真值,因此這裏使用封裝對象獲得的結果和使用false截然相反。

若是想要自行封裝基本類型值,可使用Object(..)函數(不帶new關鍵字)

var a = "abc";
var b = new String(a);
var c = Object(a);

typeof a; // "string"
typeof b; // "object"
typeof c; // "object"

b instanceof String; // true
c instanceof String; // true

Obejct.prototype.toString.call(b); // "[object String]"
Obejct.prototype.toString.call(c); // "[object String]"
複製代碼

通常不推薦直接使用封裝對象,但他們偶爾會派上用場

3、拆封

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

var a = new String("abc");
var b = new Number(42);
var c = new Boolean(true);

a.valueOf(); // "abc"
b.valueOf(); // 42
c.valueOf(); // true
複製代碼

在須要用到封裝對象的基本類型值的地方會發生隱式拆封

var a = new String("abc");
var b = a+ ""; // b的值爲"abc"
typeof a; // "object"
typeof b; // "string"
複製代碼

4、原生函數做爲構造函數

關於數組、對象、函數和正則表達式,咱們一般喜歡以常量的形式來建立它們。實際上,使用常量和使用構造函數的效果是同樣的(建立的值都是經過封裝對象來包裝)

如前所述,應該儘可能避免使用構造函數,除非十分必要,由於它們常常產生意想不到的結果。

一、Array(..)

var a = new Array(1,2,3);
a; // [1,2,3]
var b = [1,2,3];
b;// [1,2,3]
複製代碼

構造函數Array(..)不要求必須帶new關鍵字。不帶時,會被自動補上。所以Array(1,2,3)和new Array(1,2,3)效果是同樣的

var a = new Array(3);
a.length; // 3
a;
複製代碼

a 在Chrome中顯示爲[undefined x 3],這意味着它有三個值爲undefined的單元,但實際上單元並不存在

var a = new Array(3);
var b = [undefined, undefined, undefined];
var c = [];
c.length = 3;
a; 
b;
c;
複製代碼

咱們能夠建立包含空單元的數組,如c。只要將length屬性設置爲超過實際單元數的值,就能隱式的製造出空單元。另外還能夠經過delete b[1]在數組b中製造出一個空單元

b在當前版本的Chrome中顯示爲[undefined, undefined, undefined],而a和c則顯示爲[undefined x 3],firefox中a,c顯示爲[,,,]。這其中有三個逗號表明四個空單元,而不是三個。

firefox在輸出結果後面多添加了一個,緣由是從ES5規範開始就容許在列表(數組值、屬性列表等)末尾多加一個逗號,目的是爲了讓複製粘貼結果更爲準確

上例中a和b的行爲有時相同,有時又截然不同:

a.join("-"); // "--"
b.join("-"); // "--"

a.map(function(v,i){ return i; }); // [ undefined x 3 ]
b.map(function(v,i){ return i; }); // [ 0, 1, 2 ]
複製代碼

a.mp(..)之因此執行失敗,是由於數組中並存在任何單元,因此map(..)無從遍歷。而join(..)卻不同

function fakeJoin(arr, connector) {
    var str = "";
    for(var i = 0; i < arr.length; i++) {
        if (i > 0) {
            str += connector;
        }
        if (arr[i] !== undefined) {
            str += arr[i];
        }
    }
    return str;
}
var a = new Array(3);
fakeJoin(a, "-"); // "--"
複製代碼

從中能夠看出,join(..)首先假定數組不爲空,而後經過length屬性值來遍歷其中的元素。而map(..)並不作這樣的假設,所以結果也每每在預期以外,並可能致使失敗。

咱們能夠經過下述方式來建立包含undefined單元(而非「空單元」)的數組:

var a = Array.apply(null, { length: 3 });
a; // [undefined,undefined,undefined]
複製代碼

Array.apply(..)調用Array(..)函數,並將{length:3}做爲函數的參數。

咱們能夠設想apply(..)內部該數組參數名爲arr,for循環遍歷數組:arr[0]、arr[1]、arr[2]。因爲{ length: 3 }中並不存在這些屬性,因此返回值爲undefined。

咱們執行的其實是Array(undefined,undefined,undefined),因此結果是單元值爲undefined的數組,而非空單元數組。

雖然Array.apply(null, { length: 3 })在建立undefined值的數組時有些奇怪和繁瑣,但其結果遠比Array(3)更準確可靠。

二、Object(..)、Function(..)和RegExp(..)

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

var c = new Object();
c.foo = "bar";
c; // { foo: "bar"}
var d = { foo: "bar" }
d; // { foo: "bar" }

var e = new Function("a", "return a * 2;");
var f = function(a) { return a * 2; }
function g(a) { return a * 2; }
var h = new RegExp("^a*b+", "g"); // 動態定義正則表達式時頗有用
var i = /^a*b+/g;
複製代碼

三、Date(..)和Error(..)

建立日期對象必須使用new Date()。Date(..)能夠帶參數,用來指定日期和時間,而不帶參數的話則可使用當前的日期和時間。

Date(..)主要用來得到當前Unix時間戳,該值能夠經過日期對象中的getTime()來得到。

從ES5開始引入Date.now(),ES5以前使用:

if (!Date.now) {
    Date.now = function() {
        return (new Date()).getTime();
    }
}
複製代碼

構造函數Error(..)可不帶new關鍵字

建立錯誤對象主要是爲了得到當前運行棧的上下文(大部分js引擎經過只讀屬性.stack來訪問)。棧上下文信息包括函數調用棧信息和產生錯誤的代碼行號,以便於調試。

錯誤對象一般與throw一塊兒使用:

function foo(x) {
    if(!x) {
        throw new Error("x wasn't provided");
    }
}
複製代碼

四、Symbol(..)

符號是具備惟一性的特殊值(並不是絕對),用它來命名對象屬性不一樣致使重名。

符號能夠用做屬性名,但不管是在代碼仍是開發控制檯中都沒法查看和訪問它的值,只會顯示爲諸如Symbol(Symbol.create)這樣的值。

ES6有一些預約義符號,以Symbol的靜態屬性形式出現,如Symbol.create、Symbol.iterator

obj[Symbol.iterator] = function() { /*..*/}
複製代碼

咱們可使用Symbol(..)原生構造函數來定義符號。但它比較特殊,不能帶new關鍵字,不然會出錯:

var mysym = Symbol("my own symbol");
mysym; // Symbol(my own symbol)
mysym.toString(); // "Symbol(my own symbol)"
typeof mysym; // "symbol"

var a = {};
a[mysym] = "foobar";

Object.getOwnPropertySymbols(a); // [ Symbol(my own symbol) ]
複製代碼

雖然符號實際上並不是富有屬性(經過Object.getOwnPropertySymbols(..)即可以公開得到對象中全部符號),但它主要用於私有或特殊屬性。

五、原生原型

  • String#indexOf(..)

    在字符串中找到指定子字符串的位置

  • String#charAt(..)

    得到字符串指定位置上的字符

  • String#substr(..)、String#substring(..)、String#slice(..)

    得到字符串的指定部分

  • String#toUpperCase()和String#toLowerCase()

    將字符串轉爲大寫或小寫

  • String#trim()

    去掉字符串先後的空格,返回新的字符串。

以上方法並不改變原字符串的值,而是返回一個新字符串

其餘構造函數的原型包含他們各自類型所持有的行爲特徵,如Number#toFixed(..)(將數字轉換爲指定長度的整數字符串)和Array#concat(..)(合併數組)。全部的函數均可以調用Function.prototype中的apply(..)、call(..)、bind(..)

然而有些原生原型並不是普通對象那麼簡單:

typeof Function.prototype; // "function"
Function.prototype(); // 空函數
RegExp.prototype.toString(); // "/(?:)/" ---空正則表達式
"abc".match(RexgExp.prototype); // [""]
複製代碼

咱們能夠修改他們(不只僅是添加屬性):

Array.isArray(Array.prototype); // true
Array.prototype.push(1,2,3); // 3
Array.prototype; // [1,2,3]
// 須要將Array.prototype設置回空,不然會致使問題
Array.prototype.length = 0;
複製代碼

將原型做爲默認值

Function.prototype是一個空函數,RegExp.prototype是一個「空」的正則表達式,而Array.prototype是一個空數組。對未賦值的變量來講,他們是很好的默認值。

function isThisCool(vals, fn, rx) {
    vals = vals || Array.prototype;
    fn = fn || Function.prototype;
    rx = rx || RegExp.prototype;
    return rx.test(
        vals.map(fn).join("")
    )
}
isThisCool(); // true
isThisCool(
    ["a","b","c"],
    function(v){ return v.toUpperCase(); }
); // false
複製代碼

這種方法的一個好處是.prototype已被建立而且僅建立一次。相反,若是將[]、function(){}和/(?:)/做爲默認值,則每次調用isThisCool(..)時他們會被建立一次(具體建立與否取決於js引擎),這樣無疑會形成內存和CPU資源的浪費

注意:若是默認值隨後會被更改,就不要使用Array.prototype。

相關文章
相關標籤/搜索