rangechecks 检测代码检测到超出范围的数组访问。_夯实基础系列(一)数据类型及其检测及进阶...
1.系列介绍
第一节,讲解数据类型,及其常规的检测方法,数据存储形式,垃圾回收,包括面试中常常要求的手写的一些方法。
2.JS中数据类型
JS数据类型分为两大类:基本类型和引用类型。基本类型包含:
string, number. boolean, undefined, null, symbol六种;引用类型有:对象一种。
面试官:“那么什么是基本类型什么是引用类型呢?”
小明:“基本类型是简单的数据段,而引用类型是可能由多个值构成。
面试官:“那么简单类型和引用类型是如何存储的呢?”
小红:”简单类型按值访问,存储在内存栈中,引用类型,按引用访问,地址存储在栈中,内容存储在堆中。所以但进行简单类型复制的时候,复制的是一个值,而引用类型复制的时候复制的其实是一个引用地址,所以就有了深拷贝和浅拷贝一说。“
面试官:“回答的不错,下一题。”
3.检测数据类型
typeof
使用typeof能够检测出数据的类型,当检测基本类型的时候很好用,但是无法检测出对象的具体类型,例如Date和RegExp.看下面的示例。
typeof '1' === 'string'
typeof 1 === 'number'
typeof {} === 'object'
typeof undefined === 'undefined'
typeof null === 'object' // null被认为是一个空对象
typeof Symbol(1) === 'symbol'
typeof NaN === 'number'
typeof Function === 'function'
typeof true === 'boolean'
typeof能够返回的类型有以上几种:string,number,object,function,undefined,symbol,boolean 七种,但是无法知道是不是数组等。
检测数组
检测数组提供了两个操作符,isArray和instanceof。
isArray
Array.isArray() 用于确定传递的值是否是一个 Array
Array.isArray([1,2,3]) // true
instanceof
用于检测 某个对象是否是某个构造函数的实例
[] instanceof Array // true
var a = {}
a instanceof Object // true
但是检测数组的时候还是建议用isArray,因为某些场景下 instanceof并不能很好的起作用。
var iframe = document.createElement('iframe');
document.body.appendChild(iframe);
xArray = window.frames[window.frames.length-1].Array;
var arr = new xArray(1,2,3); // [1,2,3]
Array.isArray(arr); // true
arr instanceof Array; // false
万金油
Object.protptype.toString.call(obj)
使用这个方法能够准确的检测出所有的类型。
Object.protptype.toString.call({}) === "[object Object]"
Object.protptype.toString.call([]) === "[object Array]"
为什么这样子能够准确的检测类型呢?因为Object的原型对象上有一个toString的方法,所有的其他类型的数值都继承了来自对象的toString方法。但是由于每个类型都各自改写了toString的方法,所以我们必须要调用Ojeact.prototype上的toString方法。看下面?:
[].toString() // 结果是 “”
Array.prototype.hasOwnProperty('toString') // true
Number.prototype.hasOwnProperty('toString') // true
String.prototype.hasOwnProperty('toString') // true
我们再删掉数组原型上的toString方法
delete Array.prototype.toString
[].toString() // [object Array]
Object.protptype.toString.call([]) === "[object Array]"
结果一致,因为删掉了,数组原型上的toString他会根据原型链去查找对象上的toString方法。
延伸
实现一个检测类型的方法:
function detectType (obj) {
return Object.prototype.toString.call(obj).slice(8, -1).toLowerCase()
}
js中有三个特殊对象。String,Boolean, Number,通过new关键字生成的都是属于对象类型,如果用typeof检测的话。平时开发的时候需要注意一下。
4.Number
Number 类型应该是 ECMAScript 中最令人关注的数据类型了,这种类型使用 IEEE754 格式来表示 整数和浮点数值(浮点数值在某些语言中也被称为双精度数值)。
什么是IEEE754格式呢?
说实话,我看不太懂,专业术语太多了,记住js浮点型数值采用的双精度,即64位(8个字节)
0.1 + 0.2 !== 0.3的问题
由于0.1和0.2转换成二进制分别是:0.00011001100110011001100(1100无限循环) 0.001100110011001100110011(0011无限循环)。
js采用的是64位(8字节),所以会舍弃掉后面一些循环的部分,就导致了这类问题的出现。tips: 不要用浮点数进行运算,除非你对精度没有要求。
那么如何解决浮点数计算的问题呢?大家应该都能想到,先转换成整数,然后运算好以后再转换成浮点数。话不多说,上代码。
function transform (num1, num2, type) {
const typeList = ['add', 'min', 'multi', 'divide']
if (!num1 || !num2 || !type) {
throw Error('num1 & num2 & type are required')
}
if (!typeList.includes(type)) {
throw Error("There are four types can be accepted 'add', 'min', 'multi', 'divide'")
}
let maxLen = 0
let copyNum1 = 0
let copyNum2 = 0
let l = num1.toString().split('.').length > 2 ? num1.toString().split('.')[1].length : 0
let r = num2.toString().split('.').length > 2 ? num2.toString().split('.')[1].length : 0
maxLen = l copyNum1 = floatToInt(num1, maxLen)
copyNum2 = floatToInt(num2, maxLen)
if (type === 'add') {
return (copyNum2 + copyNum1) / Math.pow(10, maxLen)
} else if (type === 'min') {
return (copyNum1 - copyNum2) / Math.pow(10, maxLen)
} else if (type === 'divide') {
return copyNum1 / copyNum2
} else if (type === 'multi') {
return (copyNum1 * copyNum2) / Math.pow(Math.pow(10, maxLen), 2)
}
}
function floatToInt (num, len) {
return num * Math.pow(10, len)
}
5.垃圾收集
JavaScript 具有自动垃圾收集机制,也就是说,执行环境会负责管理代码执行过程中使用的内存。而在 C 和 C++之类的语言中,开发人员的一项基本任务就是手工跟踪内存的使用情况,这是造成许多问 题的一个根源。在编写 JavaScript 程序时,开发人员不用再关心内存使用问题,所需内存的分配以及无 用内存的回收完全实现了自动管理。这种垃圾收集机制的原理其实很简单:找出那些不再继续使用的变 量,然后释放其占用的内存。为此,垃圾收集器会按照固定的时间间隔(或代码执行中预定的收集时间), 周期性地执行这一操作。下面我们来分析一下函数中局部变量的正常生命周期。局部变量只在函数执行的过程中存在。而在 这个过程中,会为局部变量在栈(或堆)内存上分配相应的空间,以便存储它们的值。然后在函数中使 用这些变量,直至函数执行结束。此时,局部变量就没有存在的必要了,因此可以释放它们的内存以供 将来使用。在这种情况下,很容易判断变量是否还有存在的必要;但并非所有情况下都这么容易就能得 出结论。垃圾收集器必须跟踪哪个变量有用哪个变量没用,对于不再有用的变量打上标记,以备将来收 回其占用的内存。用于标识无用变量的策略可能会因实现而异,但具体到浏览器中的实现,则通常有两 个策略。
5.1 引用计数
概念就是一旦没有被引用就会被回收。但是存在一种特例就是循环引用的问题。这种情况下就会出现无法回收。导致内存泄漏。
var a = {
b: 1
}
var c = {
d: 2
}
a.c = c
c.a = a
此时a和c将得不到回收。最开始js的回收机制就是如此。此时只能手动清除
a = null
c = null
设置为null并不意味着马上会清除。只会为了将其移除执行环境,等待下次收集器回收。
5.2 标记清除
标记清除算法是一种垃圾回收算法,它是第一个可以回收被循环引用的数据结构的垃圾回收算法.现在仍旧有许多常用的垃圾回收技术使用各种各样的标记清除算法的变体.
在使用标记清除算法时,未引用对象并不会被立即回收.取而代之的做法是,垃圾对象将一直累计到内存耗尽为止.当内存耗尽时,程序将会被挂起,垃圾回收开始执行.当所有的未引用对象被清理完毕时,程序才会继续执行.
标记清除算法又被叫做追踪式垃圾收集器,这是因为这种算法追踪被程序所引用的所有对象.在程序中可以直接访问的对象是指通过堆栈上的本地变量或者任意静态变量说引用的对象.从垃圾回收的角度来看,这种对象,叫做根(roots).如果一个对象是被另外的可(直接或者间接)访问的对象中的域引用,则这个对象可以被间接访问.可访问的对象被称为可用的(live),其他的对象被称为垃圾(garbage).
这里讲几个概念
5.2.1 可达性
简单地说,“可达性” 值就是那些以某种方式可访问或可用的值,它们被保证存储在内存中。
5.2.2 根
全局变量
内部的
当前作用域链上的其他变量或者函数
本地函数的局部变量和参数 这些称之为根
如果引用或者引用链能够在根中找到,那么就说明这个值是可达的。
例如:
var a = {
b: 'xx'
}
var c= 1
a.b = c
window => a => b => a.b = c a在全局变量中,a中有个b属性的值为变量c,说明c在能够在根中找到,说明c是可达的。如果我们让 a = null,那么在全局中a的引用就丢失了。此时变量c就是不可达的,会被移除执行环境等待被回收。上图:
切断引用 a = null:
当切断的时候,a对b的引用就丢失了,圆圈部分就变得不可达,就会被标记,等待被清除。
常见手写系列
1.instanceof
instanceof的原理是查询是否在原型上出现过,出现过就返回true,否则为false,思路很简单,不断获取左边的__proto__直到等于右边的prototype,则返回true否则等于null的时候为false
function myInstanceOf (left, right) {
const rightProto = right.prototype
let leftVaule = left.__proto__
while (true) {
if (leftVaule === null) {
return false
} else if (leftVaule === rightProto) {
return true
}
leftVaule = leftVaule.__proto__
}
}
2.tyopeof 原理
不同的对象在底层都表示为二进制,在Javascript中二进制前(低)三位存储其类型信息。
000: 对象
010: 浮点数
100:字符串
110:布尔
1:整数
typeof null 为"object", 原因是因为 不同的对象在底层都表示为二进制,在Javascript中二进制前(低)三位都为0的话会被判断为Object类型,null的二进制表示全为0,自然前三位也是0,所以执行typeof时会返回"object",当对象有"[[Call]]"方法的时候会返回function,否则就是object.
3.浅拷贝和深拷贝
3.1 浅拷贝
Object.assign()
Object.create()
// 都是浅拷贝
3.2 深拷贝
1.常见的有JSON.parse(JSON.stringify(obj))
JSON.parse(JSON.stringify(obj))
但是这里有弊端,无法解决循环引用的问题,以及时间类型的拷贝,会丢失掉时区。
2.第二种手写一下,这里进攻仅供参考。需要特殊处理的地方还有很多,比如ArrayBuffer,多纬数组,循环引用等问题。
function detectType (obj) {
return Object.prototype.toString.call(obj).slice(8, -1).toLowerCase()
}
function deepClone (parent, obj = {}) {
const newObj = Array.isArray(obj) ? [] : {}
for (let key in parent) {
if (detectType(parent[key]) === 'array') {
newObj[key] = parent[key].concat([])
} else if (detectType(parent[key]) === 'regexp') {
newObj[key] = new parent[key].constructor(obj)
} else if (detectType(parent[key]) === 'date') {
newObj[key] = new parent[key].constructor(parent[key].getTime())
} else if (detectType(parent[key]) === 'object') {
const temp = {}
newObj[key] = temp
deepClone(parent[key], temp)
} else {
newObj[key] = parent[key]
}
}
return newObj
}
3.使用lodash等工具库,不过还是要了解原理哦
参考文章
JavaScript垃圾收集——标记清除和引用计数
前端面试:谈谈 JS 垃圾回收机制
关注我和我一起夯实基础
下期主要讲一下对象和函数