深拷贝和浅拷贝
数据类型
在 JavaScript 中,一共有 7 种数据类型
-
Number
-
String
-
Boolean
-
Null
-
Undefined
-
Symbol
-
Object
又可以区分为两大类
-
原始数据类型(
Number String Boolean Null Undefined Symbol
) -
复杂数据类型 Object ,Object 下存在子类型如
Array,Function,RegExp,Date
-
简单数据类型是存储在栈中的,特点是大小固定
-
复杂数据类型是存储在堆中的,特点是大小不固定
简单数据类型都是深拷贝
深拷贝浅拷贝都是针对 复杂类型来讨论的。简单数据类型 理解为全部都是深拷贝
浅拷贝
引用地址的复制就是浅拷贝
因为对象是存在栈中的,当我们创建一个对象时,其实是返回了该对象在内存中的引用地址
// obj 存放的是指向 一个对象的引用地址
const obj = {
name: '悟空',
};
将旧对象赋值给新对象的操作,其实只是复制了一份引用地址而已,新旧两个对象其实都是指向同一个内存地址
const obj = {
name: '悟空',
};
const obj1 = obj;
console.log(obj === obj1); // true 两个地址相等
所以当我们修改了新数据时,旧数据也会一起发生改变
obj1.name = '八戒';
console.log(obj.name); // 八戒
所以
引用地址的复制就是浅拷贝
哪些常见的浅拷贝方法
-
Object.assign
const obj = { name: '悟空', say() { console.log(this.name); }, }; const newObj = Object.assign({}, obj); console.log(newObj === obj); // false console.log(newObj.say === obj.say); // true
-
slice
const list = ['a', { name: '悟空' }, 'b']; const newList = list.slice(0); console.log(newList === list); // false console.log(newList[1] === list[1]); // true
-
concat
const list = ['a', { name: '悟空' }, 'b']; const newList = list.concat([]); console.log(newList === list); // false console.log(newList[1] === list[1]); // true
-
拓展运算符
const obj = { name: '悟空', say() { console.log(this.name); }, }; const newObj = { ...obj }; console.log(newObj === obj); // false console.log(newObj.say === obj.say); // true
深拷贝
实现深拷贝的方式只有两种
-
调用 JavaScript 内置的序列化方法
-
手写代码,递归克隆
调用 JavaScript 内置的序列化方法
该方法会导致 函数,undefined,Date,RegExp 等数据类型丢失
const obj = {
name: '悟空',
say() {
console.log(this.name);
},
};
const newObj = JSON.parse(JSON.stringify(obj));
console.log(newObj === obj); // false
console.log(newObj.name); // 悟空
console.log(newObj.say); // undefined
手写代码,递归克隆
实际工作中,可以优先调用别人写好的第三方库
如:
-
clone
const clone = require('clone'); const obj = { name: '悟空', person: { color: 'red', }, }; const newObj = clone(obj); console.log(newObj); // {name: '悟空', person: {…}, say: ƒ} console.log(newObj === obj); // false console.log(newObj.person === obj.person); // false console.log(newObj.say === obj.say); // true
-
loadash
const _ = require('lodash'); const obj = { name: '悟空', person: { color: 'red', }, say() { console.log(this.name); }, }; const newObj = _.cloneDeep(obj); console.log(newObj); // {name: '悟空', person: {…}, say: ƒ} console.log(newObj === obj); // false console.log(newObj.person === obj.person); // false console.log(newObj.say === obj.say); // true
-
jquery
const obj = { name: '悟空', person: { color: 'red', }, say() { console.log(this.name); }, }; const newObj = $.extend(true, {}, obj); console.log(newObj); // {name: '悟空', person: {…}, say: ƒ} console.log(newObj === obj); // false console.log(newObj.person === obj.person); // false console.log(newObj.say === obj.say); // true
-
自己实现
-
理解原始类型和复杂类型
-
通过代码区分判断原始类型和复杂类型
-
先实现原始类型的拷贝
-
实现数组类型的拷贝
-
实现函数类型的拷贝
-
实现日期类型的拷贝
-
实现正则类型的拷贝
const obj = { a: Infinity, b: '复杂', c: false, d: null, e: undefined, f: Symbol(), g: { aa: Infinity, bb: '', cc: false, dd: null, ee: undefined, ff: Symbol(), }, h: [ { aaa: NaN, bbb: Infinity, ccc: '', ddd: false, eee: null, fff: undefined, ggg: Symbol(), }, 123, ], i: function (aa, bb) { return aa + bb + this.b; }, j: new Date(), k: /good\d/gi, }; function deepclone(target) { let dist; if (target instanceof Date) { dist = new Date(target); } else if (target instanceof RegExp) { dist = new RegExp(target.source, target.flags); } else if (target instanceof Function) { dist = target; } else if (target instanceof Object) { dist = {}; if (target instanceof Array) { dist = []; } for (const key in target) { if (target.hasOwnProperty(key)) { // 判断数据类型 简单类型就直接复制、复杂类型就递归 // 判断数据类型 if (target[key] instanceof Object) { // 递归 复杂类型 dist[key] = deepclone(target[key]); } else { // 简单数据类型 dist[key] = target[key]; } } } } return dist; }
-
你是如何理解原型和继承的
设计模式
在软件工程中,设计模式(Design Pattern)是对软件设计中普遍存在(反复出现)的各种问题,所提出的解决方案。 ——维基百科
设计模式的定义是:在面向对象软件设计过程中针对特定问题的简洁而优雅的解决方案
Erich Gamma、Richard Helm、Ralph Johnson、John Vlissides 四人总结了23 种常见的软件开发设计模式。
单例
单例的定义是,保证一个类仅有一个实例
比如页面的模态框,不管调用多少次显示模态框的方法,页面中都只会有一个模态框!
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0,maximum-scale=1,minimum-scale=1,user-scalable=no"
/>
<title>设计模式.html</title>
<style>
.modal {
width: 400px;
height: 100px;
display: flex;
align-items: center;
justify-content: center;
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
border: 5px solid #666;
font-size: 40px;
box-shadow: 0 0 10px 10px #ccc;
}
</style>
</head>
<body>
<button>显示</button>
<script>
class Modal {
static instance = null;
static getInstance = function () {
if (!Modal.instance) {
Modal.instance = new Modal();
}
return Modal.instance;
};
constructor() {
this.modalDom = document.createElement('div');
this.modalDom.classList.add('modal');
this.modalDom.style.display = 'none';
this.modalDom.addEventListener('click', () => {
this.hide();
});
document.body.appendChild(this.modalDom);
}
show(msg) {
this.modalDom.style.display = 'flex';
this.modalDom.innerText = msg;
}
hide() {
this.modalDom.style.display = 'none';
}
}
const instance = Modal.getInstance();
const instance1 = Modal.getInstance();
const instance2 = Modal.getInstance();
const btn = document.querySelector('button');
btn.onclick = function () {
instance.show('显示吧');
instance.show('显示吧');
};
</script>
</body>
</html>
观察者
观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个目标对象,当这个目标对象的状态发生变化
时,会通知所有观察者对象,使它们能够自动更新
代码结构
-
有两个类
-
一个 主题 类
-
负责添加观察者
-
通知观察者
-
-
一个 观察者 类
-
负责编写接收到通知后的逻辑
-
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0,maximum-scale=1,minimum-scale=1,user-scalable=no"
/>
<title>设计模式.观察者.html</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.receive {
width: 200px;
height: 30px;
/* border: 1px solid #eee; */
border: none;
outline: none;
box-shadow: 0 0 10px 0px red;
/* display: block; */
margin: 20px;
}
</style>
</head>
<body>
<button>创建</button>
<input type="text" />
<div></div>
<script>
const btn = document.querySelector('button');
const inp = document.querySelector('input');
class Center {
constructor() {
this.list = [];
}
add(target) {
this.list.push(target);
}
broadcast(msg) {
this.list.forEach((wx) => wx.receive(msg));
}
}
const center = new Center();
btn.onclick = function () {
center.add(new Wx('input'));
center.add(new Wx('div'));
};
class Wx {
constructor(tagName) {
this.dom = document.createElement(tagName);
this.dom.classList.add('receive');
document.body.appendChild(this.dom);
}
receive(msg) {
switch (this.dom.nodeName) {
case 'INPUT':
this.dom.value = msg;
break;
case 'DIV':
this.dom.innerText = msg;
break;
default:
break;
}
}
}
inp.onkeyup = function (e) {
if (e.key === 'Enter') {
center.broadcast(this.value);
}
};
</script>
</body>
</html>
面向对象设计的五大基本原则
SOLID 是面向对象的五大原则
-
单一功能原则-SRP(Single Responsibility Principle)
规定每个类都应该有一个单一的功能,并且该功能应该由这个类完全封装起来
-
开放封闭原则-OCP(Opened Closed Principle)
对扩展开放,意味着有新的需求或变化时,可以对现有代码进行扩展,以适应新的情况。
对修改封闭,意味着类一旦设计完成,就可以独立完成其工作,而不要对类进行任何修改
-
里式替换原则-LSP(Liskov Substitution Principle)
子类对象可以在程式中代替其基类(超类)对象
-
接口隔离原则-ISP(Interface Segregation Principle)
客户端不应该依赖它不需要的接口。一个类对另一个类的依赖应该建立在最小的接口上,使用多个专门的接口比使用单一的总接口要好
-
依赖反转原则-DIP(Dependency Inversion Principle)
高层次模块不应该依赖于低层次模块,两者都应该依赖于其抽象
-
默认
依赖反转
JavaScript 垃圾回收是怎么做的
介绍
GC (Garbage Collection)垃圾回收,是 JavaScript 引擎用来控制内存释放的一种机制
为什么需要控制内存
在编写代码的时候,我们会频繁的创建数据,如基本类型、对象、函数、数组等。它们都需要占用内存,当这些数据使用完毕,我们就需要释放它们,否则会一直占用内存,最后导致内存泄漏。
可达性 Reachability
JavaScript 内存管理机制中,存在一个概念,叫做 可达性(Reachability),它是一种用来判断该数据需不需要回收的机制。
比如当该数据从 根(window)出发,发现无法访问了,这个时候,该数据就是不可达的
let obj = {
name: '白马',
};
// obj.name 可达
obj = null;
// obj.name 不可达
三种可达性
本地函数的局部变量和参数
当前嵌套执行上下文其他函数的变量和函数
全局变量
两种垃圾回收算法
-
标记清除 (Mark-Sweep)
-
引用计数 (Reference Counting)
标记清除(Mark-Sweep)
执行过程
-
假设内存中所有对象都是垃圾,全标记为 0
-
遍历内存中的对象,把不是垃圾的节点改成 1
-
清理所有标记为 0 的垃圾,销毁并回收内存空间
-
把所有内存中对象标记修改为 0,等待下一轮垃圾回收
优点
实现过程简单、遍历和打标记清除即可
缺点
内存碎片化
执行销毁垃圾后,导致空间的内存空间不连续,容易出现碎片化分配速度慢
遍历、性能低
最后 引入 标记整理(Mark-Compact)算法
在清理垃圾的同时移动内存块、使其保持连续
引用计数 (Reference Counting)
该策略是通过统计 对象 被引用的次数来判断是否是垃圾数据
let obj = { name: '悟空' }; // 1
// 引用增加
let newObj1 = obj; // 2
let newObj2 = obj; // 3
// 引用减少
newObj1 = null; // 2
newObj2 = null; // 1
obj=null // 0
优点
只要发现引用为 0,即可马上清除
缺点
如果出现互相引用、则导致无法清除
function func() {
let o1 = {};
let o2 = {};
o1.a = o2;
o2.b = o1;
}
func();
可以看到,虽然 func
已经执行完毕,但是由于存在相互引用关系,所以该对象无法回收。
结论
标记清除法更为常用