美文网首页
深浅拷贝,面向对象,Symbol,Set,Map

深浅拷贝,面向对象,Symbol,Set,Map

作者: 我家有个王胖胖 | 来源:发表于2022-03-09 17:16 被阅读0次

    一:浅拷贝与深拷贝
    对象的浅拷贝:浅拷贝是对象共用的一个内存地址,对象的变化相互影响。
    对象的深拷贝:简单理解深拷贝是将对象放到新的内存中,两个对象的改变不会相互影响。
    1.1直接赋值(浅拷贝)

    //直接赋值
    let obj1 = { 
        name: "zs",
        age:18
    };
    let obj2 = obj1
    console.log(obj2.name);//zs
    obj1.name = 'ls'
    console.log(obj2.name);//ls
    

    1.2Object.assign()--->这个视情况而定

    Object.assign()拷贝的只是属性值,假如源对象的属性值是一个指向对象的引用,它也只拷贝那个引用值。
    也就是说,对于Object.assign()而言, 如果对象的属性值为简单类型(string, number),通过Object.assign({},srcObj);得到的新对象为‘深拷贝’;如果属性值为对象或其它引用类型,那对于这个对象而言其实是浅拷贝的。

    //简单类型
    let obj1 = { 
        name: "zs",
        age:18
    };
    let obj2 = Object.assign({},obj1);
    console.log(obj2.name);
    obj1,name = "ls"
    console.log(obj2.name);
    //复杂数据类型
    let obj1 = { 
        name: "zs",
        age:18,
        children:{
            name:'zz'
        }
    };
    // let obj2 = obj1
    // console.log(obj2.name);
    // obj1.name = 'ls'
    // console.log(obj2.name);
    let obj2 = Object.assign({},obj1);
    console.log(obj2.children.name);//zz
    obj1.children.name = "wz"
    console.log(obj2.children.name);//wz
    

    1.3JSON --->深拷贝

    • 优点:能正确处理的对象只有Number、String、Array等能够被json表示的数据结构
    • 缺点:函数这种不能被json表示的类型将不能被正确处理
    let str = JSON.stringify(obj1);
    let obj2  = JSON.parse(str);
    obj1.children.name = "wz";
    console.log(obj2);
    

    1.4递归实现

    //校验数据类型
    function checkType(data) {
        return Object.prototype.toString.call(data).slice(8, -1);
    }
    //深拷贝
    function deepClone(obj) {
        let type = checkType(obj);
        let result
        //如果是时普通数据类型
        if (type == 'Object') {
            result = {}
        } else if (type == "Array") {
            result = []
        } else {
            return obj;
        }
        for (let key in obj) {
            if (Object.hasOwnProperty.call(obj, key)) {//判断是否是自身的属性
                let value = obj[key];
                let ValueType = checkType(value);
                if (ValueType == 'Object' || ValueType == "Array") {
                    result[key] = deepClone(value);
                } else {
                    result[key] = value
                }
            }
        }
        return result
    
    }
    

    二:面向对象
    原型对象:每个函数都有一个prototype属性,指向一个对象.注意这个prototype就是一个对象,这个对象的所有属性和方法,都会被构造函数所拥有.原型对象上的属性以及方法,可以被其实例对象共享,原型对象的constructor属性指向其构造函数本身.
    每个对象都有一个proto属性,这个属性指向其构造函数的prototype原型.
    原型链:每个对象都有proto属性,指向其构造函数的prototype属性,而原型对象prototype本身也是一个对象,其本身也具有proto属性,这样一层一层向上查找,就构成了原型链.
    ES5的类与继承:
    构造函数继承属性(call),原型继承继承方法

    function People(name,age){
        this.name = name;
        this.age = age;
    }
    People.prototype.sayName = function() {
        console.log('我的名字是'+this.name);
    }
    let p1 = new People('zhangsan',18)
    let p2 = new People('lisi',19)
    
    p1.sayName()
    p2.sayName()
    
    //拓展自己的属性
    function Man(name,age,sex) {
        People.call(this,name,age);//继承属性
        this.sex = sex;
    }
    //继承方法
    Man.prototype = new People();
    Man.prototype.constructor = Man;
    let man = new Man('wangwu',19,'男')
    man.sayName();
    

    ES6的类与继承

    class People {
        constructor(name, age) {
            this.name = name;
            this.age = age;
        }
        sayName() {
            console.log('我的名字是:' + this.name);
        }
    }
    
    class Man extends People {
        constructor(name, age, sex) {
            super(name, age);
            this.sex = sex;
        }
        saySex() {
            console.log('我的性别是:' + this.sex);
        }
    }
    
    let man = new Man('张三',18,'男');
    console.log(man.name);
    console.log(man.age);
    console.log(man.sex);
    man.sayName();
    man.saySex();
    

    三:Symbol:ES6 引入了一种新的原始数据类型Symbol,表示独一无二的值。它属于 JavaScript 语言的数据类型之一,其他数据类型是:undefined、null、布尔值(Boolean)、字符串(String)、数值(Number)、大整数(BigInt)、对象(Object)。
    Symbol函数可以接受一个字符串作为参数,表示对 Symbol 实例的描述,主要是为了在控制台显示,或者转为字符串时,比较容易区分。

    let s1 = Symbol('foo');
    let s2 = Symbol('bar');
    
    s1 // Symbol(foo)
    s2 // Symbol(bar)
    
    s1.toString() // "Symbol(foo)"
    s2.toString() // "Symbol(bar)"
    

    上面代码中,s1和s2是两个 Symbol 值。如果不加参数,它们在控制台的输出都是Symbol(),不利于区分。有了参数以后,就等于为它们加上了描述,输出的时候就能够分清,到底是哪一个值。
    如果 Symbol 的参数是一个对象,就会调用该对象的toString方法,将其转为字符串,然后才生成一个 Symbol 值。

    const obj = {
      toString() {
        return 'abc';
      }
    };
    const sym = Symbol(obj);
    sym // Symbol(abc)
    

    注意,Symbol函数的参数只是表示对当前 Symbol 值的描述,因此相同参数的Symbol函数的返回值是不相等的。

    我们希望重新使用同一个 Symbol 值,Symbol.for()方法可以做到这一点。它接受一个字符串作为参数,然后搜索有没有以该参数作为名称的 Symbol 值。如果有,就返回这个 Symbol 值,否则就新建一个以该字符串为名称的 Symbol 值,并将其注册到全局。

    let s1 = Symbol.for('foo');
    let s2 = Symbol.for('foo');
    console.log(s1 === s2);//false
    
    Symbol.for("bar") === Symbol.for("bar")
    // true
    
    Symbol("bar") === Symbol("bar")
    // false
    

    symbol.keyFor()方法返回一个已登记的 Symbol 类型值的key。

    let s1 = Symbol.for("foo");
    Symbol.keyFor(s1) // "foo"
    
    let s2 = Symbol("foo");
    Symbol.keyFor(s2) // undefined
    

    应用场景:
    1.作为属性名

    //当做属性名来使用
    let a = Symbol();
    let obj = {};
    //第一种写法
    obj[a] = 'aaa'
    console.log(obj[a]);//aaa
    //第二种写法
    obj = { 
        [a]:'aaa'
    };
    console.log(obj[a]);
    //第三种写法
    Object.defineProperty(obj,a,{value:'aaa'})
    console.log(obj[a]);//aaa
    

    2.消除魔术字符串

    let shareType = {
        triangle:Symbol(),
        circle:Symbol()
    }
    function getArea(type){
        let area = 0;
        switch (type) {
            case shareType.triangle:
                area = 1;
            break;
            case shareType.circle:
                area = 2;
            break;
        }
        return area;
    }
    console.log(getArea(shareType.triangle));//1
    

    Set:唯一值的集合。
    Es6的set和weakSet详解

    let s = new Set([1,2,3,2]);
    
    s.add('a').add("b");
    console.log(s.has('a'));//true
    console.log(s);//Set(5) {1, 2, 3, 'a', 'b'}
    s.delete('a');
    console.log(s);//Set(5) {1, 2, 3, 'b'}
    console.log(s.has('a'));//false
    console.log(s.size);//4
    //Set遍历
    s.forEach(item => {
      console.log(item);  //1 2 3 b
    });
    //Set遍历
    s.forEach(item => {
      console.log(item);  //1 2 3 b
    });
    for (const item of s) {
        console.log(item);//1 2 3 b
    }
    for (const item of s.keys()) {
        console.log(item);//1 2 3 b
    }
    for (const item of s.values()) {
        console.log(item);//1 2 3 b
    }
    for (const item of s.entries()) {
        console.log(item); //[1 1] [2 2] [3 3] [b b]
    }
    

    应用:
    数组去重:

    let arr2 = [1,2,3,2,2,3];
    let arr3 = new Set(arr2);
    console.log(arr3);//[1,2,3]
    

    合并去重:

    let arr4 = [1,2,3];
    let arr5 = [1, 2, 3];
    console.log(new Set([...arr4,...arr5]));
    

    set转数组:

    let set = new Set(['a','b','c']);
    console.log([...set]);
    console.log(Array.from(set));
    

    获取数组的交集:

    let  arr6 = [1,2,3]
    let  arr7 = [2,3,4]
    
    let s1 = new Set(arr6);
    let s2 = new Set(arr7);
    let result = new Set(arr6.filter(item=>{
        return s2.has(item)
    }));
    console.log(result);//Set(2) {2, 3}
    

    差集:

    et  arr6 = [1,2,3]
    let  arr7 = [2,3,4]
    
    let s1 = new Set(arr6);
    let s2 = new Set(arr7);
    let result = new Set(arr6.filter(item=>{
        return !s2.has(item)
    }));
    console.log(result);//Set(2) {4}
    

    WeakSet:只能存储对象,不可以被遍历,弱引用, 弱引用却不会屏蔽垃圾回收

    let weakSet = new WeakSet();
    weakSet.add(1);//Invalid value used in weak set
    console.log(weakSet);
    

    Map:对象的key只能是字符串(或Symbol)
    map的key可以是任意值,map在某种程度上可以替代object.
    map频繁增删键值对的场景下表现更好

    map总结.png
    map和object的对比.png
    let map = new Map();
    let obj = { 
        a:1
    };
    map.set(obj,'es6')
    console.log(map.get(obj));
    

    遍历:

    let map2 = new Map();
    let arr1 = ['a','b','c'];
    let arr2 = ['d','e','f'];
    map2.set(arr1, 'es5')
    map2.set(arr2, 'es6')
    map2.forEach((value,key) => {
        console.log(value,key); //es5 (3) ['a', 'b', 'c']  es6 (3) ['d', 'e', 'f']
    });
    
    for (const keys of map2.keys()) {
        console.log(keys);//['a', 'b', 'c']  ['d', 'e', 'f']
    }
    for (const values of map2.values()) {
        console.log(values);//es5 es6
    }
    for(const [key,values] of map2.entries()){
        console.log(key,values);//(3) ['a', 'b', 'c'] 'es5'   ['d', 'e', 'f'] 'es6'
    }
    

    Object与map的区别
    模板字符串:换行+填充

    let html = `
        <ul>
            <li></li>
        </ul>
    `;
    console.log(html);
    //<ul>
    //    <li></li>
    //</ul>
    let obj = { 
        name: '张三',
        age:18
    };
    console.log(`我的名字是${obj.name},年龄是${obj.age}`);//我的名字是张三,年龄是18
    //嵌套模板
    function isLargeScreen(){
        return true;
    }
    let class1 = 'icon';
    class1 += isLargeScreen() ? ' icon-large':' icon-small';
    console.log(class1);//icon icon-large
    
    let class2 = `icon icon-${isLargeScreen()?'big':'small'}`
    console.log(class2);//icon icon-big
    

    0.1+0.2 = 0.3? //不成立,转化为二进制的时候存在精度的缺失
    js中的整数和浮点数都是以多位的二进制数进行表示的

    JavaScript 内部只有一种数字类型Number,也就是说,JavaScript 语言的底层根本没有整数,所有数字都是以IEEE-754标准格式64位浮点数形式储存,1与1.0是相同的。因为有些小数以二进制表示位数是无穷的。JavaScript会把超出53位之后的二进制舍弃,所以涉及小数的比较和运算要特别小心。

    浮点数的存储

    JS的浮点数实现也是遵循IEEE 754标准,采用双精度存储(double precision),使用64位固定长度来表示,其中1位用来表示符号位,11位用来表示指数,52位表示尾数。如下图:

    IEEE754.png
    • 符号位(sign):第1位是正负数符号位,0代表正数,1代表负数
    • 指数位(Exponent):中间11位存储指数,用来表示次方数
    • 尾数位(mantissa):最后的52位是尾数,超出部分自动进一舍零
      浮点数的计算步骤:
      【1】首先,十进制的0.1和0.2会转换成二进制的,但是由于浮点数用二进制表示是无穷的
      0.1——>0.0001 1001 1001 1001 ...(1001循环)
      0.2——>0.0011 0011 0011 0011 ...(0011循环)
      【2】IEEE754标准的64位双精度浮点数的小数部分最多支持53位二进制,多余的二进制数字被截断,所以两者相加之后的二进制之和是
      0.0100110011001100110011001100110011001100110011001101
      【3】将截断之后的二进制数字再转换为十进制,就成了0.30000000000000004,所以在计算时产生了误差

    解决办法:
    ①转成整数进行计算
    ②引入三方js:BigNumber.js

    相关文章

      网友评论

          本文标题:深浅拷贝,面向对象,Symbol,Set,Map

          本文链接:https://www.haomeiwen.com/subject/sfpbrrtx.html