1.大数相加的溢出问题
解释为什么0.1+0.2!=0.3的问题?
由于计算机中的所有数据以二进制的形式存储,当计算0.1+0.2转换为二进制表示时会出现位数无线循环的情况。而Javascript中的数字的存储遵循IEEE 754标准,是以64为双精度格式来存储数字的。 在二进制科学计数法中,双精度浮点的小数部分最多只能保留52位,加上前面的1,其实就是保留53为有效数字,超过这个长度的位数会被舍去,(采用0舍1入的方式),造成精度丢失。
关于浮点数的运算,一般由以下五个步骤完成:对阶、尾数运算、规格化、舍入处理、溢出判断。
解决办法?
方法一:将其转换为整数后再进行运算,运算后再转换为对应的小数
var a=0.1,b=0.2
var sum=(a*100+b*100)/100
console.log(sum)//0.3
console.log(sum===0.3)//true
方法二:利用 ES6 中的极小数 Number.EPSILON 来进行判断
判断 0.1 + 0.2 是否等于 0.3,可以将两个数字相加的结果与 0.3 相减,如果相减的结果小于极小数,那么就可以认定是相等的
var a = 0.1, b = 0.2, c = 0.3;
var result = (Math.abs(a + b - c) < Number.EPSILON);
console.log(result) // true
注:1.Number.EPSILON 属性表示 1 与Number可表示的大于 1 的最小的浮点数之间的差值; 2.EPSILON 属性的值接近于 2.2204460492503130808472633361816E-16,或者 2-52。
具体计算过程见:0.1+0.2!=0.3
大数相加的具体代码实现
解法一:
- 先通过补0使两个字符串长度一样;
- 从低位开始,对应位进行相加再加进位;
- 结果大于等于10则进位1,当前位值为结果对10求余;
- 结果小于10进位为0,当前位值为结果对10取余;
- 重复上述操作,直到两者都遍历完。
第一种:
var addStrings = function (num1, num2) {
let res = '' // 结果
let flag = 0 // 进位
// 补全0
while (num1.length < num2.length) {
num1 = '0' + num1
}
while (num2.length < num1.length) {
num2 = '0' + num2
}
let i = num1.length - 1
// 从尾数开始遍历
while (i >= 0) {
flag = Number(num1[i]) + Number(num2[i]) + flag
res = (flag % 10) + res
flag = flag >= 10 ? 1 : 0
i--
}
return flag === 1 ? '1' + res : res
};
console.log(addStrings('2311111111111','22222222222'));//2333333333333
第二种:
var str1="1111111111111111111111111111111111";
var str2="222222222222222222222222";
//ES6 padStart()
var MaxLength=Math.max(str1.length,str2.length);
var len1=str1.length;
var len2=str2.length;
var temp=0;//进位
var sum=0;
var myStr="";
for(var i=0;i<MaxLength;i++){
sum=Number(str1.charAt(len1-i-1)
)+Number(str2.charAt(len2-1-i))+temp;
if(sum>9){
sum=sum%10;
temp=1
}else{
temp=0;
}
myStr=sum+myStr;
}
console.log(myStr);
不能计算0.1+0.2,不能计算负数。当打印console.log(addStrings('0.1','0.2'))//0NaN3
解法二:
- 先通过补0使两个字符串长度一样
- 取得当前环境所能表达最大的数字的长度l,取n= l-1 作为所能相加的最大的两个数
- 每一次截取后n位字符串,强转为数字,然后进行相加,再加进位
- 结果截取后n位作为这n位的值
- 如果结果长度大于n位,则进位为1,否则为0
- 如果结果长度小于n位,则不足位数用0进行补
function addBigNum(num1, num2) {
var l1 = String(num1).length, //保存num1长度
l2 = String(num2).length, //保存num2长度
flag = false, //判断是否有进位
newNum1 = String(num1), //用来后面做截取
newNum2 = String(num2),
newSum = "",
sum = '';
if (l1 < 14 && l2 < 14) {
return Number(num1) + Number(num2);
}
while (l1 >= 14 || l2 >= 14) {
newNum1 = l1 && l1 > 14 ? newNum1.slice(l1 - 14 ? l1 - 14 : 0, l1) : '';
newNum2 = l2 && l2 > 14 ? newNum2.slice(l2 - 14 ? l2 - 14 : 0, l2) : '';
//判断时候有进位
if (flag) {
newSum = String(Number(newNum1) + Number(newNum2) + 1);
flag = false;
} else {
newSum = String(Number(newNum1) + Number(newNum2));
}
if (newSum.length >= 15) {
sum = String(newSum.slice(1, newSum.length)) + String(sum);
flag = true;
} else {
sum = String(newSum) + String(sum);
}
l1 -= 14;
l2 -= 14;
//最后把不足14位的部分做加法
if (l1 < 14 && l2 < 14) {
if (flag) {
sum = String(Number(num1.slice(0, l1)) + Number(num2.slice(0, l2)) + 1) + String(sum);
} else {
sum = String(Number(num1.slice(0, l1)) + Number(num2.slice(0, l2))) + String(sum);
}
}
}
return sum;
}
console.log(addBigNum('-11111111111', '222222222222'));//211111111111
console.log(addStrings('0.1','0.2'))//0.30000000000000004
2.深拷贝与浅拷贝
如何区分深拷贝与浅拷贝?
简单点来说,就是B复制了A,当修改A时,看B是否发生变化,如果B也跟着变化,说明是浅拷贝,拿人手短;如果B没变,那就是深拷贝,自食其力。
浅拷贝:
例:
let a=[0,1,2,3,4];
b=a;
console.log(a===b);
a[0]=10;
console.log(a,b);
引用数据类型-键值对key:value key存在栈内存中;value存在堆内存中,但是栈内存会提供一个引用的地址指向堆内存中的值。如图:
当b=a进行拷贝时,其实是复制a的引用地址,而非堆里的值。
而当我们a[0]=1或10时进行数组修改时,由于a与b指向的是同一个地址,所以自然b也受a的影响,这就是所谓的浅拷贝。
基本数据类型和引用数据类型: 基本数据类型:Number String Boolean Null Underfined Symbol BigInt七类;
引用类型:Object Array Function Date RegExp
当修改a=2,对b不会造成影响,此时的b已自食其力,翅膀硬了,不受a的影响。 但这里的a、b虽然b不受a的影响,但算不上深拷贝。因为深拷贝本身只针对较为复杂的object类型的数据。
深拷贝:
如果在堆内存选中也开辟已给新的内存专门存放b,就是深拷贝。
实现简单的深拷贝
方法一:使用递归去复制所有层级的属性
function deepClone(obj){
let objClone=Array.isArray(obj)?[]:{};
if(obj && typeof obj==="object"){
for(key in obj){
if(obj.hasOwnProperty(key)){
//判断obj子元素是否是对象,如果是,递归复制
if(obj[key] && typeof obj[key]==="object"){
objClone[key]=seepClone(obj[key]);
}else{
//如果不是,简单复制
objClone[key]=obj[key];
}
}
}
}
return objClone;
}
let a=[1,24,4,56],
b=deepClone(a);
a[0]=40;
console.log(a,b);
结果显示:
function deepCopy(obj) {
//递归---自己调用自己
var cloneObject = {};
for (let key in obj) {
if (obj[key] instanceof Object) {
cloneObject[key] = deepCopy(obj[key])
} else {
cloneObject[key] = obj[key]
}
}
return cloneObject;
}
var m = deepCopy(obj)
console.log(m);
m.a = 10;
结果:
方法二:借用JSON对象的parse和stringify
function deepClone(obj){
let _obj=JSON.stringify(obj),
objClone=JSON.parse(_obj);
return objClone;
}
let a=[0,1,[2,3],4],
b=deepClone(a);
a[0]=1;
a[2][0]=1;
console.log(a,b);
JSON.stringify对象--->字符串和JSON.parse字符串--->对象,具体讲解如下: localStorage存储数组,对象,localStorage,sessionStorage存储数组对象
方法三:借用JQ的extend方法
$.extend([deep],target,object1[,objectN]) deep表示是否是深拷贝,true 深拷贝 ;false 浅拷贝 target Object类型 目标对象,其他成员的属性将被附加到该对象上; objet1 objectN可选
let a=[0,1,[2,3],4],
b=$.extend(true,[],a);
a[0]=1;
a[2][0]=1;
console.log(a,b)
注:需要依赖JQ依赖库。
slice和concat方法都不是真正的深拷贝。深拷贝是会拷贝所有层级的属性,而slice和concat拷贝的不彻底,但是二级属性还是没能拷贝成功。以slice方法为例:
let a=[0,1,[2,3],4],
b=a.slice();
a[0]=1;
a[2][0]=1;
console.log(a,b);
第一层的属性确实深拷贝,拥有了独立的内存,但更深的属性却仍然公用了地址,所以才会造成上面的问题。
Html5本地存储有哪些?它们有什么区别?
本地存储:cookie、sessionStorage、localStorage、indexedDB。
Cookie
Cookie是服务器发给客户端的特殊信息,cookie是以文本的方式保存在客户端,每次请求都必须带上它。
通过document.cookie 来设置或获取cookie的值。 产生Cookie的服务器可以向set-Coolie响应首部添加一个domain属性来控制哪些站点可以看到cookie
Set-Cookie:name="losstie"domain="m.baidu.com"
属性:
- path参数高速浏览器cookie的路径,默认cookie属于当前页面;
- secure设置secure,cookie只有在httpsIE已加密的情况下才会发送给服务端;
- HttpOnly禁止javascript操作cookie(避免跨域脚本XSS攻击,通过javascrpit的document.cookie无法访问带有HttpOnly标记的cookie)
如果不在浏览器中设置过期时间,cookie被保存在内存中,生命周期随浏览器的关闭而结束,成为会话cookie;如果没有设置过期时间,那么在浏览器关闭后消失;即持久cookie。
cookie的应用场景:
(1)判断用户是否登陆过网站,以便下次登录时能够实现自动登录(或者记住密码)。如果我们删除cookie,则每次登录必须从新填写登录的相关信息。
(2)保存上次登录的时间等信息。
(3)保存上次查看的页面
(4)浏览计数
localStorage和sessionStorage
HTML5的WebStorage提供了两种API:localStorage(本地存储)和sessionStorage(会话存储)。
存储内容类型:
localStorage和sessionStorage只能存储字符串类型,对于复杂的对象可以使用ECMAScript提供的JSON对象的stringify和parse来处理
获取方式:
localStorage:window.localStorage;;sessionStorage:window.sessionStorage;。
应用场景:
localStoragese:常用于长期登录(+判断用户是否已登录),适合长期保存在本地的数据(令牌)。 sessionStorage:敏感账号一次性登录;
二者区别:
localstorage只要在相同的协议、相同的主机名、相同的端口下,就能读取、修改到同一份localstorage数据; 原生localstorage设置过期时间的封装
而sessionStorage比localStorage更严苛一点,除了协议、主机名、端口外,还必须在统一窗口(也就是浏览器的标签页)下。
总结:
区别:
-
1.都可以用来存储数据;
-
2.cookie一条数据大小不能超过4KB,最多不能存储超过20条,如果没有设置过期时间,那么在浏览器关闭后消失;
-
3.sessionStorage是会话存储,一条大小不能超过5M,数量没有限制,关掉页面数据消失;
-
4.localStorage本地存储,一条大小不超过5M,数量没有限制,除非主动删除,否则数据不会消失。
-
5.IndexedDB是HTML5规范立新出现的浏览器内置的数据库,提供了类似数据库风格的数据存储和使用方式,且数据是永久保存,适合存储大量结构化数据。以对象的形式存储,每个对象都有一个key值索引。