02-数据类型

nobility 发布于 2022-08-24 01-ES5 1227 次阅读


数据类型

typeof 运算符
  • 用来检测变量的数据类型的运算符,返回数据类型的字符串名字
    • 对于原始数据类型来说总是返回他的数据类型名
    • 对于引用数据类型(合成数据类型)来说,就不一定了,有可能是大的引用数据类型(object),也有可能是具体的什么引用数据类型(合成数据类型)
    • 对于两个特殊的数据类型 nullundefined 来说有特殊的历史原因问题,下面讲
  • 可以有两种形式普通形式 typeof 变量 和函数形式 typeof(变量), 函数形式的括号只是提升后面表达式优先级的作用,并不是真正意义上的函数

JavaScript数据类型

Null 与 undefined

其实本质上这两个特殊数据类型表达的是一个意思,由于历史原因,null 被留下,一般 null 用于指该变量要存放的对象数据类型时赋初值时采用,undefined 用来存放原始数据类型,不必赋初值自动初始化为该值

由于 == 是可以发生数据类型自动转换的,所以是 true,=== 严格相等需要数据类型也一致,这两个数据类型就不一致所以是 false

这两个数据类型的唯一的不同是:null 可以自动转换为数字 0,而 nudefined 只能转换为 NaN

console.log(typeof undefined);  //undefined
console.log(typeof null);   //object

console.log(undefined == null); //true
console.log(undefined === null);    //false

console.log(Number(null));  //0
console.log(Number(undefined)); //NaN

布尔值

  • 布尔数据类型只有 truefalse 两种状态,true 会转化为 1,false 会转化为0
console.log(typeof true);	//boolean
console.log(typeof false);	//boolean
console.log(Number(true));	//1
console.log(Number(false));	//0

数值

  • 整数和浮点数都是数值类型,都是使用 64 位浮点数来表示的,所以 1==1.0 是 true
  • 整型的科学计数法整数的不同进制表示同其他语言,不过要注意的是八进制若有超过八的数字则会当做十进制来处理,而并不会报错
  • NaN:表示非数字,主要出现在字符串无法转换为数字时的特殊值,使用 isNaN() 函数来判断是否是 NaN
    • 0/0 的结果也是 NaN
    • NaN 不等于、不大于和不小于任何值,包括他本身,换言之 NaN 做任何逻辑计算都是 false
    • NaN 与任何数做计算都是 NaN,包括自己
  • Infinity:表示无穷,得到无穷数值溢出,和非零数值除零的结果,使用 isFinite() 函数判断是否是有限数(包括 NaN)
    • 有正负之分,正无穷与负无穷是不相等的,符合数学上负负得正的规则(唯一区分正零和负零地方)
    • 他的计算符合数学规则,若数学计算错误时返回 NaN
console.log(typeof 0);	//number
console.log(typeof 0.1);	//number
console.log(0.1 == 1);	//true


console.log(typeof NaN);	//number
console.log(NaN == NaN);	//false
console.log(NaN + NaN);	//NaN
console.log(NaN + 1);	//NaN


console.log(typeof Infinity);	//number
console.log(1/0);   //Infinity
console.log(Math.pow(2,1024));  //Infinity
console.log(Infinity == -Infinity);	//false


console.log(isNaN(NaN));    //true
console.log(isNaN("")); //false
//同等于
console.log(isNaN(Number(""))); //false

console.log(isNaN([])); //false
//同等于
console.log(isNaN(Number([]))); //false


console.log(isFinite(Infinity)); // false
console.log(isFinite(-Infinity) );// false
console.log(isFinite(NaN)); // false

console.log(isFinite(undefined)); // false
//同等于
console.log(isFinite(Number(undefined)));   //false

console.log(isFinite(null)); // true
//同等于
console.log(isFinite(Number(null)));    //true

console.log(isFinite(0)); // true

字符串

  • 使用单引号或双引号进行包裹
  • 可以使用字符数组形式对字符串中的字符进行访问
  • 也可以使用字符数组形式的 length 属性可以获得字符串的长度,该属性无法更改,因为他是常量,操作会默默失效
  • 无法对字符串内部进行增删改,因为他是常量,这些操作会默默的失效,并不会报错
console.log(typeof ""); //string
var str = "hello";
console.log(str[0]);    //h

str.length = 100;
console.log(str.length);    //5

str[0] = "H";
str[5] = "!";
delete str[1];
console.log(str);   //hello

对象

  • 是一种特殊的 Map 集合
    • 键名都是字符串类型,所以即时不加字符串的引号包裹也会自动将其转换为字符串, 但是不能使用字符串拼接的形式
    • 键值可以存放任意数据类型
  • 可以说 js 对象中只有属性,没有方法,因为函数也是一种特殊的对象数据类型
  • 与其他语言一样,对象变量中保存的都是内存地址,所以有对象引用的特性
  • 由于创建对象的字面量方式与代码块一样,所以当出现二义性时 js 引擎在解析的时候会将大括号一律解析成代码块,若想解析成为对象形式则需要手动在大括号外部增加小括号

对象的声明

  • 字面量方式:可以动态的位对象的属性进行赋值和修改操作,也可以在初始化时指定属性,使用分号隔开键值,多个键值使用逗号分隔
var obj1 = {	//定义有内容的对象
  "p":"p",
  "p1":1
};
//等价于
var obj1 = {	//定义有内容的对象
  p:"p",
  p1:1
};
//var obj1 = {	//定义有内容的对象
//  p:"p",
//  "p"+"1":1	//报错,不能使用表达式
//};
var obj = {};	//定义空对象
obj.p = "old";	//增加属性操作
obj.p = "new";	//修改属性操作
  • new 关键字方式:由构造方法创建的实例对象
console.log(typeof new Function()); //function,不难看出function是及其特殊的对象
console.log(typeof new Array());    //object
console.log(typeof new Object());   //object
console.log(typeof new String());   //object
console.log(typeof new Number());   //object
console.log(typeof new Boolean());  //object
console.log(typeof new RegExp());   //object
console.log(typeof /s/);    //object

var a = new Number(1) + new Number(1);	//这就是基本类型的包装类了
console.log(a);	//2

对象的属性操作

  • 点运算符方式修改添加操作:只用用于没有二义性的非数字开头的属性
  • 方括号运算符方式修改添加操作:键名使用引号括起来的字符串,除非是数值型(包括小数)的键名可以不使用引号,就像数组那样
var obj = {
  p1:"p",
  1:"number"
};
obj.p1 = "new";
// obj.1 = "new number";	//点有二义性,说不清是小数点还是运算符,所以是非法的
obj["1"] = "new number";
obj["p"+"1"] = "new";	//可以使用表达式
obj[1] =  "new number";	//会自动转化为字符串
  • delete 对象.属性 命令删除对象属性操作:
    • 要注意的是删除命令只有在该属性无法被删除时才返回 false
    • 并不能删除全局变量
var obj = {	
  p:"p",
  p1:1
};
delete obj.p;	//删除成功返回true,若这个属性不存在也会返回true,因为删除掉也是不存在了嘛

var a = 1;
console.log(a);	//1
delete a;
console.log(a);	//1
  • 键名 in 对象 运算符:查看该对象是否有该属性,包括继承的,不包括不可枚举的
var obj = {
  p:"p",
  1:"number"
};
console.log("p" in obj);	//true
console.log("0" in obj);	//false
  • for ... in 循环可遍历对象的属性:基于 in 操作符,只能遍历可列举的属性,也会遍历继承的属性
var obj = {
  p:"p",
  1:"number"
};

for (var key in obj) {
  console.log(key + ":" +obj[key]);
}
//1:number
//p:p
//可以看出并不是按照书写的顺序进行输出的,而是内部经过排序之后的顺序输出的

for (const key in obj) {
    if (obj.hasOwnProperty(key)) {	//使用继承自object的方法判断这个属性是否是继承的,此时只会遍历出非继承的方法了
        console.log(key + ":" +obj[key]);
    }
}
  • Object.keys(obj) 方法获取该对象的所有键名组成是数组,不包括继承的
var obj = {
  p:"p",
  1:"number"
};
var keys = Object.keys(obj);	//["p","1"]

函数

和其他编程语言一样,函数是一组可以重复利用的代码块,通过圆括号内写参数进行调用,可以有参数和返回值,不同的是:

  1. 由于 js 是弱类型语言所以无需定义参数和返回值的数据类型
  2. 传递参数时,数量不匹配也没有关系,会用 undefined 来代替
  3. 当没有返回值时则返回 undefined,一个函数可以返回不同的数据类型
  4. Js 不像 c++、Java 那样具有方法的重载,若有同名函数则会直接覆盖掉原来的函数

函数是极奇特殊的一种对象,其地位与其他任何一种数据类型都是平等的,所以他可以作为函数的参数和返回值

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

var fun = function(){};
console.log(typeof fun);    //function

console.log(typeof new Function()); //function

console.log(typeof Function()); //function

函数的声明

  1. function 命令方式
function funName(){	//命令方式必须指定函数名
  //code
}
  1. 函数表达式方式
var fun = function(){	//此方法无需指定函数名,由变量名代替,若指定函数名也只能在该函数内部使用,即递归调用
  								//或者是将此变量名和函数名使用同样的标识符,这样函数调用栈会显示函数名,而不是匿名函数
  //code
};	//由于是表达式方式,这是一条语句,建议加一个分号以示区别
  1. Function 构造方式,如果不使用 new 关键字来构造直接使用也是可以的,但是不够直观,所以建议加上 new 关键字
var fun = new Function("str","console.log(str);return 0");	//最后一个参数是执行体,前面的参数都是该函数的形参
var result = fun("hello");	//hello
console.log(result);	//0
//可以看出此方法唯一的好处就是可以将字符串作为构造成一个函数,某些情况下就需要使用他

函数的属性和方法

  • name 属性:返回当前函数的函数名,是无法动态修改的,他唯一的用途就是获取函数的真实的名字了
function fun(){	//命令方式
}
console.log(fun.name);  //fun,返回函数名



var fun =  function(){	//表达式方式
};
console.log(fun.name);  //fun,返回变量名



var fun =  function(){
};
var newFun = fun	//指向的是同一块空间
console.log(newFun.name);  //fun,返回函数第一次的变量名



var fun =  function funInner(){	//表达式方式同时又指定了函数名
};
fun.name = "newFun";	//无法修改,默默失效
console.log(fun.name);  //funInner,返回函数名

console.log((new Function).name); //anonymous,含义也是匿名的
console.log(function(){}.name);   //匿名函数返回空字符串
console.log(function fun(){}.bind(null).name);  //bound fun 会加上bound前缀
  • length 属性:返回当前函数的定义时的形参个数,是无法动态修改的
function fun(){
}
console.log(fun.length);  //0,
/*将下面代码注释掉才会是0,
因为函数也是一种数据类型,函数名也是变量,
所以也存在变量提升,若不注释则会被第二个函数覆盖掉,所以length是1*/
function fun(a){
}
fun.length = 100;	//无法修改,默默失效
console.log(fun.length);    //1
  • toString() 方法:返回函数的源码,对于系统函数,则会返回 function (){[native code]}
function fun(){
}
console.log(fun.toString());
/*
function fun(){
}
*/

console.log(Object.toString());
//function Object() { [native code] }

参数

与其他语言一样,函数参数传递都是值传递,不同的是,允许出现同名的参数,但是参数同名只会采用靠后的参数,当然一般人也不会这么做

  • arguments 对象:运行时的所有参数,他有类似数组拥有 length 属性,但又不是数组,因为不能使用数组中的一些方法,若想要使用数组方法则需要将其转换为数组
function fun(){
    console.log(arguments[0]);
    console.log(arguments[1]);
    console.log("运行时参数个数"+arguments.length+"、定义时的参数个数"+fun.length);	//与函数的length属性不同的是,获取的是运行时的形参个数
}
fun(0);
/*
0
undefined
运行时参数个数1、定义时的参数个数0
*/
fun(0,1);
/*
0
1
运行时参数个数2、定义时的参数个数0
*/

立即执行函数

其实就是定义函数后立即调用该函数,好处是:不必为函数起名,单独的作用域,不会污染全局变量

//function(){}();	
//这种方式会报错
//原因是function关键字既可以当表达式又可以当语句,所以当function出现在行首时会解析成语句,只有表达式才能立即执行
(function(){})();	//一般采取这种形式
!function(){}();	//等等等,其他形式不一一列举了
//所以立即执行函数必须将function不放在行首即可,这样就能解析成语句了
//要注意的是语句一定要带有分号结尾,否则多个立即执行函数会报错
// (function(){})()
// (function(){})()
// //TypeError: (intermediate value)(...) is not a function

闭包

Js 不像其他编程语言,他是可以在函数中定义函数,js 是有函数作用域的,然而这些函数的嵌套定义就形成了,内部函数能访问外部函数的变量,而外部函数无法访问内部函数的变量,这就形成了一个作用域链,这就是作用域的闭包

若将内部函数用某种手段暴露给外部时,只要外部存储这个内部函数的变量的指向不变,则这个内部函数依赖的宿主函数会一直存在内存中,所以此时外部调用内部函数就可以使用宿主函数的变量

function fun(i) { //宿主函数
  return function () {  //返回函数是用了宿主函数作用域中的变量
    console.log(i++);
  };
}

var f = fun(5);
f() // 5
f() // 6
f() // 7

f = null; //将函数指向丢弃,刚才返回的函数会被垃圾回收机制回收
f = fun(5); //重新调用就是一个新的宿主环境
f() // 5
f() // 6
f() // 7

由于暴露出去的内部函数可以使用宿主函数的变量,就会导致若内部函数肯定比宿主函数后执行,若同时暴露出去多个内部函数,并且这多个内部函数使用同一个宿主函数的变量就会有导致冲突,这就像 Java 中的多线程同步问题和数据库中的事务类似,只不过这个的顺序是可见的

/*由于宿主函数先执行,内部函数后执行出现的问题*/
var funArr = [];
for (var i = 0; i < 5; i++) {
  funArr[i] = function(){console.log(i);};
}

funArr[2]();  //5,预期得到2,结果得到5,宿主函数先执行后,内部函数后执行,导致宿主函数执行结束后变量被修改成5
funArr[3]();  //5
console.log(i); //5

/*
该问题的解决方案
	为每个内层函数都套上一个立即执行函数,在这个立即执行函数中创建宿主函数变量的副本,也就是作为参数传入
	这时,内部函数访问外部函数变量采用就近原则,此时的i变量就会从该立即执行函数中获取
	这样写虽然简单,但是由于内部函数依赖的宿主函数会一直存在内存中,也就是会有5个立即执行函数一直存在内存中
	程序的效率大大降低
*/
var funArr = [];
for (var i = 0; i < 5; i++) {
  (function(i){
    funArr[i] = function(){console.log(i);};
  })(i);
}
funArr[2]();  //2
funArr[3]();  //3

回调函数与闭包

函数也是一种数据类型,所以函数也可以作为函数的参数进行传递,一个外部函数的参数是函数,,因为要在这个外部函数内部要执行这个参数传递的函数,所以这个参数传递函数就称为回调函数

外部函数调用回调函数时将内部变量以参数形式传递给回调函数,回调函数中就可以直接操作外部函数中定义的变量;此时执行的顺序其实是内部函数先执行结束,外部函数后执行结束,这属于同步的回调

function fun(callback){
    let val = 100;
    callback(val);
}
fun(function(val){
    console.log(val);   // 100
})

数组

与其他编程语言不同的是,js 中的数组是一种特殊的对象类型,只不过键名是从零开始的正整数,所以数组中可以存放不同类型的数据,js 对象的属性操作,在 js 数组中同样适用,但是要注意一下几点

  • delete 删除成员只会留下空位,不会影响 length 属性
  • for ... in ... 循环会遍历出为数组添加的非下标键,对于数组的遍历不推荐使用此方式
console.log(typeof []);	//object
["a",97,true,new Function()];	//都是合法的

var arr = [1,2,3];
console.log(arr[1]);    //2
console.log(arr[1.0]);  //2
console.log(arr["1"]);  //2
console.log(arr["1.0"]);    //undefined,会当作字符串1.0的键名来寻找,而不会转换成数字
// console.log(arr.1);  //报错,存在点符号二义性

数组的声明和初始化

/*动态初始化*/
var arr = [];	//声明一个空数组
arr[0] = 0;
arr[1] = 1;
console.log(arr);

/*静态初始化*/
var arr = [0,1,2];
console.log(arr);

数组的 length 属性

  • 返回数组成员的数量,该属性是一个动态的值,始终等于键名中最大整数加 1
  • 该属性是可写的,会将超过该属性的下标自动删除,所以清空数组将 length 属性置为 0 即可, 若设置非正整数会报错
  • js 数组的 length 是一个 32 位的整数,表示范围是 2 的 32 次方减 1 个,直接修改 length 超过范围会报错,为数组超过这个范围的元素赋值时会将这个元素的键名转自动换为字符串
var arr = [1,2,3];
console.log(arr.length);	//3
arr[3] = 4;
console.log(arr.length);	//4
arr[6] = 6;
console.log(arr.length);	//7
console.log(arr);	//[ 1, 2, 3, 4, <2 empty items>, 6 ],导致出现两个空位,浪费空间
console.log(arr[5])	//undefined
//只是证明length属性等于键名中最大整数加1

var arr = [1,2,3];
console.log(arr);	//[ 1, 2, 3 ]
arr.length = 0;
console.log(arr);	//[]
arr.length = 3;
console.log(arr);   //[ <3 empty items> ],自动删除后就算添加回来length也是空位

数组的空位

  • 空位中存放的是 undefined
  • 空位的表示是 <n empty items>,n 是连序空位的个数
  • 空位会占据数组的长度计算位置
  • in 运算符对空位也会返回 false,如果是显示的存入是 undefined 会返回 true
  • 一些处理数组元素的函数会忽略空位
var arr = [,,1,,,]	//人为的创建空位
console.log(arr);	//[ <2 empty items>, 100, <2 empty items> ]
console.log(arr[0]);	//undefined
console.log(arr.length);	//5

伪数组

如果一个对象的所有键名,都是正整数和零,并且有 length 属性,那么就成为伪数组,若要使用数组中的方法则需要转化成真正的数组,常见的伪数组对象有函数中 arguments、大多数 DOM 元素集、字符串

加油啊!即便没有转生到异世界,也要拿出真本事!!!\(`Δ’)/
最后更新于 2022-08-24