Print

前端面试题2202及答案


作者: 文章来源:

本文原地址:https://www.mimiwuqi.com/webqianduan/196073.html

目录文章目录
  1. JavaScript 相关
  2. Vue
  3. Webpack 相关
  4. CSS/HTML 相关
  5. 算法
  6. git 相关

本文整理自 2021 年开始,技术面是常考的面试题,虽然已经 2202 年了,相信这些面试题依然是常考点。接下来你要面对上百道面试题,如果你是小白菜,推荐使用 2~3 周的时间来消化接下来的面试题,遇到不会的没听说过名词请立刻去搜,文章中只是简答,如果想要详细了解的话还需要你自觉去搜索。当然,大佬就绕道了。如果有错误的地方烦请各位一定要指出,免得误导更多人。接下来的题我会根据重点程度使用⭐来标记,⭐越多标明越重点,满星是 5 颗星。好了,废话不多说,让我们一起觉醒吧!

JavaScript 相关

数据类型

JavaScript 中什么是基本数据类型什么是引用数据类型?以及各个数据类型是如何存储的?⭐⭐⭐⭐⭐

基本数据类型有:

引用数据类型统称为 Object 类型,细分的话有:

基本数据类型的数据直接存储在栈中;引用数据类型的数据存储在堆中在栈中保存数据的引用地址,这个引用地址指向的是对应的数据,以便快速查找到堆内存中的对象。

顺便提一句,栈内存是自动分配内存的。而堆内存是动态分配内存的,不会自动释放。所以每次使用完对象的时候都要把它设置为 null,从而减少无用内存的消耗。

类型转换

在 JS 中为什么 0.2+0.1>0.3?⭐⭐⭐⭐

因为在 JS 中,浮点数是使用 64 位固定长度来表示的,其中的 1 位表示符号位,11 位用来表示指数位,剩下的 52 位尾数位,由于只有 52 位表示尾数位。

0.1转为二进制是一个无限循环数0.0001100110011001100......(1100 循环)

// 0.1 和 0.2 都转化成二进制后再进行运算
0.00011001100110011001100110011001100110011001100110011010 +
0.0011001100110011001100110011001100110011001100110011010 =
0.0100110011001100110011001100110011001100110011001100111

// 转成十进制正好是 0.30000000000000004

那为什么 0.2+0.3=0.5 呢?⭐⭐⭐⭐

// 0.2 和 0.3 都转化为二进制后再进行计算
0.001100110011001100110011001100110011001100110011001101 +
0.0100110011001100110011001100110011001100110011001101 = 
0.10000000000000000000000000000000000000000000000000001 //尾数为大于 52 位

// 而实际取值只取 52 位尾数位,就变成了
0.1000000000000000000000000000000000000000000000000000   //0.5

0.2 和0.3分别转换为二进制进行计算:在内存中,它们的尾数位都是等于 52 位的,而他们相加必定大于 52 位,而他们相加又恰巧前 52 位尾数都是0,截取后恰好是0.1000000000000000000000000000000000000000000000000000也就是 0.5

那既然 0.1 不是 0.1 了,为什么在 console.log(0.1)的时候还是 0.1 呢?⭐⭐⭐

console.log的时候会二进制转换为十进制,十进制再会转为字符串的形式,在转换的过程中发生了取近似值,所以打印出来的是一个近似值的字符串。

判断数据类型有几种方法⭐⭐⭐⭐⭐

  1. typeof 缺点:typeof null的值为Object,无法分辨是null还是Object
  2. instanceof 缺点:只能判断对象是否存在于目标对象的原型链上。
  3. constructor
  4. Object.prototype.toString.call()

示例:

// -----------------------------------------typeof
typeof undefined // 'undefined' 
typeof '10' // 'String' 
typeof 10 // 'Number' 
typeof false // 'Boolean' 
typeof Symbol() // 'Symbol' 
typeof Function // ‘function' 
typeof null // ‘Object’ 
typeof [] // 'Object' 
typeof {} // 'Object'

// -----------------------------------------instanceof
function Foo() { }
var f1 = new Foo();
var d = new Number(1)

console.log(f1 instanceof Foo);// true
console.log(d instanceof Number); //true
console.log(123 instanceof Number); //false   -->不能判断字面量的基本数据类型

// -----------------------------------------constructor
var d = new Number(1)
var e = 1
function fn() {
    console.log("ming");
}
var date = new Date();
var arr = [1, 2, 3];
var reg = /[hbc]at/gi;

console.log(e.constructor);//ƒ Number() { [native code] }
console.log(e.constructor.name);//Number
console.log(fn.constructor.name) // Function 
console.log(date.constructor.name)// Date 
console.log(arr.constructor.name) // Array 
console.log(reg.constructor.name) // RegExp

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

function fn() {
    console.log("ming");
}
var date = new Date();
var arr = [1, 2, 3];
var reg = /[hbc]at/gi;
console.log(Object.prototype.toString.call(fn));// "[object Function]" 
console.log(Object.prototype.toString.call(date));// "[object Date]" 
console.log(Object.prototype.toString.call(arr)); // "[object Array]"
console.log(Object.prototype.toString.call(reg));// "[object RegExp]"

instanceof 原理⭐⭐⭐⭐⭐

instanceof原理实际上就是查找目标对象的原型链。

function myInstance(L, R) {//L 代表 instanceof 左边,R 代表右边
    var RP = R.prototype
    var LP = L.__proto__
    while (true) {
        if (LP == null) {
            return false
        }
        if (LP == RP) {
            return true
        }
        LP = LP.__proto__
    }
}
console.log(myInstance({}, Object));

为什么 typeof null 是 Object⭐⭐⭐⭐

因为在JavaScript中,不同的对象都是使用二进制存储的,如果二进制前三位都是 0 的话,系统会判断为是Object类型,而 null 的二进制全是 0,自然也就判断为Object

这个 bug 是初版本的 JavaScript 中留下的,扩展一下其他五种标识位:

==和===有什么区别⭐⭐⭐⭐⭐

===是严格意义上的相等,会比较两边的数据类型和值大小:

==是非严格意义上的相等:

手写 call、apply、bind⭐⭐⭐⭐⭐

call 方法:

Function.prototype.myCall = function (context) {
    // 先判断调用 myCall 是不是一个函数
    // 这里的 this 就是调用 myCall 的
    if (typeof this !== 'function') {
        throw new TypeError("Not a Function")
    }

    // 不传参数默认为 window
    context = context || window

    // 保存 this
    context.fn = this

    // 保存参数
    let args = Array.from(arguments).slice(1) //Array.from 把伪数组对象转为数组

    // 调用函数
    let result = context.fn(...args)

    delete context.fn

    return result
}

apply 方法:

Function.prototype.myApply = function (context) {
    // 判断 this 是不是函数
    if (typeof this !== "function") {
        throw new TypeError("Not a Function")
    }

    let result

    // 默认是 window
    context = context || window

    // 保存 this
    context.fn = this

    // 是否传参
    if (arguments[1]) {
        result = context.fn(...arguments[1])
    } else {
        result = context.fn()
    }
    delete context.fn

    return result
}

bind 方法:

Function.prototype.myBind = function (context) {
    // 判断是否是一个函数
    if (typeof this !== "function") {
        throw new TypeError("Not a Function")
    }
    // 保存调用 bind 的函数
    const _this = this
    // 保存参数
    const args = Array.prototype.slice.call(arguments, 1)
    // 返回一个函数
    return function F() {
        // 判断是不是 new 出来的
        if (this instanceof F) {
            // 如果是 new 出来的
            // 返回一个空对象,且使创建出来的实例的 __proto__ 指向 _this 的 prototype,且完成函数柯里化
            return new _this(...args, ...arguments)
        } else {
            // 如果不是 new 出来的改变 this 指向,且完成函数柯里化
            return _this.apply(context, args.concat(...arguments))
        }
    }
}

字面量创建对象和 new 创建对象有什么区别,new 内部都实现了什么,手写一个 new ⭐⭐⭐⭐⭐

字面量:

new 内部:

手写 new:

// 手写一个 new
function myNew(fn, ...args) {
    // 创建一个空对象
    let obj = {}
    // 使空对象的隐式原型指向原函数的显式原型
    obj.__proto__ = fn.prototype
    // this 指向 obj
    let result = fn.apply(obj, args)
    // 返回
    return result instanceof Object ? result : obj
}

字面量 new 出来的对象和 Object.create(null)创建出来的对象有什么区别⭐⭐⭐

字面量和 new 创建出来的对象会继承 Object 的方法和属性,他们的隐式原型会指向 Object 的显式原型,

而 Object.create(null)创建出来的对象原型为 null,作为原型链的顶端,自然也没有继承 Object 的方法和属性

执行栈和执行上下文

什么是作用域,什么是作用域链?⭐⭐⭐⭐

规定变量和函数的可使用范围称作作用域;

每个函数都有一个作用域链,查找变量或者函数时,需要从局部作用域到全局作用域依次查找,这些作用域的集合称作作用域链。

什么是执行栈,什么是执行上下文?⭐⭐⭐⭐

执行上下文分为:

执行栈:

闭包

很多人都吃不透 js 闭包,这里推荐一篇文章:JavaScript 闭包实例讲解

什么是闭包?闭包的作用?闭包的应用?⭐⭐⭐⭐⭐

函数执行,形成私有的执行上下文,使内部私有变量不受外界干扰,起到保护保存的作用。

作用:

应用:

缺点:

原型和原型链

什么是原型?什么是原型链?如何理解⭐⭐⭐⭐⭐

原型:原型分为隐式原型和显式原型,每个对象都有一个隐式原型,它指向自己的构造函数的显式原型。

原型链:多个__proto__组成的集合成为原型链。

关于原型与原型链,推荐阅读:我对 js 中原型原型链的理解(有图有真相)

继承

JS 中的常用的继承方式有哪些?以及各个继承方式的优缺点。⭐⭐⭐⭐⭐

常用的继承方式:原型继承、组合继承、寄生组合继承、ES6 的 extend。

原型继承:

// ----方法一:原型继承
// 原型继承
// 把父类的实例作为子类的原型
// 缺点:子类的实例共享了父类构造函数的引用属性   不能传参

var person = {
    friends: ["a", "b", "c", "d"]
}

var p1 = Object.create(person)

p1.friends.push("aaa")//缺点:子类的实例共享了父类构造函数的引用属性

console.log(p1);
console.log(person);//缺点:子类的实例共享了父类构造函数的引用属性

组合继承:

// ----方法二:组合继承
// 在子函数中运行父函数,但是要利用 call 把 this 改变一下,
// 再在子函数的 prototype 里面 new Father(),使 Father 的原型中的方法也得到继承,
// 最后改变 Son 的原型中的 constructor

// 缺点:调用了两次父类的构造函数,造成了不必要的消耗,父类方法可以复用
// 优点可传参,不共享父类引用属性
function Father(name) {
    this.name = name
    this.hobby = ["篮球", "足球", "乒乓球"]
}

Father.prototype.getName = function () {
    console.log(this.name);
}

function Son(name, age) {
    Father.call(this, name)
    this.age = age
}

Son.prototype = new Father()
Son.prototype.constructor = Son

var s = new Son("ming", 20)

console.log(s);

寄生组合继承:

// ----方法三:寄生组合继承
function Father(name) {
    this.name = name
    this.hobby = ["篮球", "足球", "乒乓球"]
}

Father.prototype.getName = function () {
    console.log(this.name);
}

function Son(name, age) {
    Father.call(this, name)
    this.age = age
}

Son.prototype = Object.create(Father.prototype)
Son.prototype.constructor = Son

var s2 = new Son("ming", 18)
console.log(s2);

ES6 的extend

// ----方法四:ES6 的 extend(寄生组合继承的语法糖)
// 子类只要继承父类,可以不写 constructor,一旦写了,
// 则在 constructor 中的第一句话必须是 super 。

class Son3 extends Father { // Son.prototype.__proto__ = Father.prototype
    constructor(y) {
        super(200)  // super(200) => Father.call(this,200)
        this.y = y
    }
}

内存泄露、垃圾回收机制

什么是内存泄漏?⭐⭐⭐⭐⭐

内存泄露是指不再用的内存没有被及时释放出来,导致该段内存无法被使用就是内存泄漏。

为什么会导致的内存泄漏?⭐⭐⭐⭐⭐

内存泄漏指我们无法在通过 js 访问某个对象,而垃圾回收机制却认为该对象还在被引用,因此垃圾回收机制不会释放该对象,导致该块内存永远无法释放,积少成多,系统会越来越卡以至于崩溃。

垃圾回收机制都有哪些策略?⭐⭐⭐⭐⭐

深拷贝和浅拷贝

手写浅拷贝深拷贝⭐⭐⭐⭐⭐

// ----浅拷贝
// 只是把对象的属性和属性值拷贝到另一个对象中
var obj1 = {
    a: {
        a1: { a2: 1 },
        a10: { a11: 123, a111: { a1111: 123123 } }
    },
    b: 123,
    c: "123"
}
// 方式 1
function shallowClone1(o) {
    let obj = {}

    for (let i in o) {
        obj[i] = o[i]
    }
    return obj
}

// 方式 2
var shallowObj2 = { ...obj1 }

// 方式 3
var shallowObj3 = Object.assign({}, obj1)

let shallowObj = shallowClone1(obj1);

shallowObj.a.a1 = 999
shallowObj.b = true

console.log(obj1);  //第一层的没有被改变,一层以下就被改变了

// ----深拷贝

// 简易版  
function deepClone(o) {
    let obj = {}
    for (var i in o) {
        // if(o.hasOwnProperty(i)){
        if (typeof o[i] === "object") {
            obj[i] = deepClone(o[i])
        } else {
            obj[i] = o[i]
        }
        // }
    }
    return obj
}


var myObj = {
    a: {
        a1: { a2: 1 },
        a10: { a11: 123, a111: { a1111: 123123 } }
    },
    b: 123,
    c: "123"
}

var deepObj1 = deepClone(myObj)
deepObj1.a.a1 = 999
deepObj1.b = false
console.log(myObj);

// 简易版存在的问题:参数没有做检验,传入的可能是 Array、null、regExp、Date
function deepClone2(o) {
    if (Object.prototype.toString.call(o) === "[object Object]") {  //检测是否为对象
        let obj = {}
        for (var i in o) {
            if (o.hasOwnProperty(i)) {
                if (typeof o[i] === "object") {
                    obj[i] = deepClone(o[i])
                } else {
                    obj[i] = o[i]
                }
            }
        }
        return obj
    } else {
        return o
    }
}

function isObject(o) {
    return Object.prototype.toString.call(o) === "[object Object]" || Object.prototype.toString.call(o) === "[object Array]"
}

// 继续升级,没有考虑到数组,以及 ES6 中的 map、set、weakset、weakmap
function deepClone3(o) {
    if (isObject(o)) {//检测是否为对象或者数组
        let obj = Array.isArray(o) ? [] : {}
        for (let i in o) {
            if (isObject(o[i])) {
                obj[i] = deepClone(o[i])
            } else {
                obj[i] = o[i]
            }
        }
        return obj
    } else {
        return o
    }
}

// 有可能碰到循环引用问题  var a = {}; a.a = a; clone(a);//会造成一个死循环
// 循环检测
// 继续升级
function deepClone4(o, hash = new map()) {
    if (!isObject(o)) return o//检测是否为对象或者数组
    if (hash.has(o)) return hash.get(o)
    let obj = Array.isArray(o) ? [] : {}

    hash.set(o, obj)
    for (let i in o) {
        if (isObject(o[i])) {
            obj[i] = deepClone4(o[i], hash)
        } else {
            obj[i] = o[i]
        }
    }
    return obj
}

// 递归易出现爆栈问题
//  将递归改为循环,就不会出现爆栈问题了
var a1 = { a: 1, b: 2, c: { c1: 3, c2: { c21: 4, c22: 5 } }, d: 'asd' };
var b1 = { b: { c: { d: 1 } } }
function cloneLoop(x) {
    const root = {};
    // 栈 
    const loopList = [  //->[]->[{parent:{a:1,b:2},key:c,data:{ c1: 3, c2: { c21: 4, c22: 5 } }}]
        {
            parent: root,
            key: undefined,
            data: x,
        }
    ];
    while (loopList.length) {
        // 深度优先
        const node = loopList.pop();
        const parent = node.parent; //{} //{a:1,b:2}
        const key = node.key; //undefined //c
        const data = node.data; //{ a: 1, b: 2, c: { c1: 3, c2: { c21: 4, c22: 5 } }, d: 'asd' }  //{ c1: 3, c2: { c21: 4, c22: 5 } }}
        // 初始化赋值目标,key 为 undefined 则拷贝到父元素,否则拷贝到子元素
        let res = parent; //{}->{a:1,b:2,d:'asd'} //{a:1,b:2}->{}
        if (typeof key !== 'undefined') {
            res = parent[key] = {};
        }
        for (let k in data) {
            if (data.hasOwnProperty(k)) {
                if (typeof data[k] === 'object') {
                    // 下一次循环 
                    loopList.push({
                        parent: res,
                        key: k,
                        data: data[k],
                    })
                } else {
                    res[k] = data[k];
                }
            }
        }
    }
    return root
}

function deepClone5(o) {
    let result = {}
    let loopList = [
        {
            parent: result,
            key: undefined,
            data: o
        }
    ]

    while (loopList.length) {
        let node = loopList.pop()
        let { parent, key, data } = node
        let anoPar = parent
        if (typeof key !== 'undefined') {
            anoPar = parent[key] = {}
        }

        for (let i in data) {
            if (typeof data[i] === 'object') {
                loopList.push({
                    parent: anoPar,
                    key: i,
                    data: data[i]
                })
            } else {
                anoPar[i] = data[i]
            }
        }
    }
    return result
}

let cloneA1 = deepClone5(a1)
cloneA1.c.c2.c22 = 5555555
console.log(a1);
console.log(cloneA1);

// ------------------------------------------JSON.stringify()实现深拷贝

function cloneJson(o) {
    return JSON.parse(JSON.stringify(o))
}

// let obj = { a: { c: 1 }, b: {} };
// obj.b = obj;
// console.log(JSON.parse(JSON.stringify(obj))) // 报错 // Converting circular structure to JSON

深拷贝能使用 hash 递归的方式写出来就可以了,不过技多不压身,推荐还是看一看使用 while 实现深拷贝方法。

单线程,同步异步

为什么 JS 是单线程的?⭐⭐⭐⭐⭐

因为 JS 里面有可视的 Dom,如果是多线程的话,这个线程正在删除 DOM 节点,另一个线程正在编辑 Dom 节点,导致浏览器不知道该听谁的。

如何实现异步编程?

回调函数

Generator 是怎么样使用的以及各个阶段的变化如何?⭐⭐⭐

状态变化:

Promise 的原理?你是如何理解 Promise 的?⭐⭐⭐⭐⭐

做到会写简易版的 promise 和 all 函数就可以

class MyPromise2 {
    constructor(executor) {
        // 规定状态
        this.state = "pending"
        // 保存 `resolve(res)` 的 res 值
        this.value = undefined
        // 保存 `reject(err)` 的 err 值
        this.reason = undefined
        // 成功存放的数组
        this.successCB = []
        // 失败存放的数组
        this.failCB = []


        let resolve = (value) => {
            if (this.state === "pending") {
                this.state = "fulfilled"
                this.value = value
                this.successCB.forEach(f => f())
            }
        }
        let reject = (reason) => {
            if (this.state === "pending") {
                this.state = "rejected"
                this.value = value
                this.failCB.forEach(f => f())
            }
        }

        try {
            // 执行
            executor(resolve, reject)
        } catch (error) {
            // 若出错,直接调用 reject
            reject(error)
        }
    }
    then(onFulfilled, onRejected) {
        if (this.state === "fulfilled") {
            onFulfilled(this.value)
        }
        if (this.state === "rejected") {
            onRejected(this.value)
        }
        if (this.state === "pending") {
            this.successCB.push(() => { onFulfilled(this.value) })
            this.failCB.push(() => { onRejected(this.reason) })
        }
    }
}

Promise.all = function (promises) {
    let list = []
    let count = 0
    function handle(i, data) {
        list[i] = data
        count++
        if (count == promises.length) {
            resolve(list)
        }
    }
    return Promise((resolve, reject) => {
        for (let i = 0; i < promises.length; i++) { promises[i].then(res => {
                handle(i, res)
            }, err => reject(err))
        }
    })
}

以下代码的执行顺序是什么?⭐⭐⭐⭐⭐

async function async1() {
    console.log('async1 start')
    await async2()
    console.log('async1 end')
}
async function async2() {
    console.log('async2')
}
async1()
console.log('script start')

所以结果为:

async1 start
async2
script start
async1 end

宏任务和微任务都有哪些?⭐⭐⭐⭐⭐

宏任务:scriptsetTimeOutsetIntervalsetImmediate

微任务:promise.then,process.nextTickObject.observeMutationObserver

注意:Promise 是同步任务。

宏任务和微任务都是怎样执行的?⭐⭐⭐⭐⭐

例题 1:

setTimeout(function(){
    console.log('1')
});
new Promise(function(resolve){
    console.log('2');
    resolve();
}).then(function(){
    console.log('3')
});
console.log('4');
new Promise(function(resolve){
    console.log('5');
    resolve();
}).then(function(){
    console.log('6')
});
setTimeout(function(){
    console.log('7')
});
function bar(){
    console.log('8')
    foo()
}
function foo(){
    console.log('9')
}
console.log('10')
bar()

解析:

最终结果:2,4,5,10,8,9,3,6,1,7

例题 2:

setTimeout(() => {
    console.log('1');
    new Promise(function (resolve, reject) {
        console.log('2');
        setTimeout(() => {
            console.log('3');
        }, 0);
        resolve();
    }).then(function () {
        console.log('4')
    })
}, 0);
console.log('5'); //5 7 10 8 1 2 4 6 3
setTimeout(() => {
    console.log('6');
}, 0);
new Promise(function (resolve, reject) {
    console.log('7');
    // reject();
    resolve();
}).then(function () {
    console.log('8')
}).catch(function () {
    console.log('9')
})
console.log('10');

运行结果: 5 7 10 8 1 2 4 6 3

变量提升

变量和函数怎么进行提升的?优先级是怎么样的?⭐⭐⭐⭐

箭头函数和普通函数的区别?箭头函数可以当做构造函数 new 吗?⭐⭐⭐⭐⭐

说说你对代理的理解⭐⭐⭐

为什么要使用模块化?都有哪几种方式可以实现模块化,各有什么特点?⭐⭐⭐

为什么要使用模块化:

立即执行函数

在早期,使用立即执行函数实现模块化是常见的手段,通过函数作用域解决了命名冲突、污染全局作用域的问题。

(function(globalVariable){
   globalVariable.test = function() {}
   // ... 声明各种变量、函数都不会污染全局作用域
})(globalVariable)

AMD 和 CMD

鉴于目前这两种实现方式已经很少见到,所以不再对具体特性细聊,只需要了解这两者是如何使用的。

// AMD
define(['./a', './b'], function(a, b) {
  // 加载模块完毕可以使用
  a.do()
  b.do()
})
// CMD
define(function(require, exports, module) {
  // 加载模块
  // 可以把 require 写在函数体的任意地方实现延迟加载
  var a = require('./a')
  a.doSomething()
})

CommonJS

CommonJS 最早是 Node 在使用,目前也仍然广泛使用,比如在 Webpack 中你就能见到它,当然目前在 Node 中的模块管理已经和 CommonJS 有一些区别了。

// a.js
module.exports = {
    a: 1
}
// or 
exports.a = 1

// b.js
var module = require('./a.js')
module.a // -> log 1

因为 CommonJS 还是会使用到的,所以这里会对一些疑难点进行解析

先说 require 吧

var module = require('./a.js')
module.a 
// 这里其实就是包装了一层立即执行函数,这样就不会污染全局变量了,
// 重要的是 module 这里,module 是 Node 独有的一个变量
module.exports = {
    a: 1
}
// module 基本实现
var module = {
  id: 'xxxx', // 我总得知道怎么去找到他吧
  exports: {} // exports 就是个空对象
}
// 这个是为什么 exports 和 module.exports 用法相似的原因
var exports = module.exports 
var load = function (module) {
    // 导出的东西
    var a = 1
    module.exports = a
    return module.exports
};
// 然后当我 require 的时候去找到独特的
// id,然后将要使用的东西用立即执行函数包装下,over

另外虽然 exportsmodule.exports 用法相似,但是不能对 exports 直接赋值。因为 var exports = module.exports 这句代码表明了 exportsmodule.exports 享有相同地址,通过改变对象的属性值会对两者都起效,但是如果直接对 exports 赋值就会导致两者不再指向同一个内存地址,修改并不会对 module.exports 起效。

ES Module

ES Module 是原生实现的模块化方案,与 CommonJS 有以下几个区别

// 引入模块 API
import XXX from './a.js'
import { XXX } from './a.js'
// 导出模块 API
export function a() {}
export default function() {}

exports 和 module.exports 有什么区别?⭐⭐⭐

JS 模块包装格式有哪些?⭐⭐⭐

ES6 和 commonjs 的区别⭐⭐⭐

commonjs模块输出的是值的拷贝,而 ES6 输出的值是值的引用

commonjs是在运行时加载,是一个对象,ES6 是在编译时加载,是一个代码块

commonjs的 this 指向当前模块,ES6 的 this 指向 undefined

跨域

跨域的方式都有哪些?他们的特点是什么 ⭐⭐⭐⭐⭐

JSONP⭐⭐⭐⭐⭐

document.domain⭐

location.hash + iframe⭐⭐

window.name + iframe⭐⭐

postMessage⭐

CORS⭐⭐⭐⭐⭐

nginx 代理跨域⭐⭐⭐⭐

网络原理

讲一讲三次握手四次挥手,为什么是三次握手四而不是两次握手?⭐⭐⭐⭐⭐

HTTP 的结构⭐⭐⭐⭐

请求行、请求头、空行、请求体

HTTP 头都有哪些字段⭐⭐⭐⭐

说说你知道的状态码⭐⭐⭐⭐⭐

网络 OSI 七层模型都有哪些?TCP 是哪一层的⭐⭐⭐⭐

七层模型:

TCP 属于传输层。

http1.0 和 http1.1,还有 http2 有什么区别?⭐⭐⭐⭐

https 和 http 有什么区别,https 的实现原理?⭐⭐⭐⭐⭐

 localStorage、SessionStorage、cookie、session 之间有什么区别⭐⭐⭐⭐⭐

localstorage 存满了怎么办?⭐⭐⭐

怎么使用 cookie 保存用户信息⭐⭐⭐

document.cookie(“名字 = 数据;expire=时间”)

怎么删除 cookie⭐⭐⭐

目前没有提供删除的操作,但是可以把它的 Max-age 设置为 0,也就是立马失效,也就是删除了

Get 和 Post 的区别⭐⭐⭐⭐⭐

更详细的推荐阅读这篇文章:传送门

讲讲 http 缓存⭐⭐⭐⭐⭐

缓存位置:

相关文章推荐:浏览器缓存知识

tcp 和 udp 有什么区别⭐⭐⭐⭐⭐

从浏览器输入 url 后都经历了什么?⭐⭐⭐⭐⭐⭐具重要! 

推荐阅读:前端浏览器输入 URL 后发生什么?

滑动窗口和拥塞窗口有什么区别⭐⭐⭐ 

什么是 CDN?⭐⭐⭐⭐

  1. 首先访问本地的 DNS ,如果没有命中,继续递归或者迭代查找,直到命中拿到对应的 IP 地址;
  2. 拿到对应的 IP 地址之后服务器端发送请求到目的地址。注意这里返回的不直接是 cdn 服务器的 IP 地址,而是全局负载均衡系统的 IP 地址;
  3. 全局负载均衡系统会根据客户端的 IP 地址和请求的 url 和相应的区域负载均衡系统通信;
  4. 区域负载均衡系统拿着这两个东西获取距离客户端最近且有相应资源的 cdn 缓存服务器的地址,返回给全局负载均衡系统;
  5. 全局负载均衡系统返回确定的 cdn 缓存服务器的地址给客户端;
  6. 客户端请求缓存服务器上的文件

关于 cdn、回源等问题一网打尽

什么是 xss?什么是 csrf?⭐⭐⭐⭐⭐

OWASP top10 (10 项最严重的 Web 应用程序安全风险列表)都有哪些?⭐⭐⭐ 

什么是回流 什么是重绘?⭐⭐⭐⭐⭐

杂项

事件冒泡和事件捕捉有什么区别⭐⭐⭐⭐⭐

什么是防抖?什么是节流?手写一个⭐⭐⭐⭐⭐

// ------防抖函数
function debounce(func, delay) {
    let timeout
    return function () {
        let arg = arguments
        if (timeout) clearTimeout(timeout)
        timeout = setTimeout(() => {
            func(arg)
        }, delay);
    }
}

// ------立即执行防抖函数
function debounce2(fn, delay) {
    let timer

    return function () {
        let args = arguments
        if (timer) clearTimeout(timer)


        let callNow = !timer
        timer = setTimeout(() => {
            timer = null
        }, delay);
        if (callNow) { fn(args) }
    }
}
// ------立即执行防抖函数+普通防抖
function debounce3(fn, delay, immediate) {
    let timer

    return function () {
        let args = arguments
        let _this = this
        if (timer) clearTimeout(timer)

        if (immediate) {
            let callNow = !timer
            timer = setTimeout(() => {
                timer = null
            }, delay);

            if (callNow) { fn.apply(_this, args) }
        } else {
            timeout = setTimeout(() => {
                func.apply(_this, arguments)
            }, delay);
        }
    }
}

// ------节流 ,时间戳版

function throttle(fn, wait) {

    let previous = 0
    return function () {
        let now = Date.now()
        let _this = this
        let args = arguments
        if (now - previous > wait) {
            fn.apply(_this, arguments)
            previous = now
        }
    }
}

// ------节流 ,定时器版
function throttle2(fn, wait) {
    let timer
    return function () {
        let _this = this
        let args = arguments
        if (!timer) {
            timer = setTimeout(() => {
                timer = null
                fn.apply(_this, arguments)
            }, wait);
        }
    }
}

函数柯里化原理⭐⭐⭐⭐⭐

function add() {
    var args = Array.prototype.slice.call(arguments)

    var adder = function () {
        args.push(...arguments)
        return adder
    }

    adder.toString = function () {
        return args.reduce((prev, curr) => {
            return prev + curr
        }, 0)
    }

    return adder
}

let a = add(1, 2, 3)
let b = add(1)(2)(3)
console.log(a)
console.log(b)
console.log(add(1, 2)(3));
console.log(Function.toString)

什么是 requestAnimationFrame?⭐⭐⭐⭐

var s = 0
function f() {
    s++
    console.log(s);
    if (s < 999) {
        window.requestAnimationFrame(f)
    }
}
window.requestAnimationFrame(f)

js 常见的设计模式⭐⭐⭐⭐⭐

JS 性能优化的方式⭐⭐⭐⭐⭐

Vue

Vue 双向绑定

数据劫持: vue.js 是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调

阐述一下你所理解的 MVVM 响应式原理⭐⭐⭐⭐⭐

vue 是采用数据劫持配合发布者-订阅者的模式的方式,通过Object.defineProperty()来劫持各个属性的 getter 和 setter,在数据变动时,发布消息给依赖收集器(dep 中的 subs),去通知(notify)观察者,做出对应的回调函数,去更新视图。

MVVM 作为绑定的入口,整合 Observer,Compile 和 Watcher 三者,通过 Observer 来监听 model 数据变化,通过 Compile 来解析编译模板指令,最终利用 Watcher 搭起 Observer,Compile 之间的通信桥路,达到数据变化=>视图更新;视图交互变化=>数据 model 变更的双向绑定效果。

杂乱笔记

说说 vue 的生命周期⭐⭐⭐⭐⭐

vue 中父子组件的生命周期⭐⭐⭐⭐⭐ 

Vue 中的 nextTick⭐⭐⭐⭐⭐

nextTick

解释:

应用:

computed 和 watch 的区别⭐⭐⭐⭐⭐

Vue 优化方式⭐⭐⭐⭐⭐

Vue-router 的模式⭐⭐⭐⭐⭐

MVC 与 MVVM 有什么区别?它和其它框架(jquery)的区别是什么?哪些场景适合?⭐⭐⭐⭐⭐ 

diff 算法⭐⭐⭐⭐⭐

diff 算法是指对新旧虚拟节点进行对比,并返回一个 patch 对象,用来存储两个节点不同的地方,最后利用 patch 记录的消息局部更新 DOM。

虚拟 DOM 的优缺点⭐⭐⭐⭐⭐

缺点:

优点:

Vue 的 Key 的作用 ⭐⭐⭐⭐

key 主要用在虚拟 Dom 算法中,每个虚拟节点 VNode 有一个唯一标识 Key,通过对比新旧节点的 key 来判断节点是否改变,用 key 就可以大大提高渲染效率,这个 key 类似于缓存中的 etag。

Vue 组件之间的通信方式⭐⭐⭐⭐⭐

Vue-router 有哪几种钩子函数⭐⭐⭐⭐⭐

Webpack 相关

webpack 常用的几个对象及解释⭐⭐⭐⭐

loader 和 plugin 的区别是什么?⭐⭐⭐

CSS/HTML 相关

flex 布局⭐⭐⭐⭐⭐

css3 Flex 布局教程详解

grid 布局⭐⭐⭐⭐

CSS Grid 网格布局实例教程

常见的行内元素和块级元素都有哪些?⭐⭐⭐⭐⭐

请说明 px,em,rem,vw,vh,rpx 等单位的特性⭐⭐⭐⭐⭐ 

相关阅读:

常见的替换元素和非替换元素?⭐⭐

first-of-type 和 first-child 有什么区别⭐⭐⭐⭐

doctype 标签和 meta 标签⭐⭐⭐⭐⭐

doctype

meta 标签

script 标签中 defer 和 async 都表示了什么⭐⭐⭐⭐⭐

什么是 BFC?⭐⭐⭐⭐⭐

如何清除浮动⭐⭐⭐⭐⭐ 

额外标签clear:both

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <style>
    .fahter{
        width: 400px;
        border: 1px solid deeppink;
    }
    .big{
        width: 200px;
        height: 200px;
        background: darkorange;
        float: left;
    }
    .small{
        width: 120px;
        height: 120px;
        background: darkmagenta;
        float: left;
    }

    .clear{
        clear:both;
    }
    </style>
</head>
<body>
    <div >
        <div >big</div>
        <div >small</div>
        <div >额外标签法</div>
    </div>
</body>

利用 BFC:overflow:hidden

.fahter{
    width: 400px;
    border: 1px solid deeppink;
    overflow: hidden;
}

使用 after(推荐)

<style>
    .clearfix:after{/*伪元素是行内元素 正常浏览器清除浮动方法*/
        content: "";
        display: block;
        height: 0;
        clear:both;
        visibility: hidden;
    }
    .clearfix{
        *zoom: 1;/*ie6 清除浮动的方式 *号只有 IE6-IE7 执行,其他浏览器不执行*/
    }
</style>
<body>
    <div >
        <div >big</div>
        <div >small</div>
    </div>
</body>

什么是 DOM 事件流?什么是事件委托⭐⭐⭐⭐⭐

link 标签和 import 标签的区别⭐⭐⭐⭐

算法

冒泡算法排序⭐⭐⭐⭐⭐

// 冒泡排序
/* 1.比较相邻的两个元素,如果前一个比后一个大,则交换位置。

2.第一轮的时候最后一个元素应该是最大的一个。

3.按照步骤一的方法进行相邻两个元素的比较,这个时候由于最后一个元素已经是最大的了,所以最后一个元素不用比较。 */
function bubbleSort(arr) {
    for (var i = 0; i < arr.length; i++) {
        for (var j = 0; j < arr.length; j++) {
           if (arr[j] > arr[j + 1]) {
            var temp = arr[j]
            arr[j] = arr[j + 1]
            arr[j + 1] = temp
            }
        }
    }
}

var Arr = [3, 5, 74, 64, 64, 3, 1, 8, 3, 49, 16, 161, 9, 4]
console.log(Arr, "before");
bubbleSort(Arr)
console.log(Arr, "after");

快速排序⭐⭐⭐⭐⭐

/*
快速排序是对冒泡排序的一种改进,第一趟排序时将数据分成两部分,一部分比另一部分的所有数据都要小。
然后递归调用,在两边都实行快速排序。  
*/

function quickSort(arr) {
    if (arr.length <= 1) {
        return arr
    }
    var middle = Math.floor(arr.length / 2)
    var middleData = arr.splice(middle, 1)[0]

    var left = []
    var right = []

    for (var i = 0; i < arr.length; i++) {
        if (arr[i] < middleData) {
            left.push(arr[i])
        } else {
            right.push(arr[i])
        }
    }

    return quickSort(left).concat([middleData], quickSort(right))
}

var Arr = [3, 5, 74, 64, 64, 3, 1, 8, 3, 49, 16, 161, 9, 4]
console.log(Arr, "before");
var newArr = quickSort(Arr)
console.log(newArr, "after");

插入排序⭐⭐⭐⭐

function insertSort(arr) {
    // 默认第一个排好序了
    for (var i = 1; i < arr.length; i++) {
        // 如果后面的小于前面的直接把后面的插到前边正确的位置
        if (arr[i] < arr[i - 1]) {
            var el = arr[i]
            arr[i] = arr[i - 1]
            var j = i - 1
            while (j >= 0 && arr[j] > el) {
                arr[j + 1] = arr[j]
                j--
            }
            arr[j + 1] = el
        }
    }
}

var Arr = [3, 5, 74, 64, 64, 3, 1, 8, 3, 49, 16, 161, 9, 4]
console.log(Arr, "before");
insertSort(Arr)
console.log(Arr, "after");

是否回文⭐⭐⭐⭐⭐

function isHuiWen(str) {
    return str == str.split("").reverse().join("")
}

console.log(isHuiWen("mnm"));

正则表达式,千分位分隔符⭐⭐⭐⭐

function thousand(num) {
    return (num + "").replace(/d(?=(d{3})+$)/g, "$&,")
}
console.log(thousand(123456789));

斐波那契数列⭐⭐⭐⭐⭐

// num1 前一项
// num2 当前项
function fb(n, num1 = 1, num2 = 1) {
    if (n == 0) return 0
    if (n <= 2) {
        return num2
    } else {
        return fb(n - 1, num2, num1 + num2)
    }
}

数组去重的方式⭐⭐⭐⭐⭐

var arr = [1, 2, 1, 1, 1, 2, 3, 3, 3, 2]

// 最 low1
let newArr2 = []
for (let i = 0; i < arr.length; i++) {
  if (!newArr2.includes(arr[i])) {
    newArr2.push(arr[i])
  }
}
console.log(newArr2);
// 最 low2
let arr2 = [1, 2, 1, 1, 1, 2, 3, 3, 3, 2]
for (let i = 0; i < arr2.length; i++) {
  var item = arr2[i]
  for (let j = i + 1; j < arr2.length; j++) {
    var compare = arr2[j];
    if (compare === item) {
      arr2.splice(j, 1)
      j--
    }
  }
}
console.log(arr2);

// 基于对象去重
let arr3 = [1, 2, 1, 1, 1, 2, 3, 3, 3, 2]
let obj = {}
for (let i = 0; i < arr3.length; i++) {
    let item = arr3[i]
    if (obj[item]) {
       arr3[i] = arr3[arr3.length - 1]
       arr3.length--
       i-- continue;
    }
    obj[item] = item
}
console.log(arr3);
console.log(obj);

// 利用 Set
let newArr1 = new Set(arr)
console.log([...newArr1]);
let arr4 = [1, 2, 1, 1, 1, 2, 3, 3, 3, 2]

//利用 reduce
newArr4 = arr4.reduce((prev, curr) => prev.includes(curr)? prev : [...prev,curr],[])
console.log(newArr4);
console.log(document);

git 相关

git 的常用命令⭐⭐⭐⭐⭐

前端面试题2202及答案 1

更多 建站教程 请访问 https://www.mimiwuqi.com/webqianduan/