JavaScript相关概念3

深拷贝和浅拷贝

数据类型

在 JavaScript 中,一共有 7 种数据类型

  • Number

  • String

  • Boolean

  • Null

  • Undefined

  • Symbol

  • Object

又可以区分为两大类

  1. 原始数据类型(Number String Boolean Null Undefined Symbol

  2. 复杂数据类型 Object ,Object 下存在子类型如 Array,Function,RegExp,Date


  1. 简单数据类型是存储在中的,特点是大小固定

  2. 复杂数据类型是存储在中的,特点是大小不固定

简单数据类型都是深拷贝

深拷贝浅拷贝都是针对 复杂类型来讨论的。简单数据类型 理解为全部都是深拷贝

浅拷贝

引用地址的复制就是浅拷贝

因为对象是存在栈中的,当我们创建一个对象时,其实是返回了该对象在内存中的引用地址

// obj 存放的是指向 一个对象的引用地址
const obj = {
  name: '悟空',
};

将旧对象赋值给新对象的操作,其实只是复制了一份引用地址而已,新旧两个对象其实都是指向同一个内存地址

const obj = {
  name: '悟空',
};
const obj1 = obj;
​
console.log(obj === obj1); // true 两个地址相等

所以当我们修改了新数据时,旧数据也会一起发生改变

obj1.name = '八戒';
console.log(obj.name); // 八戒

所以

引用地址的复制就是浅拷贝

哪些常见的浅拷贝方法

  1. 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

  2. slice

    const list = ['a', { name: '悟空' }, 'b'];
    const newList = list.slice(0);
    console.log(newList === list); // false
    console.log(newList[1] === list[1]); // true

  3. concat

    const list = ['a', { name: '悟空' }, 'b'];
    const newList = list.concat([]);
    console.log(newList === list); // false
    console.log(newList[1] === list[1]); // true

  4. 拓展运算符

    const obj = {
      name: '悟空',
      say() {
        console.log(this.name);
      },
    };
    ​
    const newObj = { ...obj };
    console.log(newObj === obj); // false
    console.log(newObj.say === obj.say); // true

深拷贝

实现深拷贝的方式只有两种

  1. 调用 JavaScript 内置的序列化方法

  2. 手写代码,递归克隆

调用 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

手写代码,递归克隆

实际工作中,可以优先调用别人写好的第三方库

如:

  1. 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

  2. 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

  3. 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

  4. 自己实现

    1. 理解原始类型和复杂类型

    2. 通过代码区分判断原始类型和复杂类型

    3. 先实现原始类型的拷贝

    4. 实现数组类型的拷贝

    5. 实现函数类型的拷贝

    6. 实现日期类型的拷贝

    7. 实现正则类型的拷贝

    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;
    }

你是如何理解原型和继承的

一文详解-es5原型和es6-class - 掘金

设计模式

在软件工程中,设计模式(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>
​

观察者

观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个目标对象,当这个目标对象的状态发生变化

时,会通知所有观察者对象,使它们能够自动更新

代码结构

  1. 有两个类

  2. 一个 主题 类

    1. 负责添加观察者

    2. 通知观察者

  3. 一个 观察者 类

    1. 负责编写接收到通知后的逻辑

<!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 不可达
 

三种可达性

  • 本地函数的局部变量和参数

  • 当前嵌套执行上下文其他函数的变量和函数

  • 全局变量

两种垃圾回收算法

  1. 标记清除 (Mark-Sweep)

  2. 引用计数 (Reference Counting)

标记清除(Mark-Sweep)

执行过程

  1. 假设内存中所有对象都是垃圾,全标记为 0

  2. 遍历内存中的对象,把不是垃圾的节点改成 1

  3. 清理所有标记为 0 的垃圾,销毁并回收内存空间

  4. 把所有内存中对象标记修改为 0,等待下一轮垃圾回收

优点

实现过程简单、遍历和打标记清除即可

缺点

  1. 内存碎片化

    执行销毁垃圾后,导致空间的内存空间不连续,容易出现碎片化
  2. 分配速度慢

    遍历、性能低

最后 引入 标记整理(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 已经执行完毕,但是由于存在相互引用关系,所以该对象无法回收。

结论

标记清除法更为常用

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值