JavaScript 数据结构

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()描述
Numbernew Number(‘123sd’).toString() //NaN
new Number(‘123’).toString() //‘123’
重写了toString(),转换成相应的字符串格式原始值
Stringnew String(‘12df’).toString(); // ‘12df’重写了toString(),转换成相应的字符串格式原始值
Booleannew Boolean(‘fd’).toString(); // ‘true’重写了toString(),转换成相应的字符串格式原始值
Datenew Date().toString(); // “Wed Oct 11 2017 08:00:00 GMT+0800 (中国标准时间)”重写了toString(),转换成相应的字符串格式new Date().valueOf();//1515143895500Date.prototype重写了valueOf函数,将日期转换为毫秒的形式
Array[].toString() = ‘’重写了toString(),转换成相应的字符串格式,为原始值var a = new Array();
a.valueOf() === a;
对象本身,地址值
function重写了toString(),转换成相应的字符串格式对象本身,地址值
Math继承Object原型上的toString(),输出对象的类型 原始值
Object输出对象的类型 原始值对象本身,地址值
将值转换为数字 ToNumber
参数结果
undefinedNaN
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 infor 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 
  }
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值