# 数据类型

# 值类型

  • let a;//underfind (const 的定义的常量必须有初始值)
  • const a = '' //string
  • const a= 1 //number
  • const a = true //boolean
  • const a = Symbal('s')

# 引用类型

为什么设计引用类型?

性能和存储的问题,值类型占用空间小,可以存在栈中。

引用类型数据大,存在栈中不好管理。如果复制栈中的对象会很慢。
  • 数组
  • 对象
  • null
let a = 100
let b = a
a =200
console.log(b) // 100

值在栈中存储。

QQ截图20200128111606.png

let a = {age:20}
let b = a
a.age = 21
console.log(b) // {age:21}

引用类型,变量在栈中存储,对应的值存储的是引用地址,引用的值存储在堆中。 值在栈中存储。 QQ截图20200128111606.png

深拷贝和浅拷贝都是针对的引用类型,JS 的变量类型分为值类型和引用类型; 值类型:存在栈中,对值类型拷贝会对值进行复制。 引用类型:会进行地址的拷贝,最终两个变量会指向同一个地址。

// 引用类型指向同一份数据,会导致a b指向同一份数据,此时如果对其中一个进行修改,就会影响到另外一个
var a = {c: 1};
var b = a;
a.c = 2;
console.log(a.c, b.c); // 2, 2 全是2,a b指向同一份数据

# 深拷贝 VS 浅拷贝

深拷贝和浅拷贝都是针对引用类型来的,

  • 浅拷贝只拷贝一层数据。
  • 深拷贝递归遍历对象。
var a1 = {b: {c: {}};

var a2 = shallowClone(a1); // 浅拷贝
a2.b.c === a1.b.c // true

var a3 = clone(a1); // 深拷贝
a3.b.c === a1.b.c // false

简版深拷贝实现(递归)

function deepClone(json = {}) {
    if(typeof  json !== 'object'||json == null){
        return json;
    }
    let result;
    if(json instanceof Array){
        result = []
    }else{
        result = {}
    }
    for(let key in json){
        //保证key是json自身的属性
        if(json.hasOwnProperty(key)){
            result[key] = deepClone(json[key])
        }
    }

    return  result;
}

深拷贝(序列化反序列化实现)

  • JSON.parse(JSON.stringify(test))
  • Date 类型转换回来之后 Date 会变成字符串
  • 转换的对象中有函数,undefined,则序列化的结果会把函数或 undefined 丢失;
  • 有 NaN、Infinity 和-Infinity,则序列化的结果会变成 null
var test = {
name: 'a',
date: new Date,
};
//{name: "a", date: Tue Aug 03 2021 11:33:33 GMT+0800 (中国标准时间)}
console.log(JSON.parse(JSON.stringify(test)))
//{name: "a", date: "2021-08-03T03:33:33.101Z"}

(原因:string 转 obj 时候,undefined、function、symbol 会在转换过程中被忽略。。。)

const test = {
        a: null,
        b: function() {
          console.log('fff')
        },
        c: underfund
};
// {a: null, c: undefined, b: ƒ}

// {}
console.log(JSON.parse(JSON.stringify(test)))

# 浅拷贝实现

function shallowClone(source) {
    var target = {};
    for(var i in source) {
        if (source.hasOwnProperty(i)) {
            target[i] = source[i];
        }
    }

    return target;
}

# 判断引式类型

    Object.prototype.toString.call()
    Object.prototype.toString.call(undefined) //[object Undefind]
    Object.prototype.toString.call(new Error('111')) //[object Error]
    Object.prototype.toString.call(arguments) // [object Arguments]
    Object.prototype.toString.call(null) //[object Null]
    Object.prototype.toString.call([]) //[object Array]
    Object.prototype.toString.call({}) //[object Object]
    Object.prototype.toString.call(function(){}) //[object Function]
    Array.isArray
    obj instanceof Object

# typeof 运算符

可以判断的类型 可以识别所有值类型


underfind

string

number

boolean

symbal

判断函数
typeof funcation a(){} // function

# instanceof 实现

寻找左边的对象的原型链中是否存在右边对象的原型对象。

function instanceof(left,right) {
    let l = left.__proto__;
    let r = right.prototype;
    while (true){
        if(l === null){
            return false;
        }
        if(l === r){
            return  true;
        }
        l = r.__proto__;
    }

}

# 类型转换

强制类型转换比如:parseInt parseFloat toString

隐式类型转换:

  • 1 if判断
  • 2 逻辑运算
  • 3 ==判断
  • 4 字符串拼接等

比如: 12 + '10' //输出结果 1210 (先把12转为string,在进行字符串拼接)

console.log(undefined==null) //true
console.log('0'==0) //true 字符串转数字
console.log(0==false) //true 布尔转数字
console.log('0'==false) //2个都转成数字
console.log(null==false) //false
console.log(undefined==false)//false

# 判断两个对象相等


    function equal(a, b) { ... }

判断两个NaN相等?

isNaN() 来判断一个值是否是数字。原因是 NaN 与所有值都不相等,包括它自己。

// 主要利用NaN永远不等于自身
let a,b = NaN;
console.log(a == b);//false
function equal(a, b) {
    return a !== a && b !== b;
}
console.log(equal(NaN, NaN)); // true

区分0和-0?

JavaScript 采用了IEEE_754 浮点数表示法,1000(-0) 和 0000(0)都是表示 0 ,这才有了正负零的区别。


-0.1.toFixed(0) === -0
Math.round(-0.1) === -0

(-0).toString() // '0'
(+0).toString() // '0'
0 === -0 // true

function equal (a,b){
    //返回的是正负无穷 Infinity 或者 -Infinity
    return a === b && 1/a === 1/b
}


let a = { a: {age:25}, b: 2, c: {}}
let b = { c: 3, a: { name: 'kira' },f: {} };
function isObject(target){
    return (typeof target === 'object'  && target !== null)  ? true : false
}
function merge(target,source){
    let result;
    if(isObject(source)){
        Array.isArray(source) ? result = [] :result = {};
       for (let key in source){
            if(isObject(source[key])){
                merge(target[key],source[key]);
            }
            result[key] = source[key]
       }
       return result;
    }
}
merge(a,b)

# 精度问题

js中整数和小数只有一种数字类型:Number,它的实现遵循IEEE 754标准,使用64位固定长度来表示,也就是标准的double双精度浮点数。在二进制科学表示法中,双精度浮点数的小数部分最多只能保留52位,再加上前面的1,其实就是保留53位有效数字,剩余的需要舍去,遵从“0舍1入”的原则。

0.1 + 0.2 !== 0.3

浮点数转二进制的过程如下

  • 整数部分采用 /2 取余法
3 => 3/2 = 11  
1 => 1/2 = 01  
所以 3(十进制)= 11(二进制)

4 => 4%2 = 20  
2 => 2%2 = 10  
1 => 1%2 = 01  
所以 4(十进制)= 100(二进制) 

  • 小数部分采用 *2 取整法
0.5 => 0.5*2 = 1 取整 1
0.5(十进制)= 0.1(二进制)

0.1 => 0.1*2 = 0.2 取整 0
0.2 => 0.2*2 = 0.4 取整 0
0.4 => 0.4*2 = 0.8 取整 0
0.8 => 0.8*2 = 1.6 取整 1
0.6 => 0.6*2 = 1.2 取整 1
0.2 => 0.2*2 = 0.4 取整 0
0.4 => 0.4*2 = 0.8 取整 0
0.8 => 0.8*2 = 1.6 取整 1
0.6 => 0.6*2 = 1.2 取整 1
...发生循环
得到结果 0.1(十进制)= 00011001100110011001100110011... (0011)循环(二进制)

0.1 转二进制会发生无限循环,而 IEEE 754 标准中的尾数位只能保存 52 位 有效数字,所以 0.1 转二进制就会发生舍入,所以就产生了误差。

解决方法:

  • 使用 JavaScript 提供的最小精度值判断误差是否在该值范围内 Math.abs(0.1 + 0.2 - 0.3) <= Number.EPSILON
  • 转为整数计算,计算后再转回小数
  • 保留几位小数 比如金额,只需要精确到分即可
  • 使用别人的轮子,例如:math.js
  • 转成字符串相加(效率较低)