这篇文章,来聊聊 JS 中的数据类型与变量。这是在学习 JS 时最基础的一类问题,但却很重要。但愿个人分享有帮助到你。html
文章开头,我先提几个面试中遇到的问题:前端
好比:如何理解参数的按值传递?面试
什么是暂时性死区?segmentfault
什么是变量提高?数组
全局变量和 window 的属性有什么区别?为何?安全
... ...微信
这篇文章的风格,在分析知识点的同时,插入一些我经历过的面试题。异步
在 JS 中,基本数据类型有 6 种,即数值、字符串、布尔值、null、undefined、Symbol。函数
对于基本数据类型,咱们须要明白的是:基本类型在内存中的存储方式是栈。每个值都是单独存放,互不影响。学习
基本类型都是按值访问的。在比较时,按值进行比较:
1 === 1 // true
引用类型的值保存在堆中,而引用是保存在栈中。
引用类型按引用访问。在比较时,也比较的引用:
{} === {} // => false
在 JS 中,参数能够是任何类型的值,甚至能够是函数。
这里要分析的是参数是以哪一种类型传递的?引用类型仍是基本类型?
先看一个基础的例子:
var out_num = 1; function addOne(in_num) { in_num += 1; return in_num; } addOne(out_num); // => 2 out_num // => 1
这个例子中,咱们给 addOne() 函数传递一个实参 out_num,这个时 out_num 会传递给 in_num,即内部存在着 in_num = out_num
的过程。最后咱们看到的结果是 out_num 并无被函数改变,说明 in_num 和 out_num 是两个在内存中独立存放的值,即按值传递。
再来看一个变形:
var out_obj = { value: 1 }; function addOne(in_obj) { in_obj.value += 1; return in_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
则是直接操做的实际对象。实际对象的改变,会同步到全部引用这个实际对象的引用。
你再来看这个例子,或许就会更清晰一些。
var out_obj = { value: 1 }; function addOne(in_obj) { in_obj = { value: 2 }; return in_obj; } addOne(out_obj); // => { value: 2 } out_obj // => { value: 1 }
你只要抓住一点:对象的赋值就会形成引用指向的实际对象发生改变。
判断数据类型,一般有三种具体的方法:
一、typeof 操做符
typeof 操做符返回一个表示数据类型的字符串。它存在如下明显的缺陷:
typeof null // => 'object' typeof [] // => 'object'
这是由于在 JS 语言设计之初遗留的 bug。能够阅读这篇文章 http://2ality.com/2013/10/typ... 了解更多关于 typeof 处理 null 的问题。
因此 typeof 最好用于判断一些基本类型,好比数值、字符串、布尔值、undefined、Symbol。
二、instanceof 操做符
typeof 的背后是经过判断 type tags 来判断数据类型,而 instanceof 则是经过判断构造函数的 prototype 是否出如今对象原型链上的任何位置。
举个例子:
{} instanceof Object // => true [] instanceof Array // => true [] instanceof Object // => true
也判断自定义类型:
function Car(make, model, year) { this.make = make; this.model = model; this.year = year; } var auto = new Car('Honda', 'Accord', 1998); console.log(auto instanceof Car); // => true console.log(auto instanceof Object); // => true
因此,对于字面量形式的基本数据类型,不能经过 instanceof 判断:
1 instanceof Number // => false Number(1) instanceof Number // => false new Number(1) instanceof Number // => true
三、Object.prototype.toString()
这是目前最为推荐的一种方法,能够更加精细且准确的判断任何数据类型,甚至是 JSON、正则、日期、错误等等。在 Lodash 中,其判断数据类型的核心也是 Object.prototype.toString() 方法。
Object.prototype.toString.call(JSON) // => "[object JSON]"
关于这背后的原理,你能够阅读这篇文章 http://www.cnblogs.com/ziyunf...
四、其余
上面三种是通用的判断数据类型的方法。面试中还会出现如何判断一个数组、如何判断 NaN、如何判断类数组对象、如何判断一个空对象等问题。这一类问题比较开放,解决思路一般是抓住判断数据的核心特色。
举个例子:判断类数组对象。
你先要知道 JS 中类数组对象是什么样子的,并寻求一个实际的参照物,好比 arguments 就是类数组对象。那么类数组对象具备的特色是:真值 & 对象 & 具备 length 属性 & length 为整数 & length 的范围大于等于 0,小于等于最大安全正整数(Number.MAX_SAFE_INTEGER)。
在你分析特色的时候,答案就呼之欲出了。【注意全面性】
JS 数据类型的动态性将贯穿整个 JS 的学习,这是 JS 很是重要的特性,不少现象就是由于动态性的存在而成为 JS 独有。
正是因为动态性,JS 的数据类型可能在你毫无察觉的状况下,就发生了改变,直到运行时报错。
这里主要分析下面 8 种转换规则。
一、if 语句
if 语句中的类型转换是最多见的。
if (isTrue) { // ... } else {}
在 if 语句中,会自动调用 Boolean() 转型函数对变量 isTrue 进行转换。
当 isTrue 的值是 null, undefined, 0, NaN, '' 时,都会转为 false。其他值除 false 自己外都会转为 true。
二、Number() 转型函数
咱们重点关注 null undefined 以及字符串在 Number() 下的转换:
Number(null) // => 0 Number(undefined) // => NaN Number('') // => 0 Number('123') // => 123 Number('123abc') // => NaN
注意和 parseInt() 对比。
三、parseInt()
parseInt(null) // => NaN parseInt(undefined) // => NaN parseInt('') // => NaN parseInt('123') // => 123 parseInt('123abc') // => 123
四、==
这里须要注意的是:
null == undefined // => true null == 0 // => false undefined == false // => false
null 与 undefined 的相等性是由 ECMA-262 规定的,而且 null 与 undefined 在比较相等性时不能转换为其余任何值。
五、关系操做符
对于两个字符串的比较,是比较的字符编码值:
'B' < 'a' // => true
一个数值,另外一个其余类型,都将转为数字进行比较。
两个布尔值转为数值进行比较。
对象,先调用 valueOf(),若不存在该方法,则调用 toString()。
六、加法
加法中特别注意的是,数字和字符串相加,将数字转为字符串。
'1' + 2 => // '12' 1 + 2 => // 3
对于对象和布尔值,调用它们的 toString() 方法获得对应的字符串值,而后进行字符串相加。对于 undefined 和 null 调用 String() 取得字符串 'undeifned' 和 'null'。
{ value: 1 } + true // => "[object Object]true"
七、减法
对于字符串、布尔值、null 或者 undefined,自动调用 Number(),转换结果若为 NaN,那么最终结果为 NaN。
对于对象,先调用 valueOf(),若是获得 NaN,结果为 NaN。若是没有 valueOf(),则调用 toString()。
八、乘法、除法
对于非数值,都会调用 Number() 转型函数。
JS 中有三种声明变量的方式:var, let, const。
var 声明变量最大的一个特色是存在变量提高。
console.log(a); // undefined var a = 1; console.log(a); // 1
第一个打印结果表示,在声明变量 a 以前,a 就已经能够访问了,只不过并未赋值。这就是变量提高现象。(具体缘由,我放在后面分析做用域的时候来写)
let 和 const 就不存在这个问题,可是又引入了暂时性死区这样的概念。
/** * 这上面都属于变量 a 的暂时性死区 * console.log(a) // => Reference Error */ let a = 1; console.log(a); // => 1
即声明 a 以前,不可以访问 a,而直接报错。
而暂时性死区的出现又引出另一个问题,即 typeof 再也不安全。你能够参考这篇文章 http://es-discourse.com/t/why...
补充:一个经典面试题
for (var i = 0; i < 4; i++) { setTimeout(function(){ console.log(i); }, i * 1000); }
我先再也不这里展开分析,我打算放到异步与事件循环机制中去分析。不过这里将 var 替换成 let 能够做为一种解决方案。若是你有兴趣,也能够先去分析。
对于 const,这里再补充一点,用于加深对基本类型和引用类型的理解。
const a = 1; const b = { value: 1 }; a = 2; // => Error b.value = 2; // => 2 b = { value: 2 }; // => Error
本质上,const 并非保证变量的值不得改动,而是变量指向的内存地址不得改动。
直接经过 var 声明全局变量,这个全局变量会做为 window 对象的一个属性。
var a = 1; window.a // => 1
在这里提出两个问题,一是 let 声明的全局变量会成为 window 的属性吗?二是 var 声明的全局变量和直接在 window 建立属性有没有区别?
先来回答第一问题。let 声明的全局变量不会成为 window 的属性。用什么来支撑这样的结论呢?在 ES6 中,对于 let 和 const 声明的变量从一开始就造成封闭做用域。想一想以前的暂时性死区。
第二个问题,var 声明的全局变量和直接在 window 建立属性存在着本质的区别。先看下面的代码:
var a = 1; window.a // => 1 window.b = 2; delete window.a delete window.b window.a // => 1 window.b // => undefined
咱们能够看到,直接建立在 window 上的属性能够被 delete 删除,而 var 建立的全局属性则不会。这是现象,经过现象看本质,两者本质上的区别在于:
使用 var 声明的全局变量的 [[configurable]] 数据属性的值为 false,不能经过 delete 删除。而直接在对象上建立的属性默认 [[configurable]] 的值为 true,便可以被 delete 删除。(关于 [[configurable]] 属性,在后面的文章中分析对象的时候还会提到)
在这篇「数据类型与变量」文章中,分析了 7 个大类。再来回顾一下:
基本类型、引用类型、参数传递方式、如何判断数据类型、数据类型如何转换、变量提高与暂时性死区、声明全局变量。
这些不只是校招面试中的高频考点,也是学习 JS 必不可少的知识点。
Tip1:《JavaScript 高级程序设计》这本书被称做“前端的圣经”是有缘由的。对于正在准备校园招聘的你,很是有必要!书读百遍,其义自见。你会发现你在面试中遇到的绝大部分 JS 相关的知识点都能在这本书中找到“答案”!
Tip2:在准备复习的过程当中,注意知识的模块性与相关性。你得有本身划分知识模块的能力,好比今天的「数据类型与变量」模块。相关性是指,任何的知识都是由联系的,好比这里牵涉到做用域、内存等模块。
这篇文章会不断更新,若有出入,也但愿你在后台留言,或者 Email 给我:
swpu.leo@gmail.com
文章首发在 cameraee 微信公众号
并同步更新至如下平台:
CSDN https://blog.csdn.net/swpu_leo
Segment fault https://segmentfault.com/u/sw...
掘金 https://juejin.im/user/58bd20...