前端面试题总结

228 阅读13分钟

html

1、html语义化的作用

增强代码的可读性,清晰的代码结构,有利于seo,方便开发和维护

2、html新标签

canvas footer header nav video等

3、DOCTYPE的作用

DOCTYPE是用来告诉浏览器用那种规范来解析页面的,不同浏览器对页面的渲染有不同的标准。W3C标准出来之后,浏览器对页面渲染有来统一的标准,这种渲染方式,叫做标准模式

!DOCTYPE声明位于html第一行,DOCTYPE不存在或者格式不正确会进入混杂模式,DOCTYPE可以让我们指定浏览器用什么样的文档dingyi来解析页面。

4、真实DOM和虚拟DOM

在传统开发模式中,例如我们需要更新一系列的节点的时候,浏览器是收到一次dom请求就会马上执行这个流程,这也浏览器就要重复执行,会引起浏览器的回流和重绘

虚拟dom是在js对象上模拟出一个dm元素,页面上需要修改的内容,先会被应用到虚拟dom上,对于虚拟dom的修改不会引起浏览器的重排和重绘,然后通过diff算法比较虚拟节点,和真实dom的不同,找出最小变更,再把变更一次性写入正式dom中,减少对dom元素的才做,提高性能。

CSS

1、标准盒子模型和怪异盒子模型

标准盒子模型:content+padding+border

怪异盒子模型:content+border+padding+margin

image.png

<style>
  .content_box {
    /* 整个box宽度为160,content宽度为 100 */ box-sizing: content-box;
    /* 整个box宽度为100,content宽度为 40 */ box-sizing: border-box;
    border: 10px solid red;
    background-color: yellow;
    padding: 20px;
    margin: 10px;
    width: 100px;
    height: 100px;
  }
</style><body>
  <div class="content_box">
  </div>
</body>

content-boximage.png

border-box

image.png

3、rem和em的区别

rem是相对于根元素的font-size,

em是相对于父元素的font-size

4、css常用的选择器有哪些

类选择器,伪类选择器,id选择器,后代选择器, 元素选择器,属性选择器

5、css选择器的权重

!import > 行内 > id > 类(.) > 元素和伪类 > *

6、行内元素和块级元素

行内元素不能设置宽高,宽高是由内容来决定的。例如span,input

块级元素宽度是由父级元素决定的,独占一行,可以设置宽高。例如p div

7、什么是BFC

BFC格式化上下文,他在页面上创建一个独立的渲染区域,让在BFC内的元素和外部元素互不干扰,

产生BFC的方式

float:除none外

overflow: hidden

position: absolute和fixed

可以解决边距重叠问题,文字环绕图片,浮动引起的高度塌陷问题

8、水平垂直居中

flex:

display:flex
just-content:center
align-item:center

9、多行文本省略

<style>
  .sl {
    width: 100px;
    overflow: hidden;
    text-overflow: ellipsis;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
  }
  .dh {
    width: 100px;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
  }
</style><body>
  <div class="dh">
    adsadshaah和吉萨活动i啊我回答了哇好哇好棒覅八十看来办法了吧立法 a
  </div>
  <div class="sl">
    adsadshaah和吉萨活动i啊我回答了哇好哇好棒覅八十看来办法了吧立法
  </div>
</body>

10、link和@import的区别

@import是css提供的,只能导入样式列表,link是html提供的标签,不仅可以加载css,还可以定义rs

加载页面时候,link引入的css标签会被同时加载,而@import是在css加载完成之后才会被加载

JavaScript

1、js基本数据类型

null, number, undefined, string, boolean, symbol, bigint

map: 遍历数组,返回回调返回值组成的新数组
    var arr = [1, 2, 3, 4, 5, 6, 7];
    const mapArr = arr.map((item) => {
      return item + 1;
    });
console.log(mapArr); // [  2, 3, 4, 5,  6, 7, 8]
forEach: 无法break,可以用try/catch中throw new Error来停止
    let foreachArr = [];
    arr.forEach((item) => {
      try {
        if (item === 4) throw new Error("123");
        foreachArr.push(item * 2);
      } catch (e) {
        console.log(e);
      }
    });
    console.log(foreachArr); // [ 2, 4, 6, 10, 12, 14 ]
filter: 过滤
    var arr = [1, 2, 3, 4, 5, 6, 7];
    var arr2 = [3, 5, 7, 9];
    // includes可以来判断是否有nan,includes会认为空的值为undefined
    const newarr = arr.filter((item) => {
      return arr2.indexOf(item) !== -1;
    });
    console.log(newarr);
some: 有一项返回true,则整体为true
    var arr = [1, 2, 3, 4, 5, 6, 7];
    const newarr = arr.some((item) => {
      return item % 2 === 0;
    });
    console.log(newarr); // true
every: 有一项返回false,则整体为false
    var arr = [1, 2, 3, 4, 5, 6, 7];
    const newarr = arr.every((item) => {
      return item % 2 === 0;
    });
    console.log(newarr); // false
join: 通过指定连接符生成字符串
    var arr = [1, 2, 3, 4, 5, 6, 7];
    console.log(arr.join("-")); // 1-2-3-4-5-6-7
push / pop: 末尾推入和弹出,改变原数组, 返回推入/弹出项【有误】
    var arr = [1, 2, 3];
    arr.push(1);
    console.log(arr); //[ 1, 2, 3, 1 ]
    arr.pop()
    console.log(arr); //[ 1, 2, 3 ]
unshift / shift: 头部推入和弹出,改变原数组,返回操作项【有误】
    var arr = [1, 2, 3];
    arr.shift();
    console.log(arr); //[  2, 3 ]
    arr.unshift(3);
    console.log(arr); //[ 3, 2, 3 ]
sort(fn) / reverse: 排序与反转,改变原数组
    var arr = [2, 5, 123, 521, 1, 65536];
    arr.reverse(); // [ 65536, 1, 521, 123, 5, 2 ]
    const sortArr = arr.sort((a, b) => {
      return a - b;
    });
    console.log(sortArr); // [ 1, 2, 5, 123, 521, 65536 ]
concat: 连接数组,不影响原数组, 浅拷贝
    var arr = [2, 5];
    var arr2 = [1, 2, 3];
    const newArr = arr.concat(arr2);
    console.log(newArr); // [ 2, 5, 1, 2, 3 ]
slice(start, end): 返回截断后的新数组,不改变原数组
    var arr = [2, 5, 5, 7645, 12, 5];
    const newArr = arr.slice(1, 2);
    console.log(arr, newArr); // [ 2, 5, 5, 7645, 12, 5 ] [ 5 ]
splice(start, number, value...): 返回删除元素组成的数组,value 为插入项,改变原数组
var arr = [2, 5, 5, 7645, 12, 5];
    const newArr = arr.splice(1, 2);//被截断的数组
    console.log(arr, newArr); //[ 2, 7645, 12, 5 ]  [ 5, 5 ]
indexOf / lastIndexOf(value, fromIndex): 查找数组项,返回对应的下标
    var arr = [2, 5, 5, 7645, 12, 5];
    console.log(arr.indexOf(5)); // 1,只获取第一次获取到
console.log(arr.lastIndexOf(5)); // 5 获取最后一次获取到,fromIndex从哪一位向前搜索
reduce / reduceRight(fn(prev, cur), defaultPrev): 两两执行,prev 为上次化简函数的return值,cur 为当前值(从第二项开始)
    var arr = [2, 5, 5, 7645, 12, 5];
    const result = arr.reduce((prev, next) => {
      return prev + next;
    });
    console.log(result); // 7674
    // 从右边开始
    function appendCurrent (previousValue, currentValue) {
        return previousValue + "::" + currentValue;
    }
    var elements = ["abc", "def", 123, 456];
    var result = elements.reduceRight(appendCurrent);
    document.write(result); //456::123::def::abc

2、get和post参数问题

http协议没有规定get和post参数长度,get的最大长度显示是因为浏览器和web服务器限制了uri的长度.

get和post在缓存方面, get请求类似于查找过程,用户获取数据,可以不用每次与数据库相连,可以使用缓存. post不同,post一般用于修改和删除的工作,必须和数据库进行交互,不能使用缓存

3、什么是闭包

函数中使用函数外部的变量,该函数被称为闭包

闭包可能会导致内存泄漏问题,函数使用了函数外部的变量,那个变量不会被垃圾回收机制回收.

闭包可以避免全局变量的污染.

for(var i = 0; i < 3; i++) {
  setTimeout(function() {
    console.log(i);
  }, 1000);
} // 333
// 是用let的话,let会和每个代码块形成块级作用域,在打印的时候会找块级作用域中的变量,从而输出0 1 2

4、垃圾回收机制

js垃圾回收机制分为两中,引用计数和标记清除

引用计数的话,他为在内存中的每一个对象保存一个计数器,如果有其它对象指向它,则计数加一,指向删除,计数减一,当计数等于0的时候,该对象就会被当初垃圾回收. 该方法, 如果存在两个对象相互引用的时候,则这两个对象会被一直存在内存中,不会被销毁, 会存在问题

标记清除, 先建立各个对象之间的关联,会从根节点开始, 标记每个节点上的对象,若没有在这个结构上的,则会被垃圾回收机制回收.

5、js作用域和作用域链

js中作用域分为全局作用域和函数作用域

作用域链,在查找一个变量的时候在当前作用域中没有找到, 则会去他上层的作用域中查找,如果上层也没有,则一个个找上去, 这样形成的链条,则成为作用域链

6、js原型域和原型链

js中每个对象内部都存在一个属性, 就是prototype(原型),新建的对象没有prototype属性,他存在__proto__属性指向Object.prototype(若他是new Object 或者 {} 出来的), Function的prototype执行Object的prototype,function Function()中Function作为对象,他的__proto__指向Function的prototype,Function的构造函数又指向function Function()

console.log(Object.prototype); 
//Object{}
var o = new Object();
console.log(o.prototype);  //undefined
console.log(Array.prototype); 
//[Symbol(Symbol.unscopables): Object]
console.log(Function.prototype); 
//function(){}
function hello(){
console.log("hello");
}
hello.prototype = "hello world";
console.log(hello.prototype); 
//hello world

ad7fd7e87915dd455dd64e4543ef7b4.jpg

什么是原型链, js中每个对象都有一个指向它原型对象的内部链接,而这个原型对象又存在自己的原型,知道某个对象的原型为null为止,这种链式结构就是原型链

6、模块化

AMD: 异步模块化, require.js, 在使用模块前要将依赖模块全部加载完毕

CMD: seaJS

commonJS: 一个单独的文件就是一个单独的模块, 文件中的作用域独立,文件中定义的变量无法被外部使用,

通过module.exports导出所需要的内容对象, 通过require来加载

es6: 类似于commonjs,能够支持异步加载和配置模块加载,使用export到处, import导入

为什么需要模块化开发

高内聚低耦合,有利于团队开发,可重用,方便维护

7、图片的预加载和懒加载

预加载: 图片提前加载,当用户需要查看时候,可直接从本地缓存中渲染,

懒加载: 主要作为服务器前端优化,减少请求次数或延迟请求数量

图片懒加载的方式:

先是把所有的图片使用一张占位图进行占位, 给图片一个data-src, 将请求过来的图片数据存入.当页面加载完毕, 判断图片是否在页面可视窗口上,若在取出data-src中的数据存入src中,图片显示

8、手写promise

let status_pending = "pending"
let status_resolve = "resolve"
let status_reject = "reject"
class MyPromise{
    constructor(executor){
        this.status = status_pending
        this.reason;
        this.value;
        this.onfulfilledFns = []
        this.onrejectedFns = []
        const resolve = (value) => {
            if(this.status = status_pending) {
                queueMicrotask(()=>{
                    this.status = status_resolve
                    this.value = value
                    this.onfulfilledFns.foreach(fn=>{
                        fn()
                    })
                })
            }
        }
        const reject = (reason) => {
            if(this.status = status_pending) {
                queueMicrotask(()=>{
                    this.status = status_reject
                    this.reason = reason
                    this.onrejectedFns.foreach(fn=>{
                        fn()
                    })
                })
            }
        }
        executor(resolve, reject)
    }
    then(onfulfilled, onrejected) {
        return new MyPromise((resolve, reject)=>{
            if(this.status === status_pending){
                this.onfulfilledFns.push(()=>{
                    const result = onfulfilled(this.value)
                    try{
                        resolve(result)
                    } catch {
                        reject(result)
                    }
                })
                this.onrejectedFns.push(()=>{
                    const result = onrejected(this.reason)
                    try{
                        resolve(result)
                    } catch {
                        reject(result)
                    }
                })
            }
            if(this.status === status_resolve){
                const result = onfulfilled(this.value)
                    try{
                        resolve(result)
                    } catch {
                        reject(result)
                    }
            }
            if(this.status === status_reject){
                const result = onrejected(this.reason)
                    try{
                        resolve(result)
                    } catch {
                        reject(result)
                    }
            }
        })
    }
    finally(fn){
        this.then(()=>{
            fn()
        },()=>{
            fn()
        })
    }
    catch(reject){
        this.then(undefined,reject)
    }
    resolve(value){
        return new MyPromise((resolve, reject)=>{
            resolve(value)
        })
    }
    reject(reason){
        return new MyPromise((resolve, reject)=>{
            reject(reason)
        })
    }
    all(promises){
        return new MyPromise((resolve,reject)=>{
            let result = []
            promises.foreach(item=>{
                item.then(res=>{
                    result.push(res)
                    if(result.length === promises.length){
                        resolve(result)
                    }
                }, err=>{
                    reject(err)
                })
            })
        })
    }
    allSettled(promises){
        return new MyPromise((resolve,reject)=>{
            let result = []
            promises.foreach(item=>{
                item.then(res=>{
                    result.push({flag: true,res})
                    if(result.length === promises.length){
                        resolve(result)
                    }
                }, err=>{
                    result.push({flag: false, res})
                    if(result.length === promises.length){
                        resolve(result)
                    }
                })
            })
        })
    }
    any(promises){
        return new MyPromise((resolve,reject)=>{
            let result = []
            promises.foreach(item=>{
                item.then(res=>{
                    resolve(result)
                }, err=>{
                    result.push({flag: false, res})
                    if(result.length === promises.length){
                        reject(result)
                    }
                })
            })
        })
    }
    race(promises){
        return new MyPromise((resolve,reject)=>{
            let result = []
            promises.foreach(item=>{
                item.then(res=>{
                    resolve(result)
                }, err=>{
                    reject(result)
                })
            })
        })
    }
}

8、this

this总是指向函数的直接调用者, 如果又new关键字,this指向new出来的那个对象

this绑定规则

1 默认绑定

// 默认绑定
function foo() {
  console.log(this)
}
​
//独立函数调用,指向windows
foo()
function foo() {
  console.log(this)
}
var obj = {
  name : 'qzf',
  foo: foo
}
​
var bar = obj.foobar() // windows

2 隐式绑定

var obj2 = {
  name: "qzf",
  foo: function () {
    console.log(this);
  },
};
​
var obj3 = {
  name: "qzf",
  bar: obj2.foo,
};
​
obj3.bar(); // obj3

3 显示绑定

function foo() {
  console.log('函数被调用了')
}
// foo直接调用和call、apply调用的不同在于this指向的不同,
// foo()  指向window
// foo函数上有有个call,来调用foo函数var obj = {
  name: 'qzf'
}
​
// call和apply可以指定this的绑定对象
foo.call(obj)
foo.apply(obj)
​
// call和apply的区别
function sum(num1, num2){
  console.log(num1 + num2 , this)
}
​
sum.call("call", 20, 30)
sum.apply("apply", [20,30])
​
// 3、call和apply在执行函数的时候是可以明确绑定this的,这个就是显示绑定

new 绑定

function Person(name, age) {
  this.name = name;
  this.age = age;
}
​
var p1 = new Person("qzf", 19);
console.log(p1.name, p1.age);
​
var p2 = new Person("kobe", 22);
​
function a() {
  return () => {
    return () => {
      console.log(this);
    };
  };
}
console.log(a()()());

9、事件循环

js是一个单线程的应用程序,页面渲染,函数处理都在这个主线程上执行,还存在一个工作线程,这个线程可能存在浏览器或js引擎中,处理网络请求等异步操作,

当我们执行一个js文件的时候,任务进入任务队列,判断任务是否是异步任务,不是直接运行,若是异步任务则放入任务队列中,直到主线程中不存在任务,开始执行微任务队列,微任务队列开始执行,若不是异步操作,直接执行,若有微任务则加入微任务队列,若出现宏任务,则加入宏任务队列,依次执行。全部执行完成后才会宏任务队列,

10、js中事件传播机制

js中一个事件发生:

1、第一阶段:从window对象传导到目标节点上,称为捕获阶段

2、第二阶段:在目标节点上触发,称为目标阶段

3、第三阶段:从目标节点传导回window对象,称为冒泡阶段

11、js中运算符和类型

// js的内置类型。
// null Number undefined Symbol String BigInt Boolean
// typeof可以判断基本类型,除null外
typeof 1; //number
typeof "1"; // string
typeof undefined; // undefined
typeof true; // boolean
typeof Symbol(); //symbol
typeof b; // 没有声明的变量,会显示成undefined
console.log(typeof 123n); // bigint
console.log(typeof null); // object
typeof []; // object
typeof {}; // object
typeof console.log(); // function
// 若要获取一个变量的正确类型可以使用 Object.prototype.toString.call(xxxx)
// 在boolean判断中只有undefined、null、false、nan、''、0、-0为false
console.log(Boolean("")); // false
console.log(1 + "1"); // 11
console.log(2 * "2"); // 4
console.log([1, 2] + [2, 1]); // 1,22,1
console.log("a" + +"b"); // aNaNconsole.log([] == ![]); // true
console.log(Boolean([])); // true
console.log(![]); // false
console.log(0 == false); // true
console.log([] == 0); // true

12、ES6模块化和CommonJS模块化差异

CommonJs模块使用require()和module.exports,es6模块使用import和export。

CommonJS 模块是运行时加载,ES6 模块是编译时加载。

CommonJS 模块的require()是同步加载模块,ES6 模块的import命令是异步加载,有一个独立的模块依赖的解析阶段。

13、常见的web攻击方式

xss攻击(跨站脚本攻击)

攻击者在用户使用的页面中插入恶意代码,当用户浏览该页面时候,恶意代码会被执行,从而攻击用户。

防范:对用户输入的内容进行过滤,转移掉特殊字符

csrf攻击(跨站请求伪造)

攻击者诱导用户进入第三方网站,在第三方网站中发送跨站请求,他是获取了用户在被攻击网站中已经获取的凭证,盗用了用户的身份信息,发送恶意请求

防范:增加token,对token进行验证

SQL注入攻击

sql 注入攻击,是通过将恶意的 Sql语句插入到应用的输入参数中,再在后台 Sql服务器上解析执行进行的攻击

14、同源策略

同源策略是浏览器的一个安全策略,同源是指两个URL的协议,域名,端口号都相同时候才是同源,不同数据源,会发生跨域的问题。

跨域的解决方案:

  1. JSONP:动态添加一个script标签,script标签的src属性是没有跨域限制的
  2. CORS(跨资源共享): 服务端设置access-control-allow-origin
  3. nginx反向代理

webpack

webpack是对前端资源进行模块管理和打包的工具,使用webpack能够让我们进行模块化开发,他会自动帮助我们处理各个模块之间的依赖关系。通过loader的转化,任何形式的资源都可以被当作模块,并且在打包过程中,我们还可以对资源进行处理,例如压缩图片,将es6转换成es5等。

1、webpack中的loader和plugin

webpack默认只是处理js的代码,loader是文件加载器,能够加载资源文件,并且对文件进行一些处理,例如编译压缩等,最终一起打包到指定文件中,同一个文件可以使用多个loader,loader的加载顺序是相反的。plugin是对webpack现有的功能进行扩展,例如打包优化,文件压缩等,

Vue

1、vue的生命周期

vue实例从创建到销毁的过程就是vue的生命周期,也就是从开始创建,初始化数据,编译模板,挂在dom 渲染,更新等一系列操作

vue的生命周期包括,beforeCreate, created, beforeMount, mounted, beforeUpdate, updated, beforeDestory, destory等

dom元素的渲染在mounted中完成

2、v-show和v-if区别

v-show是通过display block none来对元素进行显示和隐藏的

v-if是通过对元素进行销毁和创建,来对元素进行显示和隐藏的

3、组件之间的传值

父子组件传值

<template>
    <child :msg="message"  @msgFunc="func"></child>
</template><script>
import child from './child.vue';
export default {
    components: {
        child
    },
    data () {
        return {
            message: 'father message';
        }
    },
    methods: {
        func (msg) {
            console.log(msg);
        }
    }
}
</script>
// 子=========================================
<template>
    <div>{{msg}}</div>
    <button @click="handleClick">点我</button>
</template><script>
export default {
    props: {
        msg: {
            type: String,
            required: true
        }
    },
    methods () {
        handleClick () {
            //........
            this.$emit('msgFunc');
        }
    }
}
</script>

非父子组件传值,eventBus 手写eventBus

class eventBus {
    constructor(){
        this.eventBus = {}
    }
    on(eventNames, eventCallback, thisArg) {
        let handlers = this.eventBus[eventNames]
        if(!handlers) {
            let handers = []
            this.eventBus[eventNames] = handers
        }
        handers.push({eventCallback, this})
    }
    off(eventNames,eventCallback) {
        let handlers = this.eventBus[eventNames]
        if(!handlers) {
           return
        }
        let newHandlers = [...handlers]
        for(let i = 0; i<newHandlers.length; i++){
            let handler = newHandlers[i]
            if(handler.eventCallback === eventCallback){
                const index = handlers.indexOf(handler);
                handlers.splice(index, 1);
            }
        }
    }
    emit(eventNames,...plyload){
        let handlers = this.eventBus[eventNames]
        if(!handlers) {
           return
        }
        handers.foreach(fn=>{
            fn.eventCallback.apply(fn.thisArg, plyload)
        })
    }
}

4、MVVM

M - Model,Model 代表数据模型,也可以在 Model 中定义数据修改和操作的业务逻辑
​
V - ViewView 代表 UI 组件,它负责将数据模型转化为 UI 展现出来
​
VM - ViewModel,ViewModel 监听模型数据的改变和控制视图行为、处理用户交互,简单理解就是一个同步 View 和 Model 的对象,连接 Model 和 View

5、key

vue中key是作为虚拟节点的唯一标识,通过这个keydiff算法能够更加快速准确的更新数据

没有key的时候:

获取新旧node,得到新旧node的长度,获取到最短的node,然后进行遍历,进行patch比较,遇到不一样的,则进行更新。便利完成之后,如果新的大于旧的,则新增挂在节点,旧的比新的多的话,则卸载掉后面的。

存在key的时候:

获取到新旧node,通过while循环,获取新旧node的key和type进行比较,遇到不同的则从新旧node数组中尾部重新开始遍历,直到遇到不同的,跳出循环,如果新节点较多,则进行挂载操作,旧节点较多,则进行卸载操作。如果新旧节点长度一样,但是内部顺序不一样的话,尽可能的在旧node中找到新node中所要的node,放到索要的位置。

6、v-model

v-model在vue2中

<input v-model="any">
<!--实际上就是绑定了一个value,然后接收一个input事件,那么他传递下去的值必须是value,接收的时间也必须是input事件-->
<input v-bind:value="any" v-on:input="any=$event.target.value">

vue2.2中映入了modal组件选项

<ChildComponent v-model="title" /><script>
    // 子组件中
export default {
  model: {
    prop: 'title',   // v-model绑定的属性名称
    event: 'change'  // v-model绑定的事件
  },
  props: {
​
    value: String,   // value跟v-model无关
    title: {         // title是跟v-model绑定的属性
      type: String,
      default: 'Default title'
    }
  }
}
</script>

vue3

<input :modelValue="title"  @update:modelValue = "title = $event">
<script>
    // 子组件中
export default defineComponent({
    name:"ValidateInput",
    props:{
        modelValue:String,   // v-model绑定的属性值
    },
    setup(){
        const updateValue = (e: KeyboardEvent) => {
          context.emit("update:modelValue",targetValue);   // 传递的方法
        }
    }
}
</script>

7、nextTick()

next就是在更新结束后,延迟执行这个回调,在修改数据之后,使用这个方法,能够获取到最新的视图

题目

1、数组转树

const arr = [
  {
    name: "小明",
    id: 1,
    pid: 0,
  },
  {
    name: "小花",
    id: 11,
    pid: 1,
  },
  {
    name: "小华",
    id: 111,
    pid: 11,
  },
  {
    name: "小李",
    id: 112,
    pid: 11,
  },
  {
    name: "小红",
    id: 12,
    pid: 1,
  },
  {
    name: "111",
    id: 112,
    pid: 12,
  },
  {
    name: "小王",
    id: 2,
    pid: 0,
  },
  {
    name: "小林",
    id: 21,
    pid: 2,
  },
  {
    name: "小李",
    id: 22,
    pid: 2,
  },
];
​
function arrToTree(arr, id) {
  let res = [];
  arr.forEach((item) => {
    if (item.pid === id) {
      let children = arrToTree(arr, item.id);
      if (children) {
        item.children = children;
      }
      res.push(item);
    }
  });
  return res;
}
​
console.log(arrToTree(arr, 0));

2、获取两个数组的交集

var arr1 = [1, 2, 3, 4, 5];
var arr2 = [3, 5, 7, 9];
​
const newArr = arr1.filter((item) => {
  return arr2.indexOf(item) !== -1;
});
console.log(newArr);

3、函数柯里化

function Currying(fn) {
  function curried(...args) {
    if (args.length >= fn.length) {
      fn.apply(this, args);
    } else {
      return function (...arg2) {
        return curried.apply(this, [...args, ...arg2]);
      };
    }
  }
  return curried;
}
​
const add = Currying((num1, num2, num3) => {
  console.log(num1, num2, num3, num1 + num2 + num3);
});
console.log(add(1, 2)(5));

4、防抖函数

function debounce(fn, delay, immediate) {
  let timer;
  let isFlag = false;
  return function (...args) {
    if (immediate && !isFlag) {
      fn.apply(this, args);
      isFlag = true;
    }
    clearTimeout(timer);
    timer = setTimeout(() => {
      fn.apply(this, args);
      isFlag = true;
    }, delay);
  };
}

5、节流函数

function throttle(fn, interval, option={
  leading=true,trialing=false
}) {
  let {leading, trialing} = option
  let timer;
  let lastTime = 0
  return function(...args) {
    let nowTime = new Date().getTime()
    if(lastTime==0 && !leading) lastTime = nowTime
    let remainTime = interval - (nowTime-lastTime)
    if(remainTime <= 0) {
      clearTimeout(timer)
      timer = null
      fn.apply(this, args)
      lastTime = nowTime
      return
    }
    if(trialing && !timer) {
      timer = setTimeout(() => {
        timer = null
        lastTime = !leading ? 0 : new Date().getTime()
        fn.apply(this, args)
      }, remainTime);
    }
  }
}

6、事件总线

class eventBus {
  constructor() {
    this.eventBus = {};
  }
  on(eventName, eventCallback, thisArg) {
    let handle = this.eventBus[eventName];
    if (!handle) {
      handle = [];
      this.eventBus[eventName] = handle;
    }
    handle.push({ eventCallback, thisArg });
  }
  off(eventName, eventCallback) {
    let handle = this.eventBus[eventName];
    if (!handle) {
      return;
    }
    let newHandle = [...handle];
    for (let i = 0; i < newHandle.length; i++) {
      let gg = newHandle[i];
      if (gg.eventCallback === eventCallback) {
        const index = handle.indexOf(gg);
        handle.splice(index, 1);
      }
    }
  }
  emit(eventName, ...payload) {
    let handle = this.eventBus[eventName];
    if (!handle) {
      return;
    }
    handle.forEach((item) => {
      item.eventCallback.apply(item.thisArg, payload);
    });
  }
}
​
​

7、vue响应式原理

let activeReactiveFn = null;
​
class Depend {
  constructor() {
    this.reactiveFn = new Set();
  }
  depend() {
    if (activeReactiveFn) {
      this.reactiveFn.add(activeReactiveFn);
    }
  }
  notify() {
    this.reactiveFn.forEach((fn) => {
      fn();
    });
  }
}
​
function watchFn(fn) {
  activeReactiveFn = fn;
  fn();
  activeReactiveFn = null;
}
​
let targetMap = new WeakMap();
​
function getDepend(target, key) {
  let map = targetMap.get(target);
  if (!map) {
    map = new Map();
    targetMap.set(target, map);
  }
  let depend = map.get(key);
  if (!depend) {
    depend = new Depend();
    map.set(key, depend);
  }
  return depend;
}
​
function reactive(obj) {
  return new Proxy(obj, {
    get: function (target, key, receiver) {
      let depend = getDepend(target, key);
      depend.depend();
      return Reflect.get(target, key, receiver);
    },
    set: function (target, key, newValue, receiver) {
      Reflect.set(target, key, newValue, receiver);
      let depend = getDepend(target, key);
      depend.notify();
    },
  });
}
/*
  vue2响应式原理,通过defineProperty来对传入的数据进行绑定劫持 
function reactive(obj) {
  // {name: "qqq", age: 18}
  // ES6之前, 使用Object.defineProperty
  Object.keys(obj).forEach(key => {
    let value = obj[key]
    Object.defineProperty(obj, key, {
      get: function() {
        const depend = getDepend(obj, key)
        depend.depend()
        return value
      },
      set: function(newValue) {
        value = newValue
        const depend = getDepend(obj, key)
        depend.notify()
      }
    })
  })
  return obj
}
*/
​
const infoProxy = reactive({
  address: "广州市",
  height: 1.88,
});
​
watchFn(() => {
  console.log(infoProxy.address);
});infoProxy.address = "北京市";

8、原型链继承

function Person(name, age) {
  this.name = name;
  this.age = age;
}
​
Person.prototype.eating = function () {
  console.log(this.name, "eating");
};
​
function Student(name, age, play) {
  this.play = play;
  Person.call(this, name, age);
}
​
Student.prototype = Object.create(Person.prototype);
Student.prototype.studying = function () {
  console.log(this.name, "studying");
};
​
const asd = new Student("qqq", 18, "basketball");
console.log(asd);
console.log(asd.eating());
console.log(asd.studying());
console.log("---");
console.log(asd.__proto__);
console.log("---");
console.log(Student.prototype);
​

9、generator实现async和await的功能

function test(fn) {
  const generator = fn();
  function exec(res) {
    const result = generator.next(res);
    if (result.done) {
      return result.value;
    }
    result.value.then((res) => {
      exec(res);
    });
  }
  exec();
}
​
// 请求函数
function requestData(url) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(url);
    }, 1000);
  });
}
​
function* getData() {
  let res = yield requestData("a");
  let res2 = yield requestData(res + "b");
  let res3 = yield requestData(res2 + "c");
  return res3;
}
test(getData);

10、深拷贝

function isObject(value) {
  const valueType = typeof value;
  return value !== null && (valueType === "object" || valueType === "function");
}
​
function deepClone(originValue, map = new WeakMap()) {
  if (originValue instanceof Set) {
    return new Set([...originValue]);
  }
  if (originValue instanceof Map) {
    return new Map([...originValue]);
  }
  if (typeof originValue === "symbol") {
    return Symbol(originValue.description);
  }
  if (typeof originValue === "function") {
    return originValue;
  }
  if (!isObject(originValue)) {
    return originValue;
  }
  if (map.has(originValue)) {
    return map.get(originValue);
  }
  const newObj = Array.isArray(originValue) ? [] : {};
  map.set(originValue, newObj);
  for (const key in originValue) {
    newObj[key] = deepClone(originValue[key], map);
  }
  return newObj
}
​

11、数组去重

let arr = [1, 2, 3, 3, 4, 4];
​
let newArr1 = new Set([...arr]);
console.log(newArr1);
​
let newArr2 = [];
for (let i = 0; i < arr.length; i++) {
  if (newArr2.indexOf(arr[i]) === -1) {
    newArr2.push(arr[i]);
  }
}
console.log(newArr2);

12、数组扁平化

let arr = [1, 2, [3, [4, 5]]];
​
// 方法1:使用flat
console.log(arr.flat(2));
// 方法2: 递归
function myFlat(arr) {
  let result = [];
  arr.forEach((item) => {
    if (Array.isArray(item)) {
      result.push(...myFlat(item));
    } else {
      result.push(item);
    }
  });
  return result;
}
​
console.log(myFlat(arr));
​

\