现在开始更新《你不知道的JavaScript》中卷的学习内容
这篇文章的内容比较简单,大致过一遍,稍稍记住几个特殊的小知识点即可
类型
首先内置类型应该都知道了
◦ 空值(null)
◦ 未定义(undefined)
◦ 布尔值(boolean)
◦ 数字(number)
◦ 字符串(string)
◦ 对象(object)
◦ 符号(symbol)
console.log(typeof undefined === "undefined");//true
console.log(typeof true === "boolean");//true
console.log(typeof 42 === "number");//true
console.log(typeof "42" === "string");//true
console.log(typeof {a:2} === "object");//true
console.log(typeof Symbol() === "symbol");//true
//这两个比较特殊
console.log(typeof function a(){} === "function");//true
console.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);//function
var b=new String();
console.log(typeof b);//object
var c=new Boolean();
console.log(typeof c);//object
var 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);//2
a(2,4);
JavaScript中变量是没有类型的,只有值才有。变量可以随时持有任何类型的值
当你对变量执行typeof操作时,得到的并不是该变量的类型,而是该变量持有的值的类型
var a=true;
console.log(typeof a);//boolean
a=2;
console.log(typeof a);//number
console.log(typeof typeof a);//总是返回string
对于最后一个typeof操作,可以理解为typeof (typeof a);由于typeof总是返回一个字符串,所以typeof (typeof a)相当于typeof ( "number" ),因此总是返回string
变量在未赋值的时候为undefined,那么没有在作用域中声明的变量应该是undeclared。但是好像并不是这么回事
var a;
console.log(a);//undefined
console.log(b);//Uncaught ReferenceError: b is not defined
console.log(typeof a);//undefined
console.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);//4
console.log(a[1]);//2
console.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]);//c
str[2]="1";
console.log(str);//abcdef
var c=str.toUpperCase();
console.log(str);//abcdef
console.log(c);//ABCDEF
当然,有时候使用数组函数来处理字符串会很方便
var str="abcdef";
var a=Array.prototype.join.call(str,"-");
console.log(str);//abcdef
console.log(a);//a-b-c-d-e-f
var b=Array.prototype.map.call(str,(el) => {
return el.toUpperCase()+"-";
}).join("");
console.log(str);//abcdef
console.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.45
var num2=54.;//54.00
对于特别大和特别小的数字会用指数形式来表示
var a=5e10;
console.log(a);//50000000000
var b=a*a;
console.log(b);//2.5e+21
var c=1/a;
console.log(c);//2e-11
数字值在需要的时候会被封装成Number对象,所以数字值也可以使用该对象中的方法
var num=23.123;
console.log(num.toExponential());//2.3123e+1
console.log(num.toFixed(6));//23.123000
console.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);//false
console.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));//false
console.log(Number.isInteger(12.00));//true
console.log(Number.isSafeInteger(Math.pow(2,53)));//false
console.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);//2
var 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);//number
console.log(Number.isNaN(num));//true
console.log(Number.isNaN(b));//false
console.log(Number.isNaN(a));//false
可能会想,window中不是有isNaN方法吗?干嘛不用呢?在window中确实有isNaN()方法,但是判断并不准确
var num=2/"f";
var b="f";
var a=2;
console.log(isNaN(num));//true
console.log(isNaN(b));//true
console.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);//NaN
var c=2;
console.log(c/a);//0
console.log(a/c);//Infinity
var d=-2;
console.log(d/a);//-0
console.log(a/d);//-Infinity
零值也比较特别,需要稍微注意点
console.log(0 == -0);//true
console.log(0 === -0);//true
var a=-0;
console.log(a);//-0
console.log(a.toString());//0
console.log(a+"");//0
console.log(String(a));//0
可以看出将-0转换为字符串时都会被转变为0,但是当把字符串"-0"转换为数字时却能正确表示出来
console.log(+"-0");//-0
console.log(Number("-0"));//-0
console.log(JSON.parse("-0"));//-0
这就比较有趣了,那么你可能会想,如何区分0和-0呢?有的
function isNegZero(n){
n=Number(n);
return (n === 0) && (1/n === -Infinity) ;
}
console.log(isNegZero(-0));//true
console.log(isNegZero(0));//false
可能你会觉得每次比较NaN和-0时都要自己写函数有点麻烦,没错,现在可以使用Object.is()方法,简单好用
var a=1/"f";
var b=-0;
console.log(Object.is(a,NaN));//true
console.log(Object.is(b,-0));//true
console.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);//object
console.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);//object
console.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);//object
console.log(d);//{false}
foo(d);
console.log(d);//{false}
看第一个例子,a的值被改变了应该很好理解。但是后面三个就不太好理解了,虽然bcd都是对象,但是在函数执行完后好像并没有被改变,就好像是bcd都变成了简单值
我认为这是一种错觉,注意看这些个函数中都有进行运算符操作,但是对象是不能直接进行运算符操作的,也就是在执行这些运算符操作时会对对象进行隐式拆封(下一篇会讲),简单说就是进行类型转换,将这些对象转换为对应的简单值,但是,一旦变为简单值就成了值复制,即传入到函数中的是所复制的值,不是引用,所以bcd对象都没有发生改变。这只是我的一些看法,并不一定是对的,只是用现有的知识进行解释,可能随着学习深度的加深会认识到我这种看法是错误的也不一定,见仁见智
这篇总体来说比较简单,知识点小而杂
这里把我认为所需要记住的知识点都罗列出来,可以看看
typeof操作的特例有哪两个,如何使用typeof操作符检测变量而不报错
给数字添加属性时有什么要注意的地方
字符串的不可变性以及如何反转字符串
数字中的 . 运算符 NaN和-0的检测方法
值和引用的区别
坚持每周更新,向全栈大佬进发
这是我的公众号,喜欢的朋友可以关注一下,每周更新,篇篇干货