前言
一直以来由于浮躁的内心, 导致了我JavaScript基础并不牢靠, 年前决定沉下心来, 把JavaScript所有的重难点搞定, 目前已完成大部分的内容学习. 于是打算通过写文章的形式, 再次复习一边.
本文分享的内容为: JavaScript数据类型的细节点, 我将通过各种脑图
与面试题
作为基准点, 梳理数据类型中遇到的问题. 观看本文可以复习基础内容, 还可以练习各种面试题哦! 我将会在题目放出的下方, 放答案, 方便大家自己练习👇👇
我将把这段时间关于JavaScript学习的笔记上传到个人Github/Learn-Javascript, 欢迎关注.
目录
基本数据类型
Number
Number类型难点在于数据类型的转换以及小数问题, 掌握这两点也就没有太大问题了.
目录:
数据类型转换:
1. 显式转换:
显式转换并非一个难点只是为了隐式转换做一个铺垫, 姑且以看.通过调用Number([val])
方法或者在值加一个+
号, 可以将其他类型转换为数字类型:
-
String类型 转数字:
- 数字字符串 则转换为 数字
- 空字符 转换为 0
- 其余类型全部转换为 NaN
-
Boolean类型 转数字:
- false 转换为 0
- true 转换为 1
-
null 与 undefined 转数字:
- null 转换为 0
- undefined 转换为 NaN
-
Symbol 与 BigInt:
- Symbol 会直接报错
- BigInt 正常转换
2. 隐式转换:
隐式转换是数字类型与字符串类型共有的一种转换形式, 面试题最常出现的地方, 需要牢记.
什么时候会出现隐式转换?
当我们做相等性测试(==), 或者对象类型与数字相加时,可能就会出现隐式转换, 可能有同学会疑惑, 对象类型与数字相加一般发生的不都是字符串拼接吗? 其实不是的, 如:
console.log( 10 + new Number(10) ) // 20
上述例子中, 后者是一个非标准特殊对象, 因为在实例对象Number {10}
中通过隐式转换得到了对象的原始值10
,得以正常相加, 这样的说法可能转牛角尖了, 但是更加严谨.
隐式转换过程:
-
访问对象的 [Symbol.toPrimitive] 属性, 如不存在则
-
调用对象的 ValueOf() 方法获取对象的原始值, 如不存在则
-
调用对象的 toString() 方法转换为字符串, 如需要转换为
Number类型
则 -
调用对象的 Number() 处理
因为在 步骤3 中会将对象转换为字符串类型, 这也是为什么很多时候让数字类型
去加一个对象, 会莫名变成字符串的原因, 当然还会有些问题, 我将在字符串拼接的时候,详细阐述
console.log(10 + {}); // 10[object Object]
3. 数字类型检测:
isNaN() 用来判断一个值是否为
非有效数字
, 它在内部会对值进行类型转换, 同样对于对象类型也遵循隐式转换规则
上题:
请让条件体内的 “ok” 正常输出
var a = {};
if (a == 1 && a == 2 && a == 3) {
console.log('OK');
}
答案:
- 你可以重新写 [Symbol.toPrimitive] , 也可以是ValueOf(), 随你喜欢~
var a = {
i: 0,
[Symbol.toPrimitive]() {
return ++this.i
}
}
if (a == 1 && a == 2 && a == 3) {
console.log('OK')
}
- 通过数据劫持的方式, 这个方法十分有趣, 我会在日后的文章详细阐述
let i = 0
Object.defineProperty(window, 'a', {
get() {
return ++i
}
})
if (a == 1 && a == 2 && a == 3) {
console.log('OK')
}
数字类型的细节点:
parseInt(string, radix):
-
将其他类型转换为数字类型, 必须保证[value]是一个字符串,如不是则调用toString()
-
第二参数指定的是数字的基数(采用什么进制), 安全值2~36, 该值为0则按照十进制进行解析, 其他的情况直接返回NaN
-
parseInt(0…) 以0开头,在浏览器输出的时候.会被判定为八进制,此时是浏览器做了转换, 而非方法.(node环境下同理)
-
parseInt(0x…) 以0x开头,则会默认为十六进制
数字注意点:
- 小数计算, 应该避免相等性检查, 这与计算机存储小数的方式有关, 二进制没办法精确的存储0.1这样的小数.
- 使用小数的时候, 要记得避免小数的精确度问题.
isFinite()
用于判断一个变量是否为有效数字(非infinity,-infinity,NaN)NaN
与任何数都不相等(非有效数字指的就是除了有效数字外的任何值, 所以他可以任何值)
题1:
console.log(0.1 + 0.2 == 0.3) // 输出 ? 为什么?
题2:
let arr = [27.2, 0, '0013', '14px', 123]
arr = arr.map(parseInt)
console.log(arr);
题1答案:
什么, 0.3 - 0.2 ≠ 0.1 ? 这篇文章对题目的成因做了详细的阐述,由于篇幅问题, 我就不在这里解释啦
题2答案:
如果你不了解parseInt()与数组的map方法, 这题就没办法做对了.
由于map的特性, 他将会把数组的值
和索引
作为参数传递给回调函数, 因此根据下图, 我们就可以得到这样的执行过程
let arr = [27.2, 0, '0013', '14px', 123]
arr = arr.map(parseInt(item, index)) // item 为数组的值, index为索引
console.log(arr);
//-----------------------
arr.map(parseInt(27.2, 0)) // 第一次循环
arr.map(parseInt(0, 1)) // 第二次
arr.map(parseInt('0013', 2)) // 第三次
//....以此类推
String
字符串的细节点在于字符串拼接以及隐式转换规则, 它与转换为数字的
隐式转换规则
同理, 但没有步骤4
字符串拼接:
-
遇到对象类型会遵循
隐式转换规则
进行转换, 但是空数组[]
会比较特殊, 因为他在步骤3的时候会转换为空字符串""
, 所以拼接的时候,不会有任何的效果. -
代码块拼接根据位置结果也将会有所不同:
{} + 10 // {} 会被当做代码块直接执行, 所以不会有任何效果
10 + {} // 10[object Object]
( {} + 10 ) // [object Object]10
总结:
这类题目其实非常简单, 我们只需要记住隐式转换的规则, 以及发生字符串拼接的时候留意引用数据类型, 基本上是不会做错的
let result = true + 21.2 + null + undefined + "2" + [] + null + 9 + false;
console.log(result);
Boolean
- 0, null, NaN, undefined, 空字符串(
' '
) 会被转换为false 其余全是true - ![value] 取反
- !![value] 相当于 Boolean([value])
Null与undefined
null和undefined的特性已经在文章中穿插, 所以这里仅对他们本身的相等性测试做一下阐述
- 尽量使用三等号进行判断
- 字符串==对象时, 会把对象转换为字符串再进行比较
- 在其他情况下,都将其他数据类型转换为数字进行比较
null
和 undefined
与任何值都不相等, 仅在双等号下与彼此相等
NaN == NaN // => false 因为非有效数字可以是很多种值, 所以不相等
null == undefined // => true
null === undefined // => false
Symbol
作用:
- Symbol类型是唯一值,字面量中使用是给键添加一个
[]
- Symbol 能够起到唯一标识的作用且创建的属性会被隐藏,即在循环时,该属性将不会被访问到,或者该库内的脚本,被第三方应用的时候,该属性也不会被访问到,不会引起冲突.
- 宏观管理标识:保证标志唯一性(vuex/redux)
注意点:
由于在循环中Symbol的属性是不可被访问的, 我们在对一个对象的全部属性进行遍历时, 最严谨的做法应该是要把Symbol属性考虑进去的.此外, 还需要考虑Symbol属性的兼容性问题.
获取一个对象全部属性的正确方式:
let obj = {
name: 'link',
age: 12,
[Symbol('AA')]: '男'
}
let arr = []
for (item in obj) {
arr.push(item)
}
if(typeof Symbol !== 'undefined') { // 判断当前浏览器是否支持Symbol
arr = arr.concat(Object.getOwnPropertySymbols(obj))
}
BigInt
BigInt类型还没有碰到什么面试题, 日后补充!
引用数据类型
Object:
由于对象类型大部分考核内容与数据类型本身无关,如原型, 原理链. 如何进行对象的深浅合并,深浅克隆.我将在其他文章中进行详细阐述.
常用API
如果你还对String,数组,Math的API还不太熟悉, 欢迎关注我的公众号😝前端Link, 回复 “API”, 我将一些常用的API整理成了脑图, 并且做了详细的阐述, 对萌新十分友好,也十分方便记忆
Function(重点)
目录:
函数创建过程
函数使用很常见, 但是我相信仔细研究他的创建和执行过程的人还不多, 但是这有助于我们理解函数的特性.比如构造函数和普通函数在初始化this时会有什么不同?
-
开辟一个堆内存, 有一个16进制的内存用于地址指向.
-
存储内容: 在代码执行前, 上下文的代码是以代码字符串的形式存储在对应的堆内存中, 就像一个普通对象.
-
创建[[scope]]声明作用域,(就是函数创建的执行上下文)
-
把地址赋值给某个变量, 供其指向
function name (){
console.log(1)
}
一个上述的函数, 他的堆就大概长这样.
函数堆0x001 |
---|
[[scope]] |
‘console.log(1)’ |
name: 'abc' , prototype ,__proto__ |
函数执行过程
this指向问题
正常的普通函数执行:看函数执行前是否有“点”,有,“点”前面是谁this就是谁,没有“点”,this是window「严格模式下是undefined」
在什么情况下this指向会被初始化?
-
事件绑定
-
构造函数
-
执行函数(普通函数, 成员访问, 匿名函数, 回调函数)
-
箭头函数(generator函数)
-
基于call/apply/bind 改变this指向
事件绑定
当按钮被点击后, 函数执行时, (*)
和(**)
都是指向当前节点对象, (***)
而兼容IE678的方法 则指向window对象
, 严格模式下指向为undefined
事件绑定其实比较特别, 因为它可以被看成一个"异步任务", 尽管它不存在.
去调用函数, 但是在click事件被触发的时候, 其实是由DOM元素执行了这个函数.
<button id="btn">click</button>
// Dom0 :
btn.onclick = function () { // (*)
console.log(this) // 当前节点
}
// Dom2 :
btn.addEventListener('click',function() { // (**)
console.log(this) // 当前节点
})
btn.attachEvent('click', function () { // (***)
console.log(this) // window/undefined
})
回调函数
回调函数的情况, 很容易把人搞晕, 到其实只要注意函数是否存在调用者, 也就是是否有
.
就可以判断回调函数的this
了
var i = 1
function fn(callback) {
var i = 2
// callback -> 匿名函数
callback()
}
fn(function () {
console.log(this.i) // 1 this指向了window
})
arr.forEach(
function (item, index) {
console.log(this) //->forEach第二个参数「对象」 forEach内部做处理了
},
{ xxx: 'xxx' }
)
函数形参默认值
先看题:
// 请问下列代码执行会输出什么?
var x = 1;
function func(x, y = function anonymous1(){x = 2}){
var x = 3;
y();
console.log(x);
}
func(5)
console.log(x);
答案:
var x = 1;
function func(x, y = function anonymous1(){x = 2}){
var x = 3;
y();
console.log(x); // => 3
}
func(5)
console.log(x); // => 1
你可能觉得奇怪为什么是3?
作用机制: 如果当前函数使用了形参默认值, 并且函数中有基于let/const/var声明的变量(无论变量名是否与形参一致),则在函数执行时除了生产一个私有的上下文,还会在{}
块内生产一个块级上下文(该块级作用域链指向函数的上下文)
注意: (如果函数体中的变量名与形参一致,则最开始会把形参变量的值同步一份给同名的私有变量)
总结: 尽量不要在函数中使用形参默认值, 容易造成各种各样奇奇怪怪的问题, 而且这样做会开辟一个新的块级上下文, 十分占用内存
解决办法:
function foo (x) {
if (typeof x === 'undefined') {
... // 对形参做处理
}
}
(完)
感谢😘
如果觉得文章内容对你有帮助:
- ❤️欢迎关注点赞哦! 我会尽最大努力产出高质量的文章
- 个人公众号: 前端Link
- 联系作者: linkcyd 😁