一、数据类型转换
数据类型分类:
- 基本数据类型:
Number
String
Boolean
null
undefined
symbol
bigint
- 引用数据类型:
object
function
1.1 其它数据类型转换为Number类型规则
- 手动转换
- Number([value])
- ParseInt/ParseFloat([value])
- 隐式转换(浏览器内部默认要先转换成Number再进行计算的)
- isNaN([value])
- 数学运算(+前后如果出现字符串就是字符串拼接,不属于数学运算)
- 在 == 比较时,有些值需要转换为数字再进行比较
1.2.1 其他类型转换为数字规则
- Boolen转换为数字
- true -> 1
- false -> 0
- null 转换为数字
- null -> 0
- undefined 转换为数字
- undefined -> NaN
- 字符串转换为数字
- 字符串中只有全是有效数字,才能正常转换
"12" -> 12
"000012" -> 12
- 字符串中只要出现任何一个非有效数字字符,结果都为NaN
"12px" -> NaN
- 字符串中只有全是有效数字,才能正常转换
- 对象转换为数字,会先转换成字符串,再转换为数字
1.2.2 parseInt / parseFloat
parseInt([value])
把value转换为数字(内核机制:需要把value先转换为字符串,然后从字符串左侧第一个字符开始查找,把找到的有效数字字符转换为数字,直到遇到一个非有效数字字符为止);如果value不是字符串先转换为字符串,再转换为数字parseInt([value], [n])
把[value]看做[n]进制的数据,最后转换为十进制;-
如果[n]不写,默认是10(即默认十进制),0和10一样,都是十进制
-
特殊情况:如果字符串是以
0x
开头,则默认是16进制 -
[n] 范围 0~36之间,如果不在这个范围,则结果是NaN 2
-
1.2.3 关于NaN
- 任何一个数和NaN相加,结果都是NaN
1.2 其它数据类型转换为String类型规则
- 能使用的方法
- toString()
- String()
- 隐式转换(一般都是调用toString())
- 加号运算时候,如果某一边出现字符串,则是字符串拼接
- 把对象转换为数字,需要先toString()转换为字符串,再转换为数字
- 基于alert/confirm/prompt/document.write...这些方式输出内容,都是先转换为字符串,再输出内容的
- 空数组转换为字符串
[] -> ""
- 空对象转换为字符串(调用toString())
{} -> "[object Object]"(检测数据类型)
1.3 其它数据类型转换为Boolean类型规则
- 基于以下方式可以把其他数据类型转换为Boolean
- ! 转换为布尔值后取反
- !! 转换为布尔类型
- Boolean([value])
- 隐式转换
- 在循环或者条件判断中,条件处理的结果就是Boolean值类型
- 规则:只有
0
NaN
null
undefined
空字符串""
这五个值转换成布尔值为false,其余都为true
1.4 在==(弱比较)比较的过程中,数据转换的规则
- 类型一样的几个特殊点:
{} == {} -> false
对象比较的是堆内存的地址[] == [] -> false
NaN == NaN -> false
- 类型不一样的转换规则
null == undefined -> true
但是换成===结果是false(因为数据类型不一致),剩下null/undefined和其他任何数据类型都不相等- 字符串 == 对象 要把对象转换为字符串
- 剩下如果==两边数据类型不一致,都需要转换为数字再进行比较
二、数据类型面试题
面试题1
let res = 100 + true + 21.1 + null + undefined + "Hello" + [] + null + 9 + false;
console.log(res) // NaNHellonull9false
面试题2
console.log([] == false) // true
console.log(![] == false) // true
面试题3
var a = ?;(在a == 什么的情况下才能让以下判断成立)
if(a == 1 && a == 2 && a == 3){
console.log(1);
}
var a = {
i: 0,
toString() {
return ++this.i
}
};
if (a == 1 && a == 2 && a == 3) {
console.log('ok');
}
// 对象转换为字符串:valueOf() toString()
// a.toString() //=> Object.prototype.toString() 检测数据类型的
// 总体实现思路:重写对象原型上的toString方法,给一个初始值,然后让a的值每次累加
面试题4
let arr = [10.18, 0, 10, 25, 23];
arr = arr.map(parseInt);
console.log(arr); // [10, NaN, 2, 2, 11]
// 把parseInt当做一个回调函数,第一个参数是数组中的当前项,第二个参数是当前项的索引
/*
* parseInt(10.18, 0) => 10
* parseInt(0, 1) => NaN
* parseInt(10, 2) => '10' 二进制 转换为 十进制 2
* parseInt(25, 3) => '25' 三进制 转换为 十进制 2
* parseInt(23, 4) => '23' 四进制 转换为 十进制 11
* 输出 [10, NaN, 2, 2, 11]
*/
三、堆栈内存
3.1 关于堆栈内存
- 浏览器会在计算机内存中开辟一块内存,专门用来执行JS代码的 => 栈内存:执行环境栈(ECStack)
- EC(G) => 全局执行上下文,用来供全局代码执行
- 形成上下文的目的:区分不同区域的代码执行
- VO(G) => 全局变量对象:存储全局上下文中声明的变量
- 当前区域的执行上下文形成后会进栈执行,有的执行上下文在代码执行完成之后会出栈
- 在代码执行之前,先做词法分析及变量提升,然后代码执行;如果是函数执行,还要做初始化作用域链,形参赋值,初始化arguments,初始化this,变量提升等
- 基本数据类型直接存储到栈内存;引用数据类型,浏览器会为它单独开辟一块内存:堆内存
- 每一个堆内存都有个16进制堆内存地址,把键值对分别存储到堆中,把16进制地址放到栈内存中存储:方便后期变量的关联
- 栈内存 还是 堆内存,都是浏览器从计算机中分配出来的内存,开辟的越多,电脑性能越慢 -> 性能优化:内存优化
- 栈内存:代码执行 & 存储基本类型值
- 堆内存:存储引用数据类型值
3.2 关于变量提升
- 声明就是创建变量,定义就是给变量赋值
- 变量提升:当前上下文代码执行之前,会把
var/function
声明或定义,带var的只声明,带function的声明+定义;如果遇到了{}(块级作用域)
,新老浏览器的表现不一致(兼容ES3、ES6) - 老浏览器:IE浏览器 <= IE10:
- 不管{},还是一如既往地function声明+定义,而且也不会存在块级作用域
- 新版本浏览器
- {}块级作用域下的function只声明不定义
- {}块级作用域中出现function/let/const会创建一个块级上下文
四、堆栈内存面试题
面试题1
let a = { n: 1 };
let b = a;
a.x = a = { n: 2 };
console.log(a.x); // undefined
console.log(b); // {n: 2}
解析:
代码运行时,浏览器会为运行代码提供一个环境,叫做
ECStack执行环境栈
,即栈内存
ECStack执行环境栈
中有EC(G)全局执行上下文
,在EC(G)全局执行上下文
中的执行变量赋值操作因为变量赋值永远是,先创建值,再赋值给变量
基本数据类型在栈内存即全局上下文中,引用数据类型因为数据过于复杂,浏览器会为它专门开辟堆内存
所以本题,第一步
let a = { n: 1 };
,浏览器会先开辟一个堆内存,把键值对n: 1
存储到这个堆内存中,堆内存有个十进制的内存地址为AAAFFF000
,变量a
指向地址AAAFFF000
第二步
let b = a;
, 变量b也指向a的内存地址,即b = AAAFFF000
第三步
a.x = a = { n: 2 };
,由于带成员访问的要优先处理,即此行可拆解为a.x = {n:2}
和a = {n:2}
,浏览器又开辟一个堆内存,把键值对{n: 2}
存储到这个堆内存中, 此堆内存有个新的地址为AAAFFF111
;
a.x = {n:2}
: 在a即AAAFFF000
中,创建属性x, 属性值为AAAFFF111
a = {n:2}
: a又重新指向内存地址AAAFFF111
(即原内存地址AAAFFF000
已不存在)此时 a 指向内存地址
AAAFFF111
,其中只有键值对{n: 2}
,所以a.x 为undefined b 也为AAAFFF111
,即{n: 2}
面试题2
var a = 0;
if (true) {
a = 1;
function a() { };
a = 21;
console.log(a);
}
console.log(a); // 21 1
解析:
- 如果是<=IE10版本浏览器:
- 变量提升:function a() { };
- a = 0
- a = 1
- a = 21
- 输出 21 21
- 如果是新版本浏览器:
解析:
var a = 0;
首先,在EC(G)全局执行上下文
进行变量提升,声明var a 及 function a; 在VO(G)全局变量对象
中,把0赋值给a
if (true) { }
-> 浏览器开辟了一个块级作用域EC(BLOCK)
(私有作用域),里面放置私有变量AO(BLOCK)
在块级作用域中,同样先进行变量提升:首先声明+定义function a(){},即浏览器开辟的一个函数a的堆内存,并把a指向这个堆内存地址
然后按照顺序执行代码:
a = 1;
a进行重新赋值,原内存地址舍弃function a() { };
,此时函数a已经在块级作用域下进行过变量提升,不再执行,但是遇到这行代码后,会把代码之前所有对a的操作映射给全局一份,后面的则不会再处理了(认为后面的都是自己私有的了),即此时,全局下的a也指向了1继续执行代码
a = 21;
,即私有域中,21又赋值给了a所以此时输出,21 (私有域) 1(全局)
面试题3
var x = 1;
function func(x, y = function anonymous1() { x = 2 }) {
x = 3;
y();
console.log(x);
}
func(5)
console.log(x);
// 2 1
var x = 1;
function func(x, y = function anonymous1() { x = 2 }) {
var x = 3;
y();
console.log(x);
}
func(5)
console.log(x);
// 3 1