js数据类型与隐式类型转换
执行环境
执行环境是js中最为重要的一个概念。执行环境定义了变量或函数有权访问的其他数据,决定了它们各自的行为。每个执行环境都有一个与之关联的变量对象(variable object),环境中定义的所有变量和函数都保存在这个对象中。虽然我们写的代码无法访问这个对象,但解析器在处理数据时会在后台使用它。
全局执行环境
全局执行环境是最外围的一个执行环境。根据ECMAScript实现所在的宿主环境不同,表示执行环境的对象也不一样。在Web浏览器中,全局执行环境被认为是window对象,因此所有全局变量和函数都是作为window对象的属性和方法创建的。某个执行环境中的所有代码执行完毕后,该环境被销毁,保存在其中的所有变量和函数定义也随之销毁(全局执行环境直到应用程序退出---例如关闭网页或浏览器时才会被销毁)。
函数执行环境
每个函数都有自己的执行环境。当执行流进入一个函数时,函数的环境就会被推入一个环境栈中。而在函数执行之后,栈将其环境弹出,把控制权返回给之前的执行环境。ECMAScript程序中的执行例句正式由这个方便的机制控制这。
堆栈内存
- js中有两大内存:堆内存(heap)、栈内存(stack)
- 堆内存作用:用来存储内容的(对象存储的是键值对,函数存储的是代码字符串)
- 栈内存作用:也可以被成为作用域,是代码解析和执行的环境
堆栈内存的释放问题
堆内存:
堆内存的释放只要没有变量占用这个堆内存,浏览器就会在空闲的时候把它释放掉,所以在项目中我们尽可能把不被使用的堆内存手动释放掉。
栈内存:
函数执行会形成一个私有的占内存,一般函数执行完成,栈内存会自己释放,除非栈内存中存在某一个东西(例如:栈内存中开辟的堆内存)被栈内存意外的变量(或者是其他东西)占用了,此时的栈内存就不能被释放掉;全局作用域(栈内存)是在一开始加载js的时候诞生的,当前页面关闭的时候会自动释放掉。
基础数据类型
js中的基础数据类型,这些值都有固定的大小,往往都是保存在内存空间中,由系统自动分配存储空间。我们可以直接操作保存在栈内存空间的值,因此基础数据类型都是按值访问
基础数据类型:Number String Undefined Boolean
引用数据类型
由于引用数据类型存储的值过于复杂(结构复杂即内容较多),渲染引擎会开辟一个新的内存空间,单独来存储这些值,最后把内存空间引用地址赋值给对应的变量,后期所有的操作都是基于地址找到空间,然后对空间中的内容进行操作。
引用数据类型:Object Array Date RegExp Math Function
对象数据类型
- 开辟一个内存空间(有一个16进制的地址)
- 把对象中的属性名和属性值一次存储到内存空间中
- 把内存空间的地址赋值给变量
函数数据类型
- 开辟一个内存空间(有一个16进制的地址)
- 把函数体中的代码当作"字符串"存储到内存空间中
- 函数创建的时候都是无意义的字符串所以说函数只创建不执行是毫无意义的
- 变量提升只对当前作用域下的
var/function
处理,主要原因是函数中存的都是字符串,我们看到的函数中的var/function
此时还都是字符串呢 - 当函数执行的时候,就是要把这堆字符串拿出来执行的
- 把内存空间地址赋值给变量
在前端面试中经常会遇到这样一个类似的题目
var a = 10;
var b = a;
b = 20;
// 这时a的值是多少?
在栈内存中的数据发生复制行为时,系统会自动为新的变量分配一个新值。var b = a
执行之后,a与b的值都等于10,但是他们其实已经是相互独立互不影响的值了。
var a = {name:'小明',age:'10' };
var b = a;
b.name = '小红'
// 这时a.name的值是多少?
我们通过var b = a
执行一次复制引用类型的操作。引用类型的复制同样也会为新的变量自动分配一个新的值保存在栈内存中,但是不同的是,这个新的值,仅仅只是引用类型的一个地址指针。当地址指针相同时,尽管他们相互独立,但是在堆内存中访问到的具体对象实际上是同一个。
隐式类型转换
js中的数据类型是非常弱的,在使用算数运算符时,运算符两边的数据类型可以是任意的,比如,一个字符串可以和一个数字相加。之所以不同的数据类型之间可以做运算,是因为js引擎在运算之前会悄悄地把他们进行了隐式类型转换。
我们先来看机组典型的js中运算符操作结果
console.log([] == []) // false
console.log([] == ![]) // true
console.log([] !== []) // true
console.log(NaN != NaN) // true
console.log(null == undefined) // true
console.log(null === undefined) // false
console.log(1 == true) // true
console.log(null > 0) // false
console.log(true + 1) // 2
console.log(undefined + 1) // NaN
console.log({} + 1) // [object Object]1
console.log([] + {}) // [object Object]
console.log([2,3] + [1,2]) // 2,31,2
原始值 | 转化为值类型 | 转化为字符串 | 转化为Boolean |
---|---|---|---|
false | 0 | "false" | false |
true | 1 | "true" | true |
0 | 0 | "0" | false |
1 | 1 | "1" | true |
"0" | 0 | "0" | true |
NaN | NaN | "NaN" | false |
Infinity | Infinity | "Infinity" | true |
"" | 0 | "" | false |
[ ] | 0 | "" | true |
[20] | 20 | "20" | true |
function(){} | NaN | "function(){}" | true |
{} | NaN | "[object Object]" | true |
null | 0 | "null" | false |
undefined | NaN | "undefined" | false |
比较运算
js为我们提供了严格比较与类型转换比较两种模式,严格比较(===)只会在操作符两侧的操作对象类型一致,并且内容一致时才会返回
true
,否则返回false。而更为广泛使用的==
操作符则会首先将操作对象转化为相同类型,在进行比较。
相等操作符会对操作值进行隐式转换后进行比较:
- 如果一个操作值为布尔值,则在比较之前先将其转换为数值
- 如果一个操作值为字符串,另一个操作值为数值,则通过Number()函数将字符串转换为数值
- 如果一个操作值是对象,另一个不是,则调用对象的valueOf()方法,得到的结果按照前面的规则进行比较
- null与undefined是相等的
- 如果一个操作值为NaN,则相等比较返回false
- 如果两个操作值都是对象,则比较它们是不是指向同一个对象
加法运算操作符:
加法运算符在js中也用作字符串拼接,所以加法操作符的规则分为两种
- 如果一个数是NaN,则结果就是NaN
- 如果是Infinity+Infinity,结果是Infinity
- 如果是-Infinity+(-Infinity),结果是-Infinity
- 如果是Infinity+(-Infinity),结果是NaN
如果有一个操作值是字符串,则: - 如果两个都是字符串则,拼接起来
- 一个有一个操作值是字符串,则将另外的值转换成字符串,然后拼接起来
- 如果又一个值是对象、数值或者布尔值,则调用toString()方法取得字符串值,然后再应用前面的字符串规则。对于undefined和null,分别调用String()显式转换为字符串。
逻辑操作符(!、&&、||)
逻辑非(!)操作符首先通过Boolean()函数将它的操作值转换为布尔值,然后求反。
逻辑与(&&)操作符,如果一个操作值不是布尔值时,遵循以下规则进行转换:
- 如果第一个操作数经Boolean()转换后为true,则返回第二个操作值,否则返回第一个值(不是Boolean()转换后的值)
- 如果有一个操作值为null,返回null
- 如果有一个操作值为NaN,返回NaN
- 如果有一个操作值为undefined,返回undefined
逻辑或(||)操作符,如果一个操作值不是布尔值,遵循以下规则:
- 如果第一个操作值经Boolean()转换后为false,则返回第二个操作值,否则返回第一个操作值(不是Boolean()转换后的值)
- 对于undefined、null和NaN的处理规则与逻辑与(&&)相同
关系操作符(<, >, <=, >=)
与上述操作符一样,关系操作符的操作值也可以是任意类型的,所以使用非数值类型参与比较时也需要系统进行隐式类型转换:
- 如果两个操作值都是数值,则进行数值比较
- 如果两个操作值都是字符串,则比较字符串对应的字符编码值
- 如果只有一个操作值是数值,则将另一个操作值转换为数值,进行数值比较
- 如果一个操作数是对象,则调用valueOf()方法(如果对象没有valueOf()方法则调用toString()方法),得到的结果按照前面的规则执行比较
- 如果一个操作值是布尔值,则将其转换为数值,再进行比较