类型和原生函数及类型转换

Posted zheoneandonly

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了类型和原生函数及类型转换相关的知识,希望对你有一定的参考价值。

一、内置类型:

空值:null

未定义:undefined

布尔值:boolean

数字:number

字符串:string

对象:object

符号:symbol(ES6新增)

1.null类型的值类型是object,因为javascript语言这一历史遗留问题,如果要查看null的类型不能直接使用typeof,需要设置复合条件来查询:

var a = null;
(!a && typeof a === "object");//true

2.function作为object的子类型,用typeof查看其类型时显示的是function。

function a (){
    //888
}
console.log(typeof a);//function

function除了类型这一特性比较特殊,还有length属性表示的是函数声明的参数的个数。

function b(a,b){
    /*..*/
}
console.log(b.length);//2

3.数组Array类型也是object的一个子类型,但是在使用typeof查看类型时却是object,这点与function有些差别,需要注意。

typeof [1,2,3] === "object";//true

4.JavaScript的变量是没有类型,其类型是由值来决定的,变量可以随时持有任何类型的值。

var a = 123;
typeof a;//number
a = true;
typeof a;//boolean
//顺便在这里说明一下typeof运算符返回的值始终是一个字符串
typeof typeof 88;//"string"

5.undefined和undeClared

可能很多人都认为undefined和undeClared是同一种含义,但是在JavaScript中他们完全是两回事。undefined表示声明未赋值,undeClared表示没有声明。但是使用typeof检测类型时都是返回undefined,这就是非常的尴尬了。

console.log(typeof a);    //undefined -- typeof只执行类型检测不会隐式添加全局变量
if(a){                    //这里会报错
    console.log("aa");
}

其实typeof对undeClared(未声明的变量)检测返回undefined是一种安全机制,我们都知道JavaScript语言最开始的设计目的就是为了尽可能的保证程序可执行,因为多脚本异步加载且共享全局命名空间,如果我们需要检测一个全局变量是否声明,或者兼容API:

if(b){    //报错
    //..
}
if(typeof b!== "undefined"){
    //..这里才会真正保证不出错
}
if(typeof abc === "undefined"){
    abc = function(){...}//兼容API
}
//在全局上检测变量是否声明还有一个办法
if(window.b){
    //...
}
if(!window.b){
    //...
}

关于undefined还有一些其他特性,与这期博客的内容没有什么相关性,在我的另一篇博客有详细介绍:JavaScript中调皮的undefined

 二、数组与字符串的值

字符串在很多时候被误解为字符组成的数组,因为都有length属性和ES5支持concat(...)拼接方法,还有些时候在一些内置方法里呈现出相识的特性,这绝对不能说,字符串是字符数组。

var a = "foo";
var b = ["f","o","o"];
console.log(a.length);//3
console.log(b.length);//3
console.log(a.indexOf("o"));//1
console.log(b.indexOf("o"));//1
var c = a.concat("bar");        //foobar
var d = b.concat(["b","a","r"]);//["f","o","o","b","a","r"]
console.log(a === c);//false
console.log(b === d);//false
console.log(a);//"foo"
console.log(b);//["f","o","o"]
console.log(a[1]);//"o"
console.log(b[1]);//"o"

上面这些代码好像可以说字符串和数组没有区别,但是在这里下结论还为时过早,值得留意的是IE中a[1]这种写法是不符合语法的。正确是做法是a.charAt(1)。最大的区别就是字符串的成员函数不会改变其原始值,而数组的成员函数都是在其原始值上操作。

var a = "foo";
var b = ["f","o","o"];
var c = a.toUpperCase();
console.log(a === c);//false
b.push("!");
console.log(b);//["f","o","o","!"]

因为字符串与数组存在很多相识的特性,所以很多字符串操作借用数组的成员方法来实现就会方便很多。

var a = "foo";
var c = Array.prototype.join.call(a,"-");
var d = Array.prototype.map.call(a,function(v){
    return v.toUpperCase() + ".";
});
console.log(c);//"f-o-o"
console.log(d);//["F.", "O.", "O."]
var e = Array.prototype.map.call(a,function(v){
    return v.toUpperCase() + ".";
}).join("");
console.log(e);//"F.O.O."

关于字符串借用数组的成员方法实现操作有一个很经典的面试题,就是实现字符串的反转操作,我们知道在字符串的成员方法中没有像数组一样的reverse()。但是我们可以通过字符串与数组相似的特性,借用数组的成员方法来实现。但绝对不是Array.prototype.reverse.call(...)这么简单的操作。

var a = "foo";
console.log(Array.prototype.reverse.call(a));//报错
var c = a.split("").reverse().join("");
console.log(c);//"oof"

关于字符串与数组的值在一定程度上存在很多类似特性,并不是值本身具备的特性,这里涉及到了字符串操作时的隐式类型转换操作,在这里暂时不做介绍,后面会有详细的博客来介绍关于类型转换的知识点。而关于数组的值本身还有一个非常令人费解的问题,这个问题就是“稀疏”数组,这个问题有时候会让你抓狂。而这一部分需要在原生函数的基础上来解析,所以我们先来了解原生函数。

三、原生函数

 在前面介绍变量类型(严格来说是值类型)的时候,我没有做分类这些基本的解析,不是忘记,而是需要更多的知识点来佐证一些内容,涉及JavaScript的值类型和内置的typeof的值类型判断,还有toString()的机制,甚至一些操作带来的隐式类型转换不只是在变量类型的基础上能说的清楚的。这里面有很大一部分的内容就需要原生函数来回答。

1.值类型分类:

  原始值:number、boolean、string、undefined、null

  引用值:array、object、function、tate、RegExp

2.一个被忽略的问题,原始值类型有属性和方法?

在JavaScript中一直有一个误导的说法,就是万物皆对象。但是我要告诉你,原始值没有属性和方法,JavaScript的元素值本身也不是对象,而原始值在有时候能呈现对象的特性是因为在操作的背后隐式的存在类型转换,这个隐式的转换就是显式的原生函数构造(封装)基本类型值的封装对象。所以不要误认为JS中的构造函数和JAVA中的构造函数是一样的,在JS中构造函数封装获得的是一个对象,并不是JS中的原始值类型。

var a = "abc";
var b = new String("abc");
console.log(a === b);//false
console.log(a == b);//true
console.log(typeof a);//string
console.log(typeof b);//object

3.聊了这么多原生函数,到底什么是原生函数?原生函数怎么使用?原生函数有什么用呢?

通过原生函数(如new String("abc"))创建的是一个封装了基本类型值的封装对象,那JavaScript中都有哪些原生函数呢?String()、Number()、Boolean()、Array()、Object()、Function()、RegExp()、Date()、Error()、Symbol()。

通过原生函数构造基本类型值的封装对象:

//值类型
var a = "abc";
//通常的基础类型对象封装方法(String类型对象)
var b = new String("abc");
//通过基础类型值变量直接封装
var c = Object(a);
console.log(c);//String {0: "a", 1: "b", 2: "c", length: 3, [[PrimitiveValue]]: "abc"}
console.log(b);//String {0: "a", 1: "b", 2: "c", length: 3, [[PrimitiveValue]]: "abc"}

封装基础类型对象的语法很简单,这里就不累述了,细心的你应该发现原生函数和值类型并不一一对应,undefined和null没有对应的原生函数。另一问题的答案也浮出了水面,那就是原始值类型没有属性和方法,那原始值具备的对象特性从何而来,有一部分已经可以从上面的示例代码中找到答案了,我们可以看到String对象有length属性,前面提到的字符串类似数组的一些特性,都可以在上面的代码中得到答案,就是这些操作的背后都存在着隐式的基础类型封装操作。当我们用字符串调用length属性时,浏览器引擎帮我们隐式的封装了一个String对象,这个length属性就是由这个隐式封装的对象提供的。(关于类型转换和隐式类型转换会有更详细的博客文章解析,这篇博客不过多介绍。)

4.引用值类型的原生函数及其构造的对象

 因为引用值类型本身就是object类型或者object的子类型,通过原生函数构造的对象与常量形式创建的对象没有区别,但是反而通过原生函数构建的对象在有时候会产生意想不到的bug。(珍惜生命,远离构造函数。)

a.数组的元素函数Array(...):

var a = new Array(1,2,3);
console.log(a);//[1,2,3]
var b = [1,2,3];
console.log(b);//[1,2,3]

根据示例来看好像没啥区别,但是当我们只给原生函数添加一个参数时,这个数字参数定义的却是数组的长度。这和我们前面提到的稀松数组出现的问题是一样的,浏览器对这种情况表现的就大相庭径,以下方面的示例来说:

var a = new Array(3);
console.log(a.length);//3

然而我们通过各个浏览器的控制台打印数组时,给我们的结果却令人费解。(因为我使用的都是最新版的浏览器,所以结果和你的可能会有些出入。)在老版本的Chrome中打赢的是[undefined,undefined,undefined],老版本的Firefox打印的是[,,,]。

IE打印的是:[]

Chrome打印的是:(3) [empty × 3]

Firefox打印的是:[ <3 empty slots> ]

var a = new Array(3);
console.log(a[1]);//undefine

浏览器的表现绝不是空悬来风,看下面的代码你会更加头痛:

var a = new Array(3);
var b = [undefined,undefined,undefined];
console.log(a[1]);//undefined
console.log(b[1]);//undefined
console.log(a.map(Math.sqrt));//(3) [empty × 3]
console.log(b.map(Math.sqrt));//(3) [NaN, NaN, NaN]

//模仿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; } console.log(fakeJoin(a,"-"));//--

由上面的示例代码可以得出,稀疏数组所表现的视觉上的undefined并不是真正数组元素就是undefined,我们可以把它理解为一种数组空元素的类型转换,其并具备undefined的操作特性,而是保持数组空元素的纯粹空的特性,不具备被访问操作的能力。而join方法的操作我已经在示例中给出仿写方法的代码,其内部并不具操作元素值的行为,所以表现上有所差别,这个示例非常合适说明了数组纯空元素的特性,一种是访问值获得的是undefined,另一种是在纯空元素的值上操作是不成立的。

b.时间对象Date(...)和错误提示对象Error(...)

 因为Date(...)和Error(...)没有对应的常量,相对其他原生函数来说就正常多了。这里就简单的展示一下语法:

//三种实例化时间对象的语法
var date = new Date();
var date1 = Date();
var date2 = Date(date);
console.log(date == date1);//true
//两种获得距离1970年1月1号之间的毫秒数
console.log(Date.now());//ES5
console.log(date.getTime());

兼容ES5以前版本浏览器的Date.now():

if(!Date.now){
    Date.now = function(){
        return (new Date()).getTime();
    }
}

关于Error对象就不多做解释了,我直接复制了手册的一套代码:以下实例中 try 语句块包含了未定义的函数 "adddlert" ,执行它会产生错误,catch 语句块会输出该错误的信息:

try {
    adddlert("Welcome");
}
catch(err) {
    document.getElementById("demo").innerhtml = 
    err.name + "<br>" + err.message;
}
//如果你要测试这套代码,一定记得先定义一个Id为demo的元素

关于Symbol(...)原生函数就放到ES6部分详细介绍,Object和function原生函数也不在这里介绍了,会有具体针对这两个对象解析的博客。

这篇博客主要是为了做类型转换详细剖析而铺垫,内容不难,但涉及了很多语法特性的细节,还是慎重对待吧。

 



以上是关于类型和原生函数及类型转换的主要内容,如果未能解决你的问题,请参考以下文章

在代码片段中包含类型转换

JS的数据类型判断函数数组对象结构处理日期转换函数,浏览器类型判断函数合集

(转) Java中的负数及基本类型的转型详解

蓝鸥原生JS:js的引入方式及js的基本数据类型

C++ 各类型转换及关键字

PHP从基础语法到原生项目开发