第三章 原生函数
内建函数又叫原生函数,如String、Number、Boolean、Array、Object、Function
var a = new String('abc);
typeof a; // object 不是string
a instanceof String;//true
Object.prototype.toString.call(a);//[Object String]
使用new String(“a”)创建出来的是字符串“a”的封装对象,而非基本类型值
1.内部属性[[Class]]
所有typeof返回值为"object"的对象都包含一个内部属性[[Class]](可以把它看做一个内部的分类,而非传统意义上的类)。这个属性无法直接访问,一般通过Object.prototype.toString(..)查看
例如:
Object.prototype.toString.call([1,2,3]);// "[Object Array]"
Object.prototype.toString.call(/regex-literal/i);//"[Object RegExp]"
基本类型
虽然诸如Null(0和Undefined()这样的原生构造函数函数并不存在,但是他们内部的[[class]]属性依然是“Null”和“Undefined”
其他基本类型(如字符串、数字和布尔值)情况有所不同,通常称为“包装”,基本类型值被各自的封装对象自动包装,他们内部[[class]]属性分别为“String”、“Number”、“Boolean”
Object.prototype.toString.call(11);// "[Object Number]"
Object.prototype.toString.call("21");// "[Object String]"
Object.prototype.toString.call(true);// "[Object Boolean]"
总而言之:基本类型的[[class]]属性值为其自身,复杂数据类型的[[class]]属性值与创建该对象的内建原生构造函数相对应
2. 封装对象包装
封装对象扮演者非常重要的角色。由于基本类型值没有.length和.toString()这样的操作,需要通过封装对象才能访问,此时js会自动为基本类型包装一个封装对象:
var a = "abc";
a.length;// 3
a.toUpperCase();//"ABC"
一般情况下,我们不需要直接使用封装对象。最好的办法是让JS引擎自己决定什么时候应该封装对象。
3.拆封
如果想得到封装对象中的基本类型,可以使用valueOf()函数:
var a = new String("a");
var b = new Number(22);
var c = new Boolean(true);
a.valueOf();//a
b.valueOf();//22
c.valueOf();//true
在需要用到封装对象的基本类型值的地方会发生隐式拆封。
4.原生函数作为构造函数
关于数组、对象、函数和正则表达式,我们通常喜欢以常量的形式创建,实际上使用常量和使用构造函数的效果一样。应该尽量避免使用构造函数,否则会产生意想不到的结果
(1) Array
构造函数Array不要求必须带new关键字,不带时会被自动补上。
Array构造函数只带一个关键字时,该参数会被作为数组的预设长度,而非只充当数组中一个元素。数组并没有预设长度这个概念,这样创建出来的只是一个空数组,只不过他的length被设置成了指定的值。我们将包含至少一个“空单元”的数组叫做稀疏数组。
var a = new Array(3);
var b = [undefined,undefined,undefined];
var c = [];
c.length = 3
a;//[empty x 3]
b;//[undefined,undefined,undefined]
c;//[empty x 3]
a.join('-');//"--"
b.join('-');//"--"
a.map(function(v,i){return i});// [empty × 3]
b.map(function(v,i){return i});// [0, 1, 2]
上例中a,b的行为有时相同,有时不同。
a.map之所以失败因为数组中不存在任何单元,所以map无从遍历,join不一样.join的实现先假定数组不为空,然后通过length遍历其中元素,map不做这样的假定因此结果超出预期
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;
console.log(str)
}
var a = [1,2,3];
fakeJoin(a,"-");
我们可以使用下述方式创建包含undefined单元的数组:
var a = Array.apply(null,{length:3});
a;// [undefined,undefined,undefined]
永远不要创建和使用空单元数组。
(2)Object(..)、Function(..)、RegExp(..)
除非万不得已,不要使用Object(..)、Function(..)、RegExp(..)创建对象
RegExp(..)有时还是很有用的,比如动态定义正则表达式。
(3)Date(..)和Error(..)
相比于其他原生构造函数,这两个的用处大得多,因为没有对应的常量形式作为他们的代替。
创建日期对象那个必须使用new Date(),可以带参数,用来指定日期和时间,不带参数使用当前日期和时间。
Date()主要用来获得当前时间戳,该值可以通过日期对象中的getTime()获得。ES5引入一个更简单的方法,Date.now()
(new Date.now()).getTime()与Date.now()效果相同
构造函数Error带不带new关键字都行。创建错误对象注意主要是为了获得运行栈的上下文,方便调试。
(4)Symbol
ES6的基本数据类型——符号(Symbol),具有唯一性的特殊值,用来命名对象不容易导致重名。
可以用Symbol()原生构造函数自定义符号。但是不能带new关键字
var sym = Symbol("my symbol");
sym;// Symbol(my symbol)
typrof(sym);//"symbol"
(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()
去掉字符串前后的空格,返回新的字符串。
以上方法并不改变原字符串的值,而是返回一个新的字符串。
其他构造函数的原型包含他们各自类型所特有的行为特征,比如Number#tofixed(..)(将数字转换为指定长度的整数字符串)和Array#concat(..)(合并数组)。所有的函数都可以调用Function.prototype中的apply(..)、call(..)、bind(..)。
typeof Function.prototype;//“function”
Function.prototype(); //空函数
我们甚至可以修改这些函数,不仅仅是增加属性。
Function.prototype是一个函数,RegExp.prototype是一个正则表达式,Array.prototype是一个数组。
js为基本数据类型提供了封装对象,成为原生函数(String、Number、Boolean等)。他们为基本数据类型值提供了该子类型所特有的方法和属性。
对于简单标量基本类型值,如“abc”,如果要访问length属性或String.prototype属性,js引擎会自动对该值进行封装(用相应类型的封装对象来包装它)来实现对这些属性和方法的访问。
网友评论