JS 学习

原型和原型链

什么是原型?

在这里插入图片描述

  • 每个对象都有_proto_属性,并且指向它的原型对象
  • 每个构造函数都有它的prototype原型对象
    prototype原型对象里的constructor指向它的构造函数
    new一个构造函数会形成它的实例对象
原型的作用:
  1. 数据共享 节约内存内存空间
  2. 实现继承
原型链

当查找一个对象的某个属性时,会先从它自身的属性上查找,如果找不到的话会从它的_proto_属性上查找,就是这个构造函数的prototype属性,如果还没找到就会继续在_proto_上查找,直到最顶层,找不到则为undefined,像这样一层一层去查找形成一个链式的称为原型链
原型和原型链介绍

执行上下文与执行上下文栈

执行上下文是当前JavaScript代码被解析和执行时所在环境

全局执行上下文

  1. 在执行全局代码前将window确定为全局执行上下文
  2. 对全局数据进行预处理
  3. var定义的全局变量==>undefined, 添加为window的属性
  4. function声明的全局函数==>赋值(fun), 添加为window的方法
  5. this==>赋值(window)
  6. 开始执行全局代码

函数执行上下文

  1. 在调用函数, 准备执行函数体之前, 创建对应的函数执行上下文对象(虚拟的, 存在于栈中)
  2. 对局部数据进行预处理
  3. 形参变量==>赋值(实参)==>添加为执行上下文的属性
  4. arguments==>赋值(实参列表), 添加为执行上下文的属性
  5. var定义的局部变量==>undefined, 添加为执行上下文的属性
  6. function声明的函数 ==>赋值(fun), 添加为执行上下文的方法
  7. this==>赋值(调用函数的对象)
  • 变量提升与函数提升
    • 变量提升: 在变量定义语句之前, 就可以访问到这个变量(undefined)
    • 函数提升: 在函数定义语句之前, 就执行该函数
    • 先有变量提升, 再有函数提升
  • 理解
    • 执行上下文: 由js引擎自动创建的对象, 包含对应作用域中的所有变量属性
    • 执行上下文栈: 用来管理产生的多个执行上下文
  • 分类:
    • 全局: window
    • 函数: 对程序员来说是透明的
  • 生命周期
    • 全局 : 准备执行全局代码前产生, 当页面刷新/关闭页面时死亡
    • 函数 : 调用函数时产生, 函数执行完时死亡
  • 包含哪些属性:
    • 全局 :
      • 用var定义的全局变量 ==>undefined
      • 使用function声明的函数 ===>function
      • this ===>window
    • 函数
      • 用var定义的局部变量 ==>undefined
      • 使用function声明的函数 ===>function
      • this ===> 调用函数的对象, 如果没有指定就是window
      • 形参变量 ===>对应实参值
      • arguments ===>实参列表的伪数组

执行上下文栈

每个函数都有自己的执行环境,当执行流进入一个函数时,函数的环境就会被推入一个环境栈中。函数执行完后,栈将其环境弹出,把控制权返回给之前的执行环境。
一个程序代码中包含多个函数,也就是包含多个函数执行上下文,为了管理好多个执行上下文之间的关系,JavaScript中创建了执行上下文栈来管理执行上下文。执行上下文栈是具有后进先出结构的栈结构,用于存储在代码执行期间创建的所有执行上下文。
当JavaScript引擎运行JavaScript代码时它会创建一个全局执行上下文并将其push到当前调用栈。(函数还没解析或者是执行、调用)仅存在全局执行上下文,每当引擎发现函数调用时,引擎都会为该函数创建一个新的函数执行上下文,并将其推入到堆栈的顶部(当前执行栈的栈顶)。当引擎执行其执行上下文位于堆栈顶部的函数之后,将其对应的函数执行上下文将会从堆栈中弹出,并且控件到达当前堆栈中位于其下方的上下文(如果有下一个函数的话)

  • 执行上下文创建和初始化的过程
    • 全局:
      • 在全局代码执行前最先创建一个全局执行上下文(window)
      • 收集一些全局变量, 并初始化
      • 将这些变量设置为window的属性
    • 函数:
      • 在调用函数时, 在执行函数体之前先创建一个函数执行上下文
      • 收集一些局部变量, 并初始化
      • 将这些变量设置为执行上下文的属性

作用域与作用域链

  • 理解:
    • 作用域: 一块代码区域, 在编码时就确定了, 不会再变化
    • 作用域链: 多个嵌套的作用域形成的由内向外的结构, 用于查找变量,
      作用域链的作用是保证执行环境里有权访问的变量和函数是有序的,作用域链的变量只能向上访问,变量访问到window对象即被终止,作用域链向下访问变量是不被允许的。
  • 分类:
    • 全局
    • 函数
    • js没有块作用域(在ES6之前)
  • 作用
    • 作用域: 隔离变量, 可以在不同作用域定义同名的变量不冲突
    • 作用域链: 查找变量
  • 区别作用域与执行上下文
    • 作用域: 静态的, 编码时就确定了(不是在运行时), 一旦确定就不会变化了
    • 执行上下文: 动态的, 执行代码时动态创建, 当执行结束消失
    • 联系: 执行上下文环境是在对应的作用域中的

闭包

  • 理解:
    • 当嵌套的内部函数引用了外部函数的变量时就产生了闭包
    • 通过chrome工具得知: 闭包本质是内部函数中的一个对象, 这个对象中包含引用的变量属性
  • 作用:
    • 延长局部变量的生命周期
    • 让函数外部能操作内部的局部变量
  • 写一个闭包程序
    function fn1() {
      var a = 2;
      function fn2() {
        a++;
        console.log(a);
      }
      return fn2;
    }
    var f = fn1();
    f();
    f();
    
  • 闭包应用:
    • 模块化: 封装一些数据以及操作数据的函数, 向外暴露一些行为
    • 循环遍历加监听
    • JS框架(jQuery)大量使用了闭包
  • 缺点:
    • 变量占用内存的时间可能会过长
    • 可能导致内存泄露
    • 解决:
      • 及时释放 : f = null; //让内部函数对象成为垃圾对象

内存溢出与内存泄露

  1. 内存溢出
  • 一种程序运行出现的错误
  • 当程序运行需要的内存超过了剩余的内存时, 就出抛出内存溢出的错误
  1. 内存泄露
  • 占用的内存没有及时释放
  • 内存泄露积累多了就容易导致内存溢出
  • 常见的内存泄露:
    • 意外的全局变量
    • 没有及时清理的计时器或回调函数
    • 闭包

js判断类型

1、typeof检测不出null 和 数组,结果都为object,所以typeof常用于检测基本类型
null 有属于自己的类型 Null,而不属于Object类型,typeof 之所以会判定为 Object 类型,是因为JavaScript 数据类型在底层都是以二进制的形式表示的,二进制的前三位为 0 会被 typeof 判断为对象类型,而 null 的二进制位恰好都是 0 ,因此,null 被误判断为 Object 类型。

2、instanceof不能检测出number、boolean、string、undefined、null、symbol类型,所以instancof常用于检测复杂类型以及级成关系
instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。
3、constructor
null、undefined没有construstor方法,因此constructor不能判断undefined和null。但是contructor的指向是可以被改变,所以不安全
4、Object.prototype.toString.call全类型都可以判断,但是不能准确地判断他是哪一个类的实例,不能用对象本身的toString()方法,因为都被重写了

手写instanceof

function myInstanceof (left, right) {
    // 基本数据类型直接返回false
    if (typeof left !== 'object' || left === null) return false
    // getProtypeOf是Object对象自带的一个方法,能够拿到参数的原型对象
    let proto = Object.getPrototypeOf(left)
    while (true) {
        // 查找到尽头,还没找到
        if (proto == null) return false
        // 找到相同的原型对象
        if (proto == right.prototype) return true
        proto = Object.getPrototypeOf(proto)
    }
}

测试

console.log(myInstanceof("111", String)); //false
console.log(myInstanceof(new String("111"), String));//true

浅谈Object.prototype.toString.call()方法

  • 在JavaScript里使用typeof判断数据类型,只能区分基本类型,即:number、string、undefined、boolean、object。
  • 对于null、array、function、object来说,使用typeof都会统一返回object字符串。
    要想区分对象、数组、函数、单纯使用typeof是不行的。在JS中,可以通过Object.prototype.toString方法,判断某个对象之属于哪种内置类型。分为null、string、boolean、number、undefined、array、function、object、date、math。

1. 判断基本类型

Object.prototype.toString.call(null); // "[object Null]"
Object.prototype.toString.call(undefined); // "[object Undefined]"
Object.prototype.toString.call(“abc”);// "[object String]"
Object.prototype.toString.call(123);// "[object Number]"
Object.prototype.toString.call(true);// "[object Boolean]"

2. 判断原生引用类型

**函数类型**
Function fn(){
  console.log(“test”);
}
Object.prototype.toString.call(fn); // "[object Function]"
**日期类型**
var date = new Date();
Object.prototype.toString.call(date); // "[object Date]"
**数组类型**
var arr = [1,2,3];
Object.prototype.toString.call(arr); // "[object Array]"
**正则表达式**
var reg = /[hbc]at/gi;
Object.prototype.toString.call(reg); // "[object RegExp]"
**自定义类型**
function Person(name, age) {
    this.name = name;
    this.age = age;
}
var person = new Person("Rose", 18);
Object.prototype.toString.call(arr); // "[object Object]"

很明显这种方法不能准确判断person是Person类的实例,而只能用instanceof 操作符来进行判断,如下所示:

console.log(person instanceof Person); // true

3. 判断原生JSON对象

var isNativeJSON = window.JSON && Object.prototype.toString.call(JSON);
console.log(isNativeJSON);// 输出结果为”[object JSON]”说明JSON是原生的,否则不是;

注意:Object.prototype.toString()本身是允许被修改的,而我们目前所讨论的关于Object.prototype.toString()这个方法的应用都是假设toString()方法未被修改为前提的。

undefined 和 null 区别

  1. null
    什么都没有,表示一个空对象引用(主动释放一个变量引用的兑现那个,表示一个变量不再指向任何引用地址)
  2. undefined
    没有设置值的变量,会自动赋值undefined
  3. 区别
    typeof undefined // undefined
    typeof null // object
    null === undefined // false
    null == undefined // true

普通函数和箭头函数的区别

  1. 普通函数
    可以通过bind、call、apply改变this指向
    可以使用new
  2. 箭头函数
  • 本身没有this指向
  • 它的this在定义的时候继承自外层第一个普通函数的this
  • 被继承的普通函数的this指向改变,箭头函数的this指向会跟着改变
  • 箭头函数外层没有普通函数时,this指向window
  • 不能通过bind、call、apply改变this指向
  • 使用new调用箭头函数会报错,因为箭头函数没有constructor

this指向问题

this面试题

document.write和innerHTML的区别

document.write 将内容写入页面,清空替换掉原来的内容,会导致重绘
document.innerHTML 将内容写入某个Dom节点,不会重绘

栈和堆的区别


  1. 动态分配内存,内存大小不一,也不会自动释放


  2. 自动分配相对固定大小的内存空间,并由系统自动释放

  3. 基本类型都是存储在栈中,每种类型的数据占用的空间的大小是确定的,并由系统自动分配和释放。内存可以及时回收。

  4. 引用类型的数据都是存储在堆中。准确说是栈中会存储这些数据的地址指针,并指向堆中的具体数据。

JS哪些操作会造成内存泄露

内存泄漏是指一块被分配的内存既不能使用,也不能回收,直到浏览器进程结束。
1、意外的全局变量
2、闭包
3、没有清理的dom元素
dom元素赋值给变量,又通过removeChild移除dom元素。但是dom元素的引用还在内存中
4、被遗忘的定时器或者回调

谈谈垃圾回收机制方式及内存管理

JavaScript 在定义变量时就完成了内存分配。当不在使用变量了就会被回收,因为其开销比较大,垃圾收集器会定期(周期性)找出那些不在继续使用的变量,然后释放其内存。

  1. 垃圾回收
  • 标记清除法
    当变量进入环境时,将这个变量标记为’进入环境’。当标记离开环境时,标记为‘离开环境’。离开环境的变量会被回收
  • 引用技计数法
    跟踪记录每个值被引用的次数,如果没有被引用,就会回收
  1. 内存管理
    内存分配=》内存使用=》内存回收

JS中的执行机制(setTimeout、setInterval、promise、宏任务、微任务)

1、执行机制

JS 是单线程的,处理 JS 任务(程序)只能一个一个顺序执行,所以 JS 中就把任务分为了同步任务和异步任务。同步的进入主线程先执行,异步的进入Event Table并注册函数,当指定的事情完成时,Event Table会将这个函数移入事件队列Event Queue,等待主线程内的任务执行完毕,然后就会从事件队列 Event Queue 中读取对应的函数,进入主线程执行。

除了广义的同步任务和异步任务,JS 对任务还有更精细的定义:

  • macro-task(宏任务):包括整体代码script,setTimeout,setInterval
  • micro-task(微任务):Promise,process.nextTick
    微任务先于宏任务执行(除了一开始的整体代码 script)。执行过程中,不同类型的任务会进入对应的事件队列Event Queue,比如setTimeout和setInterval会进入相同的Event Queue。

1.1、执行优先级

  • 同步代码执行顺序优先级高于异步代码执行顺序优先级
  • process.nextTick() > Promise.then() > setTimeout > setImmediate
    (注意:process.nextTick 是 node 中的方法,而在浏览器中执行时(比如在vue项目中),会退化成setTimeout,所以在浏览器中 process.nextTick 会比 Promise.then() 慢)

1.2、总结

总得来说,在 JS 中,先是执行整体的同步任务代码,遇到微任务就会将其放在微任务事件队列,遇到宏任务就会放在宏任务事件队列中。

然后整体的同步任务代码执行完后,就会先执行微任务队列中的任务,等待微任务队列中的所有任务执行完毕后,此时才会从宏任务队列中找到第一个任务进行执行。该任务执行过程中,如果遇到微任务就会放到微任务队列中,等到该任务执行完后,就会查看微任务队列中有没有微任务,如果有就先执行完微队列中的任务,否则执行第二个宏任务。以此类推。

为什么JavaScript是单线程?

javaScript的单线程,与它的用途有关。作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。

为了利用多核CPU的计算能力,HTML5提出Web Worker标准,允许JavaScript脚本创建多个线程,但是子线程完
全受主线程控制,且不得操作DOM。所以,这个新标准并没有改变JavaScript单线程的本质。
为什么JavaScript是单线程

js事件循环机制

在这里插入图片描述
如上图为事件循环示例图(或JS运行机制图),流程如下:

  • step1:主线程读取JS代码,此时为同步环境,形成相应的堆和执行栈;

  • step2: 主线程遇到异步任务,指给对应的异步进程进行处理(WEB API);

  • step3: 异步进程处理完毕(Ajax返回、DOM事件处罚、Timer到等),将相应的异步任务推入任务队列;

  • step4: 主线程执行完毕,查询任务队列,如果存在任务,则取出一个任务推入主线程处理(先进先出);

  • step5: 重复执行step2、3、4;称为事件循环。

执行的大意:

同步环境执行(step1) -> 事件循环1(step4) -> 事件循环2(step4的重复)…

其中的异步进程有:

  • a、类似onclick等,由浏览器内核的DOM binding模块处理,事件触发时,回调函数添加到任务队列中;

  • b、setTimeout等,由浏览器内核的Timer模块处理,时间到达时,回调函数添加到任务队列中;

  • c、Ajax,由浏览器内核的Network模块处理,网络请求返回后,添加到任务队列中。

JS创建对象的6种方式总结

一、new 操作符 + Object 创建对象

var person = new Object();
    person.name = "lisi";
    person.age = 21;
    person.family = ["lida","lier","wangwu"];
    person.say = function(){
        alert(this.name);
    }

二、字面式创建对象

var person ={
        name: "lisi",
        age: 21,
        family: ["lida","lier","wangwu"],
        say: function(){
            alert(this.name);
        }
    };

以上两种方法在使用同一接口创建多个对象时,会产生大量重复代码,为了解决此问题,工厂模式被开发。

三、工厂模式

function createPerson(name,age,family) {
    var o = new Object();
    o.name = name;
    o.age = age;
    o.family = family;
    o.say = function(){
        alert(this.name);
    }
    return o;
}

var person1 =  createPerson("lisi",21,["lida","lier","wangwu"]);   //instanceof无法判断它是谁的实例,只能判断他是对象,构造函数都可以判断出
var person2 =  createPerson("wangwu",18,["lida","lier","lisi"]);console.log(person1 instanceof Object);                           //true

工厂模式解决了重复实例化多个对象的问题,但没有解决对象识别的问题(但是工厂模式却无从识别对象的类型,因为全部都是Object,不像Date、Array等,本例中,得到的都是o对象,对象的类型都是Object,因此出现了构造函数模式)。

四、构造函数模式

function Person(name,age,family) {
    this.name = name;
    this.age = age;
    this.family = family;
    this.say = function(){
        alert(this.name);
    }
}
var person1 = new Person("lisi",21,["lida","lier","wangwu"]);
var person2 = new Person("lisi",21,["lida","lier","lisi"]);
console.log(person1 instanceof Object); //true
console.log(person1 instanceof Person); //true
console.log(person2 instanceof Object); //true
console.log(person2 instanceof Person); //trueconsole.log(person1.constructor);      //constructor 属性返回对创建此对象的数组、函数的引用

对比工厂模式有以下不同之处:

1、没有显式地创建对象

2、直接将属性和方法赋给了 this 对象

3、没有 return 语句

以此方法调用构造函数步骤 {

1、创建一个新对象

2、将构造函数的作用域赋给新对象(将this指向这个新对象)

3、执行构造函数代码(为这个新对象添加属性)

4、返回新对象 ( 指针赋给变量person ??? )

}

可以看出,构造函数知道自己从哪里来(通过 instanceof 可以看出其既是Object的实例,又是Person的实例)

构造函数也有其缺陷,每个实例都包含不同的Function实例( 构造函数内的方法在做同一件事,但是实例化后却产生了不同的对象,方法是函数 ,函数也是对象)详情见构造函数详解

因此产生了原型模式

五、原型模式

function Person() {
}

Person.prototype.name = "lisi";
Person.prototype.age = 21;
Person.prototype.family = ["lida","lier","wangwu"];
Person.prototype.say = function(){
    alert(this.name);
};
console.log(Person.prototype);   //Object{name: 'lisi', age: 21, family: Array[3]}

var person1 = new Person();        //创建一个实例person1
console.log(person1.name);        //lisi

var person2 = new Person();        //创建实例person2
person2.name = "wangwu";
person2.family = ["lida","lier","lisi"];
console.log(person2);            //Person {name: "wangwu", family: Array[3]}
// console.log(person2.prototype.name);         //报错
console.log(person2.age);              //21

原型模式的好处是所有对象实例共享它的属性和方法(即所谓的共有属性),此外还可以如代码第16,17行那样设置实例自己的属性(方法)(即所谓的私有属性),可以覆盖原型对象上的同名属性(方法)。具体参见原型模式详解

六、混合模式(构造函数模式+原型模式)

function Person(name,age,family){
    this.name = name;
    this.age = age;
    this.family = family;
}

Person.prototype = {
    constructor: Person,  //每个函数都有prototype属性,指向该函数原型对象,原型对象都有constructor属性,这是一个指向prototype属性所在函数的指针
    say: function(){
        alert(this.name);
    }
}

var person1 = new Person("lisi",21,["lida","lier","wangwu"]);
console.log(person1);
var person2 = new Person("wangwu",21,["lida","lier","lisi"]);
console.log(person2);

可以看出,混合模式共享着对相同方法的引用,又保证了每个实例有自己的私有属性。最大限度的节省了内存

继承

1.借助构造函数实现继承(部分继承)

  /**
   * 借助构造函数实现继承
   */
  function Parent1() {
    this.name = 'parent';
  }
  Parent1.prototype.say = function() {}; // 不会被继承
  function Child1() {
    // 继承:子类的构造函数里执行父级构造函数
    // 也可以用apply
    // parent的属性都会挂载到child实例上去
    // 借助构造函数实现继承的缺点:①如果parent1除了构造函数里的内容,还有自己原型链上的东西,自己原型链上的东西不会被child1继承
    // 任何一个函数都有prototype属性,但当它是构造函数的时候,才能起到作用(构造函数是有自己的原型链的)
    Parent1.call(this);
    this.type = 'child1';
  }
  console.log(new Child1);

(1)如果父类的属性都在构造函数内,就会被子类继承。
(2)如果父类的原型对象上有方法,子类不会被继承。

2:借助原型链实现继承

/**
   * 借助原型链实现继承
   */
  function Parent2() {
    this.name = 'name';
    this.play = [1, 2, 3]
  }
  function Child2() {
    this.type = 'child2';
  }
  Child2.prototype = new Parent2(); // prototype使这个构造函数的实例能访问到原型对象上
  console.log(new Child2().__proto__);
  console.log(new Child2().__proto__ === Child2.prototype); // true

  var s1 = new Child2(); // 实例
  var s2 = new Child2();
  console.log(s1.play, s2.play);
  s1.play.push(4);

  console.log(s1.__proto__ === s2.__proto__); // true // 父类的原型对象

(1)原型链的基本原理:构造函数的实例能访问到它的原型对象上
(2)缺点:原型链中的原型对象,是共用的

3:组合方式

第一种

 /**
   * 组合方式
   */
  function Parent3() {
    this.name = 'name';
    this.play = [1, 2, 3];
  }
  function Child3() {
   Parent3.call(this);
   this.type = 'child3';
  }
  Child3.prototype = new Parent3();
  var s3 = new Child3();
  var s4 = new Child3();
  s3.play.push(4);
  console.log(s3.play, s4.play);
  // 父类的构造函数执行了2次
  // 构造函数体会自动执行,子类继承父类的构造函数体的属性和方法

第二种优化

  /**
   * 组合继承的优化方式1:父类只执行了一次
   */
  function Parent4() {
    this.name = 'name';
    this.play = [1, 2, 3];
  }
  function Child4() {
    Parent4.call(this);
    this.type = 'child4';
  }
  Child4.prototype = Parent4.prototype; // 继承父类的原型对象
  var s5 = new Child4();
  var s6 = new Child4();
  console.log(s5 instanceof Child4, s5 instanceof Parent4); // true
  console.log(s5.constructor); // Parent4  //prototype里有个constructor属性,子类和父类的原型对象就是同一个对象, s5的constructor就是父类的constructor

第三种

   /**
   * 组合继承优化2
   */
  function Parent5() {
    this.name = 'name';
    this.play = [1, 2, 3];
  }
  function Child5() {
    Parent5.call(this);
    this.type = 'child5';
  }
  //可多继承父类实例属性方法,可传递参数;
  Child5.prototype = Object.create(Parent5.prototype);  // Object.create创建的对象就是参数
   Child5.prototype.constructor = Child5;
   var s7 = new Child5();
   console.log(s7 instanceof Child5, s7 instanceof Parent5);
   console.log(s7.constructor); // 构造函数指向Child5

优点:

可以取到父类实例属性方法,父类原型属性方法;

解决实例共享父类实例属性的问题;

父类构造函数只使用一次;

可多继承父类实例属性方法,可传递参数;
createObject()方式

二、优缺点:

原型链继承的缺点
1、字面量重写原型
一是字面量重写原型会中断关系,使用引用类型的原型,并且子类型还无法给超类型传递参数。
2、借用构造函数(类式继承)
借用构造函数虽然解决了刚才两种问题,但没有原型,则复用无从谈起。所以我们需要原型链+借用构造函数的模式,这种模式称为组合继承
3、组合式继承
组合式继承是比较常用的一种继承方法,其背后的思路是 使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。这样,既通过在原型上定义方法实现了函数复用,又保证每个实例都有它自己的属性。

为什么使用严格模式:

  • 消除Javascript语法的一些不合理、不严谨之处,减少一些怪异行为;
  • 消除代码运行的一些不安全之处,保证代码运行的安全;
  • 提高编译器效率,增加运行速度;
  • 为未来新版本的Javascript做好铺垫。

字符串对象转换为json对象

JSON.parse(str)

深拷贝

var deepClone = (obj) => {
    var newObj = null; // 初始化对象
    if(typeof obj == "object") {
        if(Array.isArray(obj)) {  // 判断是不是数组
            newObj = [];
            for(var item of obj) {
                newObj.push(deepClone(item));
            }
        } else if (Object.prototype.toString.call(obj) == "[object Object]") { // 判断是不是对象  
            newObj = {}; //空对象
            for (var key in obj) {
                newObj[key] = deepClone(obj[key]);
            }
        } else {  // 其他的就是 比如 函数  正则 null  Date Math  Set, Map 等js内置对象了等等
            newObj = obj;
        }
    } else {  // 不是object类型的比如 string number undefined sympol
        newObj = obj;
    }
    return newObj;
}
var test = { a: 'hello', b: [1, 2]}
 
var temp = deepClone(test);
test.b[0] = 3;
 
console.log(temp, test);

JSON.stringify 和 JSON.parse
用 JSON.stringify 把对象转换成字符串,再用 JSON.parse 把字符串转换成新的对象。使用条件是:可以转成 JSON 格式的对象才能使用这种方法,如果对象中包含 function 或 RegExp 这些就不能用这种方法了。

deepCopy = (Obj) => {
    let _obj = JSON.stringify(obj);
    let objClone = JSON.parse(_obj);
    return objClone;
  }

  o = deepCopy(obj)

  o.name = 'cll'
  o.color.push('绿色')
  console.log(o);
  console.log(obj);

Object.assign方法实行的是浅拷贝,而不是深拷贝。也就是说,如果源对象某个属性的值是对象,那么目标对象拷贝得到的是这个对象的引用。

循环引用

在面试的时候被问到一个问题,再进行深拷贝时遇到循环引用时怎么办?
当时一下子懵了,从俩没有想过这样的问题。
查了一下,解决方法应该是 用一个 Map 来存储引用类型,然后每次遇到引用属性时,就用 has 查看是否已经有了这个引用。

function deepCopy(target, map) {
  // typeof 筛选出 obj  array null  ,前面过滤掉 null
  if (!target || typeof target !== "object") {
    return null;
  }
  let result = Array.isArray(target) ? [] : {};
  Object.keys(target).forEach((property) => {
    if (typeof target[property] !== "object") {
      result[property] = target[property];
    } else {
      //防止循环引用
      if (map.has(result[property])) {
        result[property] = undefined;
      } else {
        result[property] = deepCopy(target[property]);
        map.set(result[property], true);
      }
    }
  });
  return result;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值