JavaScript 基础问题:数据类型与变量

2018-11-1511:35:14WEB前端开发Comments1,923 views字数 5428阅读模式

JS 中的数据类型与变量。这是最基础的一类问题,但却很重要。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7866.html

比如:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7866.html

如何理解参数的按值传递?文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7866.html

什么是暂时性死区?文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7866.html

什么是变量提升?文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7866.html

全局变量和 window 的属性有什么区别?为什么?文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7866.html

... ...文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7866.html

以上的问题均来自面试。如果你并不清楚,我觉得你有必要接着读下去。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7866.html

基本数据类型

在 JS 中,基本数据类型有 6 种,即数值、字符串、布尔值、null、undefined、Symbol。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7866.html

对于基本数据类型,我们需要明白的是:基本类型在内存中的存储方式是栈。每一个值都是单独存放,互不影响。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7866.html

基本类型都是按值访问的。在比较时,按值进行比较:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7866.html

1 === 1 // true
复制代码

引用数据类型

引用类型的值保存在堆中,而引用是保存在栈中。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7866.html

引用类型按引用访问。在比较时,也比较的引用:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7866.html

{} === {} // => false
复制代码

参数的传递方式

在 JS 中,参数可以是任何类型的值,甚至可以是函数。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7866.html

这里要分析的是参数是以哪种类型传递的?引用类型还是基本类型?文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7866.html

先看一个基础的例子:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7866.html

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 是两个在内存中独立存放的值,即按值传递。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7866.html

再来看一个变形:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7866.html

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 }
复制代码

问题来了?函数参数不是按值传递吗?为什么这里函数内部的处理反映到外部了?这是一个超级超级超级的理解误区。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7866.html

**首先,我们还是得摆正观点,即函数参数是按值传递的。**那这里怎么理解呢?对于引用类型而言,前面说引用类型分为引用和实际的内存空间。在这里 out_obj 依旧传递给 in_obj,即 in_obj = out_objout_obj 和 in_obj 是两个引用,它们在内存中的存储方式是独立的,但是它们却指向同一块内存。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7866.html

in_obj.value = 1 则是直接操作的实际对象。实际对象的改变,会同步到所有引用这个实际对象的引用。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7866.html

JavaScript 基础问题:数据类型与变量
JavaScript 基础问题:数据类型与变量

你再来看这个例子,或许就会更清晰一些。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7866.html

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 }
复制代码

你只要抓住一点:对象的赋值就会造成引用指向的实际对象发生改变。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7866.html

如何判断数据类型

判断数据类型,通常有三种具体的方法:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7866.html

1、typeof 操作符文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7866.html

typeof 操作符返回一个表示数据类型的字符串。它存在以下明显的缺陷:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7866.html

typeof null // => 'object'

typeof [] // => 'object'
复制代码

这是因为在 JS 语言设计之初遗留的 bug。可以阅读这篇文章 2ality.com/2013/10/typ… 了解更多关于 typeof 处理 null 的问题。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7866.html

所以 typeof 最好用于判断一些基本类型,比如数值、字符串、布尔值、undefined、Symbol。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7866.html

2、instanceof 操作符文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7866.html

typeof 的背后是通过判断 type tags 来判断数据类型,而 instanceof 则是通过判断构造函数的 prototype 是否出现在对象原型链上的任何位置。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7866.html

举个例子:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7866.html

{} instanceof Object // => true

[] instanceof Array // => true
[] instanceof Object // => true
复制代码

也判断自定义类型:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7866.html

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 判断:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7866.html

1 instanceof Number // => false

Number(1) instanceof Number // => false

new Number(1) instanceof Number // => true
复制代码

3、Object.prototype.toString()文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7866.html

这是目前最为推荐的一种方法,可以更加精细且准确的判断任何数据类型,甚至是 JSON、正则、日期、错误等等。在 Lodash 中,其判断数据类型的核心也是 Object.prototype.toString() 方法。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7866.html

Object.prototype.toString.call(JSON) // => "[object JSON]"
复制代码

关于这背后的原理,你可以阅读这篇文章 www.cnblogs.com/ziyunfei/ar…文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7866.html

4、其他文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7866.html

上面三种是通用的判断数据类型的方法。面试中还会出现如何判断一个数组、如何判断 NaN、如何判断类数组对象、如何判断一个空对象等问题。这一类问题比较开放,解决思路通常是抓住判断数据的核心特点。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7866.html

举个例子:判断类数组对象。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7866.html

你先要知道 JS 中类数组对象是什么样子的,并寻求一个实际的参照物,比如 arguments 就是类数组对象。那么类数组对象具有的特点是:真值 & 对象 & 具有 length 属性 & length 为整数 & length 的范围大于等于 0,小于等于最大安全正整数(Number.MAX_SAFE_INTEGER)。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7866.html

在你分析特点的时候,答案就呼之欲出了。【注意全面性】文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7866.html

数据类型如何转换

JS 数据类型的动态性将贯穿整个 JS 的学习,这是 JS 非常重要的特性,很多现象就是因为动态性的存在而成为 JS 独有。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7866.html

正是由于动态性,JS 的数据类型可能在你毫无察觉的情况下,就发生了改变,直到运行时报错。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7866.html

这里主要分析下面 8 种转换规则。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7866.html

1、if 语句文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7866.html

if 语句中的类型转换是最常见的。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7866.html

if (isTrue) {
    // ...
} else {}
复制代码

在 if 语句中,会自动调用 Boolean() 转型函数对变量 isTrue 进行转换。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7866.html

当 isTrue 的值是 null, undefined, 0, NaN, '' 时,都会转为 false。其余值除 false 本身外都会转为 true。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7866.html

2、Number() 转型函数文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7866.html

我们重点关注 null undefined 以及字符串在 Number() 下的转换:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7866.html

Number(null) // => 0
Number(undefined) // => NaN
Number('') // => 0
Number('123') // => 123
Number('123abc') // => NaN
复制代码

注意和 parseInt() 对比。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7866.html

3、parseInt()文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7866.html

parseInt(null) // => NaN
parseInt(undefined) // => NaN
parseInt('') // => NaN
parseInt('123') // => 123
parseInt('123abc') // => 123
复制代码

4、==文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7866.html

这里需要注意的是:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7866.html

null == undefined // => true

null == 0 // => false
undefined == false // => false
复制代码

null 与 undefined 的相等性是由 ECMA-262 规定的,并且 null 与 undefined 在比较相等性时不能转换为其他任何值。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7866.html

5、关系操作符文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7866.html

对于两个字符串的比较,是比较的字符编码值:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7866.html

'B' < 'a' // => true
复制代码

一个数值,另一个其他类型,都将转为数字进行比较。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7866.html

两个布尔值转为数值进行比较。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7866.html

对象,先调用 valueOf(),若不存在该方法,则调用 toString()。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7866.html

6、加法文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7866.html

加法中特别注意的是,数字和字符串相加,将数字转为字符串。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7866.html

'1' + 2 => // '12'
1 + 2 => // 3
复制代码

对于对象和布尔值,调用它们的 toString() 方法得到对应的字符串值,然后进行字符串相加。对于 undefined 和 null 调用 String() 取得字符串 'undeifned' 和 'null'。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7866.html

{ value: 1 } + true // => "[object Object]true"
复制代码

7、减法文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7866.html

对于字符串、布尔值、null 或者 undefined,自动调用 Number(),转换结果若为 NaN,那么最终结果为 NaN。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7866.html

对于对象,先调用 valueOf(),如果得到 NaN,结果为 NaN。如果没有 valueOf(),则调用 toString()。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7866.html

8、乘法、除法文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7866.html

对于非数值,都会调用 Number() 转型函数。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7866.html

变量提升与暂时性死区

JS 中有三种声明变量的方式:var, let, const。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7866.html

var 声明变量最大的一个特点是存在变量提升。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7866.html

console.log(a); // undefined
var a = 1;
console.log(a); // 1
复制代码

第一个打印结果表示,在声明变量 a 之前,a 就已经可以访问了,只不过并未赋值。这就是变量提升现象。(具体原因,我放在后面分析作用域的时候来写)文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7866.html

let 和 const 就不存在这个问题,但是又引入了暂时性死区这样的概念。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7866.html

/**
* 这上面都属于变量 a 的暂时性死区
* console.log(a) // => Reference Error
*/
let a = 1;
console.log(a); // => 1
复制代码

即声明 a 之前,不能够访问 a,而直接报错。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7866.html

**而暂时性死区的出现又引出另外一个问题,即 typeof 不再安全。**你可以参考这篇文章 es-discourse.com/t/why-typeo…文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7866.html

补充:一个经典面试题文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7866.html

for (var i = 0; i < 4; i++) {
    setTimeout(function(){
        console.log(i);
    }, i * 1000);
}
复制代码

我先不再这里展开分析,我打算放到异步与事件循环机制中去分析。不过这里将 var 替换成 let 可以作为一种解决方案。如果你有兴趣,也可以先去分析。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7866.html

对于 const,这里再补充一点,用于加深对基本类型和引用类型的理解。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7866.html

const a = 1;
const b = { value: 1 };

a = 2; // => Error
b.value = 2; // => 2
b = { value: 2 }; // => Error
复制代码

本质上,const 并不是保证变量的值不得改动,而是变量指向的内存地址不得改动。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7866.html

声明全局变量

直接通过 var 声明全局变量,这个全局变量会作为 window 对象的一个属性。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7866.html

var a = 1;
window.a // => 1
复制代码

在这里提出两个问题,一是 let 声明的全局变量会成为 window 的属性吗?二是 var 声明的全局变量和直接在 window 创建属性有没有区别?文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7866.html

先来回答第一问题。**let 声明的全局变量不会成为 window 的属性。**用什么来支撑这样的结论呢?**在 ES6 中,对于 let 和 const 声明的变量从一开始就形成封闭作用域。**想想之前的暂时性死区。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7866.html

第二个问题,**var 声明的全局变量和直接在 window 创建属性存在着本质的区别。**先看下面的代码:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7866.html

var a = 1;
window.a // => 1

window.b = 2;

delete window.a
delete window.b

window.a // => 1
window.b // => undefined
复制代码

我们可以看到,**直接创建在 window 上的属性可以被 delete 删除,而 var 创建的全局属性则不会。**这是现象,通过现象看本质,二者本质上的区别在于:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7866.html

使用 var 声明的全局变量的 [[configurable]] 数据属性的值为 false,不能通过 delete 删除。而直接在对象上创建的属性默认 [[configurable]] 的值为 true,即可以被 delete 删除。(关于 [[configurable]] 属性,在后面的文章中分析对象的时候还会提到)文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7866.html

小结

在这篇**「数据类型与变量」**文章中,分析了 7 个大类。再来回顾一下:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7866.html

基本类型、引用类型、参数传递方式、如何判断数据类型、数据类型如何转换、变量提升与暂时性死区、声明全局变量。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7866.html

这些不仅是校招面试中的高频考点,也是学习 JS 必不可少的知识点。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7866.html

Tip1:《JavaScript 高级程序设计》这本书被称作“前端的圣经”是有原因的。对于正在准备校园招聘的你,非常有必要!**书读百遍,其义自见。**你会发现你在面试中遇到的绝大部分 JS 相关的知识点都能在这本书中找到“答案”!文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7866.html

Tip2:在准备复习的过程中,注意知识的模块性与相关性。你得有自己划分知识模块的能力,比如今天的「数据类型与变量」模块。相关性是指,任何的知识都是由联系的,比如这里牵涉到作用域、内存等模块。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7866.html

作者:OLeo
链接:https://juejin.im/post/5bec3299e51d4552da47be00
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7866.html

文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7866.html
  • 本站内容整理自互联网,仅提供信息存储空间服务,以方便学习之用。如对文章、图片、字体等版权有疑问,请在下方留言,管理员看到后,将第一时间进行处理。
  • 转载请务必保留本文链接:https://www.cainiaoxueyuan.com/gcs/7866.html

Comment

匿名网友 填写信息

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定