原生函数
常用的原生函数:
- String()
- Number()
- Boolean()
- Array()
- Object()
- Function()
- RegExp()
- Date()
- Error()
- Symbol()
使用原生函数当作构造函数:
var str = new String('hello');
typeof str; //"object"
str instanceof String; //true
Object.prototype.toString.call(str); //"[object String]"
复制代码
通过构造函数(如new String(..)
)创建出来的是封装了基本类型值(如hello
)的封装对象。
请注意:
typeof
在这里返回的是对象类型的子类型。
console.log(str); //String {"hello world"}
复制代码
通过原生函数创建的是封装对象,而非基本类型。
内部属性 [[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]"
Object.prototype.toString.call("hello"); //"[object String]"
Object.prototype.toString.call(2019); //"[object Number]"
Object.prototype.toString.call(true); //"[object Boolean]"
复制代码
虽然Null()
和Undefined()
这样的原生构造函数并不存在,但是内部[[Class]]
属性值仍然是"Null"
和"Undefined"
。
其他基本类型值(如字符串、数字和布尔)通常被各种的封装对象自动包装,所以它们的内部[[Class]]
属性值分别为"String"
、"Number"
和"Boolean"
。
封装对象包装
封装对象有着十分重要的作用。由于基本类型值没有.length
,valueOf()
和.toString()
这样的属性和方法,需要通过封装对象才能访问,但是JavaScript
会自动帮我们为基本类型值进行包装。
var foo = "hello";
foo.length; //5
foo.toUpperCase(); //"HELLO"
复制代码
使用封装对象时有些地方需要特别注意。
var foo = new Boolean(false);
if(!foo){
console.log('ok');
}
复制代码
上面代码打印不会被输出,因为其实我们是给false
创建一个封装对象,这个对象是一个真值。
如果想要自行封装基本类型值,可以使用Object(..)
函数:
var foo = "ok";
var bar = new String(foo);
var baz = Object(foo);
typeof foo; // "string"
typeof bar; // "object"
typeof baz; // "object"
bar instanceof String; //true
baz instanceof String; //true
Object.prototype.toString.call(bar); // "[object String]"
Object.prototype.toString.call(baz); // "[object String]"
复制代码
拆封
如果想要得到封装对象中的基本类型值,可以使用valueOf()
函数:
var number = new Number(2019);
var str = new String('ok');
var bool = new Boolean(true);
number.valueOf(); //2019
str.valueOf(); //"ok"
bool.valueOf(); //true
复制代码
还有一个需要注意的就是隐式拆封:
var str = new String('ok');
var foo = str + '';
typeof str; //"object"
typeof foo; //"string"
复制代码
原生函数作为构造函数
Array(..)
Array
构造函数只带一个数字参数的时候,该参数会被作为数组的预设长度(length),而 非只充当数组中的一个元素。使用Array
构造函数时也可以省略new
操作符。
var foo = new Array("t","o","m");
foo; //["t", "o", "m"]
var baz = Array("t","o","m");
baz; //["t", "o", "m"]
var bar = new Array(10);
bar; //[empty × 10]
复制代码
这样的结果也会带来一些问题:
var a = new Array(3);
var b = [ undefined, undefined, undefined ];
var c = [];
c.length = 3;
var d = [ , , , ];
console.log(a); //[empty × 3]
console.log(b); //[undefined, undefined, undefined]
console.log(c); //[empty × 3]
console.log(d); //[empty × 3]
复制代码
b
在当前版本的 Chrome
中显示为 [ undefined, undefined, undefined ]
,而 a
,c
,d
则显示 为 [ empty x 3 ]
。d
实际得到的是[ , , ]
包含三个空单元的数组。
更糟糕的是,上例中 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.map(..)
之所以执行失败,是因为数组中并不存在任何单元,所以 map(..)
无从遍历。而 join(..)
却不一样,join(..)
首先假定数组不为空,然后通过 length
属性值来遍历其中的元 素。而 map(..)
并不做这样的假定,因此结果也往往在预期之外,并可能导致失败。
我们可以通过下述方式来创建包含 undefined
单元(而非“空单元”)的数组:
var foo = Array.apply(null, { length: 3 });
foo; //[undefined, undefined, undefined]
复制代码
Object(..)
创建一个Object
可以通过new Object()
或者字面量的方式。
var obj = new Object();
obj.name = "tom";
console.log(obj); //{name: "tom"}
var foo = {
name:"tom"
}
console.log(foo); //{name: "tom"}
复制代码
在实际情况中没有必要使用new Object()
来创建对象,因为这样就无法像常量形式那样一 次设定多个属性,而必须逐一设定。
Function(..)
每个函数都是Function
类型的实例,而且都与其他引用类型一样具有属性和方法。由于函数是对象,因此函数名实际上也是一个指向函数对象的指针,不会与某个函数绑定。
var foo = new Function("num1", "num2", "return num1 + num2")
复制代码
从技术角度,这是一个函数表达式,我们不推荐这种方法定义函数,因为这种语法会导致解析两次代码(第一个是解析常规代码,第二次是解析传入构造函数中的字符串),从而影响性能。
RegExp(..)
JavaScript
通过RegExp(..)
构造函数来支持正则表达式。
var foo = new RegExp( "[0-9A-Z]", "g" );
var bar = /[0-9A-Z]/g;
复制代码
强烈建议使用常量形式(如/[0-9A-Z]/g
)来定义正则表达式,这样不仅语法简单,执行效率 也更高,因为 JavaScript
引擎在代码执行前会对它们进行预编译和缓存。
RegExp(..)
有时还是很有用的,比如动态定义正则表达式时:
var name = "Kyle";
var namePattern = new RegExp("(" + name + ")", "ig" );
var a = "Kyle".match(namePattern);
var b = "tom".match(namePattern);
console.log(a); //["Kyle"]
console.log(b); //null
复制代码
Date(..)
创建日期对象必须使用new Date()
,这里只简单说明一下。
var date = new Date();
复制代码
Error(..)
构造函数 Error(..)
(与前面的 Array()
类似)带不带 new
关键字都可。
创建错误对象主要是为了获得当前运行栈的上下文(大部分 JavaScript
引擎 通过只读属性 .stack 来访问)。栈上下文信息包括函数调用栈信息和产生错误的代码行号, 以便于调试。
function bar (args) {
if(!args){
throw new Error("args in empty");
}
}
复制代码
小结
本小节只是简明扼要的说明了一下JavaScript
中的原生函数,形成基本的概念,后续会进行深入说明。