第二天

226 阅读6分钟

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);

浅拷贝.PNG 引用数据类型-键值对key:value key存在栈内存中;value存在堆内存中,但是栈内存会提供一个引用的地址指向堆内存中的值。如图:

image.png

当b=a进行拷贝时,其实是复制a的引用地址,而非堆里的值。

image.png 而当我们a[0]=1或10时进行数组修改时,由于a与b指向的是同一个地址,所以自然b也受a的影响,这就是所谓的浅拷贝。

image.png

基本数据类型和引用数据类型: 基本数据类型:Number String Boolean Null Underfined Symbol BigInt七类;

引用类型:Object Array Function Date RegExp

当修改a=2,对b不会造成影响,此时的b已自食其力,翅膀硬了,不受a的影响。 但这里的a、b虽然b不受a的影响,但算不上深拷贝。因为深拷贝本身只针对较为复杂的object类型的数据。

image.png

image.png

深拷贝:

如果在堆内存选中也开辟已给新的内存专门存放b,就是深拷贝。

image.png

实现简单的深拷贝

方法一:使用递归去复制所有层级的属性

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);
 

结果显示:

image.png

  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;

结果:

image.png

方法二:借用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);

image.png

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依赖库。 image.png

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);

image.png

第一层的属性确实深拷贝,拥有了独立的内存,但更深的属性却仍然公用了地址,所以才会造成上面的问题。

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值索引。