JS中类型与值

现在开始更新《你不知道的JavaScript》中卷的学习内容

这篇文章的内容比较简单,大致过一遍,稍稍记住几个特殊的小知识点即可

 

类型

首先内置类型应该都知道了

◦ 空值(null)

◦ 未定义(undefined)

◦ 布尔值(boolean)

◦ 数字(number)

◦ 字符串(string)

◦ 对象(object)

◦ 符号(symbol)

console.log(typeof undefined === "undefined");//trueconsole.log(typeof true === "boolean");//trueconsole.log(typeof 42 === "number");//trueconsole.log(typeof "42" === "string");//trueconsole.log(typeof {a:2} === "object");//trueconsole.log(typeof Symbol() === "symbol");//true
//这两个比较特殊console.log(typeof function a(){} === "function");//trueconsole.log(typeof null ==="object");//true

对于null的类型为object,是个bug,而且由来已久。只需记住即可,不必考虑太多,至于如何检测这个null,可以这么写:

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

对于function这个类型比较特殊,虽然是内置类型,但返回的依然是function,至于为啥,暂时也不知道,先记下吧

var a=new Function();console.log(typeof a);//functionvar b=new String();console.log(typeof b);//objectvar c=new Boolean();console.log(typeof c);//objectvar d=new Date();console.log(typeof d);//object

还有就是它也是有length属性的

function a(x,y){  console.log(arguments);//Arguments(2) [2, 4, callee: ƒ, Symbol(Symbol.iterator): ƒ]}console.log(a.length);//2a(2,4);

JavaScript中变量是没有类型的,只有值才有。变量可以随时持有任何类型的值

当你对变量执行typeof操作时,得到的并不是该变量的类型,而是该变量持有的值的类型

var a=true;console.log(typeof a);//booleana=2;console.log(typeof a);//numberconsole.log(typeof typeof a);//总是返回string

对于最后一个typeof操作,可以理解为typeof (typeof  a);由于typeof总是返回一个字符串,所以typeof (typeof  a)相当于typeof ( "number" ),因此总是返回string

 

变量在未赋值的时候为undefined,那么没有在作用域中声明的变量应该是undeclared。但是好像并不是这么回事

var a;console.log(a);//undefinedconsole.log(b);//Uncaught ReferenceError: b is not definedconsole.log(typeof a);//undefinedconsole.log(typeof b);//undefined

可以看到直接输出这个未声明的变量b是会报错(b is not defined),但是使用typeof操作符居然只是显示(undefined)。这个情况我还真没有太注意过

假设现在要使用一个叫abc的全局变量,但是不知道它有没有被声明过。那么如何检测才能在没有声明的情况下也不抛出错误呢?

//第一种情况if(abc){  console.log("abc is defined");//Uncaught ReferenceError:abc is not defined}//第二种情况if(typeof abc !== "undefined"){  console.log("abc is here");//没有任何输出内容}

第一种情况很明显会报错,而第二种就不会有任何问题,当然了也不会执行里面的代码

当你要使用第三方库的时候,可能要检测某个变量是否已经声明过,这时候就可以用到第二种方法。如果使用第一种就会直接报错,中断程序,这可能不是你想要的吧

 

先说数组

数组中可以容纳任何类型的值,字符串,数字,对象,其他数组都行

var a=["1",2,[3],{a:5}];console.log(a.length);//4console.log(a[1]);//2console.log(a[2][0]);//3

也可以直接给数组指定长度,只是未赋值都会显示未empty

a.length=7;console.log(a);//(7) ["1", 2, Array(1), {…}, empty × 3]

使用delete运算符可以将单元从数组中删除,但是数组的length并不会改变,依然保留了被删除单元的位置

delete a[0];console.log(a);//(7) [empty, 2, Array(1), {…}, empty × 3]console.log(a.length);//7

数组也是对象,所以也可以给它添加属性和值,但是不会算入长度中

a["foo"]="foo";console.log(a);//(7) [empty, 2, Array(1), {…}, empty × 3, foo: "foo"]console.log(a.length);//7

有趣的一点是,当给它添加的属性可以被强制转换成十进制的数字,那么这个属性就会被当成一个数字索引来处理

a["7"]="第八个";console.log(a);//(8) [empty, 2, Array(1), {…}, empty × 3, "第八个", foo: "foo"]console.log(a.length);//8

再来看看字符串

字符串具有不可变性,就是字符串的成员函数不会改变其原始值,而是创建并返回一个新的字符串

var str="abcdef";console.log(str[2]);//cstr[2]="1";console.log(str);//abcdef
var c=str.toUpperCase();console.log(str);//abcdefconsole.log(c);//ABCDEF

当然,有时候使用数组函数来处理字符串会很方便

var str="abcdef";var a=Array.prototype.join.call(str,"-");console.log(str);//abcdefconsole.log(a);//a-b-c-d-e-fvar b=Array.prototype.map.call(str,(el) => {  return el.toUpperCase()+"-";}).join("");console.log(str);//abcdefconsole.log(b);//A-B-C-D-E-F-

但是,有一个函数是字符串所不能使用的,就是reverse()。但是如果想让字符串反转还是有方法的

var c=str.split("").reverse().join("");console.log(c);//fedcba

这个方法仅适用于简单的字符串,如果是有复杂字符的(Unicode,如星号,多字节字符等)就不能使用了

 

接下来是数字

在JavaScript中没有真正意义上的整数,因为它使用的“双精度”格式(即64位二进制)。就是说,它里面的整数就是没有小数的十进制数,42.00就等同于整数42

在JavaScript中,数字的最前面和最后的零都可以省略不写

var num1=.45;//0.45var num2=54.;//54.00

对于特别大和特别小的数字会用指数形式来表示

var a=5e10;console.log(a);//50000000000var b=a*a;console.log(b);//2.5e+21var c=1/a;console.log(c);//2e-11

数字值在需要的时候会被封装成Number对象,所以数字值也可以使用该对象中的方法

var num=23.123;console.log(num.toExponential());//2.3123e+1console.log(num.toFixed(6));//23.123000console.log(num.toPrecision(6));//23.1230/*numObj.toPrecision(precision)以指定的精度返回该数值对象的字符串表示numObj.toExponential(fractionDigits)以指数表示法返回该数值字符串表示形式numObj.toFixed(digits)使用定点表示法来格式化一个数值*/

但是,这里有一个特殊的地方要特别注意,就是 . 运算符是一个有效的数字字符,会被优先识别为数字字面量的一部分,然后才是对象属性访问运算符

console.log(43.toFixed(2));//报错
console.log((43).toFixed(2));//43.00
console.log(43..toFixed(2));//43.00
console.log(0.43.toFixed(2));//0.43
console.log(43 .toFixed(2));//43.00

对于最后一种方式,就是在数字后面添加一个空格,但是这种方式也比较少见,也容易引起误会,不推荐使用。推荐使用前3种方式

 

不知道有没有碰到过这种情况

console.log(0.1+0.2 === 0.3);//falseconsole.log(0.1+0.2);//0.30000000000000004

这就是使用二进制浮点数带来的精度问题,在处理小数问题时偶有发生。这时候不必使用===进行判断,而是设置一个误差范围值,两者的误差在这个范围之内就判定相等

这个误差范围值通常是2^-52,在Number中被定义为EPSILON,可直接使用

if((0.1+0.2)-0.3 < Number.EPSILON){  console.log("true");//true}

对于检测一个数是否为整数,可以使用Number.isInteger()方法,而检测一个数是否为安全的整数,可以使用Number.isSafeInteger()方法

console.log(Number.isInteger(12.22));//falseconsole.log(Number.isInteger(12.00));//trueconsole.log(Number.isSafeInteger(Math.pow(2,53)));//falseconsole.log(Number.isSafeInteger(Math.pow(2,53)-1));//true

 

这里还有几个特殊数值要注意

undefined类型只有一个值,即undefined。null类型也只有一个值,即null

但null是一个特殊关键字,不是标识符,不能被当成变量来使用和赋值,但是undefined可以被当成变量来使用和赋值,不过这几乎不会发生

void运算符可以使表达式不返回值,即返回结果是undefined,但是它不会改变表达式的结果

var a=42;console.log(void a, a);//undefined 42

如果数学运算符的操作数不是数字类型(或者无法被解析为常规的十进制或十六进制),那么就会返回一个NaN,就意味着运算失败

var num=2/"1";//进行了强制类型转换,下一篇会讲console.log(num);//2var num1=2/"f";console.log(num1);//NaN

那么如何检测变量的值是否为NaN呢?

console.log(NaN === NaN);//false//可以使用Number.isNaN()if(!Number.isNaN){//使用这个兼容ES6之前的浏览器  Number.isNaN=function(n){    return (    typeof n === "number" && window.isNaN(n)    );  };}var num=2/"f";var b="f";var a=2;console.log(typeof num);//numberconsole.log(Number.isNaN(num));//trueconsole.log(Number.isNaN(b));//falseconsole.log(Number.isNaN(a));//false

可能会想,window中不是有isNaN方法吗?干嘛不用呢?在window中确实有isNaN()方法,但是判断并不准确

var num=2/"f";var b="f";var a=2;console.log(isNaN(num));//trueconsole.log(isNaN(b));//trueconsole.log(isNaN(a));//false

可以看出,b也会被当成NaN,返回了true,显然是不应该的,所以不推荐使用window.isNaN()方法,推荐使用Number.isNaN()方法

 

无穷数也是一个要注意的值

var a=1/0;console.log(a);//Infinity(正无穷大,Number.POSITIVE_INFINITY)var b=1/-0;console.log(b);//-Infinity(负无穷大,Number.NEGATIVE_INFINITY)

那么无穷数/无穷数呢?有穷正数/Infinity呢?有穷负数/Infinity呢?

console.log(a/b);//NaNvar c=2;console.log(c/a);//0console.log(a/c);//Infinityvar d=-2;console.log(d/a);//-0console.log(a/d);//-Infinity

零值也比较特别,需要稍微注意点

console.log(0 == -0);//trueconsole.log(0 === -0);//true
var a=-0;console.log(a);//-0console.log(a.toString());//0console.log(a+"");//0console.log(String(a));//0

可以看出将-0转换为字符串时都会被转变为0,但是当把字符串"-0"转换为数字时却能正确表示出来

console.log(+"-0");//-0console.log(Number("-0"));//-0console.log(JSON.parse("-0"));//-0

这就比较有趣了,那么你可能会想,如何区分0和-0呢?有的

function isNegZero(n){  n=Number(n);  return (n === 0) && (1/n === -Infinity) ;}console.log(isNegZero(-0));//trueconsole.log(isNegZero(0));//false

可能你会觉得每次比较NaN和-0时都要自己写函数有点麻烦,没错,现在可以使用Object.is()方法,简单好用

var a=1/"f";var b=-0;console.log(Object.is(a,NaN));//trueconsole.log(Object.is(b,-0));//trueconsole.log(Object.is(b,0));//false
Object.is(value1,value2);

Object.is() 方法判断两个值是否是相同的值。具体比较的内容可以参考MDN官方文档,反正这个方法可以直接比较-0和0,以及NaN,好用

 

看了这么多,再来讲讲值和引用

在JavaScript中,对值和引用的赋值/传递在语法上没有什么区别,完全就是根据值的类型来决定,这句话一定要记住

如果是简单值(标量基本类型值),则总是通过值复制的方式进行赋值/传递,包括null,undefined,字符串,数字,布尔值以及symbol

如果是复合值,即对象和函数,那么总是通过引用复制的方式来赋值/传递

那么考虑下面这个例子

function foo(x){  x.push("a");  x=["q","w","e"];  x.push("b");}var a=[1,2,3];foo(a);console.log(a);//(4) [1, 2, 3, "a"]

仔细思考一下,为什么a输出的不是["q","w","e","b"]呢?可以分析一下

a是一个对象,所以foo(a)执行时,实际上是将a的引用副本传入了进去,此时x=a;就是x也指向a这个对象。当x.push("a")时,实际上也是在a对象添加,所以会输出[1, 2, 3, "a"]。然后x=["q","w","e"];此时x不再指向a对象,而是指向["q","w","e"]这个对象。并不是说将x之前指向的对象变成了["q","w","e"]这个对象,而是重新指向了["q","w","e"]这个对象

那么问题又来了,那如何通过x改变a这个对象呢?

function foo(x){  x.push("a");  x.length=0;//清空数组  x.push("q","w","e");  x.push("b");}var a=[1,2,3];foo(a);console.log(a);//(4) ["q", "w", "e", "b"]

注意看这个函数里面的x,有一个x.length=0的语句,这里是将x所指向的a对象的数组清空,然后再添加"q","w","e",至始至终都还是指向a对象。这么写就可以改变a对象了

还有一个问题也需要注意

//第一个例子function bo(x){  x.a=22;}var obj={  a:2}bo(obj);console.log(obj);//{a: 22}//第二个例子function foo(x){  x += "a";}var str="qwer";var b=new String(str);console.log(typeof b);//objectconsole.log(b);//String {"qwer"}foo(b);console.log(b);//String {"qwer"}//第三个例子function bar(x){  x +=1;}var num=2;var c=new Number(num);console.log(typeof c);//objectconsole.log(c);//Number {2}foo(c);console.log(c);//Number {2}//第四个例子function bool(x){  x=true;}var bool=false;var d=new Boolean(bool);console.log(typeof d);//objectconsole.log(d);//{false}foo(d);console.log(d);//{false}

看第一个例子,a的值被改变了应该很好理解。但是后面三个就不太好理解了,虽然bcd都是对象,但是在函数执行完后好像并没有被改变,就好像是bcd都变成了简单值

我认为这是一种错觉,注意看这些个函数中都有进行运算符操作,但是对象是不能直接进行运算符操作的,也就是在执行这些运算符操作时会对对象进行隐式拆封(下一篇会讲),简单说就是进行类型转换,将这些对象转换为对应的简单值,但是,一旦变为简单值就成了值复制,即传入到函数中的是所复制的值,不是引用,所以bcd对象都没有发生改变。这只是我的一些看法,并不一定是对的,只是用现有的知识进行解释,可能随着学习深度的加深会认识到我这种看法是错误的也不一定,见仁见智

 

这篇总体来说比较简单,知识点小而杂

这里把我认为所需要记住的知识点都罗列出来,可以看看

typeof操作的特例有哪两个,如何使用typeof操作符检测变量而不报错

给数字添加属性时有什么要注意的地方

字符串的不可变性以及如何反转字符串

数字中的 . 运算符 NaN和-0的检测方法

值和引用的区别

 

坚持每周更新,向全栈大佬进发

这是我的公众号,喜欢的朋友可以关注一下,每周更新,篇篇干货

 

                                                                            

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值