# js

# 数组的操作

sort():排序,传参的正反,控制升序降序,返回的是重新排序的原数组 splice():向数组的指定index处插入,返回的是被删除掉的元素集合,会改变原数组 pop:后删除 push:后插入 unshift:前插入 shift:前删除

数组去重

# 堆和栈存储机制有什么不同

堆 是一种非连续的树形存储数据结构,每个节点有一个值,整棵树是经过排序的,特点是根节点的值最小或最大,且根节点的两个子树也是一个堆,常用来实现优先对列,存取随意。

栈 是连续存储的数据结构,具有先进后出的机制

# js执行机制和事件循环(Event Loop)同步异步、宏任务微任务

浏览器是多线程的,js是异步单线程的,也就是同一时间只能处理一件事,优点是避免造成线程阻塞 JS引擎线程(web worker)可以把单线程变为多线程,本质上是加持了别的东西

常驻线程(一直待命的线程)
1、GUI渲染线程(给浏览器画画用的DOM/BOM)
2、JS引擎线程
3、浏览器事件线程(onclick)
非常驻线程(有需求时)
4、定时器触发线程
5、http异步线程
6、EventLoop(事件循环)处理线程

  调用栈(call stack)        任务队列(Task Queue)(Message Queue)
                                宏任务              微任务

执行栈执行后,会异步不是
异步任务不是马上放进任务队列里的,是在特定的执行时间

setTimeout()
1

# 什么会造成内存泄漏?

setTimeout第一个参数是字符串会造成内存泄漏 闭包,console.log,循环等也会造成内存泄漏

# 递归和闭包

# 浏览器的兼容

# 浅拷贝和深拷贝:JSON.parse(JSON.stringify(obj))深拷贝的问题

浅拷贝和深拷贝都是拷贝一个新的对象,区别是如果属性是基本数据类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址,所以如果其中一个对象改变了这个地址,就会影响到另一个对象。深拷贝则是将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,并且不会影响元对象

浅拷贝只指向某个对象的指针地址,而不是复制对象本身,新旧对象共享同一块内存,但是深拷贝会创造一个一模一样的对象,新旧对象不共享内存,互不影响

// 普通赋值
let a ={num:1}; let b = a; a={num:2}; console.log(a,b);//{num:2} {num:1}
let a1 ={num:1}; let b1 = a1; a1.num=3; console.log(a1,b1);//{num:3} {num:3}
// a.num=xxx,这样会修改原来内存的值。a={num:2},相当于重新分配了内存。
1
2
3
4

浅拷贝的实现方式:

# 1、Object.assign()

可以把任意多个源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象

let obj1={person:{name:'Jak',age:18},sports:'basketball'};
let obj2=Object.assign({},obj1);
obj2.person.name="Tom";
obj2.sports='football';
console.log(obj1)//{person:{name:'Tom',age:18},sports:'football'};
1
2
3
4
5

# 2、函数库lodash的_.clone方法

var lodash = require('lodash');
var obj1 = {
    a:1,
    b:{f:{g:1}},
    c:[1,2,3]
}
var obj2 = lodash.clone(obj1);
console.log(obj1.b.f===obj2.b.f);//true
1
2
3
4
5
6
7
8

# 3、展开运算符(es6)

与Object.assign()功能相同

let obj1 = {name:'Tom'};
let obj2 = {...obj1};
obj1.name='Jak';
console.log('obj2',obj2);//obj2 {name:'Jak'}
1
2
3
4

# 4、Array.prototype.concat()

concat拼接数组

let arr = [{name:'Bob'}];
let arr2 = arr.concat();
arr2[0].name='wade';
console.log(arr);//[{name:'wade'}]
1
2
3
4

# 5、Array.prototype.slice()

使用slice(start=0,end)方法会产生一个新的数组,不会改变原始数组,但是后期改变数组内数据,依照浅拷贝原则

    // 引用类型 另一个对象会受影响
   let arr = [{name:'Bob'},{name:'Tom'},{name:'Jak'}];
    let arr1 = arr.slice();
    arr1[0].name='wade';
    arr[1].name='Aile';
    console.log(arr,arr1);//[{name:'wade'},{name:'Aile'},{name:'Jak'}], [{name:'wade'},{name:'Aile'},{name:'Jak'}]

    let arr2 = [{name:'Bob'},{name:'Tom'},{name:'Jak'}];
    let arr3 = arr2.slice(0,2);
    arr3[0].name='Zroz';
    arr2[1].name='City';
    console.log(arr2,arr3);//[{name:'Zroz'},{name:'City'},{name:'Jak'}], [{name:'Zroz'},{name:'City'}]

    // 基本类型(string,number,boolean)不会受影响
    let arr4=[1,2,3,4,5,6];
    let arr5 = arr4.slice();
    arr5[0]=0;
    console.log(arr4,arr5)//[1,2,3,4,5,6],[0,2,3,4,5,6]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

深拷贝的实现方式:

# JSON.parse(JSON.stringify())

let arr = [1,2,{name:'Jak'}];
let arr2 = JSON.parse(JSON.stringify(arr));
arr2[2].name='Bob';
console.log(arr,arr2);//[1,2,{name:'Jak'}],[1,2,{name:'Bob'}]

let arr3 = [1,2,{name:'Jak'},NaN,undefined,function(){}];
let arr4 = JSON.parse(JSON.stringify(arr));
console.log(arr3,arr4);//[1,2,{name:'Jak'},NaN,undefined,fn],[1,2,{name:'Jak'}]
1
2
3
4
5
6
7
8

注意:
1、如果obj里面存在时间对象,JSON.parse(JSON.stringify(obj))之后,时间对象变成了字符串。
2、如果obj里有RegExp、Error对象,则序列化的结果将只得到空对象。
3、如果obj里有函数,undefined,则序列化的结果会把函数, undefined丢失。
4、如果obj里有NaN、Infinity和-Infinity,则序列化的结果会变成null。
5、JSON.stringify()只能序列化对象的可枚举的自有属性。如果obj中的对象是有构造函数生成的, 则使用JSON.parse(JSON.stringify(obj))深拷贝后,会丢弃对象的constructor。
6、如果对象中存在循环引用的情况也无法正确实现深拷贝

# 2、loadsh的_.cloneDeep方法

var _ = require('lodash');
var obj1 = {
    a: 1,
    b: { f: { g: 1 } },
    c: [1, 2, 3]
};
var obj2 = _.cloneDeep(obj1);
console.log(obj1.b.f === obj2.b.f);// false
1
2
3
4
5
6
7
8

# 3、jQuery.extend()方法

$.extend(deepCopy,target,obj1,[objN])//第一个参数为true,就是深拷贝
1
var $ = require('jquery');
var obj1 = {
    a: 1,
    b: { f: { g: 1 } },
    c: [1, 2, 3]
};
var obj2 = $.extend(true, {}, obj1);
console.log(obj1.b.f === obj2.b.f); // false
1
2
3
4
5
6
7
8

# 4、手写递归方法

递归方法实现深拷贝原理:遍历对象、数组直到里边都是基本数据类型,然后再去复制,就是深拷贝

有种特殊情况需要注意,就是对象存在循环引用的情况,即对象的属性直接的引用了自身的情况,解决这种情况,可以额外开辟一个存储空间,来存储当前对象和拷贝对象的对应关系,当需要拷贝当前对象时,先去存储空间找,有没有拷贝过这个对象,如果有直接返回,如果没有继续拷贝。

function deepClone(obj,hash = new WeakMap()){
    if(obj === null) return obj;//如果时null或者undefined我就不进行拷贝操作
    if(obj instanceof Date) return new Date(obj);
    if(obj instanceof RegExp) return new RegExp(obj);
    // 可能是对象或者普通的值 如果是函数的话是不需要深拷贝
    if(typeof obj !== "object") return obj;
    // 是对象的话就要进行深拷贝
    if(hash.get(obj)) return hash.get(obj);
    let cloneObj = new obj.constructor();
    // 找到的是所属类原型上的constructor,而原型上的 constructor指向的是当前类本身
    hash.set(obj,cloneObj);
    for(let key in obj){
        if(obj.hasOwnProperty(key)){
            // 实现一个递归拷贝
            cloneObj[key]=deepClone(obj[key],hash);
        }
    }
    return cloneObj;
}

let obj = {name:1,address:{x:100}};
obj.o = obj;//对象存在循环引用的情况
let d = deepClone(obj);
obj.address.x = 200;
console.log(d)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

# 原型,原型链,继承

可以将js中的对象分为普通对象和函数对象
JavaScript中每个对象都有_proto_原型链内置属性,它的作用是指向构建它的构造函数的原型对象。
JavaScript中只有函数对象中有ProtoType原型对象,它的作用是让它实例化的对象们可以找到公用的属性和方法。
当访问一个对象的属性时,如果该对象内部不存在这个属性,那么它就会去_proto_原型链属性指向的对象,也就是父对象上找,一直找,直到_proto_属性的终点为null,然后返回undefined,通过_proto_属性将对象连接起来的这条链路就叫原型链。

圣杯模式

圣杯模式是为防止修改子对象属性时父对象也随之改变,所以通过new一个空的函数对象使它的prototype原型指向父对象的prototype原型,而子对象的prototype原型指向空对象prototype原型,最后记得将子对象的构造器归位就可以

	function inherit(Child, Parent){
		function F(){}
		F.prototype = Parent.prototype
		Child.prototype = new F()
        Child.prototype.constructor = Child
	}
1
2
3
4
5
6
Last Updated: 3/16/2022, 10:26:58 PM