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()方法的原理

  1. 逐步判断,先判断调用的数据类型是否是null和undefined 。是直接返回,如果不是将参数转换成对象继续判断(装箱)
  2. 该对象的 [Symbol.toStringTag] 属性值作为 tag, 如无该属性,或该属性值不为字符串类型,则取该对象的内置属性值,比如true的tag为"Boolean",数组类型的tag为“Array”
  3. 返回[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() 方法的缺点

  1. 会进行装箱操作 ,基本数据类型用对应的引用数据类型包装起来,会产生很多临时对象
  2. 无法区分自定义对象类型
手写instanceof方法
  1. 参数有两个,实例A(对象或者函数)和构造函数B。返回值是boolean值
  2. 去实例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(描述) 创建共享symbol
  • Symbol.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()
引用类型转化为原始类型 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
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

== 运算符的隐式转化

规则

  1. 其中一个数x为Number 返回toNumber(y) == Number
  2. 其中一个数x为String或Number,另一个为Object 返回 String|Number == ToPrimitive(y)
  3. 有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中的假值只有falsenullundefined空字符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是一种接口,为不同的数据结构提供统一的访问机制

有什么用?

  1. 为各种数据结构,提供一个统一的、简便的访问接口
  2. 任何数据结构只要部署 Iterator 接口,就可以完成for..of遍历操作
  3. 使得数据结构的成员能够按某种次序排列

原理是什么
内部实现了并返回了迭代器对象,对象中有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.这个对象中存储了当前取出的数据和是否取完了的标记

使用场合

  1. for-of
  2. 对数组和 Set 结构进行解构赋值时,会默认调用Symbol.iterator方法。
  3. 扩展运算符(…)也会调用默认的 Iterator 接口。
  4. 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
  1. 给对象部署[Symbol.iterator]属性,该属性的值是迭代器对象。
  2. 通过generator生产器函数生成迭代器对象
Object.prototype[Symbol.iterator] = function* () {
  const keys = Object.getOwnPropertyNames(this)
  for (let i = 0; i < keys.length; i++) {
   yield keys[i];//返回key 
  }
}
Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐