美文网首页
深浅拷贝,面向对象,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