一:浅拷贝与深拷贝
对象的浅拷贝:浅拷贝是对象共用的一个内存地址,对象的变化相互影响。
对象的深拷贝:简单理解深拷贝是将对象放到新的内存中,两个对象的改变不会相互影响。
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和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
网友评论