类型和原生函数及类型转换(一)

一、内置类型:

空值: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原生函数也不在这里介绍了,会有具体针对这两个对象解析的博客。

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

原文地址:https://www.cnblogs.com/ZheOneAndOnly/p/10275012.html

时间: 2024-10-06 21:21:40

类型和原生函数及类型转换(一)的相关文章

《你不知道的JavaScript》整理(五)——值与原生函数

一.值 1)数字 JavaScript只有一种数值类型:number(数字),包括"整数"和带小数的十进制数. //数字的语法 var a = 5E10; // 50000000000 a.toExponential(); // "5e+10" var b = a * a; // 2.5e+21 var c = 1 / a; // 2e-11 var d = 0.42; var e = .42; //数字前面的0可以省略 var f = 42.; //小数点后小数部

JavaScript中判断原生函数的两个示例

原文链接: Detect if a Function is Native Code with JavaScript原文日期: 2014-08-17翻译日期: 2014-08-20翻译人员: 铁锚 我总是经常碰到需要检查某个function是否是原生代码的情况  -- 这是功能测试中一个很重要的内容: 函数是浏览器内置支持的,还是通过第三方类库模拟的.要检测这一点,最简单的办法当然是判断函数的 toString 方法返回的值啦. JavaScript代码 判断函数是否是原生方法其实相当简单: //

SQL Server系统函数:类型转换函数

原文:SQL Server系统函数:类型转换函数 1.基本的转化 SELECT CAST(2008 as varchar(4)) + ' year!' SELECT CONVERT(varchar(4),2008) + ' year!' 2. 把日期转化为文本 SELECT CONVERT(VARCHAR(30),GETDATE(),120) --年-月-日 时:分:秒(24h) SELECT CONVERT(VARCHAR(10),GETDATE(),120) --年-月-日 时:分:秒(24

尽可能使用PHP原生函数?

今天在阅读kohana源码中的Arr类的时候发现了这样一个函数 /** * Fill an array with a range of numbers. * * // Fill an array with values 5, 10, 15, 20 * $values = Arr::range(5, 20); * * @param integer $step stepping * @param integer $max ending number * @return array */ public

[转]Delphi 变体类型(Variant)的介绍(流与变体类型的相互转换、变体类型常用的函数)

Delphi 变体类型(Variant)的介绍(流与变体类型的相互转换.变体类型常用的函数) 一.变体类型常用的函数介绍: Variant: 一种可以拥有各种数据类型: 也可以告诉目前存储的数据是什么类型(通过使用VarType函数): 可以给相同的Variant分配不同的数据类型,只要Variant包含数字值就可以执行算法: variant数组只不过是variant型的数组,不必包含同类型的数据: 1.  VarArrayOf函数:更快捷的创建一维变体数组,可以创建全异的数值数组: funct

C语言不进行类型检查 和函数可以不进行前向声明

编译器为gcc4.5 #include <stdio.h> int main() { printf("%d\n",add(3)); printf("%d\n",add('c')); printf("%d\n",add(3.2)); return 0; } int add(int a) { return a; } C 语言可以不进行前向声明,前提是定义的函数必须是 int型返回值和int型的形参. C语言不进行类型检查 和函数可以不进行

C++ 无符号类型的运算对象参与的类型转换

我的主力博客:半亩方塘 当某个运算符的运算对象类型不一致,且其中某一个运算对象是无符号类型时,这个需要进行相应地类型转换,至于类型转换的结果,则 依赖于机器中各个整数类型的相对大小 . 当某个运算符的运算对象类型不一致,且其中某一个运算对象是无符号类型时,分为以下几种情况: 当 无符号类型 的运算对象 大于 有符号类型 的运算对象时,则将 有符号类型转换为相应的无符号类型后再进行运算,转换规则见我的博客 C++ 中有符号类型到无符号类型的转换 当 有符号类型 的运算对象 大于 无符号类型 的运算

IntelliSense 无法重载仅按返回类型区分的函数

IntelliSense:无法重载仅按返回类型区分的函数       d:\programfiles (x86)\microsoft sdks\windows\v7.0a\include\winbase.h         3540 在VS2010下用C语言写Windows系统服务,从另一个c#的项目中Copy过来一段代码,修改后再编译,就产生了这个错误! 在网上搜索得到的答案是:"无法重载仅按返回类型区分的函数"这种情况一般只会发生在有同名函数的情况下,但是我那段代码里却没有同名函数

Delphi 变体类型(Variant)的介绍(流与变体类型的相互转换、变体类型常用的函数)

来源:http://blog.csdn.net/xiongmao000738/article/details/6863988 一.变体类型常用的函数介绍: Variant: 一种可以拥有各种数据类型: 也可以告诉目前存储的数据是什么类型(通过使用VarType函数): 可以给相同的Variant分配不同的数据类型,只要Variant包含数字值就可以执行算法: variant数组只不过是variant型的数组,不必包含同类型的数据: 1.  VarArrayOf函数:更快捷的创建一维变体数组,可以