原生函数----《你不知道的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。