面试题

133 阅读45分钟

JS基本数据类型以及对基本数据类型string访问length时发生了什么

原文链接blog.csdn.net/sinat_41696…

JS的基本类型和引用类型

基本类型

JS的基本类型有undefined、null、boolean、string、number、bigint、symbol。基本类型的值保存在栈中,访问的时候直接访问的是值本身。

引用类型

JS的引用类型有Object、Array、Date、RegExp、Function、Map、Set。引用类型的值是保存在堆内存中的地址,访问的时候访问的是这个地址。我们知道引用类型一般有属性和方法,但是上文我们提到了string是一个基本类型,那么它为什么会有.length这个方法呢?

基本包装类型

在JS的基本类型中其中有三个是基本包装类型,其中包括有:Boolean、String、Number,这三个基本包装类型和其他引用类型一样,拥有内置的方法可以对数据进行操作。

自定义使用基本包装类型

  • 通过new String来自定义使用基本包装类型
// 自定义创建基本包装类型
const str = new String('haha');
console.log(str.toUpperCase());  //HAHA
  • 直接字面量的string和通过new String创建的有什么区别?
通过字面量进行直接赋值的形式本质是一个基本数据类型string,但是通过new String创建的本质是一个object对象。
const strs = new String('1111');
const str2 = '2222';

console.log(typeof strs); //object
console.log(typeof str2); //string

事件循环

原文:blog.csdn.net/lq313131/ar…

1.js任务分为同步任务和异步任务,异步任务又分为宏任务和微任务,其中异步任务属于耗时的任务。

  • 主线程可以理解为js从上到下依次执行
  • 整体代码script、setTimeout、setInterval、setImmediate、i/o操作(输入输出,比如读取文件操作、网络请求)、ui render(dom渲染,即更改代码重新渲染dom的过程)、异步ajax等
  • Promise(then、catch、finally)、async/await、process.nextTick、Object.observe(⽤来实时监测js中对象的变化)、 MutationObserver(监听DOM树的变化)

2.执行顺序 js代码在执行的时候,会先执行同步代码,遇到异步宏任务则将异步宏任务放入宏任务队列中,遇到异步微任务则将异步微任务放入微任务队列中,当所有同步代码执行完毕后,再将异步微任务从队列中调入主线程执行,微任务执行完毕后,再将异步宏任务从队列中调入主线程执行,一直循环至所有的任务执行完毕(完成一次事件循环EventLoop)。

一次事件循环只能处理一个宏任务,一次事件循环可以将所有的微任务处理完毕。

浏览器实现异步的几种写法

原文章:developer.aliyun.com/article/125… 回调函数

回调函数是指在异步任务执行完毕后,会调用的函数,回调函数是异步任务的一种实现方式。

console.log(1);
setTimeout(() => {
  console.log(2);
}, 0);
console.log(3);

// 1
// 3
// 2

Promise

Promise 是异步任务的另一种实现方式。

console.log(1);

Promise.resolve()
  .then(() => {
    console.log(2);
  })
  .then(() => {
    console.log(3);
  });

console.log(4);

// 1
// 4
// 2
// 3

async/await

async/await 是异步任务的另一种实现方式,本质上是类似于 Promise 的实现方式。

console.log(1);

async function async1() {
  await async2();
  console.log(2);
}

async function async2() {
  console.log(3);
}

async1();

console.log(4);

// 1
// 3
// 4
// 2

异步的优缺点

优点

  1. 通过异步任务,可以避免阻塞主线程,提高代码的执行效率。
  2. 通过异步任务,可以实现多个任务的并行执行,提高代码的执行效率。

缺点

  1. 通过异步任务,无法获取异步任务的返回值,只能通过回调函数来获取异步任务的返回值。
  2. 需要通过回调函数来实现异步任务,回调函数的嵌套层级会很深,导致代码的可读性很差。
  3. 需要解决异步同步的问题,比如在异步任务中,如果需要使用同步任务,就可能需要使用多个回调函数来实现。

JS手写实现Promise.all

  1. 传入一个 Promise 对象数组。
  2. 返回一个新的 Promise 对象。
  3. 创建一个空数组,用于存储每个 Promise 对象的结果。
  4. 遍历 Promise 对象数组,对于每个 Promise 对象,将它的结果添加到结果数组中。
  5. 如果有一个 Promise 对象失败,则将返回的 Promise 对象立即拒绝,并返回失败原因。
  6. 如果所有的 Promise 对象都成功,则返回包含所有结果的数组的 Promise 对象。
Promise.all = function (promises) {
    let results = [];
    let length = promises.length;
    let promiseCount = 0;
    return new Promise((resolve, reject) => {
        for (let i = 0; i < promises.length; i++) {
            Promise.resolve(promises[i]).then(res => {
                results[i] = res;
                promiseCount++;

                if (promiseCount === length) {
                    resolve(results);
                }
            }, err => {
                reject(err);
            })
        }
    })
}

this的指向

1.事件绑定中的this

给dom元素的某个事件行为绑定方法,当时间触发方法执行,方法中的this就是当前dom元素本身。

document.body.onclick = function () { 
    console.log(this) 
} 
document.body.addEventListener('click', function () { 
    console.log(this) 
})

注:IE6-IE8中基于attachEvent实现事件绑定,事件触发方法执行时,方法中的this不在元素本身,一般情况下都是指向window

/* document.body.attachEvent('onclick', function () { 
    console.log(window) 
    console.log(this) 
}) */

2. 普通函数执行中的this

函数执行,看函数前是否有“点”,有“点”,“点”前面是谁,this就指向谁,如果没有“点”,this就指向window(js严格模式下是undefined)

function fn(){
      console.log(this);
    }
    fn(); //window

    let obj={
      fn:function(){
        console.log(this);
      },
      fn1:{
        fn2:function(){
          console.log(this);
        }
      }
    }
    obj.fn(); //obj
    obj.fn1.fn2(); //obj.fn1

闭包中的this 一般都指向window

let obj={ 
    fn:function(){ 
        return function(){ 
            console.log(this); 
        } 
    }
 } 
 obj.fn()();

自执行函数执行,其内的 this 一般都是window,严格模式下为undefined

回调函数中的this为window或者undefined,除非做过特殊处理(如:数组的forEach方法·)

 [1,2,3].forEach(function(){
      console.log(this);	//window
    });

    //forEach对内部this进行处理
    [1,2,3].forEach(function(){
      console.log(this);	//{a:10}
    },{a:10})

3. 箭头函数中的this

箭头函数中没有自己的this,所用到的this都是上下文中的this

let obj = {
	n: 1000,
	fn() {
		setTimeout(function () {
			// this ==> window
			console.log(this)
		}, 500)

		setTimeout(() => {
			// this 所处上下文中的this => obj
			console.log(this)
		}, 1000);
	}
}
obj.fn()

4.基于 call/apply/bind 强制改变中的 this

原文件:blog.csdn.net/m0_60273757…

call、apply、bind对比总结

相同点:

  • 都可以改变函数内部的this指向

不同点

  • call和apply会调用函数并且改变函数内部this指向
  • call 和apply传递的参数不一样, call传递参数aru1, aru2..形式apply必须数组形式[arg]
  • bind 不会调用函数可以改变函数内部this指向

主要应用场景

  • call 经常做继承
  • apply 经常跟数组有关系.比如借助于数学对象实现数组最大值最小值
  • bind 不调用函数但是还想改变this指向比如改变定时器内部的this指向

手写call、apply、bind

原文件:www.jianshu.com/p/daaac369a…

1. 手写call函数

           let person = {
                name: 'lisi',
                getName() {
                    return this.name;
                }
            }
            let obj = {
                name: '张三'
            }
            // 在函数原型上添加自定义mycall函数
            Function.prototype.myCall = function(context){
                // this 哪个方法调用指向这个方法
                // console.log(this)
                if(typeof this !== 'function') {
                    throw new Error(`${this} is not a function}`
                }
                // context 可能传null,undefined, ''
                context = context || window
                // 获取参数,除了第一个
                let arg = [...arguments].slice(1);
                // 传过来的this上添加一个调用的方法,后面调用后删除
                context.fn = this;
                // 把参数传进去后调用,把调用结果返回出去,后删除context上的fn方法
                let result = context.fn(...arg);
                delete context.fn;
                return result;

            }
            let name = person.getName.myCall(obj);
            console.log(name)

2. 手写apply

    Function.prototype.myApply = function (context) {
        if(typeof this !== 'function') { 
            throw new Error(`${this} is not a function}` 
        }
        
        context = context || window;
        context.fn = this;
        let result;
        if(arguments[1]) {
            result = context.fn(...arguments[1])
        } else {
            result = context.fn();
        }
        delete context.fn;
        return result;
    }

3. 手写bind

    Fucntion.prototype.myBind = function(context) {
        if(typeof this !== 'function') {
            throw new Error(`${this} is not a function}`
        }
        context = context || window;
        context.fn = this;
        // 获取参数
        let args = [...arguments].slice(1);
        return () => {
            return context.fn.apply(context, [...args])
        }
    }

虚拟dom优势,劣势(简单的修改需要很多对比)

原文件:cd.itheima.com/news/202304…

什么是虚拟DOM

虚拟DOM本质上是JavaScript对象,是对真实DOM的抽象 状态变更时,记录新树和旧树的差异
最后把差异更新到真正的dom中.

虚拟DOM的作用:

使用原生js或者jquery写页面的时候会发现操作DOM是一件非常麻烦的一件事情,往往是DOM标签和js逻辑同时写在js文件里,数据交互时不时还要写很多的input隐藏域,如果没有好的代码规范的话会显得代码非常冗 余混乱,耦合性高并且难以维护。

另外一方面在浏览器里一遍又一遍的渲染DOM是非常非常消耗性能的,常常会出现页面卡死的情况;所以尽量减少对DOM的操作成为了优化前端性能的必要手段,vdom就是将DOM的对比放在了js层,通过对比不同之处来选择新渲染DOM节点,从而提高渲染效率。

diff算法?????

虚拟DOM的优缺点

优点:

  • 性能优化:通过比较虚拟DOM树的差异,只更新需要变化的部分,减少了对实际DOM的操作次数,提高了性能和渲染效率。
  • 跨平台能力:虚拟DOM是与具体平台无关的,可以在不同的前端框架和环境中使用,提供了更大的灵活性和可复用性。
  • 简化逻辑:虚拟DOM提供了一种更抽象的方式来处理DOM操作,开发者可以专注于应用程序的状态变化,而无需直接操作DOM,简化了代码逻辑。

缺点:

  • 内存消耗:虚拟DOM需要在内存中维护一份虚拟DOM树的副本,相比直接操作DOM会占用更多的内存。
  • 学习成本:使用虚拟DOM需要学习和理解相关的概念和技术,对于新手可能需要一定的学习成本。

算法:小青蛙上楼梯,一次一步或者两步,到n有多少中跳法?

原文件:blog.csdn.net/qq_44376306…

第一种:

function jumpFloor(n) {
  if (n <= 0) return 0;
  if (n == 1) return 1;
  if (n == 2) return 2;
  return jumpFloor(n - 1) + jumpFloor(n - 2)
}
console.log(jumpFloor(30)) // 输出 1346269

第二种:

function jumpFloor2(n) {
   var target = 0, number1 = 1, number2 = 2;
   if(n<=0)return 0;
   if(n == 1) return 1;
   if(n==2) return 2;
   for(var i=3;i<=n;++i) {
      target = number1 + number2;
      number1 = number2;
      number2 = target;
   }
   return target;
}
 
console.log(jumpFloor2(100)) // 输出 573147844013817200000
console.log(jumpFloor2(1000)) // 输出 7.0330367711422765e+208
console.log(jumpFloor2(10000)) // 输出 Infinity

谈谈你在项目里面用到的 cookie, sessionstorage localstorage  ,分别什么场景?

原文件:zhuanlan.zhihu.com/p/622560321

cookie

Cookie实际上是一小段的文本信息,是服务器发送到用户浏览器保存在本地的一小块数据。客户端请求服务器,如果服务器需要记录该用户状态,就使用response向客户端浏览器颁发一个Cookie。客户端会把Cookie保存起来。当浏览器下次向同一服务器再发起请求时,浏览器把请求的网址连同该Cookie一同提交给服务器。服务器检查该Cookie,以此来辨认用户状态。Cookie 使基于无状态的 HTTP 协议记录稳定的状态信息成为了可能。

1、会话 Cookie 和持久 Cookie

  • 会话Cookie:若不设置过期时间,则表示这个Cookie的生命期为浏览器会话期间,关闭浏览器窗口,Cookie就消失。这种生命期为浏览器会话期的 Cookie 被称为会话 Cookie。会话Cookie一般不存储在硬盘上而是保存在内存里。
  • 持久Cookie:若设置了过期时间,浏览器就会把 Cookie 保存到硬盘上,关闭后再次打开浏览器,这些 Cookie 仍然有效直到超过设定的过期时间。存储在硬盘上的 Cookie 可以在浏览器的不同进程间共享。

2、Cookie具有不可跨域名性

就是说,浏览器访问百度不会带上谷歌的 Cookie

Session

  • 当服务器收到请求需要创建Session对象时,首先会检查客户端请求中是否包含Sessionid。
  • 如果有Sessionid,服务器将根据该id返回对应Session对象;如果客户端请求中没有Sessionid,服务器会创建新的Session对象,并把Sessionid在本次响应中返回给客户端。
  • 通常使用 Cookie 方式存储 Sessionid 到客户端,在交互中浏览器按照规则将Sessionid发送给服务器。
  • 如果用户禁用Cookie,则要使用URL重写,可以通过response.encodeURL(url) 进行实现;当浏览器支持Cookie时,url 不做任何处理;当浏览器不支持Cookie的时候,将会重写 URL 将SessionID拼接到访问地址后。

Cookie 和 Session 的区别

① 保存方式

Cookie保存在浏览器端,Session保存在服务器端;

② 使用机制

具体见二、三点;

③ 存储内容

  • Cookie 只能保存字符串类型,以文本的方式;
  • Session 通过类似Hashtable的数据结构来保存,能支持任何类型的对象(Session中可含有多个对象);

④ 存储的大小

  • 单个 Cookie 保存的数据不能超过4kb;
  • Session 大小没有限制。

⑤ 安全性

Cookie存在的攻击:Cookie欺骗,Cookie截获;Session的安全性大于Cookie。

localStorage 和 sessionStorage 的对比

① 生命周期

  • localStorage:生命周期是永久的,关闭页面或浏览器之后localStorage中的数据也不会消失。localStorage除非主动删除数据,否则数据永远不会消失。
  • sessionStorage:生命周期是在仅在当前会话下有效。sessionStorage引入了一个“浏览器窗口”的概念,sessionStorage是在同源的窗口中始终存在的数据。只要这个浏览器窗口没有关闭,即使刷新页面或者进入同源另一个页面,数据依然存在。但是sessionStorage在关闭了浏览器窗口后就会被销毁。同时独立的打开同一个窗口同一个页面,sessionStorage也是不一样的

② 作用域

  • sessionStorage 在不同的浏览器窗口中不共享,不同页面或标签页间无法共享sessionStorage的信息。
  • localstorage 在所有同源窗口中都是共享的,相同浏览器的不同页面间可以共享相同的 localStorage(页面属于相同域名和端口)

③ 存储位置

localStorage 和 sessionStorage 都保存在客户端,不与服务器进行交互通信。

④ 存储大小

localStorage 和 sessionStorage 的存储数据大小一般都是:5MB

⑤ 获取方式

localStorage:window.localStorage

sessionStorage:window.sessionStorage

⑥ 存储内容类型

localStorage 和 sessionStorage 只能存储字符串类型,对于复杂的对象可以使用 ECMAScript 提供的 JSON 对象的 stringify 和 parse 来处理

手写一个并发请求,一共有urls条请求,每次发送max个,返回最终的结果?

原文件:blog.csdn.net/qq_38256117…

// 高并发控制函数
function multiRequest(urls, max) {
    const len = urls.length;
    const result = new Array(len).fill(false);
    let count = 0;
    return new Promise(resolve => {
        // 首次并发max个请求
        while (count < max) {
            next();
        }
        function next() {
            const cur = count++;
            if (cur > len) {
                // 如果所有的请求都返回结果,结束该函数,resolve虽有结果
                !result.includes(false) && resolve(result);
                return;
            }
            fetch(urls[cur]).then(res => {
                result[cur] = res;
                // 一个并发请求结束后,开始新的请求
                next();
            });
        }
    });
}
// Promise模拟接口异步请求
function fetch(url) {
    return new Promise(resolve => {
        setTimeout(() => {
            console.log(url);
            resolve('data:' + url);
        }, 2000);
    });
}
// 高并发调用,
// 参数1:接口请求地址数组,
// 参数2:控制并发数量
multiRequest([
    'api/1',
    'api/2',
    'api/3',
    'api/4',
    'api/5',
    'api/6',
    'api/7',
    'api/8',
    'api/9'
], 3).then(result => {
    console.log('所有请求处理完毕');
    console.log(result);
});

如何比较浮点数

浮点数和整数的比较: parseFloat和parseInt

浮点数的比较: 判断差值是否小于最小精度 Math.abs(0.1+0.2-0.3)< Math.EPSILON

如何判断js类型

方法一:typeof方法

基本数据类型除了null外都返回对应类型的字符串。

typeof 1 // "number" 
typeof 'a'  // "string"
typeof true  // "boolean"
typeof undefined // "undefined"
typeof Symbol() // "symbol"
typeof 42n // "bigint"

因为历史遗留的原因。typeof null 尝试返回为null失败了,所以要记住,**typeof null返回的是object。**
null返回“object”
typeof null  // "object"

特殊值NaN返回的是 "number"

typeof NaN // "number"

而复杂数据类型里,除了函数返回了"function"其他均返回“object”

typeof({a:1}) // "object" 普通对象直接返回“object”
typeof [1,3] // 数组返回"object"
typeof(new Date) // 内置对象 "object"

函数返回"function"

typeof function(){} // "function"

所以我们可以发现,typeof可以判断基本数据类型,但是难以判断除了函数以外的复杂数据类型。于是我们可以使用第二种方法,通常用来判断复杂数据类型,也可以用来判断基本数据类型。

方法二、object.prototype.toString.call方法 ,他返回"[object, 类型]",注意返回的格式及大小写,前面是小写,后面是首字母大写。

基本数据类型都能返回相应的类型。

Object.prototype.toString.call(999) // "[object Number]"
Object.prototype.toString.call('') // "[object String]"
Object.prototype.toString.call(Symbol()) // "[object Symbol]"
Object.prototype.toString.call(42n) // "[object BigInt]"
Object.prototype.toString.call(null) // "[object Null]"
Object.prototype.toString.call(undefined) // "[object Undefined]"
Object.prototype.toString.call(true) // "[object Boolean]

复杂数据类型也能返回相应的类型

Object.prototype.toString.call({a:1}) // "[object Object]"
Object.prototype.toString.call([1,2]) // "[object Array]"
Object.prototype.toString.call(new Date) // "[object Date]"
Object.prototype.toString.call(function(){}) // "[object Function]"

方法三、obj instanceof Object ,可以左边放你要判断的内容,右边放类型来进行JS类型判断,只能用来判断复杂数据类型,因为instanceof 是用于检测构造函数(右边)的 prototype 属性是否出现在某个实例对象(左边)的原型链上。

[1,2] instanceof Array  // true
(function(){}) instanceof Function // true
({a:1}) instanceof Object // true
(new Date) instanceof Date // true

== 和 ===区别,分别在什么情况使用?

原文件:blog.csdn.net/weixin_4447…

一、等于操作符

我们提到在JavaScript中存在隐式转换。等于操作符(==)在比较中会先进行类型转换,再确定操作数是否相等。

遵循以下规则:

  • 如果任一操作数是布尔值,则将其转换为数值再比较是否相等
let result1 = (true == 1); // true
  • 如果一个操作数是字符串,另一个操作数是数值,则尝试将字符串转换为数值,再比较是否相等
let result1 = ("55" == 55); // true
  • 如果一个操作数是对象,另一个操作数不是,则调用对象的 valueOf()方法取得其原始值,再根据前面的规则进行比较
let obj = {valueOf:function(){return 1}}
let result1 = (obj == 1); // true
  • nullundefined相等
let result1 = (null == undefined ); // true
  • 如果有任一操作数是 NaN ,则相等操作符返回 false
let result1 = (NaN == NaN ); // false

如果两个操作数都是对象,则比较它们是不是同一个对象。如果两个操作数都指向同一个对象,则相等操作符返回true

let obj1 = {name:"xxx"}
let obj2 = {name:"xxx"}
let result1 = (obj1 == obj2 ); // false

二、全等操作符

全等操作符由 3 个等于号( === )表示,只有两个操作数在不转换的前提下相等才返回 true。即类型相同,值也需相同

let result1 = ("55" === 55); // false,不相等,因为数据类型不同
let result2 = (55 === 55); // true,相等,因为数据类型相同值也相同

undefined 和 null 与自身严格相等

let result1 = (null === null)  //true
let result2 = (undefined === undefined)  //true

JavaScript处理大数字的方法

原文件:www.php.cn/faq/526260.…

  • 可以处理超过JavaScript Number类型最大值的大数字,最大的范围是2^53 - 1
    const bigNumber = 123456789123456789n;
  • 使用第三方库

使用bignumber.js库的示例

const BigNumber = require('bignumber.js');
const num1 = new BigNumber('123456789123456789');
const num2 = new BigNumber('987654321987654321');
const result = num1.plus(num2);
console.log(result.toString());
  • 使用数学库
const math = require('mathjs');
const num1 = math.bignumber('123456789123456789');
const num2 = math.bignumber('987654321987654321');
const result = math.add(num1, num2);
console.log(result.toString());

数组的方法?哪些会改变原数组?

一、不会改变原来数组的有:

  1. concat()

    concat() 方法用于连接两个或多个字符串。

    该方法没有改变原有字符串,但是会返回连接两个或多个字符串新字符串。

  2. every()

    every() 方法用于检测数组所有元素是否都符合指定条件(通过函数提供)。

    every() 方法使用指定函数检测数组中的所有元素:

    1. 如果数组中检测到有一个元素不满足,则整个表达式返回 false ,且剩余的元素不会再进行检测。
    2. 如果所有元素都满足条件,则返回 true。
  3. some()

    some() 方法用于检测数组中的元素是否满足指定条件(函数提供)。

    some() 方法会依次执行数组的每个元素:

    1. 如果有一个元素满足条件,则表达式返回true , 剩余的元素不会再执行检测。
    2. 如果没有满足条件的元素,则返回false。

    注意: some() 不会对空数组进行检测。

    注意: some() 不会改变原始数组。

  4. filter()

    filter() 方法创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素。

    注意: filter() 不会对空数组进行检测。

    注意: filter() 不会改变原始数组。

  5. map()

    map() 方法返回一个新数组,数组中的元素为原始数组元素调用函数处理后的值。

    map() 方法按照原始数组元素顺序依次处理元素。

    注意: map() 不会对空数组进行检测。

    注意: map() 不会改变原始数组。

  6. slice()

    slice() 方法可从已有的数组中返回选定的元素。

    slice()方法可提取字符串的某个部分,并以新的字符串返回被提取的部分。

    注意: slice() 方法不会改变原始数组。

二、会改变原来数组的有:

  1. pop()

    pop() 方法用于删除数组的最后一个元素并返回删除的元素。

    注意: 此方法改变数组的长度!

    提示: 移除数组第一个元素,请使用 shift() 方法。

  2. push()

    push() 方法可向数组的末尾添加一个或多个元素,并返回新的长度。

    注意: 新元素将添加在数组的末尾。

    注意: 此方法改变数组的长度。

    提示: 在数组起始位置添加元素请使用 unshift() 方法。

  3. shift()

    shift() 方法用于把数组的第一个元素从其中删除,并返回第一个元素的值。

    注意: 此方法改变数组的长度!

    提示: 移除数组末尾的元素可以使用 pop() 方法。

  4. unshift()

    unshift() 方法可向数组的开头添加一个或更多元素,并返回新的长度。

    注意: 该方法将改变数组的数目。

    提示: 将新项添加到数组末尾,请使用 push() 方法。

  5. reverse()

****reverse() 方法用于颠倒数组中元素的顺序。

         6.sort()

        sort() 方法用于对数组的元素进行排序。

     排序顺序可以是字母或数字,并按升序或降序。

     默认排序顺序为按字母升序。

注意: 当数字是按字母顺序排列时"40"将排在"5"前面。

     使用数字排序,你必须通过一个函数作为参数来调用。

     函数指定数字是按照升序还是降序排列。

     这些说起来可能很难理解,你可以通过本页底部实例进一步了解它。

注意: 这种方法会改变原始数组!。

    7.splice()

splice() 方法用于添加或删除数组中的元素。

        注意: 这种方法会改变原始数组。

vue路由的两种模式 hash与history

单页面

整个应用只有一个主页面,其余的“页面”,实际上是一个个的“组件”。单页应用中的“页面跳转”,实际上是组件的切换,在这个过程中,只会更新局部资源,页面不会整个刷新。也正是因为这个原因,当我们的前端代码有更改,重新部署之后,如果用户不主动刷新,就会发现有“资源缓存”。

单页应用的实现 单页应用的实现主要有两种方式,hash模式和history模式。

Hash具体的原理如下: 在这种模式下,URL 中的路由路径会以 # 符号作为分隔符。

  • Vue 路由会创建一个全局的监听器,监听浏览器的 hashchange 事件。
  • 当用户点击链接或执行特定操作时,Vue 路由会将对应的路由路径解析出来,并将该路径的哈希部分设置为当前 URL 的哈希部分
  • 浏览器会自动触发 hashchange 事件,Vue 路由监听到事件后,根据新的哈希值找到对应的路由配置,并根据配置信息动态加载对应的组件,更新页面内容,完成路由导航的过程。

History 模式的原理如下

  • 当用户访问一个 Vue 路由应用时,服务器需要配置一个默认页面(比如 index.html),用于处理与前端路由相关的请求。
  • 当用户切换路由时,Vue 路由会调用浏览器的 History API,通过 pushState 或 replaceState 方法修改当前的历史记录,并将新的路径添加到浏览器的历史栈中。
  • Vue 路由还会监听 popstate 事件,当用户点击浏览器的前进或后退按钮时,会触发该事件,Vue 路由会根据新的路径找到对应的路由配置,并动态地加载所需的组件并更新页面内容,完成路由导航的过程。

预编译顺序

原文件:www.cnblogs.com/ghostdot/p/… blog.csdn.net/baidu_36095…

函数预编译主要分为四步:

1、创建执行上下文对象AO ,即作用域。

2、提升变量以及形参,值为undefined

3、形参替换

4、寻找函数声明

举例: image.png

  • 第一步:创建AO作用域对象
  • 第二步:提升所有的形参和变量,此时
{
    a:undefined,
    b:undefined,
    c:undefined,
    d:undefined
}
  • 第三步:形参替换,因为执行的是fn(1,2),所以此时会替换掉a跟c的形参
{
    a:1,
    b:undefined,
    c:2,
    d:undefined
}
  • 寻找函数声明,在fn中声明了两个函数,分别是function a与function c,他们会替换掉a,c的值,此时
{
    a:function(){},
    b:undefined,
    c:function(){},
    d:undefined
}

有一个拖拽元素,拖拽过程中很卡,怎么解决?

原文件:segmentfault.com/a/119000003…

我们在拖拽元素的过程中,频繁的触发回流操作,而回流操作本身就是非常昂贵的。这个时候就很容易出现阻塞,导致网页卡顿。这时我们加上setTimeout,将任务放到浏览器最早可得的空闲时段执行,那些计算量大、耗时长的任务,分别放到setTimeout(f,0)里面执行(分片塞入队列),这样即使在复杂程序没有处理完时,我们操作页面,也是能得到即时响应的。

进程、线程

原文件: blog.csdn.net/weixin_5113…

简单讲,计算机就像工厂,进程是个大车间,计算机内部有很多个这样的大车间。线程是工人,每一个车间里的工人至少有一个。 工厂的空间是工人们共享的,这象征一个进程的内存空间是共享的,每个线程都可用这些共享内存。 多个工厂之间独立存在。

  • 多进程:在同一个时间里,同一个计算机系统中如果允许两个或两个以上的进程处于运行状态。多进程带来的好处是明显的,比如你可以听歌的同时,打开编辑器敲代码,编辑器和听歌软件的进程之间丝毫不会相互干扰。
  • 多线程:程序中包含多个执行流,即在一个程序中可以同时运行多个不同的线程来执行不同的任务,也就是说允许单个程序创建多个并行执行的线程来完成各自的任务。
  • 以 Chrome 浏览器中为例,当你打开一个 Tab 页时,其实就是创建了一个进程,一个进程中可以有多个线程(下文会详细介绍),比如渲染线程、JS 引擎线程、HTTP 请求线程等等。当你发起一个请求时,其实就是创建了一个线程,当请求结束后,该线程可能就会被销毁。

页面白屏有哪些情况导致

  • 前端JS代码问题: 常用框架,比如 Vue React Angular 都是依靠JS进行驱动的,在渲染页面的时候需要加载很大的JS文件,在JS解析加载完成之前无法展示页面,从而导致了白屏。
  • 用户端浏览器兼容问题: 比如 Vue 在 IE 低版本浏览器中,再比如在低版本浏览器里运行了ES6以上的原生ES代码等等。
  • 接口请求慢: 第一个打开的页面,如果请求一个缓慢的后端接口,引发JS阻塞,导致页面白屏。
  • 缓存问题: 在用户端会默认缓存 index.html 入口文件,而由于构建工具(比如Webpack,rollup等)在每次代码更新后,打包生成的css/js文件名都带有哈希值,跟上次的文件名都不同,因此会出现找不到 css/js 文件的情况,导致白屏的产生。

如何解决白屏问题

  • CDN资源优化
  • 减少DOM树渲染的时间(HTML层级不要太深,标签语义化…)
  • 减少HTTP请求次数和请求大小
  • 把CSS放在Head中,将js放在body的最后,避免阻塞
  • 使用ssr预渲染

html4和html5的区别?

原文件:blog.csdn.net/weixin_5503…

git rebase和git merge的区别?

git rebase和git merge都是把一个分支的提交合并到另一个分支上,但是它们两个内部操作又是不一样的。

  • git merge:将develop分支合并到master上,最后会剩下下图右侧的样子,3,6,7的commit会生成一个新的提交。如果分支特别多,会造成提交比较混乱。

image.png

  • git rebase:找到公共节点3,3以后的节点截取下来嫁接到develop分支上,但是新生的5,7和原来的commit号不一样。

image.png

什么是HTTP代理?

HTTP代理是一种充当客户端和服务器之间的中间人的服务器。当客户端发起请求时,HTTP代理会拦截请求并将其转发给目标服务器。一旦目标服务器响应,HTTP代理会拦截响应并将其转发回客户端。HTTP代理可以被用于多种场景,例如加强安全、缓存内容以加速访问、访问受限资源等等。

HTTP代理的作用

加强安全性: HTTP代理可以拦截和检查请求和响应,从而过滤出不安全或有害的内容,同时保护客户端和服务器的隐私和安全。

提高性能: HTTP代理可以缓存常用内容,从而加速响应速度。当客户端请求相同的内容时,HTTP代理可以直接从缓存中返回响应,而无需重新请求服务器。这可以显著提高页面加载速度和减轻服务器负载。

访问受限资源: 有些资源可能因为地理位置、政策、权限等原因而被访问受限。HTTP代理可以通过转发请求和响应,从而使客户端绕过这些限制,访问受限资源。

正向代理和反向代理?

正向代理

  • 科学上网: 比如用户正常不能访问谷歌的,但是我们可以通过代理服务器。当我们访问谷歌时,先把请求发给代理服务器,代理服务器再去访问谷歌。最后代理服务器再将结果返回给用户。

image.png

  • 加速访问:如果电信用户想访问联通服务器,那速度会很慢的,但是现在有台代理服务器,同时支持电信和联通,那电信用户就可以先访问代理服务器,代理服务器再联通服务器。
  • 缓存数据:

image.png

  • 隐藏访问者

image.png

反向代理

  • 保护服务器 image.png
  • 负载均衡

image.png

rem、em、px、%、vh?

  • px: 相对长度单位,相对于屏幕分辨率。
  • em: em 是相对长度单位。相对于当前对象内文本的字体尺寸。如当前对行内文本的字体尺寸未被人为设置,则相对于浏览器的默认字体尺寸(1em = 16px。
  • rem: rem,相对单位,相对的只是HTML根元素font-size的值。如果想要简化font-size的转化,我们可以在根元素html中加入font-size: 62.5%,这样页面中1rem=10px、1.2rem=12px、1.4rem=14px、1.6rem=16px;使得视觉、使用、书写都得到了极大的帮助。
  • vh、vw:vw ,就是根据窗口的宽度,分成100等份,100vw就表示满宽,50vw就表示一半宽。(vw 始终是针对窗口的宽),同理,vh则为窗口的高度
  • %: 像vw、vh,比较容易混淆的一个单位是%,不过百分比宽泛的讲是相对于父元素,不是相对于可视窗口的。

H5移动端的适配方案

方法一:VW

  • vw ,就是根据窗口的宽度,分成100等份,所以如果设计稿是375px,此时的对应关系就是100vw=375px,所以1px对应多少vw呢,1px=100/375=0.266vw。
  • 接下来就是按照设计稿的元素多少px*0.266就转换成了vw单位。 image.png 注:如果每次都乘以0.266,就会很麻烦,所以接下来介绍下插件

image.png 这样配置好后,在写样式的时候,就直接按照设计稿写多少px就行,最后都会帮我们转成对应的vw。

方法二: rem

rem的大小是根据根元素html的font-size, 1rem就对应html的font-size的大小。默认font-size=16px,所以1rem = 16px;

实现rem不同设备下等比例缩放: 在不同的设备下只要改变了根元素的font-size,就能实现等比缩放。

  1. 如果iphone6时,手机的宽度是375px,又因为100vw = 375px, 那如果100px对应的vw是多少呢,100px对应的vw就是=100vw*100px/375=26.666vw。此时设置font-size=26.666vw,样式设置为1rem,他们之间的换算就是1rem= 26.66vw = 100px。所以rem和px之间的换算是100倍。

image.png

BFC

原文件:zhuanlan.zhihu.com/p/393737011

BFC:

我们可以把BFC看作为页面的一块渲染区域,他有着自己的渲染规则,简单来说BFC可以看作元素的一种属性,当元素拥有了BFC属性后这个元素就可以看作是隔离了的独立容器,容器里面的元素不会在布局上影响到外面的元素。

如何触发BFC

  • 浮动元素 float 不是 none
  • 绝对定位 position 是 absolute 或者 fixed
  • 块级元素 ovefflow 不是 visible
  • display属性值:inline-display/table-cell/table-caption/flex/inline-flex

BFFC的特性和作用与那些喃

  • 避免外边框(margin)重叠
  • 清除浮动
  • 可以防止元素被浮动元素覆盖

浏览器输入url后都发生了什么?

原文件:zhuanlan.zhihu.com/p/519407969

流程:

1、浏览器的地址栏输入URL并按下回车。

2、浏览器查找当前URL是否存在缓存,并比较缓存是否过期。

3、DNS解析URL对应的IP。

4、根据IP建立TCP连接(三次握手)。

5、HTTP发起请求。

6、服务器处理请求,浏览器接收HTTP响应。

7、关闭TCP连接(四次挥手)。

8、渲染页面,构建DOM树。

一、URL

我们常见的URL是这样的:www.baidu.com,这个域名由三部分组成:协议名、域名、端口号,这里端口是默认所以隐藏。通常端口号不常见是因为大部分的都是使用默认端口,如HTTP默认端口80,HTTPS默认端口443。

二、缓存

可以讲一下之前的强制缓存,协商缓存。

三、DNS域名解析

  • 请求发起后,浏览器首先会解析这个域名,首先它会查看本地硬盘的 hosts 文件
  • 如果在本地的 hosts 文件没有能够找到对应的 ip 地址,浏览器会发出一个 DNS请求到本地DNS服务器
  • 如果没有,本地DNS服务器还要向DNS根服务器进行查询
  • 如果没有,告诉本地DNS服务器,你可以到域服务器上去继续查询,并给出域服务器的地址。
  • 最后,本地DNS服务器向域名的解析服务器发出请求,这时就能收到一个域名和IP地址对应关系,本地DNS服务器不仅要把IP地址返回给用户电脑,还要把这个对应关系保存在缓存中,以备下次别的用户查询时,可以直接返回结果,加快网络访问。

四、TCP连接

五、浏览器向服务器发送HTTP请求

完整的HTTP请求包含请求起始行、请求头部、请求正文三部分。

六、浏览器接收响应

主要包括状态码,响应头,响应报文三个部分。 可以看下状态码

七、关闭TCP连接或继续保持连接

八、渲染页面,构建DOM树

1.浏览器的渲染过程:

  • 解析 HTML 构建 DOM(DOM 树),并行请求 css/image/js
  • CSS 文件下载完成,开始构建 CSSOM(CSS 树)
  • CSSOM 构建结束后,和 DOM 一起生成 Render Tree(渲染树)
  • 布局(Layout):计算出每个节点在屏幕中的位置
  • 显示(Painting):通过显卡把页面画到屏幕上

2. DOM 树 和 渲染树 的区别:

  • DOM 树与 HTML 标签一一对应,包括 head 和隐藏元素
  • 渲染树不包括 head 和隐藏元素

3. CSS会阻塞DOM解析吗?

原文件:www.cnblogs.com/sexintercou…

  • css加载不会阻塞DOM树的解析;
  • JS 会阻塞 DOM 解析 当解析器遇到script标签时,浏览器会停止DOM的解析,会一直等到该script的加载并执行后,才继续往下解析。

比较合理的解释就是,首先浏览器无法知晓JS的具体内容,倘若先解析DOM,万一JS内部全部删除掉DOM,那么浏览器就白忙活了,所以就干脆暂停解析DOM,等到JS执行完成再继续解析。

  • CSS 会阻塞 JS 的执行

可以这样理解,浏览器并不知道js中的代码会干些什么,js可以去改动DOM,也可以获取/改变css样式。js要获取正确的样式就必须等css加载完。

4. 重绘重排?

重绘:当一个元素的外观发生改变,但没有改变布局,重新把元素外观绘制出来的过程,叫做重绘。

重排:当DOM的变化影响了元素的几何信息(元素的的位置和尺寸大小),浏览器需要重新计算元素的几何属性,将其安放在界面中的正确位置,这个过程叫做重排。

什么情况下会触发重绘: 触发回流时一定会触发重绘,除此之外,例如颜色修改,设置圆角、文本方向修改,阴影修改等。

什么情况下会触发回流

  1. 添加或删除可见的DOM元素
  2. 元素的位置发生变化
  3. 元素的尺寸发生变化
  4. 内容发生变化
  5. 页面一开始渲染的时候
  6. 浏览器的窗口尺寸变化

如何减少重排

  1. 将多次样式修改合并成一次
  2. 将需要多次重排的元素,position属性设为 absolute 或 fixed(使元素脱离了文档流,它的变化不会影响到其他元素;)
  3. 复杂元素处理先设置display:none,处理完再显示
  4. 缓存频繁操作的属性;
  5. 减少使用table布局
  6. 使用事件委托绑定事件处理程序
  7. 利用 DocumentFragment 操作DOM节点
  8. 减少DOM操作

怎么解决前端的跨域问题

原文件:www.rstk.cn/news/303241…

跨域 跨域是指从一个网站的域名,去请求另一个网站的资源。一般情况下,浏览器会限制脚本跨域请求,以保证用户的信息安全。(域名、协议、端口)

常见的跨域解决方案

  • JSONP: 利用script标签可以跨域访问数据的特性,通过在URL中添加回调函数的参数,让服务端返回一段指定的 JS 代码,并在客户端中执行该代码。由于script标签的src属性是不受同源策略限制的,因此这种方式可以在浏览器中获取远程数据。

JSONP优点:兼容性好,易于实现。 JSONP缺点:只能用于GET请求,不安全。

  • CORS: 服务端通过设置Access-Control-Allow-Origin等HTTP协议的header来允许当前域名的脚本获取指定的资源。

CORS优点:安全可靠,支持POST请求。 CORS缺点:不适用于低版本浏览器,需要服务器设置额外的头信息,部分特殊情况下需要前后端一同配置。

  • 代理:代理方式是前端请求URL时,将URL发送到自己的服务器,然后在服务器上再次请求目标数据,并将目标数据返回给前端页面。这样前端页面就可以避免跨域请求了。

代理优点:适用于全部浏览器,支持POST请求,安全可靠。 代理缺点:需要额外搭建服务器,增加了服务器成本和代码维护难度。

301和302的区别

  • 301重定向是页面永久性转移,搜索引擎在抓取新内容的同时也将旧的网址替换成重定向之后的网址;
  • 302重定向是页面暂时性转移,搜索引擎会抓取新的内容而保存旧的网址并认为新的网址只是暂时的。

vue里面的data为什么不直接返回对象,而是返回一个函数

这是因为Vue在创建组件实例时,会为每个实例都创建一个独立的 data 对象,而不是多个组件实例共享同一个 data 对象。如果 data 属性是一个简单的对象,那么所有的组件实例都将共享同一个对象,这样就会导致一个组件实例的数据变化会影响到其他组件实例的数据。 因此,将 data 属性设置为函数,可以保证每个组件实例都会返回一个新的独立的数据对象,从而避免组件之间的数据交叉污染。

实现继承的几种方式

原文件:www.zhihu.com/tardis/bd/a…

css的选择器及优先级

原文件:xiaozhao.vip/article/det…

!important > 行内样式 > ID选择器 > 类选择器 > 元素选择器 > 通配符选择器 > 继承 > 浏览器默认属性

内联(行内)样式 > 内部样式表 > 外部样式表 > 导入样式(@import)。

伪元素与伪类选择器

原文件:blog.csdn.net/qq_45867247…

image.png

image.png

image.png

image.png

image.png

image.png

类数组

原文件:www.cnblogs.com/shangsi/p/1…

什么是类数组?

类数组指包含 length 属性或可迭代的对象。

顾名思义,这玩意儿肯定是个长得像数组,但又不算数组的东西。那到底是个啥,其实它就是一个对象,一个长的像数组的对象。

和数组的区别

1、都有length属性
2、类数组也可以for循环遍历,有的类数组还可以通过 for of 遍历
3、类数组不具备数组的原型方法,因此类数组不可调用相关数组方法(如push,slicec,concat等等)

都有哪些类数组

  • 函数的参数arguments
  • 通过getElementsByTagName,getElementsByClassName等方法获取的dom列表(也叫 HTMLCollection)
  • 通过querySelectorAll(),getElementsByName等方法获取的NodeList节点列表

类数组转换成数组

方法一:通过 call 调用数组的 slice 方法来实现转换

 Array.prototype.slice.call(arrayLike);

方法二:通过 apply 调用数组的 concat 方法来实现转换

Array.prototype.concat.apply([], arrayLike);

方法三:通过 call 调用数组的 splice 方法来实现转换

Array.prototype.splice.call(arrayLike, 0);

方法四: 通过 Array.from 方法来实现转换

Array.from(arrayLike);

实现两栏布局左边固定右边自适应

浮动 + margin-left

image.png

浮动 + BFC

image.png

定位 + margin-left

image.png

定位

image.png

flex布局 image.png

table布局

image.png

手写数组扁平化

原文件:blog.csdn.net/m0_65335111…

Array.prototype.flat() 用于将嵌套的数组“拉平”,变成一维的数组。

该方法返回一个新数组,对原数据没有影响。

  • 不传参数时,默认“拉平”一层,可以传入一个整数,表示想要“拉平”的层数。
  • 传入 <=0 的整数将返回原数组,不“拉平”。
  • Infinity 关键字作为参数时,无论多少层嵌套,都会转为一维数组。
  • 如果原数组有空位,Array.prototype.flat() 会跳过空位。

使用for循环+concat实现:

const arr = [1,[2,3],[4,[5,[6]],7]];
 
function flatten(arr) {
    let result = [];
    for (let i = 0; i < arr.length; i++) {
      if (Array.isArray(arr[i])) {
        result = result.concat(flatten(arr[i]));
      } else {
      result = result.concat(arr[i])
      }
    }
    return result
  }
  
  console.log(flatten(arr));//[1,2,3,4,5,6,7]

reduce实现

const arr = [1,[2,3],[4,[5,[6]],7]];
 
const flatten = (arr, deep = 1) => {
    if (deep <= 0) return arr;
    return arr.reduce((res, curr) => res.concat(Array.isArray(curr) ? flatten(curr, deep - 1) : curr), [])
}
 
console.log(flatten(arr, Infinity));//[1,2,3,4,5,6,7]

SSR

原文件:github.com/yacan8/blog…

先是浏览器请求URL,前端服务器接收到URL请求之后,根据不同的URL,前端服务器向后端服务器请求数据,请求完成后,前端服务器会组装一个携带了具体数据的HTML文本,并且返回给浏览器,浏览器得到HTML之后开始渲染页面,同时,浏览器加载并执行 JavaScript 脚本,给页面上的元素绑定事件,让页面变得可交互,当用户与浏览器页面进行交互,如跳转到下一个页面时,浏览器会执行 JavaScript 脚本,向后端服务器请求数据,获取完数据之后再次执行 JavaScript 代码动态渲染页面。

ssr优点:

  • 利于首屏页面的渲染
  • 更利于网站的SEO

ssr1缺点:

  • 服务器端压力较大
  • 开发条件受限
  • 学习成本较高

谈谈你对webpack的理解?

webpack是一个打包模块化js的工具,在webpack里一切文件皆模块,通过loader转换文件,通过plugin注入钩子,最后输出由多个模块组合成的文件,webpack专注构建模块化项目。WebPack可以看做是模块的打包机器:它做的事情是,分析你的项目结构,找到js模块以及其它的一些浏览器不能直接运行的拓展语言,例如:Scss,TS等,并将其打包为合适的格式以供浏览器使用。

说说webpack与grunt、gulp的不同?

三者都是前端构建工具,grunt和gulp在早期比较流行,现在webpack相对来说比较主流,不过一些轻量化的任务还是会用gulp来处理,比如单独打包CSS文件等。grunt和gulp是基于任务和流(Task、Stream)的。类似jQuery,找到一个(或一类)文件,对其做一系列链式操作,更新流上的数据,整条链式操作构成了一个任务,多个任务就构成了整个web的构建流程。webpack是基于入口的。webpack会自动地递归解析入口所需要加载的所有资源文件,然后用不同的Loader来处理不同的文件,用Plugin来扩展webpack功能。所以,从构建思路来说,gulp和grunt需要开发者将整个前端构建过程拆分成多个Task,并合理控制所有Task的调用关系;webpack需要开发者找到入口,并需要清楚对于不同的资源应该使用什么Loader做何种解析和加工对于知识背景来说,gulp更像后端开发者的思路,需要对于整个流程了如指掌webpack更倾向于前端开发者的思路

什么是bundle,什么是chunk,什么是module?

bundle:是由webpack打包出来的文件

chunk:代码块,一个chunk由多个模块组合而成,用于代码的合并和分割

module:是开发中的单个模块,在webpack的世界,一切皆模块,一个模块对应一个文件,webpack会从配置的entry中递归开始找出所有依赖的模块

什么是Loader?什么是Plugin?

1)Loaders是用来告诉webpack如何转化处理某一类型的文件,并且引入到打包出的文件中 2)Plugin是用来自定义webpack打包过程的方式,一个插件是含有apply方法的一个对象,通过这个方法可以参与到整个webpack打包的各个流程(生命周期)。

有哪些常见的Loader?他们是解决什么问题的?

file-loader:把文件输出到一个文件夹中,在代码中通过相对URL去引用输出的文件

url-loader:和file-loader类似,但是能在文件很小的情况下以base64的方式把文件内容注入到代码中去

source-map-loader:加载额外的Source Map文件,以方便断点调试

image-loader:加载并且压缩图片文件

babel-loader:把ES6转换成ES5css-loader:加载CSS,支持模块化、压缩、文件导入等特性

style-loader:把CSS代码注入到JavaScript中,通过DOM操作去加载CSS。

eslint-loader:通过ESLint检查JavaScript代码

有哪些常见的Plugin?他们是解决什么问题的?

原文件:www.cnblogs.com/ifon/p/1592…

  • html-webpack-plugin:(打包html文件)自动生成一个index.html文件(可以指定模板)将打包的js文件,自动通过script标签插入到body中
  • webpack-dev-server: 开启一个开发服务器,帮助我监视文件变动,做到按需更新页面
  • webpack-parallel-uglify-plugin 多进程执行代码压缩,提升构建速度。
  • mini-css-extract-plugin 分离样式文件,css提取为独立文件,支持按需加载。
  • clean-webpack-plugin 打包前清空目录
  • happypack 通过多进程模型,来加速代码构建。

Loader和Plugin的不同?

原文件:www.cnblogs.com/zhilili/p/1…

image.png

image.png

webpack的构建流程是什么?

原文件:www.cnblogs.com/luhu123/p/1…

webpack,一个静态模块打包器,前端的所有资源文件都会作为模块处理。 它将根据模块的依赖关系进行静态分析,打包生成对应的静态资源.

webpack 五个核心概念:

  • 入口指示 webpack 以哪个文件为入口起点开始打包,分析构建内部依赖图。
  • 输出指示 webpack 打包后的资源 bundles 输出到哪里去,以及如何命名。
  • Loader加载器让 webpack 能够去处理那些非 JavaScript 文件
  • 模式(Mode)指示 webpack 使用相应模式的配置。develop模式,production模式,不同的运行环境上线运行。

描述一下编写loader或plugin的思路?

webpack实现热更新原理

juejin.cn/post/684490…

如何利用webpack来优化前端性能?

  • 压缩代码删除冗余的代码注释,简化代码的写法,可以利⽤webpack的 UglifyJsPlugin 和 ParallelUglifyPlugin 来压缩JS⽂件, 利⽤ cssnano来压缩css

  • 利⽤CDN加速: 在构建过程中,将引⽤的静态资源路径修改为CDN上对应的路径。可以利⽤webpack对 于 output 参数和各loader的 publicPath 参数来修改资源路径

  • Tree Shaking: 将代码中永远不会⾛到的⽚段删除掉。可以通过在启动webpack时追加参数 --optimize-minimize 来实现

  • Code Splitting: 将代码按路由维度或者组件分块(chunk),这样做到按需加载,同时可以充分利⽤浏览器缓存

  • 提取公共第三⽅库: SplitChunksPlugin插件来进⾏公共模块抽取,利用浏览器缓存可以⻓期缓存这些⽆需频繁变动的公共代码

如何提高webpack的构建速度?

  1. 多⼊⼝情况下,使⽤ CommonsChunkPlugin 来提取公共代码

  2. 通过 externals 配置来提取常⽤库

  3. 利⽤ DllPlugin 和 DllReferencePlugin 预编译资源模块 通过 DllPlugin

  4. 来对那些我们引⽤但是绝对不会修改的npm 包来进⾏预编译,再通过 DllReferencePlugin 将预编译的模块加载进来。

  5. 使⽤ Happypack 实现多线程加速编译

  6. 使⽤ webpack-uglify-parallel 来提升 uglifyPlugin 的压缩速度。 原理上 webpack-uglify-parallel 采⽤了多核并⾏压缩来提升压缩速度

  7. 使⽤ Tree-shaking 和 Scope Hoisting 来剔除多余代码

怎么配置单页应用?怎么配置多页应用?

如何在vue项目中实现按需加载?

1.import动态导入

const Home = () => import( /* webpackChunkName: "Home" */ '@/views/Home.vue');

使用vue异步组件resolve

//const 组件名 = resolve => require([‘组件路径’],resolve)
//(这种情况下一个组件生成一个js文件)
const Home = resolve => require(['../view/Home'],resolve)

**使用webpack require.ensure()

const Home = () => require.ensure([], (require) => require('@/views/Home.vue'))

vue3的异步组件加载方式defineAsyncComponent

异步组件defineAsyncComponent加载不能用在首次展示或者首次重定向的页面

import { defineAsyncComponent } from 'vue'
 
const ExcelPreview = defineAsyncComponent(() =>
import( /* webpackChunkName: "ExcelPreview" */ '@/views/ExcelPreview.vue')
)

webpack5相对于4的更新

!!!!!需要多看

原文件:blog.51cto.com/u_14115828/…

vite为什么比webpack快

webpack会把所有的文件都build一遍,他需要从入口文件,去索引整个项目的文件,编译成一个或多个单独的js文件,不管模块是否被执行,都要变异打包到这个bundle里面,随着项目越来越复杂,项目的模块越来越多,打包后的bundle也越来越大,打包的速度自然也会越来越慢,整个过程是非常消耗时间的,就导致了随着项目越来越复杂,他的启动时间也会越来越长。

而vite在启动的时候不需要打包,也就意味着他不需要拆分模块的依赖,不需要编译,所以他的启动速度是非常快的。当浏览器请求某个模块的时候,在根据需要,对模块的内容进行编译,vite是按需动态的编译方式,极大的缩短了编译时间。vite是基于es build预构建的,es build使用了go语言的编写比js编写的打包器预构建快。