《你不知道的JavaScript》阅读笔记(4)

149 阅读8分钟

《你不知道的JavaScript》阅读笔记(4)

一、类型

JS中有七种内置类型:null、undefined、boolean、number、string、symbol、object,除了object外其他的被称为基本类型

typeof可以查看一个变量的类型,但是typeof null为object,一般我们判断是不是为null可以如下判断

const a=null
!a&&typeof a==='object' //true

看如下代码:

typeof function a(){} //'function'

function 也是JS中的一个内置类型,但本质上它是object的子类型,这些之前都有提到过,另外数组其实也是object的子类型,但是通过typeof来判断数组最后还是会返回object

JavaScript中的变量它是没有类型的,但是变量可以被赋值为任意类型的值,所以JS不同于JAVA,它不是强类型语言

在JS中声明了,但是没有赋值则默认为undefined,undefined和undeclared是两个完全不同的概念,一个声明了,一个未声明

typeof 具有安全机制,可以用来检查变量,如果未声明的变量,typeof也会返回undefined,比如微信小程序的wx这个全局变量,如果我们要判断是否有这个全局变量就可以使用typeof,否则很容易报referenceError错误,当然还有种方法就是通过直接通过window.a来判断

二、值

1、数组

JS中的数组可以容纳任何的值,且不需要预设大小,且可以不按顺序的对任意索引进行赋值,未赋值的则为undefined,数组也是对象,比如最常见的它有length属性

除了数组还有一种和数组很像的值,我们称为类数组,常见的就是函数中我们可以通过arguments来访问传入的参数

function foo(){
	const list=Array.prototype.slice.call(arguments)
	return list
}
foo('foo','bar') //['foo','bar']

可以看到slice可以将类数组转化为数组,es6提供了一个非常强大的方法也可以做到这一点就是Array.from

2、字符串

字符串和数组有很多相似地方,有时候你就可以把字符串理解成数组(其实完全是两码事),字符串也具有indexOf,concat,slice等方法,但JS中的字符串是不可变的,很多人可能会疑问不是可以修改值的吗,其实本质上是创建一个新的值,并不会改变原始值

3、数字

默认为十进制,分为整数和小数,小数就是浮点数(64位二进制),对于较大数字可以使用指数来访问

浮点数的相加判断不准确问题,如下

0.1+0.2===0.3//false

我们可以通过一种变式来判断,也就是如果左右两边的差值小于最小误差那么就是相等的而这个最小误差就是2的-52次方,一般存在Number.EPSILON中,可以改为一下方法来判断是否相同

function isEqual(n1,n2){
	return Math.abs(n1-n2)<Number.EPSILON
}
isEqual(0.1+0.2,0.3) //true

JS能被安全呈现出来的最大数保存在Number.MAX_SAFE_INTEGER为2^53-1,最小的安全数则保存在Number.MIN_SAFE_INTEGER,如果一些数据库里一些64位数返回给前端需要转换为字符串

整数检测Number.isInteger,是否为安全整数检测Number.isSafeInteger

void运算符可以返回undefined,例如void 1

NaN表示不是数字,判断一个变量是不是NaN不能直接通过===或者==来进行判断,需通过isNaN

JS中的无穷数Infinity、-Infinity,当数字超出Number.MAX_VALUE时会显示为Infinity

零值在JS中被分为+0和-0为什么要区分正负主要是有些时候需要判断方向

0/1=0
0/-2=-0
function isNegZero(n) {
       n = Number( n );
       return (n === 0) && (1 / n === -Infinity);
}
isNegZero( -0 );//true

Object.is可以判断两个值是否绝对相等

const a=+0;
const b=-0;
Object.is(a,b)//false

在JS中基本类型的值都是通过复制的方式来传递的,而对象则是通过引用的方式,可以看下面几个例子

function foo(x) {
         x.push( 4 );
         x; // [1,2,3,4]
					// 然后
					x.length = 0; // 清空数组 
					x.push( 4, 5, 6, 7 );
					x; // [4,5,6,7]
}
var a = [1,2,3];
foo( a );
a//[4,5,6,7]
function foo(x) {
         x.push( 4 );
         x; // [1,2,3,4]
				 // 然后
				 x = [4,5,6]; 
				x.push( 7 );
				x; // [4,5,6,7]
}
var a = [1,2,3];
foo( a );
a; // 是[1,2,3,4],不是[4,5,6,7]

三、原生函数

1、String()

2、Number()

3、Boolean()

4、Array()

5、Object()

6、Function()

7、RegExp

8、Date

9、Error

10、Symbol

原生函数可以当作构造函数来使用,都可以使用new关键字来创建实例

内部[[class]]属性:内部clss属性在JS中是精确的类型属性,我们可以通过Object.prototype.toString来访问

Object.prototype.toString.call('123');
// "[Object String]"

大多数情况下类型会返回对应的原生函数,比如字符串返回String,数组返回Array等,但也是有一些特殊情况

Object.prototype.toString.call(null);
// "[Object Null]"
Object.prototype.toString.call(undefined);
// "[Object Undefined]"

之前也提到过,我们定义的基本类型可以访问自身的属性是因为JS引擎会存在一个中间步骤,就是创建对应包装类的实例,然后在调用实例上的属性或者方法,所以如果你要平凡的调用方法建议就直接使用包装类来创建

注意:

const a=new Boolean(false)
if(!a){
	console.log('执行了')
}
//实际上并不会执行判断,因为包装类的真值和基本类型是有区别的,之后会讲到

如果想得到包装类的基本类型,可以使用valueOf

const a=new Boolean(false)
if(!a.valueOf()){
	console.log('执行了')
}
//执行了

创建数组的方式有很多,如果使用构造函数来创建,可以传入一个预设宽度,但是表面上看是创建了三个长度单位的数组,实际上并不是的,只不过是把创建对象的length属性设置为长度,而实际上并没有这些空单位,所以如果你在chrome上使用构造函数来创建,你的输出往往会是[undefined*3],这和[undefined,undefined,undefined]实际上有很大的差距,如下

const a=new Array(3);
const b=[undefined,undefined,undefined]
a.map((item,index)=>index)//[undefined*3]
b.map((item,index)=>index)//[0,1,2]
//a其实并没有数据,所以map无从遍历

实际上有一种方法可以使用构造函数来创建[undefined,undefined,undefined],那就是apply

const a=Array.apply(null,{length:3})
//[undefined,undefined,undefined]

你可能会觉得apply参数不是要接受数组吗,其实不光是数组,类数组也是可以的,而这里一个类数组,追过它只有长度一个属性,各个索引的值都为空,所以apply内部会像for循环一样遍历这个空的类数组,最后得到的效果就像是Array(undefined,undefined,undefined)

尽量不要使用Function、Object、RegExp来创建对应的对象,RegExp有时候对于动态定义正则还是挺有帮助的

Symbol不能使用new关键字来创建,Symbol对象不能直接访问值,使用Object.getOwnPropertySymbols可以访问对象上的symbol属性

原生函数的原型对象上有一些我们常用的方法,比如数组的push、pop字符串的charAt等,这也是为什么我们可以访问这些属性的原因,他们都定义在原生函数的原型对象上了

Array.prototype、Function.prototype、RegExp.prototype是空数组、空函数、空正则,他们有时候作为默认值非常好用,因为他相比于我们手动创建的对象来说在执行过程中不需要平凡的创建

四、强制类型转换

将值从一个类型转换成另一个类型,这是显示的,叫做类型转换,隐式的情况我们则称为强制类型转换

类型转换发生在编译过程中,而强制类型转换则发生在运行时

const a=44
const b=a+'' //"44",发生在运行时,隐式强制类型转换
const c=String(a) //显示的强制类型转换

toString(),处理非字符串到字符串的强制类型转换方法,对于普通对象来说toString返回前面所讲的内部class属性,其他的一些对象比如Array,内部自定义了toString方法返回结果是使用‘,’连接的字符串

工具函数JSON.stringify()在对象序列化为字符串时也用到了toString()方法

注意:JSON.stringify在对象中遇到undefined、function、symbol会自动忽略,在数组中会转换为null保持占位,而如果是循环引用对象则会直接报错

如果对象实现了toJSON方法,则调用JSON.stringify方法会先使用这个toJSON方法,在使用JSON.stringify方法,所以一定要保证toJSON返回的值是一个安全的JSON

const a={b:1}
a.c=a;
a.toJSON=function(){
	return {b:this.b}
}
JSON.stringify(a)
//'{"b":1}'

JSON.stringify(a,replacer)第二个参数可以用作筛选作用,如果传入数组,则数组为所要筛选的键,如果是函数则对每个属性执行一次属性,参数为键和值

const a={
	b:42,
  c:33,
	d:'33'
}
JSON.stringify(a,["b,"c"])//"{"b":"42","c":"33"}" 
JSON.string(a,function(k,v){
	if(k==='c') return v
})
// "{"c":"33"}"

toNumber:非数字转换为数字,true转化为1,false转换为0,undefined转换为NaN、null转化为0

null、undefined、””、false、+0、-0、NaN都是假值会被强制转换为false,其他为真值

String和Number之间的转换除了使用构造函数外还有其他方法

var a = 42;
var b = a.toString();
var c = "3.14";
var d = +c;
b; // "42"
d; // 3.14

~位运算符,转换为32位在取反,~x类似于-(x+1),当x位-1时它会返回假值0,否则返回真值,用途

const a='hello world'
if(~a.indexOf('lo')){ //true 
	console.log('执行了') 
}

~~截除小数,类似Math.floor,但对于负数来说则类似Math.ceil

parseInt是转化字符串用的,其中第二个参数是用来定义第一个参数基底,传入2则为二进制,以此类推,尽量不要传入数字因为这是个处理字符串的函数,如果你传入了数字可能会有意想不到的结果

一元运算符!是用来强制转化为布尔值使用的,他可以将真值反转为假值,假值反转为真值,所以强制转化布尔值的最常用方法就是!!

如果我们要判断三个之中有且仅有一个值为true,我们可能会有以下代码

function onlyOne(a,b,c){
	return !!((a && !b && !c) ||
          (!a && b && !c) || (!a && !b && c));
}

但是如果我们将布尔值转化为数字则有以下代码

function onlyOne() {
         var sum = 0;
for (var i=0; i < arguments.length; i++) {
     sum += arguments[i];
}
}
    return sum == 1;
}

可以看到其实布尔值通过+运算符会进行转化,true→1,false→0

||和&&两个运算符可能很多人会觉得是逻辑运算符,但在JS中这两个的返回并不一定是boolean,称他们为选择运算符可能更为准确,他们的返回是两个操作数中的其中一个

var a = 42;
var b = "abc";
var c = null;
a || b; a && b;
c || b; c && b;
// 42
// "abc"
// "abc"
// null

首先他们会对操作数进行隐式转化为布尔值,对于&&如果最后结果为true,则返回后一个操作数,否则就返回第一个操作数,而||则正好相反

||在我们平常会用来设置默认值,而&&则会被我们用来把关函数,如下

function bar(a){
	const c=a||'hello'
  console.log(c)
}
bar() //hello
bar('world') //world
const a=42
function foo(){}
a&&foo()//如果a为真值,则执行foo函数,否则不执行

==和===的区别是==会在比较时进行强制类型转换,在比较对象时则是没有区别都是比较引用地址是否相同,规则如下

//数字和字符串的比较都会转化为数字
const a=44;
const b='44'
a==b//true,都为数字44
// 布尔值和其他类型,布尔值会转为数字,true转为1,false转为0

不要使用等号来进行布尔值的判断

null和undefined在宽松相等下是一纸,但如果与0,false、“”这些假值相比返回的还是false

注意

"0" == null; //false
"0" == undefined; //false
"0" == false; //true
"0" == NaN; //false
"0" == 0; //true
"0" == ""; //false
false == null; //false
false == undefined; //false
false == NaN;//false
false == 0; //true
false == ""; //true
false == []; //true
false == {};//false
"" == null; //false
"" == undefined; //false
"" == NaN; //false
"" == 0; //true
"" == []; //true
"" == {}; //false
0 == null; //false
0 == undefined; //false
0 == NaN; //false
0 == [];//true
0 == {};//false