1、扁平的数组结构转化为json树形结构
思路:
第一步把数组数据转化为map数据结构如:
let map = {1:{id:1,title:"eeeee",parent_id:0},....}
第二步,定义一个空数组result,循环list数组,
如果parent_id 等于0的话,直接把当前项放到数组result中,
如果当前项parent_id 可以在 map对象里面找到,说明存在父节点,
然后拿到父节点的对象,往里面添加一个children属性,如果没有给一个空数组,有该属性就直接把当前项放到chindren中,如下
let parent = map[item.parent_id]
parent.children = parent.children || []
parent.children.push(item)
整体代码:
<script>
let list = [
{id:1,title:'eeeee',parent_id:0},
{id:2,title:'55555',parent_id:0},
{id:3,title:'66666',parent_id:2},
{id:4,title:'99999',parent_id:2},
{id:5,title:'00000',parent_id:2}
]
function treeArr(list){
let result = []
let map = list.reduce((pre,cur)=>{
pre[cur.id] = cur
return pre
},{})
for (const item of list) {
if(item.parent_id === 0){
result.push(item)
continue;
}
if(item.parent_id in map){
let parent = map[item.parent_id]
parent.children = parent.children || []
parent.children.push(item)
}
}
return result
}
console.log( treeArr(list))
</script>
结果:
2、json树形结构转化为扁平数组结构
function flatArr(data){
return data.reduce((pre,cur)=>{
const { id, title,parent_id,children=[]} = cur //解构
//当数组为空,并且给了一个初始值,reduce不会执行
return pre.concat([{id,title,parent_id}],flatArr(children))
},[])
}
console.log(flatArr(treeArrs,"---"))
3、防抖函数
防抖概念:
就是在一定时间内,如果事件多次触发,只执行最后一次触发的事件
应用场景:
1.scroll事件滚动触发
2.搜索框输入查询
3.表单验证
4.按钮提交事件
5.浏览器窗口缩放,resize事件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<style>
#contain {
height: 200px;
width: 200px;
background-color: aqua;
}
</style>
</head>
<body>
<div id="contain"></div>
</body>
<script>
let contain = document.querySelector("#contain");
let count = 0;
function contains(e) {
//this指向window,e为undefined
console.log(this, "===========",e);
contain.innerHTML = count++;
}
没有第三个参数,就是立即执行参数:
function debounce(func, wait) {
let timeOut = "";
return function () {
let args = arguments//拿到event事件传过去
let context = this;//改变this指向
//console.log(this, "===========debounce",arguments);
clearTimeout(timeOut);
timeOut = setTimeout(function () {
func.apply(context,args);
}, wait);
};
}
contain.onmousemove = debounce(contains, 300);
有第三个参数,有立即执行参数:
function debounce(func, wait, immediate) {
let timeOut = "";
return function () {
let args = arguments; //拿到event事件传过去
let context = this; //改变this指向
//console.log(this, "===========debounce",arguments);
clearTimeout(timeOut);
if (immediate) {
//会立刻执行
let callNow = !timeOut
console.log(timeOut,"========0000")
timeOut = setTimeout(function () {
timeOut = null
}, wait);
if(callNow){
func.apply(context, args);
}
} else {
//不会立刻执行
timeOut = setTimeout(function () {
func.apply(context, args);
}, wait);
}
};
}
contain.onmousemove = debounce(contains, 300, true);
</script>
</html>
防抖最终版:带有取消防抖按钮
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<style>
#contain {
height: 200px;
width: 200px;
background-color: aqua;
}
</style>
</head>
<body>
<div id="contain"></div>
<button id="btn">取消防抖</button>
</body>
<script>
let contain = document.querySelector("#contain");
let btn = document.querySelector("#btn");
let count = 0;
function contains(e) {
//this指向window,e为undefined
console.log(this, "===========", e);
contain.innerHTML = count++;
}
function debounce(func, wait, immediate) {
let timeOut , result;
let debounced = function () {
let args = arguments; //拿到event事件传过去
let context = this; //改变this指向
//console.log(this, "===========debounce",arguments);
if(timeOut) {
clearTimeout(timeOut);
}
if (immediate) {
//会立刻执行
let callNow = !timeOut
console.log(timeOut,"========0000")
timeOut = setTimeout(function () {
timeOut = null
}, wait);
if(callNow){
result = func.apply(context, args);
}
} else {
//不会立刻执行
timeOut = setTimeout(function () {
func.apply(context, args);
}, wait);
}
return result
}
debounced.cancle = function(){
clearTimeout(timeOut)
timeOut = null
}
return debounced
}
let doSome = debounce(contains, 5000);
//取消防抖
btn.onclick = function(){
console.log('取消防抖')
doSome.cancle()
}
contain.onmousemove =doSome;
</script>
</html>
4、forEach
第一个参数是遍历的数组内容,
第二个参数是对应的数组索引,
第三个参数是数组本身
1.forEach并不支持break操作,使用break会导致报错。
2.forEach中使用return无效
3.想要返回某个索引方法
let arr = [1, 2, 3, 4];
function find(array, num) {
let _index;
array.forEach((self, index) => {
if (self === num) {
_index = index;
};
});
return _index;
};
let index = find(arr, 2);
console.log(index)
4.循环的同时不能完全删除数组,index隐性自增
5、for与forEach的区别
1.for循环可以使用break跳出循环,但forEach不能。
2.for循环可以控制循环起点(i初始化的数字决定循环的起点),forEach只能默认从索引0开始。
3.for循环过程中支持修改索引(修改 i),但forEach做不到(底层控制index自增,我们无法左右它)。
6、script标签里面使用async和defer的区别:
当浏览器在解释html的时候,一旦遇到script标签,
就会停止加载先把script里面的内容先处理掉,如果
script标签里面有外部文件,就会先把外部文件下载
或者执行的步骤,这样浏览器才会继续加载。如果外部文件
刚好放在一个网络比较差的服务器上,那么整个页面的加载会
收到很大的影响。这就是同步带来的阻塞弊端。
用async和defer可以解决阻塞的问题,
当浏览器在加载页面的时候,遇到script带有async属性,
就会进入下载脚本,同时页面也会继续加载。因为不确定什么时候
脚本加载完成,有时候是页面加载完有时候页面还没加载完,
如果脚本是与页面DOM操作有关联的话,就会报错。
这时候用defer来推迟脚本执行是必要的,如果浏览器在
加载页面的时候,遇到script标签带有defer属性,就会马上
进入下载,同时页面也会继续加载,不管脚本是否下载完成,
都要等到页面加载完成后才会执行。并且这两种属性紧适用于外链接
,而且考虑到兼容性问题(async 在IE<=9时不支持,其他浏览器OK;defer 在IE<=9时支持但会有bug),
所以建议最好还是把script标签放到页面最后面比较合适。
7、如何改变一个函数a的上下文?
1.call()函数的用法
Function : 调用的函数
context : 新的对象上下文,函数中的this指向context,
若context为null|undefined,则执行window// arg1,arg2 : 参数列表 Function.call(context,arg1,arg2,...)
2.apply()函数的用法
[argArray] 这里传递的是数组
Function.apply(context,[argArray])
3.bind()函数的使用
bind()
函数创建一个新函数,在调用时设置 this 关键字为提供的值,在执行新函数时,将给定的参数列表作为原函数的参数序列,从前往后匹配。语法如下:
Function.bind(context,arg1,arg2,...)
总结:
call()函数、apply()函数、bind()函数三者都会改变函数调用时的执行主体,修改this的指向。 call()函数、apply()两个函数是立即执行返回,而bind()函数是返回一个新函数,在任何使用可以调用。 apply()函数第二个入参为数组,与其他 2 个函数不一致。
8、数组中哪些常用方法会修改原数组
1.pop()
pop() 方法用于删除数组的最后一个元素并返回删除的元素。
此方法改变数组的长度!
移除数组第一个元素,请使用 shift() 方法。
2.push()
push() 方法可向数组的末尾添加一个或多个元素,并返回新的长度。
新元素将添加在数组的末尾。
此方法改变数组的长度。
在数组起始位置添加元素请使用 unshift() 方法。
3.reverse()
方法用于颠倒数组中元素的顺序。
4.sort()
let arr = [23,12,1,34,116,8,18,37,56,50];
function sequence(a,b){
return a - b;
}
console.log(arr.sort(sequence));
5.splice()
方法用于添加或删除数组中的元素。
这种方法会改变原始数组。
splice(index) ——> 从index的位置开始,删除之后的所有元素(包括第index个)
若 index < 0 , 则删除最后-index个元素
splice(index,howmany) ——> 删除从index位置开始的数,howmany为删除的个数
若 howmany 小于等于 0,则不删除
splice(index ,howmany , item1, …, itemX )
index >0 时
(1. howmany 为 0 时 不删除只添加 —— 在index位置前添加item1, …, itemX的数
(2. howmany > 0 删除且添加 —— 删除从index位置开始的数,howmany为删除的个数,并且在index位置前添加item1, …, itemX的数
index <0 时
最后一个数为 -1 依次倒数第二个数为-2
(1. howmany 为 0 时 不删除只添加 —— 在-index位置前添加item1, …, itemX的数
(2. howmany > 0 删除且添加 —— 删除从-index位置开始的数,howmany为删除的个数,并且在-index位置前(相当于往后 -2前是 -1)添加item1, …, itemX的数
9、数组中哪些常用方法会返回新数组
1.filter
filter() 方法创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素。
filter() 不会对空数组进行检测。
filter() 不会改变原始数组。
2.map
方法返回一个新数组,数组中的元素为原始数组元素调用函数处理后的值。
方法按照原始数组元素顺序依次处理元素。
不会对空数组进行检测。
不会改变原始数组。
10、数组去重有哪些办法?
1.indexOf:
for(let i = 0;i<=arr.length-1;i++) {
if(arr.indexOf(arr[i])!==i){
arr.splice(i,1);
i--
}}
console. log(arr);
2.filter+indexOf:
var arr = [23,12,12,1,34,116,23,50,1];
var newArr=arr. filter(function(item, index){
return arr. indexOf(item)===index;
})
console. log(newArr);
3.利用空对象判断已经存储在空数组的数据数值
let obj={};
let newArr=[];
for(let i =0;i<=arr.length-1 ; i++){
if(!obj[arr[i]]){
obj[arr[i]]=true;
newArr.push(arr[i]);
}
}
console.log(newArr);
4.set类型存储数据时 自动 存储不重复的
const set = new Set(arr)
const newArr =[...set]
console.log(newArr)
5.reduce + includes
let array = arr.reduce((pre,cur)=>{
if(pre.includes(cur)=== false){
pre.push(cur)
}
return pre
},[])
console.log(array)
11、取数组最后一位有哪些办法?
1.arr.length-1
let arr = [23,12,12,1,34,116,23,50,1];
console.log(arr[arr.length-1])//1
2.slice
console.log(arr.slice(-1)[0])//1
3.使用pop()方法获取
console.log(arr.pop())//1
4.使用reduce
let tail = arr.reduce((pre,cur,index,array)=>{
if(index === arr.length-1) pre = cur
return pre
},0)
console.log(tail)//1
12、浅拷贝
1.
2.Object.assign()
3.展开符号...
4.for in
13、深拷贝
let obj = {
name:'222',
b:[
{
age:12
}
],
show(){
}
}
1.使用for of + object.entries、、、、、、
function copy(data){
let obj = data instanceof Array ? []: {}
for(let [k,v] of Object.entries(data)){
console.log(k)
obj[k] = typeof v === "object" ? copy(v) : v
}
return obj
}
let obj1 = copy(obj)
2.使用 for in 、、、、、、、、、、、、
function copy(data){
let obj = data instanceof Array ? []: {}
if(data && typeof data ==='object'){
for(key in data){
if(data.hasOwnProperty(key)){
if(data[key]&& typeof data[key] ==='object'){
obj[key] = copy(data[key])
}else{
obj[key] = data[key]
}
}
}
}
return obj
}
let obj1 = copy(obj)
14、类和函数
class User{
constructor(name){
this.name = name}
show(){
}}
let u = new User('8888')
for(let key in u){
console.log(key,"------User")//只能遍历出来name
}
console.log(
JSON.stringify(
Object.getOwnPropertyDescriptor(User.prototype,"show"),null,2)
) //enumerable:false,所以show不能遍历出来
函数:show可以遍历出来,因为enumerable:true,可以枚举出来
15、js 继承方式
1.es6的class继承:
super()代表父类构造函数,调用之后生成一个继承父类的this对象
class Father3{
constructor(name){
this.name = name
console.log(name)
}
infor(){
console.log(this.name)
}
}
class Son extends Father3{
constructor(name){
super(name)
this.name = name
}
infor1(){
console.log(this.name,"========")
}}
let s = new Son('8888')
s.infor()
s.infor1()
2.原型链继承:(缺点:new 出来的实例,o2.age = '18',影响到o3.age的值了)
1.把b作为a的原型
let a = {name:'aaa'}
let b = {age:12}
Object.setPrototypeOf(a,b)
console.log(a,b.age)
2.函数
function Parent(){
this.age = 20
}
function Child(){
this.name ='张三'
}
Child.prototype = new Parent()
let o2 = new Child()
let o3 = new Child()
console.log(o2.name,o2.age)
3.借用构造函数继承:(缺点:不能继承父类原型链上的属性和方法)
function Parent(){
this.age = 20
}
function Child(){
this.name ='张三'
Parent.call(this)
}
let o2 = new Child()
console.log(o2.name,o2.age)
、、、、、、、、、、、
let hd = {
data: [1, 2, 3, 34, 5, 7]
};
Object.setPrototypeOf(hd, {
max() {
return this.data.sort((a, b)=>b -a)[0];
}
})
let xj = {
lessons: { js: 87, php: 63, node: 99, linux: 88 },
get data() {
return Object.values(this.lessons)
}
}
console.log(hd.max.apply(xj));
4、 Object.create方法
let person = {
name: 'a',
age: 10,
hoby: ['唱', '跳'],
showName() {
console.log('my name is: ', this.name)
}
}
let child1 = Object.create(person)
child1.showName()
5、组合式继承:(弥补了原型链和构造函数继续的缺点)
function Parent(){
this.age = 20
}
function Child(){
Parent.call(this)
this.name ='张三'
}
Child.prototype = new Parent()//这种写法缺点:如下
Child.prototype = Parent.prototype //这种写法缺点:给Child原型链上定义一个方法,Parent也有该方法
let o2 = new Child()//每次new 一个实例的时候,都调用了new Parent()
console.log(o2.name,o2.age)
6、寄生组合式继承
function Parent(){
this.age = 20
}
function Child(){
Parent.call(this)
this.name ='张三'
}
Child.prototype = Object.create(Parent.prototype)//浅拷贝
let o2 = new Child()
console.log(o2.name,o2.age)
7、原型工厂封装继承
function extend(sub,sup){
sub.prototype = Object.create(sup.prototype);
Object.defineProperty(sub.prototype, "constructor", {
value: sub,
enumerable: false
})
}
function User(name, age) {
this.name = name; this.age = age;
User.prototype.show = function() {
console.log(this. name, this. age);
}
};
function Admin(... args) {
User. apply(this, args);
}
extend(Admin,User)
let admin = new Admin("999",19)
admin.show()
9、对象工厂派生对象并实现继承
function User(name, age) {
this.name = name;
this.age = age;
User.prototype. show = function() {
console.log(this.name, this.age);
}
};
function admin(name,age){
const instance = Object.create(User.prototype)
User.call(instance,name,age)
return instance
}
let hd = admin("88",19)
hd.show()
15、代理:Proxy 支持的拦截操作主要有以下十三种:
1.代理对象
let proxy = new Proxy(obj,{
get(obj,property){
return obj[property]
},
set(obj,property,value){
obj[property] = value
return true//不写严格模式下报错
}
})
proxy.name = '88888888888'
console.log(proxy.name)
2.函数代理
3.数组代理
16、双向绑定的页面渲染
<script>
function View(){
let proxy = new Proxy({},{
get(obj,property){},
set(obj,property,value){
document.querySelectorAll(`[v-model="${property}"]`).forEach(item=>{
item.value = value
})
document.querySelectorAll(`[v-bind="${property}"]`).forEach(item=>{
item.innerHTML = value
})
}
})
this.init= function(){
const els = document.querySelectorAll('[v-model]')
els.forEach(item=>{
item.addEventListener('keyup',function(){
proxy[this.getAttribute('v-model')] = this.value
})
})
}
}
new View().init()
</script>
17、Chrome V8 执行 JavaScript 原理总结
最初的 V8 没有字节码,直接将 JavaScript 源码编译为机器码执行,这种架构导致内存占用过高,所以后来 V8 引入了字节码。V8 执行 JavaScript 的原理,大致可以分为三个步骤:
- 解析器(parser)将 JavaScript 源码解析为 AST(抽象语法树),解析过程分为词法分析和语法分析,V8 通过预解析提升解析效率;
- 解释器 Ignition 根据 AST 生成字节码并执行。这个过程中收集执行反馈信息,交给 TurboFan 进行优化编译;
- TurboFan 根据 Ignition 收集的反馈信息,将字节码编译为优化后的机器码,后续 Ignition 用优化机器码代替字节码执行,进而提升性能。
18、前端跨域问题
jsonp:通过script标签实现,只能get请求
Access-Control-Allow-Origin:必填,表示可以允许请求的来源。可以填写具体的源名称,也可以填写*表示允许任何源请求。
b、Access-Control-Allow-Methods:表示允许的请求方法列表。
c、Access-Control-Allow-Credentials:一个布尔值,表示是否允许发送cookie。默认情况下,cookie 不包含在 CORS 请求中。如果设置为 true,则表示服务器具有显式权限。Cookies 可以包含在请求中并一起发送到服务器。
vue框架的跨域:配置webpack.config.js文件(本地跨域方案)
19、判断一个对象是否为空
// 返回一个包含所有自身属性(不包含继承属性)的数组。
(类似于 Object.keys(), 但不会受enumerable影响, Object.keys返回所有可枚举属性的字符串数组). Reflect.ownKeys(target) //可以读取到symbol为属性的
Object.Keys(target) //不可以识别到symbol为属性的值,枚举为false也不能读取
var foo = {
a: 'name',
b: null,
c: undefined,
d: function () { },
e: Symbol(),
[Symbol('bar')]: 25
}
Object.defineProperty(foo, 'f', {
value: 42,
enumerable: false
});
foo.__proto__ = {
g: 233
}
console.log(Reflect.ownKeys(foo));
console.log(Object.keys(foo));
、、、、、、、、、
// 判断一个对象是否存在某个属性,和 in 运算符 的功能完全相同。
Reflect.has(target, name)
20、对象数组去重
21、reverse数组反转
let arr = [1,2,3,4,5,6]
1.arr.reverse()//会改变原数组
2.[...arr].reverse()//不会改变原数组
3.arr.slice().reverse()//不会改变原数组
4.for(let i = arr,length-1;i>=0;i--){
aa.push(arr[i)
}
5.reduce+unshift
15、
16、
17、
18、
19、
20、