# 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()
# 什么会造成内存泄漏?
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},相当于重新分配了内存。
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'};
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
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'}
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'}]
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]
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'}]
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
2
3
4
5
6
7
8
# 3、jQuery.extend()方法
$.extend(deepCopy,target,obj1,[objN])//第一个参数为true,就是深拷贝
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
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)
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
}
2
3
4
5
6