js数据类型的区别和使用讲解
javascript的数据类型可以分为两种:原始类型和引用类型
原始类型也称为基本类型或简单类型,因为其占据空间固定,是简单的数据段,为了便于提升变量查询速度,将其存储在栈(stack)中(按值访问)。
引用类型由于其值的大小会改变,所以不能将其存放在栈中,否则会降低变量查询速度,因此其存储在堆(heap)中,存储在变量处的值是一个指针,指向存储对象的内存处(按址访问)
[注意]对于引用类型的值,可以为其添加属性和方法,也可以改变和删除其属性和方法;但基本类型不可以添加属性和方法
基本数据类型: undefined、null、string、number、boolean。(按值访问,可操作保存在变量中的实际的值。基本类型值指的是简单的数据段。)
引用类型: object、array、regexp、date、function(当复制保存着对象的某个变量时,操作的是对象的引用,但在为对象添加属性时,操作的是实际的对象。引用类型值指那些可能为多个值构成的对象。)
区别
基本类型值不允许添加属性和方法,引用类型可以
//基本类型 let str = 'bbj'; str.age = 10; str.say = function () { console.log(112); }; console.log(str); console.log(str.age); console.log(str.say); //bbj //undefined //undefined
//引用类型 let str1 = new object(); str1.age = 18; str1.say1 = function () { console.log(this.age); } console.log(str1.age); str1.say1(); //18 //18
2. 在复制变量值时,基本类型会在变量对象上创建一个新值,再复制给新变量。此后,两个变量的任何操作都不会影响到对方;而引用类型是将存储在变量对象的值复制一份给新变量,但是两个变量的值都指向存储在堆中的一个对象,也就是说,其实他们引用了同一个对象,改变其中一个变量就会影响到另一个变量。
//基本类型 let str = 'bbj'; let str1 = str; str = 'ddj'; console.log(str); console.log(str1); //ddj //bbj
//引用类型 //1.对其中一个变量直接赋值不会影响到另一个变量(并未操作引用的对象) let str = ['bbj', 'ddj']; let str1 = str; str = ['bbj', 'ddj', 'ggj']; console.log(str); console.log(str1); // [ 'bbj', 'ddj', 'ggj' ] // [ 'bbj', 'ddj' ] //2.使用push(操作了引用的对象) let str = ['bbj', 'ddj']; let str1 = str; str.push('aaj'); console.log(str); console.log(str1); //[ 'bbj', 'ddj', 'aaj' ] //[ 'bbj', 'ddj', 'aaj' ] //3.应该是str赋值的时候相当于重新创建变量 let str = ['bbj', 'ddj']; let str1 = str; str = ['bbj', 'ddj', 'ggj']; str.push('aaj'); console.log(str); console.log(str1); //[ 'bbj', 'ddj', 'ggj', 'aaj' ] //[ 'bbj', 'ddj' ]
3.参数的传递
ecmascript所有的函数的参数都是按值传递的。函数外部的值赋值给函数内部的参数,与一个变量复制到另一个变量一样。基本类型值的传递和基本类型一样,引用类型的传递和引用类型的复制一样。
let num = 10; function add (num) { num += 10; return num; } console.log(num); console.log(add(num)); // 10 // 20
let person = {}; function setname (obj) { obj.name = 'bbj'; obj = new object(); obj.name = 'ddj'; return obj; } setname(person); console.log(person.name); console.log(setname(person).name); // bbj // ddj
即使在函数内部修改了参数的值,但原始的引用(person对象,存储在堆上)仍保持不变。具体传递的obj不是指针而是指针引用的对象(副本copy)。实际上,当在函数内部重写obj时,这个变量的引用的就是一个局部对象了,而这个局部对象会在函数执行完毕后立即被销毁。
扩展:值传递与引用传递
值传递和引用传递,属于函数调用时参数的求值策略(evaluation strategy),这是对调用函数时,求值和传值的方式的描述,而非传递的内容的类型(内容指:是值类型还是引用类型,是值还是指针)。值类型/引用类型,是用于区分两种内存分配方式,值类型在调用栈上分配,引用类型在堆上分配。一个描述内存分配方式,一个描述参数求值策略,两者之间无任何依赖或约束关系。
区别 | 值传递 | 引用传递 |
---|---|---|
根本区别 | 会创建副本(copy) | 不创建副本 |
所以 | 函数中无法改变原始对象 | 函数中可以改变原始对象 |
对于值传递,无论是值类型还是引用类型,都会在调用栈上创建一个副本,不同是,对于值类型而言,这个副本就是整个原始值的复制。而对于引用类型而言,由于引用类型的实例在堆中,在栈上只有它的一个引用(一般情况下是指针),其副本也只是这个引用的复制,而不是整个原始对象的复制。
这便引出了值类型和引用类型(这不是在说值传递)的最大区别:值类型用做参数会被复制,但是很多人误以为这个区别是值类型的特性。其实这是值传递带来的效果,和值类型本身没有关系。只是最终结果是这样。
4.两种类型的比较
let a = '123'; let b = '123'; console.log(a == b); console.log(a === b); // true // true let aa = {}; let bb = {}; console.log(aa == bb); console.log(aa === bb); // false // false
undefined
undefined类型只有一个值,即特殊的undefined。在使用var声明变量但未对其加以初始化时,这个变量的值就是undefined。不过,一般建议尽量给变量初始化,但是在早期的js版本中是没有规定undefined这个值的,所以在有些框架中为了兼容旧版,会给window对象添加undefined值。
出现场景
[1]已声明未赋值的变量
[2]获取对象不存在的属性
[3]无返回值的函数的执行结果
[4]函数的参数没有传入
[5]void(expression)
null
null类型只有一个值,就是null。逻辑角度看,null值表示一个空对象指针,而这也正是使用typeof操作符检测null时会返回object的原因。
如果定义的变量将用于保存对象,最好将该变量初始化为null。这样一来,只要直接检测null值就可以知道相应的变量是否已经保存了一个对象的引用了。实际上undefined值是派生自null值的,所以undefined == null
尽管null和undefined有这样的关系,但它们的用途完全不同。无论在什么情况下都没有必要把一个变量的值显式地设置为undefined,可是同样的规则对null却不适用。换句话说,只要意在保存对象的变量还没有真正保存对象,就应该明确地让该变量保存null值。这样做不仅可以体现null作为空对象指针的惯例,而且也有助于进一步区分null和undefined。
[注意]null是空对象指针,而[]是空数组,{}是空对象,三者不相同
出现场景
对象不存在时
boolean
该类型只有两个字面值:true和false。这两个值与数字值不是一回事,因此true不一定等于1,而false也不一定等于0。
虽然boolean类型的字面值只有两个,但javascript中所有类型的值都有与这两个boolean值等价的值。要将一个值转换为其对应的boolean值,可以调用类型转换函数boolean()
各种数据类型及其对象的转换规则
数据类型 | 转换为true的值 | 转换为false的值 |
---|---|---|
boolean | true | false |
string | 任何非空的字符串 | “”(空字符串) |
number | 任何非0数值(包括无穷大) | 0和nan |
object | 任何对象 | null |
undefined | 不适用 | undefined |
!!一般用来将后面的表达式强制转换为布尔类型的数据(boolean),也就是只能是true或者false;
两个感叹号的作用就在于,如果明确设置了变量的值(非null/undifined/0/”“等值),结果就会根据变量的实际值来返回,如果没有设置,结果就会返回false。
出现场景
[1]条件语句导致执行的隐士类型转换
[2]字面量或变量定义
number
javascript只有一种数字类型,既可以表示32位的整数,还可以表示64位的浮点数
这种类型用来表示整数和浮点数值,还有一种特殊的数值,即nan(非数值 not a number)。这个数值用于表示一个本来要返回数值的操作数未返回数值的情况(这样就不会抛出错误了)。例如,在其他语言中,任何数值除以0都会导致错误,从而停止代码执行。但在javascript中,任何数值除以0会返回nan,因此不会影响其他代码的执行。
nan本身有两个非同寻常的特点。首先,任何涉及nan的操作(例如nan/10)都会返回nan,这个特点在多步计算中有可能导致问题。其次,nan与任何值都不相等,包括nan本身
isnan()函数,用于判断是否是一个非数字类型。如果传入的参数是一个非数字类型,那么返回true;否则返回false;
isnan()函数,传入一个参数,函数会先将参数转换为数值。
如果参数类型为对象类型,会先调用对象的valueof()方法, 再确定该方法返回的值是否可以转换为数值类型。如果不能,再调用对象的tostring()方法,再确定返回值。
string
string类型有些特殊,因为字符串具有可变的大小,所以显然它不能被直接存储在具有固定大小的变量中。由于效率的原因,我们希望js只复制对字符串的引用,而不是字符串的内容。但是另一方面,字符串在许多方面都和基本类型的表现相似,而字符串是不可变的这一事实(即没法改变一个字符串值的内容),因此可以将字符串看成行为与基本类型相似的不可变引用类型
string类型是javascript中唯一没有固定大小的原始类型
字符串的值是不可变的。要改变一个字符串的值,首先要销毁原来的字符串,再用另一个包含新值的字符串去填充该字符串。
boolean、number、string 这三个是javascript中的基本包装类型,也就是这三个其实是一个构造函数,他们是function的实例,是引用类型,至于这里的string与以上说的string是同名,是因为其实上文说的string是指字符串,这里的string指的是string这个构造函数,上面那么写,是为了更好的理解,因为javascript是松散类型的。
其实string只是string的一个实例,类似于c#中的string,和string.
注意,typeof 变量 如果值是”string” 的话,也就是这个变量是字符串,在javascript中,字符串是基本类型,而在c#或java中,字符串是引用类型,但是javascript中的string是引用类型,因为它是javascript中定义好的基本包装类型,在c#中,string跟string其实是一样的。
javascript三个基本数据类型都有相应的对象类;分别为sring,number,boolean类;
javascript可以灵活的将一种类型的值转换为另一种类型;
当我们在对象环境中使用字符串时,即当我们试图访问这个字符串的属性或方法时;
javascript会为这个字符串值内部地创建一个string包装对象;
string对象会暂时代替原始的字符串值,完成我们的访问;
这个被内部创建的string对象是瞬间存在的,它的作用是使我们可以正常访问属性和方法;
string对象在使用过后会被系统丢弃掉;
而原始值并不会被改变;
以上同样适用于数字和布尔值类型;
使用object()函数,任何数字、字符串、布尔值都可以转换为它对应的包装对象;
引用类型
引用类型是一种用于将数据和功能组织在一起的数据结构(也常被成为类),引用类型的值(对象)是引用类型的一个实例。
但是js中没有类的概念,因此引用类型也可以被称为对象定义,因为他们描述的是一类对象所具有的属性和方法。
对象是某个特定引用类型的实例,新对象是使用new操作符后跟一个构造函数来创建的,构造函数本身就是一个函数,只不过该函数是出于创建新对象的目的而定义的。
对象是某个特定引用类型的实例,新对象是使用new操作符后跟一个构造函数来创建的,构造函数本身就是一个函数,只不过该函数是出于创建新对象的目的而定义的。
ecmascript提供了很多原生引用类型(如:object),以便开发人员用以实现常见的计算任务
object
我们看到的大部分引用类型都是object类型的实例,object的实例本身不具备很多功能,单对于在应用程序中存储和传输数据而言,是非常理想的选择。
创建object类型
直接new
语法: new操作符跟obeject构造函数
let obj = new object();
对象字面量
语法:花括号,里面的属性用键值对形式,每个属性用逗号隔开,最后一个属性不用逗号。
let person = { name: 'bbj', age: 18, sex: 'n' }
访问属性
通过点表示法
let person = new object(); person.name = 'bbj';方括号
var o = new object(); o.age = 22; console.log(o["age"]); //22 //方括号可以用变量来访问属性 var otherage = "age"; console.log(o[otherage]); //22
array
创建数组
new array()
//创建一个空数组 var arr1 = new array(); //创建一个长度为10的空数组, var arr2 = new array(10); //创建一个包含一个字符串good的数组 var arr3 = new array("good");
数组字面量
var cars = ["bmw","benz","ferrari"]; //注意,创建数组不要留空项。浏览器兼容性问题,ie8或以前版本会认为这是有3项,下面这种不建议。 let nums = [1,2,];
访问数组
cars[0]; //下标访问
date
创建对象
new date();
regexg
es通过regexg类型来支持正则表达式,语法:var e = /pattern/flag
其中pattern表示正则表达式,flag表示标识,标识可以一个或多个
flags | 说明 |
---|---|
g | 全局模式,该模式应用于所有的字符串 |
i | 不区分大小写模式,确定匹配项时忽略模式与字符串的大小写 |
m | 多行模式,一行文本到尾后还会查下一行的字符串,如果有的话 |
创建regexg对象
字面量方式
var p1 = /at/g; //匹配所有"at" 的实例 var p2 = /[bc]at/i //匹配第一个bat或cat,不区分大小写 var p3 = /.at/gi //匹配所有at结尾的组合,不区分大小写 //正则中如果想在字符中包含元字符需要对其进行转义 //这里和p3不同的是对.这个元字符进行转义,用\符号转义 var p4 = /\.at/gi; //这里的意思是匹配所有的".at",不区分大小写。
使用new regexg构造函数
//regexg() 接受两个参数,一个是正则表达式,一个是标志 var pattern1 = new regexg("at","gi"); //pattern1和pattern2完全等价 var pattern2 = /at/gi;
function
定义函数
//1.函数声明 function getname(){ var name = 'ry-yuan'; return name; } //2.函数表达式 var getage = function(){ var age = 100; return age; } //3.使用function构造函数,前面1-n个是参数,最后一个参数的是函数体,这种不推荐使用。 var sum = new function("num","return num");
函数声明和函数表达式的区别
//函数声明,我们先运行sayhello,但是不会报错,因为函数声明已经在解析时被提到顶部 sayhello(); //hello everyone function sayhello(){ console.log("hello everyone"); } //函数表达式,用var定义函数名.用函数表达式,不会提升,所以先运行saybye就会找不到函数体 saybey(); //报错 typeerror: saybye is not a function var saybye = function(){ console.log("bye bye"); }
函数名是指向函数的指针
一个函数在js种就是一个function的实例,函数名就是对实例的引用,一个指针,前面的对象中也有说过。那么一个函数就可以有多个函数名了。
//定义一个函数,函数名是sum var sum = funtion(num1,num2){ return num1+num2; } //讲sum复制给othersum,那么othersum和sum都指向同一个function对象 othersum = sum; othersum(100,420); //520 sum(1300+14); //1314 //对sum设置为null,sum变量就不在指向function对象 sum = null; //othersum依然能够使用 othersum(1,9); //10
函数没有重载
上面说了,函数名只是函数的指针,函数名是变量一样,重复复制就会覆盖原来的。
在java语言来说,有不同的参数数量也称为重载,但是js中没这种操作
//函数声明,fn为函数名 function fn(num1, num2){ return num1+ num2; } //再来函数声明,fn为函数名 function fn(num){ return num; } //fn只会指向最后一次声明的函数 fn(1,43); //1
函数像值一样传递
因为函数名本来就是一个变量,所以函数也可以像值一样被传递
//声明一个函数fn1,它可以接受两个参数,一个是函数,一个是值 function fn1(fn,value){ return (fn(value)); } //声明一个fn2,接受一个参数 function fn2(val){ console.log(val+1000); } //fn2当成参数传递给fn1 fn1(fn2,24); //1024
推荐阅读
-
微信小程序 image组件binderror使用例子与js中的onerror区别
-
使用nvm和nrm优化node.js工作流的方法
-
js中null与空字符串""的区别讲解
-
浅析Linux中使用nohup及screen运行后台任务的示例和区别
-
JS中的位置和宽度:clientWidth、offsetWidth、scrollWidth等区别介绍
-
select count()和select count(1)的区别和执行方式讲解
-
JS中使用new Date(str)创建时间对象不兼容firefox和ie的解决方法(两种)
-
详解在React.js中使用PureComponent的重要性和使用方式
-
浅谈JS和jQuery的区别
-
sql中varchar和nvarchar的区别与使用方法