JavaScript 数据结构
数据类型
基本数据类型: Number、String(单引号双引号都可)、Boolean、Null、Undefind、Symbol和BigInt
引用数据类型:Object、Function、Array
面试题
- JS数据类型有哪些
- 介绍一下Symbol和Bigint
- 如何判断一个数据类型
- Object.prototype.toString.call() 的缺点?
- 各个方法的原理是什么
- typeof(NaN) typeof(Null)
- 手写 instanceof 方法
- null==undefined 和 null===undefined
- 隐式转换规则 === 和 == 的区别
如何判断数据类型
方法 | 检测的范围 | 实现原理 |
---|---|---|
typeof A | 基本数据类型都能判断,除了null->object 引用数据类型都返回object,除了 function 可以检测出来 | 根据二进制判断 对象低三位000 null的二进制表示全为0 |
A(实例) instanceof B(构造函数) | 检测对象的具体类型 | 通过查找A的原型链有没有B的显式原型 |
Object.prototype.toString.call() | 可以判断所有数据类型 | 下面解是 |
其他API方法 | 数组 Array.isArray() |
typeof Symbol("test") //'symbol'
typeof null //'object'
typeof 123n // 'bigint'
typeof undefined; // "undefined"
typeof function(){}) //'function'
typeof []; //’array‘
typeof NaN; //‘number’
Object.prototype.toString.call()方法的原理
- 逐步判断,先判断调用的数据类型是否是null和undefined 。是直接返回,如果不是将参数转换成对象继续判断(装箱)
- 该对象的
[Symbol.toStringTag]
属性值作为 tag, 如无该属性,或该属性值不为字符串类型,则取该对象的内置属性值,比如true的tag为"Boolean",数组类型的tag为“Array” - 返回
[Object tag]
//部署Symbol.toStringTag 的例子。可以看出,属性值期望是一个字符串,否则会被忽略。
var o1 = { [Symbol.toStringTag]: "A" };
var o2 = { [Symbol.toStringTag]: null };
var o3 = { [Symbol.toStringTag]: "null" };
Object.prototype.toString.call(o1); // => "[object A]"
Object.prototype.toString.call(o2); // => "[object Object]"
Object.prototype.toString.call(o3); // => "[object null]"
Object.prototype.toString.call() 方法的缺点
- 会进行装箱操作 ,基本数据类型用对应的引用数据类型包装起来,会产生很多临时对象
- 无法区分自定义对象类型
手写instanceof方法
- 参数有两个,实例A(对象或者函数)和构造函数B。返回值是boolean值
- 去实例A的原型链上找是否有B的原型,找到则返回true
function instanceof(A,B){
if(typeof B !== 'function' || (typeof A !=='object' && typeof A !== 'function') || A === null )return false;
let pt = A.__proto__;
while(pt){ //原型链的尽头是Object.__proto__ = null;
if(pt === B.prototype) return true;
else pt = pt.__proto__;
}
return false;
}
介绍一下Symbol和Bigint
Symbol数据类型是什么
Symbol数据类型表示独一无二的值(类似字符串),目的是为了解决全局变量冲突的问题。
Symbol类型的属性适合作为对象的私有属性,传统遍历方法无法获取到。
let s = Symbol();
//Symbol函数可以接受一个字符串作为参数,表示对 Symbol 实例的描述,主要是为了在控制台显示,或者转为字符串时,比较容易区分
let s1 = Symbol('foo');
let s2 = Symbol('bar');
s1 // Symbol(foo)
s2 // Symbol(bar)
s1.toString() // "Symbol(foo)"
s2.toString() // "Symbol(bar)"
//作为属性名的 Symbol
//作为对象使用时需要用大括号,不可以用点,因为点运算默认后面是字符串。
let mySymbol = Symbol();
let a = {
[mySymbol]: 'Hello!'
};
常见方法和内置属性
Symbol.for(描述)
创建共享symbolSymbol.toStringTag
属性:Object.prototype.toString.call()
的实现原理Symbol.iterator
属性: 具有Symbol.iterator
属性的数据结构就部署了Iterator 接口,该数据结构就是可遍历的
BigInt数据类型是什么
BigInt用于当整数值大于Number数据类型支持的范围时,可以表示JavaScript 中的任意精度的整数。
Number类型的范围
(-9007199254740991 (-(2^53^-1)),9007199254740991(2^53-1))
隐式类型转化
重点
1.原理
2.关于空值的一些比较
原理概述
隐式转换主要涉及三种转换规则
- 将值转为数字,
ToNumber()
如果值是对象则ToPrimitive(obj, Number)
- 将值转为字符串,
ToString()
如果值是对象则ToPrimitive(obj, String)
- 将对象转为原始值,对象的
Symbol.ToPrimitive(input [, PreferredType]
将对象转换为原始值- 如果参数为number 先执行对象的
valueOf()
,再执行对象的toString()
- 如果参数为String 先执行对象的
toString()
,再执行对象的valueOf()
- 如果参数为number 先执行对象的
引用类型转化为原始类型 ToPrimitive() 了解
语法:ToPrimitive(input [, PreferredType])
说明:把参数input转化为原始数据类型。如果input可以同时转化为多个原始数据,那么会优先参考PreferredType的数据类型。
当input为引用类型时,判断PreferredType
,只要传的值不是string
,那就认为是number
,默认为number。
- 当没有传PreferredType值时,如果该对象为Date类型,则PreferredType被设置为String, 否则PreferredType被设置为Number
- 如果值为string,先执行
toString()
,如果没有返回原始值继续执行valueOf()
- 如果值为number,先执行
valueOf()
,如果没有返回原始值继续执行toString()
如果PreferredType
被标记为Number
1.如果输入的值已经是一个原始值,则直接返回它
2.否则,如果输入的值是一个对象,则调用该对象的valueOf()
方法,
如果valueOf()
方法的返回值是一个原始值,则返回这个原始值。
3.否则,调用这个对象的toString()
方法,如果toString()
方法返回的是一个原始值,则返回这个原始值。
4.否则,抛出TypeError异常。
如果PreferredType
被标记为String
1.如果输入的值已经是一个原始值,则直接返回它
2.否则,调用这个对象的toString()
方法,如果toString()
方法返回的是一个原始值,则返回这个原始值。
3.否则,如果输入的值是一个对象,则调用该对象的valueOf()
方法,
如果valueOf()
方法的返回值是一个原始值,则返回这个原始值。
4.否则,抛出TypeError异常。
toString() 和 valueOf() 了解
Obeject.prototype.toString()
输出数据的类型
内置对象 | toString() | 描述 | valueOf() | 描述 |
---|---|---|---|---|
Number | new Number(‘123sd’).toString() //NaN new Number(‘123’).toString() //‘123’ | 重写了toString(),转换成相应的字符串格式 | 原始值 | |
String | new String(‘12df’).toString(); // ‘12df’ | 重写了toString(),转换成相应的字符串格式 | 原始值 | |
Boolean | new Boolean(‘fd’).toString(); // ‘true’ | 重写了toString(),转换成相应的字符串格式 | 原始值 | |
Date | new Date().toString(); // “Wed Oct 11 2017 08:00:00 GMT+0800 (中国标准时间)” | 重写了toString(),转换成相应的字符串格式 | new Date().valueOf();//1515143895500 | Date.prototype重写了valueOf函数,将日期转换为毫秒的形式 |
Array | [].toString() = ‘’ | 重写了toString(),转换成相应的字符串格式,为原始值 | var a = new Array(); a.valueOf() === a; | 对象本身,地址值 |
function | 重写了toString(),转换成相应的字符串格式 | 对象本身,地址值 | ||
Math | 继承Object原型上的toString(),输出对象的类型 原始值 | |||
Object | 输出对象的类型 原始值 | 对象本身,地址值 |
将值转换为数字 ToNumber
参数 | 结果 |
---|---|
undefined | NaN |
null | +0 |
布尔值 | true->1 false->0 |
字符串 | 全数字-> 数字 有字母->NaN ToNumber(‘’) = 0 |
对象 | 先进行 ToPrimitive(obj, Number)转换得到原始值,在进行ToNumber转换为数字 |
将值转换为字符串 toString
参数 | 结果 |
---|---|
undefined | ‘undefined’ |
null | ‘null’ |
布尔值 | ‘true’ 或 ‘false’ |
数字 | 数字转换为字符串 1.765转换为’1.765’ |
对象(obj) | 先进行 ToPrimitive(obj, String)转换得到原始值,在进行ToString转换为字符串 |
练习题
习题1
({} + {}) = ?
两个对象的值进行+运算符,肯定要先进行隐式转换为原始类型才能进行计算。
1.进行ToPrimitive转换,由于没有指定PreferredType类型,{}会使默认值为Number,进行ToPrimitive(input, Number)运算。
2.所以会执行valueOf方法,({}).valueOf(),返回的还是{}对象,不是原始值。
3.继续执行toString方法,({}).toString(),返回"[object Object]“,是原始值。
故得到最终的结果,”[object Object]" + “[object Object]” = “[object Object][object Object]”
习题2
2 * {} = ?
1.首先***运算符只能对number类型进行运算**,故第一步就是对{}进行ToNumber类型转换。
2.由于{}是对象类型,故先进行原始类型转换,ToPrimitive(input, Number)运算。
3.所以会执行valueOf方法,({}).valueOf(),返回的还是{}对象,不是原始值。
4.继续执行toString方法,({}).toString(),返回"[object Object]“,是原始值。
5.转换为原始值后再进行ToNumber运算,”[object Object]"就转换为NaN。
故最终的结果为 2 * NaN = NaN
习题3
面试题:如何让x==1&&x==2&&x==3
成立
const a = {
i: 1,
toString: function () {
return a.i++;
}
}
if (a == 1 && a == 2 && a == 3) {
console.log('hello world!');
}
1.a == 1,a为对象,1为Number。a 要转换ToPrimitive(a, Number),先调用valueOf()返回a本身,a的地址值不是原始值,调用toString()返回1后,i=i+1=2 。 这里函数作用域里是使用了其他作用域的变量形成了闭包,i会保持在内存中
2.a==2 和 a==3,同理返回true
参考文章
作者:keenjaan
链接:https://juejin.cn/post/6844903557968166926
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
== 运算符的隐式转化
规则
- 其中一个数x为Number 返回
toNumber(y) == Number
- 其中一个数x为String或Number,另一个为Object 返回
String|Number == ToPrimitive(y)
- 有Boolean类型时,Boolean转化为Number类型比较。
//undefined和null在==中相等
undefined == undefined //true
null == null//true
null == undefined //true
+0 == -0 //true
NaN == NaN //false
==和===的区别
==
会做类型转换,再进行值的比较。
===
强制相等,数据类型和值都相等,两个操作数在不转换的前提下相等才返回 true。
空值比较
/*
ToPrimitive([])先调用valueOf()返回地址,不是初始值,调用toString,返回'',调用ToNumber转化为数字0
*/
1 == [] //1==0 输出false
if判断的时候默认是转化为Boolean类型
ToBoolean(参数)
指其他类型转换为布尔类型的操作
js中的假值只有false
、null
、undefined
、空字符
、0和NaN
,其它值转为布尔型都为true。
if({})console.log('xxx') //xxx
新增数据结构Set和Map
面试题
- map和weakmap区别
- map和object区别
let map = new Map();
map.set('xx','xx1');
map.set('yy','yy1')
map.set('zz','zz1')
for (const item of map) {
console.log(item);
/*
[ 'xx', 'xx1' ]
[ 'yy', 'yy1' ]
[ 'zz', 'zz1' ]
*/
}
console.log(map); //{ 'xx' => 'xx1', 'yy' => 'yy1', 'zz' => 'zz1' }
Map和Object的区别
类型 | key的类型 | 顺序 | 内置方法 | 同名碰撞 | 继承 |
---|---|---|---|---|---|
Map | 任意类型 | key有序,按照插入顺序保持元素顺序 | size 返回长度是iterator可迭代对象,可以使用 for-of | Map的key存的是内存地址,只要地址不一样,就是两个不同的键,这就解决了同名属性的碰撞问题 | |
Object | 字符串、Symbol | 不是按插入顺序(key先排数字从小到大开头,然后是字符串时间顺序) | 可以利用Symbol解决同名碰撞问题 | 会继承原型链上的属性和方法,可以使用Object.create(null) 来创建对象 |
Map和WeakMap/Set和WeakSet
WeakMap的说明
WeakMap的key只能是对象(除null),键名所指向的对象不计入垃圾回收机制,不算做引用。也就是说,如果该对象的其他引用消失之后,就可以被回收了。WeakMap 里面的键名对象和所对应的键值对会自动消失,不用手动删除引用。
WeakMap的作用
WeakMap的的键所对应的对象,可能会在将来消失。WeakMap结构有助于防止内存泄漏。
区别
类型 | key | 方法 |
---|---|---|
Map | 任意值 | 多了 遍历 、size 、clear |
WeakMap | 对象 | 只有get、set、has、delete |
对象、数组的遍历方法
内容
- for in、for of 区别,分别对对象和数组使用问结果
- 讲一下数组的遍历方法,filter与map的使用场景,some,every的区别
- map的操作原理
- map和forEach的区别
- 使用迭代器实现for-of
- 手写数组去重
- 手写数组扁平化
- map和filter的区别
- 数组的常用方法
- 用reduce实现map
for-in 和 for-of 的区别?
区别 | for in | for of |
---|---|---|
适用数据结构 | 对象-属性 数组-索引 | iterator对象 数组-元素 |
遍历的值 | 键名 | 键值 |
遍历范围 | 所有(包括原型)的可枚举属性 | 不包含原型 |
说明 | 遍历顺序不确定 | 不能实现数组的赋值,只用于遍历,因为for (let item of arr) 相当于把数组中的元素取出来赋值给局部遍历item |
iterator迭代器
集合概念有数组、对象、Map、Set,需要有一个统一的接口机制来处理所有不同的数据结构
是什么
迭代器iterator是一种接口,为不同的数据结构提供统一的访问机制
有什么用?
- 为各种数据结构,提供一个统一的、简便的访问接口
- 任何数据结构只要部署 Iterator 接口,就可以完成
for..of
遍历操作 - 使得数据结构的成员能够按某种次序排列
原理是什么
内部实现了并返回了迭代器对象,对象中有next方法,通过next方法进入下一个状态。
Generator生成器可以用来生成迭代器对象。
for…of循环的原理 循环遍历迭代器对象的next方法获取数据
// 如果需要实现逆序:i初始化为items.length-1,依次i--
//[Symbol.iterator] = createIterator
function createIterator(items) {
var i = 0;
return {//迭代器是一个对象,它具有一个 next 方法,该方法会返回一个对象,包含 value 和 done 两个属性
next: function () {
var done = i >= items.length;
var val = !done ? items[i++] : undefined;
return {
done: done,
value: val
}
}
}
}
1.可迭代的数据内部都有[Symbol.iterator]
的属性,也称为实现了Iterator接口
2.[Symbol.iterator]
的属性会返回一个函数createIterator
函数
3.[Symbol.iterator]
返回的函数执行之后会返回一个迭代器对象
4.[Symbol.iterator]
函数返回的迭代器对象中有一个名称叫做next的方法
5.next方法每次执行都会返回一个对象{value: 10, done: false}
6.这个对象中存储了当前取出的数据和是否取完了的标记
使用场合
for-of
- 对数组和 Set 结构进行解构赋值时,会默认调用Symbol.iterator方法。
- 扩展运算符(…)也会调用默认的 Iterator 接口。
- yield*后面跟的是一个可遍历的结构,它会调用该结构的遍历器接口。
for-of的简单实现
for…of循环的原理 循环遍历迭代器对象的next方法获取数据
function myForOf(iteratorObj, fn) {
// 如果传入的对象不具备迭代接口,抛出异常
if (typeof iteratorObj[Symbol.iterator] != 'function') {
throw new TypeError(`${iteratorObj} is not iterable`)
}
// 获取迭代器对象
let iterator = iteratorObj[Symbol.iterator]()
// 遍历迭代器对象
let i
while (!(i = iterator.next()).done) {//调用对象的next方法,返回值{value:a,done:false}
fn(i.value)
}
}
使用[Symbol.iterator] + generator函数 实现对象的for-of
- 给对象部署
[Symbol.iterator]属性
,该属性的值是迭代器对象。 - 通过generator生产器函数生成迭代器对象
Object.prototype[Symbol.iterator] = function* () {
const keys = Object.getOwnPropertyNames(this)
for (let i = 0; i < keys.length; i++) {
yield keys[i];//返回key
}
}