这篇文章,我们来聊一聊JS中的数据类型和变量。这是最基本的问题类型,但它很重要。例如:如何理解按值传递参数?什么是临时死区?什么是变量提升?全局变量和窗口属性有什么区别?为什么?...以上问题均来自采访。如果您不清楚,我认为您需要继续阅读。基本数据类型在JS中,有6种基本数据类型,分别是数值、字符串、布尔值、null、undefined、Symbol。对于基本数据类型,我们需要了解的是,基本类型在内存中的存储方式是栈。每个值单独存储,互不影响。原始类型按值访问。比较时,按值比较:1===1//true引用数据类型引用类型的值存放在堆中,而引用存放在栈中。引用类型通过引用访问。比较的时候也要比较引用:{}==={}//=>false参数传递方式在JS中,参数可以是任何类型的值,甚至可以是函数。这里要分析的是传递的是哪种类型的参数?引用类型还是基本类型?先看一个基本的例子:varout_num=1;functionaddOne(in_num){in_num+=1;returnin_num;}addOne(out_num);//=>2out_num//=>1在这个例子中,我们传递了一个实参out_num给添加一个()函数。这时候out_num会传给in_num,也就是里面有一个in_num=out_num的过程。***我们看到的结果是out_num没有被函数改变,说明in_num和out_num是两个独立存储在内存中的值,也就是按值传递。再看一个变形:varout_obj={value:1};functionaddOne(in_obj){in_obj.value+=1;returnin_obj;}addOne(out_obj);//=>{value:2}out_obj//=>{value:2}问题来了?函数参数不是按值传递的吗?为什么函数内部的处理会反映到外面呢?这是超超超超的误会。首先,我们还是要搞清楚函数参数是按值传递的。那么这里怎么理解呢?对于引用类型,上面说了,引用类型分为引用和实际内存空间。这里out_obj还是传递给了in_obj,即in_obj=out_obj,out_obj和in_obj是两个引用,它们在内存中的存储方式是独立的,但是指向同一块内存。in_obj.value=1是要直接操作的实际对象。对实际对象的更改将同步到对实际对象的所有引用。如果你再看这个例子,它可能会变得更清楚。varout_obj={value:1};functionaddOne(in_obj){in_obj={value:2};returnin_obj;}addOne(out_obj);//=>{value:2}out_obj//=>{value:1}你只需要把握一点:对象的赋值会导致引用指向的实际对象发生变化。如何判断数据类型判断数据类型通常有以下三种具体方法:1.typeof运算符typeof运算符返回一个表示数据类型的字符串。它有以下明显的缺陷:typeofnull//=>'object'typeof[]//=>'object'这是因为JS语言设计之初遗留下来的一个bug。你可以阅读这篇文章http://2ality.com/2013/10/typ...来了解更多关于typeof对null的处理。所以typeof***用来判断一些基本类型,比如数字、字符串、布尔值、undefined、Symbol。2、instanceof运算符后面typeof是通过判断type标签来判断数据类型,而instanceof是判断构造函数的原型是否出现在对象原型链的任何地方。例如:{}instanceofObject//=>true[]instanceofArray//=>true[]instanceofObject//=>true也判断自定义类型:functionCar(make,model,year){this.make=make;this.model=model;this.year=year;}varauto=newCar('Honda','Accord',1998);console.log(autoinstanceofCar);//=>trueconsole.log(autoinstanceofObject);//=>true因此,对于字面量形式的基本数据类型,不能用instanceof来判断:1instanceofNumber//=>falseNumber(1)instanceofNumber//=>falsenewNumber(1)instanceofNumber//=>true3,Object.prototype.toString()这个是目前最推荐的方法,可以更精细准确地判断任何数据类型,甚至JSON、正则表达式、日期、错误等。在Lodash中,判断数据类型的核心是Object.prototype.toString()方法。Object.prototype.toString.call(JSON)//=>"[objectJSON]"关于这背后的原理,可以看这篇文章http://www.cnblogs.com/ziyunf...4.其他上面三个的首先是判断数据类型的通用方法。诸如如何判断数组、如何判断NaN、如何判断类数组对象、如何判断空对象等问题也会出现在面试中。这类问题比较开放,解决方法通常是抓住判断数据的核心特征。例如:判断类数组对象。你首先要知道JS中的类数组对象是什么样子的,求一个实际的引用,比如arguments是类数组对象。那么类数组对象的特点是:真值&对象&具有长度属性&长度为整数&长度范围大于等于0且小于等于安全正整数(Number.MAX_SAFE_INTEGER)。当你分析特征时,答案就呼之欲出了。【注意全面性】如何转换数据类型JS数据类型的动态将贯穿整个JS学习。这是JS的一个非常重要的特性。很多现象都是JS特有的,因为动态的存在。正是因为动态的特性,JS的数据类型可能会在你察觉不到的情况下发生变化,直到运行时报错。这里我们主要分析以下8种转换规则。1、if语句中的类型转换是最常见的。if(isTrue){//...}else{}在if语句中,会自动调用Boolean()转换函数,将变量isTrue进行转换。当isTrue的值为null,undefined,0,NaN,''时,会变为false。所有剩余的值,除了false本身,都变为true。2.Number()转换函数我们重点关注Number()下nullundefined和strings的转换:Number(null)//=>0Number(undefined)//=>NaNNumber('')//=>0Number('123')//=>123Number('123abc')//=>NaN注意和parseInt()比较。3.parseInt()parseInt(null)//=>NaNparseInt(undefined)//=>NaNparseInt('')//=>NaNparseInt('123')//=>123parseInt('123abc')//=>1234、==这里要注意:null==undefined//=>truenull==0//=>falseundefined==false//=>falsenull和undefined相等是ECMA-262规定的,null和undefined不能在比较相等时转换为任何其他值。5、对于两个字符串的比较,关系运算符是比较的字符编码值:'B'<'a'//=>true,a值,其他类型,会转为数字进行比较。两个布尔值转换为数值进行比较。object,先调用valueOf(),如果方法不存在则调用toString()。6.加法特别注意数字和字符串的加法,将数字转换成字符串。'1'+2=>//'12'1+2=>//3对于对象和布尔值,调用它们的toString()方法获取对应的字符串值,然后将字符串相加。在undefined和null上调用String()返回字符串'undeifned'和'null'。{value:1}+true//=>[objectObject]true"7.对于string、Boolean、null或undefined,减法自动调用Number()。如果转换结果为NaN,则最终结果为NaN。对于对象,首先调用valueOf(),如果得到NaN,则结果为NaN。如果没有valueOf(),则调用toString()。8.乘法和除法中对于非数值,会调用Number()转换函数。变量提升和临时死区JS中变量的声明方式有3种:var、let、const。var声明变量的特点之一是存在变量提升。console.log(a);//undefinedvara=1;console.log(a);//1***打印结果表明在声明变量a之前,a已经可以访问了,只是还没有赋值a价值。这就是变吊现象。(具体原因后面我分析作用域的时候会写)let和const没有这个问题,但是引入了临时死区的概念。/***以上都属于变量a的临时死区*console.log(a)//=>ReferenceError*/leta=1;console.log(a);//=>1表示声明前a、无法访问a,直接报错。临时死区的出现导致了另一个问题,即typeof不再安全。可以参考这篇文章http://es-discourse.com/t/why...补充:一道经典的面试题for(vari=0;i<4;i++){setTimeout(function(){console.log(i);},i*1000);}这里就不展开分析了,打算放到异步和事件循环机制中分析。但是这里把var换成let不失为一个解决办法。有兴趣的也可以先分析一下。对于const,这里多加一点,加深对基本类型和引用类型的理解。const=1;constb={value:1};a=2;//=>Errorb.value=2;//=>2b={value:2};//=>Error本质上const是不保证的变量的值不能改变,但是变量指向的内存地址不能改变。Declaraglobalvariable直接通过var来声明一个全局变量,它会作为window对象的一个??属性。vara=1;window.a//=>1这里提两个问题。一是,let声明的全局变量会不会成为window的属性?另外就是var声明的全局变量和window中直接创建的属性有区别吗?先回答***问题吧。用let声明的全局变量不会成为窗口的属性。用什么来支持这样的结论?在ES6中,用let和const声明的变量从一开始就形成了一个封闭的作用域。想想之前的临时死区。第二个问题是var声明的全局变量和直接在window上创建的属性有本质区别。先看下面这段代码:vara=1;window.a//=>1window.b=2;deletewindow.adeletewindow.bwindow.a//=>1window.b//=>undefined我们可以直接看到在window的Properties中创建的属性可以通过delete删除,而通过var创建的全局属性则不能。这是一个现象。透过现象看本质,两者的本质区别在于用var声明的全局变量的[[configurable]]数据属性的值为false,不能通过delete删除。直接在对象上创建的属性默认[[configurable]]值为true,即可以通过delete删除。(关于[[configurable]]属性,后面文章分析对象的时候会提到)总结在这篇《数据类型和变量》一文中,分析了7类。再回顾一下:基本类型、引用类型、参数传递方式、如何判断数据类型、如何转换数据类型、变量提升和临时死区、声明全局变量。这些不仅是校招面试中的高频考点,也是学习JS必不可少的知识点。
