面试:其他相关内容/Deno/OS/设计模式/性能优化

文章目录

  • Deno
    • 只有一个可执行文件
    • 安全控制
    • 浏览器支持
    • 模块加载
    • 内置功能,无需外部工具
    • Deno和Node.JS区别
  • 操作系统
    • cpu、核、进程与线程
      • 基础概念
      • 串行,并发与并行
      • 进程和线程的区别
        • 进程
        • 线程
        • 区别
        • 协程
  • 设计模式
    • 发布-订阅者/观察者模式
      • class写法
      • function写法
    • EventEmitter 实现
    • 单例模式
    • 工厂模式
    • 装饰者模式
  • 性能优化/工程化
    • webpack
      • 打包原理
    • 打包流程
      • 目的
      • Webpack和grunt和gulp有啥不同
      • webpack、rollup、parcel优劣
      • 什么是bundle,什么是chunk,什么是module
      • 什么是loader,什么是plugin,有什么不同?
      • 有哪些常见的Loader?他们是解决什么问题的?
      • 有哪些常见的Plugin?他们是解决什么问题的?
      • 是否写过webpack的Loader和Plugin?描述⼀下编写loader或plugin的思路?
      • webpack-dev-server和http服务器如nginx有什么不同
      • webpack的热更新是如何做到的?说明其原理?
      • 如何提⾼webpack的打包速度/减少打包时间?
      • 如何提⾼webpack的构建速度?
      • 怎么配置单页应用?怎么配置多页应用?
      • 如何在vue项目中实现按需加载?
      • treeshaking
        • 为什么需要tree shaking
    • 客户端渲染/服务端渲染
      • 客户端渲染SSR(client side rendering)
      • 服务端渲染SSR (server side rendering)
      • 应用场景
  • 其他
    • ajax/axios区别
      • axios拦截器
    • 模块化
      • 立即执行函数
      • AMD/CMD
      • CommonJS
      • ES Module
      • ES6 Module和CommonJS模块的区别
    • 前端模块与组件的区别
    • JS 面向对象编程
      • 对象字面量/为Object实例添加属性方法
      • 工厂模式
      • 构造函数
      • 原型模式
      • 构造函数+原型模式
      • class(ES6)
  • 场景题
    • 一个父元素中不断有div被加入,如何给这些div绑定事件
    • 实现一个搜索框
    • 高效查找数据
    • 无限下拉
      • setTimeout
      • requestAnimationFrame
      • DocumentFragment
      • 分页
      • 虚拟列表
  • 其他
    • 前端新趋势
      • ES2020
      • Vue3/vite
      • 低代码
      • serverless
      • WebAssembly
      • webpack5.0
      • 微前端(架构及发展趋势)
    • 服务端渲染Server Side Render(SSR)
    • 对前后端分离的理解
    • RESTful API
    • 前端性能优化相关
      • 回流重回、缓存、网络协议相关、怎么减少请求次数等等
      • 怎么评价页面性能
    • 小程序的原理
    • webAPP的适配
    • 前端四大存储方式
    • Restful接口规范
    • MVC/MVP/MVVM
      • MVC
      • MVP
      • MVVM
        • MVVM的实现方式
        • Vue中的MVVM
    • CDN
      • 缓存

Deno

跟 Node.js 一样,Deno 也是一个服务器运行时,但是支持多种语言,可以直接运行 JavaScript、TypeScript 和 WebAssembly 程序。

它内置了 V8 引擎,用来解释 JavaScript。同时,也内置了 tsc 引擎,解释 TypeScript。它使用 Rust 语言开发,由于 Rust 原生支持 WebAssembly,所以它也能直接运行 WebAssembly。它的异步操作不使用 libuv 这个库,而是使用 Rust 语言的 Tokio 库,来实现事件循环(event loop)。

只有一个可执行文件

Deno 只有一个可执行文件,所有操作都通过这个文件完成。它支持跨平台(Mac、Linux、Windows)。

安全控制

Deno 具有安全控制,默认情况下脚本不具有读写权限。如果脚本未授权,就读写文件系统或网络,会报错。
必须使用参数,显式打开权限才可以。

浏览器支持

Deno 支持 Web API,尽量跟浏览器保持一致。

它提供 window 这个全局对象,同时支持 fetch、webCrypto、worker 等 Web 标准,也支持 onload、onunload、addEventListener 等事件操作函数。

此外,Deno 所有的异步操作,一律返回 Promise。

模块加载

Deno 只支持 ES 模块,跟浏览器的模块加载规则一致。没有 npm,没有 npm_modules 目录,没有require()命令(即不支持 CommonJS 模块),也不需要package.json文件。

所有模块通过 URL 加载,比如import { bar } from “https://foo.com/bar.ts”(绝对 URL)或import { bar } from ‘./foo/bar.ts’(相对 URL)。因此,Deno 不需要一个中心化的模块储存系统,可以从任何地方加载模块。

但是,Deno 下载模块以后,依然会有一个总的目录,在本地缓存模块,因此可以离线使用。

内置功能,无需外部工具

Deno 内置了开发者需要的各种功能,不再需要外部工具。打包、格式清理、测试、安装、文档生成、linting、脚本编译成可执行文件等,都有专门命令。

Deno和Node.JS区别

  1. Deno是一个基于V8构建的安全的TypeScript的Google运行时引擎。

它建立了:

l Rust(Deno的核心是用Rust编写的,node用C++编写的)

l Tokio(以Rust编写的事件循环)

l TypeScript(Deno支持开箱即用的JavaScript和typeScript)

l V8(谷歌在Chrome和node中使用的JavaScript进行时)

  1. 它支持Type2.8开箱即用,没有package.json,没有npm 不追求兼容Node,通过URL方式引入依赖而非通过本地模块,并且在第一次运行的时候进行加载和缓存,并仅在代码使用运行,依赖才会更新。面试:其他相关内容/Deno/OS/设计模式/性能优化_第1张图片

操作系统

cpu、核、进程与线程

基础概念

  • 物理CPU
    物理CPU是相对于虚拟CPU而言的概念,指实际存在的CPU处理器,安装在PC主板或服务器上

  • 物理核
    CPU中包含的物理内核(核心)个数,比如多核CPU,单核CPU(古老的CPU)。这个多核或者单核已经集成在CPU内部了。

  • 物理核数量 = cpu数(机子上装的cpu的数量) * 每个cpu的核心数

  • 逻辑核(逻辑CPU或虚拟核)
    所谓的4核8线程,4核指的是物理核心。用Intel的超线程技术(HT)将物理核虚拟而成的逻辑处理单元,现在大部分的主机的CPU都在使用HT技术,用一个物理核模拟两个虚拟核,即每个核两个线程,总数为8线程

单核cpu和多核cpu

  • 都是一个cpu,不同的是每个cpu上的核心数。
  • 多核cpu是多个单核cpu的替代方案,多核cpu减小了体积,同时也减少了功耗。
  • 一个核心只能同时执行一个线程。

串行,并发与并行

串行

  • 多个任务,执行时一个执行完再执行另一个。
  • 比喻:吃完饭再看视频。

并发

  • 多个线程在单个核心运行,同一时间一个线程运行,系统不停切换线程,看起来像同时运行,实际上是线程不停切换。
  • 比喻: 一会跑去厨房吃饭,一会跑去客厅看视频。

并行

  • 每个线程分配给独立的核心,线程同时运行。
  • 比喻:一边吃饭一边看视频。

进程和线程的区别

开个QQ,开了一个进程;开了迅雷,开了一个进程。在QQ的这个进程里,传输文字开一个线程、传输语音开了一个线程、弹出对话框又开了一个线程。所以运行某个软件,相当于开了一个进程。在这个软件运行的过程里(在这个进程里),多个工作支撑的完成QQ的运行,那么这“多个工作”分别有一个线程。所以一个进程管着多个线程。通俗的讲:“进程是爹妈,管着众多的线程儿子”…
面试:其他相关内容/Deno/OS/设计模式/性能优化_第2张图片

进程

进程就好比一个程序运行的实例,它是资源(CPU,内存等)分配的最小单位。计算机在同一时刻执行的进程不会超过CPU核心数,多进程能够的正常运行,靠的是CPU在进程间的快速切换。

线程

线程是程序实例执行时的最小单位,是CPU调度和分派的基本单位。线程是程序的实际执行者,线程之间可以共享进程的资源。
联系:一个进程可以包含多个线程,但一个线程只能属于一个进程。

线程切换

  • cpu给线程分配时间片(也就是分配给线程的时间),执行完时间片后会切换到另一个线程。
    切换之前会保存线程的状态,下次时间片再给这个线程时才能知道当前状态。
  • 从保存线程A的状态再到切换到线程B时,重新加载线程B的状态的这个过程就叫上下文切换。而上下切换时会消耗大量的cpu时间。

区别

• 进程是资源分配的最小单位,线程是cpu调度和分派的最小单位
• 进程有独立的地址空间,启动一个进程,就会单独为其分配地址空间,这种代价相对于线程很高;线程可以共享进程的资源,不需要单独申请地址空间。因此,CPU切换一个线程比进程花费小,同时创建一个线程也比进程小很多。
• 线程间通信更方便,共享进程的全局变量等;进程通信要通过IPC通信。
• 进程更健壮,多个进程有一个挂掉,不会影响正常运行;一个进程中多个线程,其中一个挂掉,整个进程就挂了。

【区别】:

  1. 调度:线程作为调度和分配的基本单位,进程作为拥有资源的基本单位;

  2. 并发性:不仅进程之间可以并发执行,同一个进程的多个线程之间也可并发执行;

  3. 拥有资源:进程是拥有资源的一个独立单位线程不拥有系统资源但可以访问隶属于进程的资源

  4. 进程所维护的是程序所包含的资源(静态资源), 如:地址空间,打开的文件句柄集,文件系统状态,信号处理handler等;线程所维护的运行相关的资源(动态资源),如:运行栈,调度相关的控制信息,待处理的信号集等;

  5. 系统开销:在创建或撤消进程时,由于系统都要为之分配和回收资源,导致系统的开销明显大于创建或撤消线程时的开销。但是进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。

  6. 线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个进程死掉就等于所有的线程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。

【联系】:

  1. 一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程;

  2. 资源分配给进程,同一进程的所有线程共享该进程的所有资源;

  3. 处理机分给线程,即真正在处理机上运行的是线程;

  4. 线程在执行过程中,需要协作同步。不同进程的线程间要利用消息通信的办法实现同步。

做个简单的比喻:进程=火车,线程=车厢

  • 线程在进程下行进(单纯的车厢无法运行)
  • 一个进程可以包含多个线程(一辆火车可以有多个车厢)
  • 不同进程间数据很难共享(一辆火车上的乘客很难换到另外一辆火车,比如站点换乘)
  • 同一进程下不同线程间数据很易共享(A车厢换到B车厢很容易)
  • 进程要比线程消耗更多的计算机资源(采用多列火车相比多个车厢更耗资源)
  • 进程间不会相互影响,一个线程挂掉将导致整个进程挂掉(一列火车不会影响到另外一列火车,但是如果一列火车上中间的一节车厢着火了,将影响到所有车厢)
  • 进程可以拓展到多机,进程最多适合多核(不同火车可以开在多个轨道上,同一火车的车厢不能在行进的不同的轨道上)
  • 进程使用的内存地址可以上锁,即一个线程使用某些共享内存时,其他线程必须等它结束,才能使用这一块内存。(比如火车上的洗手间)-“互斥锁”
  • 进程使用的内存地址可以限定使用量(比如火车上的餐厅,最多只允许多少人进入,如果满了需要在门口等,等有人出来了才能进去)-“信号量”

协程

英文Coroutines,是一种比线程更加轻量级的存在。正如一个进程可以拥有多个线程一样,一个线程也可以拥有多个协程。
面试:其他相关内容/Deno/OS/设计模式/性能优化_第3张图片
协程的特点在于是一个线程执行,那和多线程比,协程有何优势?

  1. 极高的执行效率:因为子程序切换不是线程切换,而是由程序自身控制,因此,没有线程切换的开销,和多线程比,线程数量越多,协程的性能优势就越明显;

  2. 不需要多线程的锁机制:因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不加锁,只需要判断状态就好了,所以执行效率比多线程高很多。

设计模式

发布-订阅者/观察者模式

class写法

class Publisher{
  constructor(name){
    this.name = name
    this.subsribers = []
    this.state = 'XXXX'
  }
  // 被观察者要提供一个接受观察者的方法
  subsribe(subsriber){
  	if (this.subsribers.indexOf(subsriber) === -1) {
  		this.subsribers.push(subsriber)
  	}
  }
  
  unsubsribe(subsriber){
  	if (this.subsribers.indexOf(subsriber) !== -1) {
  		this.subsribers = this.subsribers.filter(sub => sub !== subsriber)
  	}
  }

  // 改变被观察着的状态
  publish(newState){
    this.state = newState
    this.subsribers.forEach(sub =>{
      sub.update(newState)
    })
  }
}

class Subsriber{
  constructor(name, cb){
  	this.name = name
    this.cb = cb
  }

  update(newState){
  	console.log(this.name);
    this.cb(newState)
  }
}

// 被观察者 灯
function func1(newState) {
	console.log(newState);
}
function func2(newState) {
	console.log(newState);
}
function func3(newState) {
	console.log(newState);
}
let pub = new Publisher('灯')
let sub1 = new Subsriber('func1', func1)
let sub2 = new Subsriber('func2',func2)
let sub3 = new Subsriber('func3',func3)
 
// 订阅/观察者
pub.subsribe(sub1)
pub.subsribe(sub2)
pub.unsubsribe(sub2)
pub.subsribe(sub3)

// 发布
pub.publish('hello world')

function写法

function Subscriber() {
    this.subs = []; // 存放订阅者回调函数
}

Subscriber.prototype.subscribe = function (sub) {
    if(this.subs.indexOf(sub) === -1){
        this.subs.push(sub);
    }
};

Subscriber.prototype.unsubscribe = function(sub){
    console.log("unsubscribe");
    this.subs = this.subs.filter(s => s !== sub);
};

Subscriber.prototype.publish = function () {
    console.log("publish");
    this.subs.forEach(cb => {
         cb();
    });
};

var subsriber = new Subscriber();

function func1() {
    console.log("func1");
}
function func2() {
    console.log("func2");
}
function func3() {
    console.log("func3");
}

subsriber.subscribe(func1);
subsriber.subscribe(func2);
subsriber.subscribe(func2);
subsriber.subscribe(func3);
subsriber.unsubscribe(func2);

subsriber.publish();

EventEmitter 实现

class EventEmitter {
    constructor() {
        this.events = {};
    }
    
    on(event, callback) {
        let callbacks = this.events[event] || [];
        callbacks.push(callback);
        this.events[event] = callbacks;
        return this;
    }
    
    off(event, callback) {
        let callbacks = this.events[event];
        this.events[event] = callbacks && callbacks.filter(fn => fn !== callback);
        return this;
    }
    
    emit(event, ...args) {
        let callbacks = this.events[event];
        callbacks.forEach(fn => {
            fn(...args);
        });
        return this;
    }
    
    once(event, callback) {
        let wrapFun = function (...args) {
            callback(...args);
            this.off(event, wrapFun);
        };
        this.on(event, wrapFun);
        return this;
    }
}

单例模式

工厂模式

工厂模式是编程领域一种广为人知的设计模式,它抽象了创建具体对象的过程。JS 中创建一个函数,把创建新对象、添加对象属性、返回对象的过程放到这个函数中,用户只需调用函数来生成对象而无需关注对象创建细节,这叫工厂模式:

function createPerson(name, age) {
  var person = new Object();
  person.name = name;
  person.age = age;
 
  person.greeting = function() {
    alert('Hi!');
  };
  return person;
}
 
var person1 = createPerson("leon", "20");

优缺点:

  • 优点:工厂模式解决了对象字面量创建对象代码重复问题,创建相似对象可以使用同一API。
  • 缺点:因为是调用函创建对象,无法识别对象的类型。
  • 解决办法:构造函数

装饰者模式

性能优化/工程化

阮一峰 - 网页性能管理

webpack

关于webpack的面试题

打包原理

把所有依赖打包成一个 bundle.js 文件,通过代码分割成单元片段并按需加载。

打包流程

  1. 初始化参数从配置文件和 Shell 语句中读取与合并参数,得出最终的参数。
  2. 开始编译:用上一步得到的参数初始化 Compiler 对象,加载所有配置的插件,执行对象的 run 方法开始执行编译
  3. 确定入口:根据配置中的 entry 找出所有的入口文件
  4. 编译模块:从入口文件出发,调用所有配置的 Loader 对模块进行翻译,再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理。
  5. 完成模块编译:在经过第 4 步使用 Loader 翻译完所有模块后,得到了每个模块被翻译后的最终内容以及它们之间的依赖关系
  6. 输出资源:根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 Chunk,再把每个 Chunk 转换成一个单独的文件加入到输出列表,这步是可以修改输出内容的最后机会。
  7. 输出完成:在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统

目的

用webpack优化前端性能是指优化webpack的输出结果,让打包的最终结果在浏览器运行快速高效。

  • 压缩代码:删除多余的代码、注释、简化代码的写法等等方式。可以利用webpack的UglifyJsPluginParallelUglifyPlugin来压缩JS文件, 利用cssnano(css-loader?minimize)来压缩css
  • 利用CDN加速:在构建过程中,将引用的静态资源路径修改为CDN上对应的路径。可以利用webpack对于output参数和各loader的publicPath参数来修改资源路径
  • 删除死代码(Tree Shaking):将代码中永远不会走到的片段删除掉。可以通过在启动webpack时追加参数 --optimize-minimize 来实现
  • Code Splitting: 将代码按路由维度或者组件分块(chunk),这样做到按需加载,同时可以充分利⽤浏览器缓存
  • 提取公共第三⽅库: SplitChunksPlugin插件来进⾏公共模块抽取,利⽤浏览器缓存可以⻓期缓存这些⽆需频繁变动的公共代码

1.性能优化
2.减少浏览器向服务器的请求次数
3.节约服务器的带宽资源

Webpack和grunt和gulp有啥不同

Webpack是一个模块打包器,webpack可以递归的打包项目中的所有模块(递归:指定一个入口,分析模块的依赖,它会递归的查找所有相关的依赖),最终生成几个打包后的文件,他和其他的工具的最大的不同在于它支持code-splitting(代码分割),模块化(AMD,ESM,CommonJS)开发,全局的分析工具(分析整个项目引入的模块)。
webpack是基于入口的。webpack会自动地递归解析入口所需要加载的所有资源文件,然后用不同的Loader来处理不同的文件,用Plugin来扩展webpack功能。

webpack:

webpack是一个模块打包器,强调的是一个前端模块化方案,更侧重模块打包,我们可以把开发中的所有资源都看成是模块,通过loader和plugin对资源进行处理。,然后将所有这些模块打包成⼀个或多个 bundle。

Grunt、Gulp是基于任务运⾏的⼯具:

它们会⾃动执⾏指定的任务,就像流⽔线,把资源放上去然后通过不同插件进⾏加⼯,它们包含活跃的社区,丰富的插 件,能⽅便的打造各种⼯作流。

webpack、rollup、parcel优劣

  • webpack适⽤于⼤型复杂的前端站点构建: webpack有强⼤的loader和插件⽣态,打包后的⽂件实际上就是⼀个⽴即执⾏函数,这个⽴即执⾏函数接收⼀个参数,这个参数是模块对象,键为各个模块的路径,值为模块内容。⽴即执行函数内部则处理模块之间的引⽤,执⾏模块等,这种情况更适合⽂件依赖复杂的应⽤开发.
  • rollup适⽤于基础库的打包,如vue、d3等: Rollup 就是将各个模块打包进⼀个⽂件中,并且通过 Tree-shaking 来删除⽆⽤的代码,可以最⼤程度上降低代码体积,但是rollup没有webpack如此多的的如代码分割、按需加载等⾼级功能,其更聚焦于库的打包,因此更适合库的开发.
  • parcel适⽤于简单的实验性项⽬: 他可以满⾜低⻔槛的快速看到效果,但是⽣态差、报错信息不够全⾯都是他的硬伤,除了⼀些玩具项⽬或者实验项⽬不建议使⽤

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

  • bundle:由webpack打包出来的文件
  • chunk:指webpack在进行模块依赖分析的时候,代码分割出来的代码块
  • module:开发中的单个模块,在webpack的世界,⼀切皆模块,⼀个模块对应⼀个⽂件,webpack会从配置的entry中递归开始找出所有依赖的模块

什么是loader,什么是plugin,有什么不同?

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

  1. 不同的作用
    Loader直译为"加载器"。Webpack将一切文件视为模块,但是webpack原生是只能解析js文件,如果想将其他文件也打包的话,就会用到loader。 所以Loader的作用是让webpack拥有了加载和解析非JavaScript文件的能力
    Plugin直译为"插件"。Plugin可以扩展webpack的功能,让webpack具有更多的灵活性。 在 Webpack 运行的生命周期中会广播出许多事件,Plugin 可以监听这些事件,在合适的时机通过 Webpack 提供的 API 改变输出结果。

  2. 不同的用法

  • Loader在module.rules中配置,也就是说他作为模块的解析规则而存在。 类型为数组,每一项都是一个Object,里面描述了对于什么类型的文件(test),使用什么加载器(loader)和使用的参数(options
  • Plugin在plugins中单独配置。 类型为数组,每一项是一个plugin的实例,参数都通过构造函数传入。

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

  • file-loader:把文件输出到一个文件夹中,在代码中通过相对 URL 去引用输出的文件
  • url-loader:和 file-loader 类似,但是能在文件很小的情况下以 base64 的方式把文件内容注入到代码中去(把图片转化成一个base64的字符串,然后直接放到bundle.js里,而不是单独生成一个图片文件)
  • source-map-loader:加载额外的 Source Map 文件,以方便断点调试
  • image-loader:加载并且压缩图片文件
  • babel-loader:把 ES6 转换成 ES5
  • css-loader:加载 CSS,支持模块化、压缩、文件导入等特性,会帮我们分析出几个css文件之间的关系,最终把这些css文件合并成一段css
  • style-loader:把 CSS 代码注入到 JavaScript 中,通过 DOM 操作去加载 CSS,会把‘css-loader’生成的内容挂在到页面的head部分
  • eslint-loader:通过 ESLint 检查 JavaScript 代码
  • sass-loader:会先对sass代码进行翻译,翻译为css代码后给到css-loader,都处理好了之后再交给‘style-loader’挂在到页面上
  • postcss-loader:自动添加css3的厂商前缀;

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

  • define-plugin:定义环境变量
  • commons-chunk-plugin:提取公共代码
  • uglifyjs-webpack-plugin:通过UglifyES压缩ES6代码
  • webpack-parallel-uglify-plugin: 多核压缩,提⾼压缩速度
  • webpack-bundle-analyzer: 可视化webpack输出⽂件的体积
  • mini-css-extract-plugin: CSS提取到单独的⽂件中,⽀持按需加载

是否写过webpack的Loader和Plugin?描述⼀下编写loader或plugin的思路?

Loader像⼀个"翻译官"把读到的源⽂件内容转义成新的⽂件内容,并且每个Loader通过链式操作,将源⽂件⼀步步翻译成想要的样⼦。

  • 编写Loader时要遵循单⼀原则,每个Loader只做⼀种"转义"⼯作。 每个Loader的拿到的是源⽂件内容( source ),可以通过返回值的⽅式将处理后的内容输出,也可以调⽤ this.callback() ⽅法,将内容返回给webpack。 还可以通过this.async() ⽣成⼀个 callback 函数,再⽤这个callback将处理后的内容输出出去。 此外 webpack 还为开发者准备了开发loader的⼯具函数集—— loader-utils 。

  • 相对于Loader⽽⾔,Plugin的编写就灵活了许多。 webpack在运⾏的⽣命周期中会⼴播出许多事件,Plugin 可以监听这些事件,在合适的时机通过 Webpack 提供的 API 改变输出结果。

webpack-dev-server和http服务器如nginx有什么不同

webpack-dev-server使用内存来存储webpack开发环境下的打包文件,并且可以使用模块热更新,他比传统的http服务器对开发更加简单高效

webpack的热更新是如何做到的?说明其原理?

webpack的热更新又称热替换(Hot Module Replacement),缩写为HMR。 这个机制可以做到不用刷新浏览器而将新变更的模块替换掉旧的模块。

面试:其他相关内容/Deno/OS/设计模式/性能优化_第4张图片

  1. 首先要知道server端和client端都做了处理工作第一步,在 webpack 的 watch 模式下,文件系统中某一个文件发生修改,webpack 监听到文件变化,根据配置文件对模块重新编译打包,并将打包后的代码通过简单的 JavaScript 对象保存在内存中。
  2. 第二步是 webpack-dev-server 和 webpack 之间的接口交互,而在这一步,主要是 dev-server 的中间件 webpack-dev-middleware 和 webpack 之间的交互,webpack-dev-middleware 调用 webpack 暴露的 API对代码变化进行监控,并且告诉 webpack,将代码打包到内存中。
  3. 第三步是 webpack-dev-server 对文件变化的一个监控,这一步不同于第一步,并不是监控代码变化重新打包。当我们在配置文件中配置了devServer.watchContentBase 为 true 的时候,Server 会监听这些配置文件夹中静态文件的变化,变化后会通知浏览器端对应用进行 live reload。 注意,这儿是浏览器刷新,和 HMR 是两个概念。
  4. 第四步也是 webpack-dev-server 代码的工作,该步骤主要是通过 sockjs(webpack-dev-server 的依赖)在浏览器端和服务端之间建立一个 websocket 长连接,将 webpack 编译打包的各个阶段的状态信息告知浏览器端,同时也包括第三步中 Server 监听静态文件变化的信息。浏览器端根据这些 socket 消息进行不同的操作。当然服务端传递的最主要信息还是新模块的 hash 值,后面的步骤根据这一 hash 值来进行模块热替换
  5. webpack-dev-server/client 端并不能够请求更新的代码,也不会执行热更模块操作,而把这些工作又交回给了 webpack,webpack/hot/dev-server 的工作就是根据 webpack-dev-server/client 传给它的信息以及 dev-server 的配置决定是刷新浏览器呢还是进行模块热更新。 当然如果仅仅是刷新浏览器,也就没有后面那些步骤了。
  6. HotModuleReplacement.runtime 是客户端 HMR 的中枢,它接收到上一步传递给他的新模块的 hash 值,它通过 JsonpMainTemplate.runtime 向 server 端发送 Ajax 请求,服务端返回一个 json,该 json 包含了所有要更新的模块的 hash 值,获取到更新列表后,该模块再次通过 jsonp 请求,获取到最新的模块代码。 这就是上图中 7、8、9 步骤。
  7. 而第 10 步是决定 HMR 成功与否的关键步骤,在该步骤中,HotModulePlugin 将会对新旧模块进行对比,决定是否更新模块,在决定更新模块后,检查模块之间的依赖关系,更新模块的同时更新模块间的依赖引用。
  8. 最后一步,当 HMR 失败后,回退到 live reload 操作,也就是进行浏览器刷新来获取最新打包代码。

大概流程是用webpack-dev-server启动一个服务之后,浏览器和服务端是通过websocket进行长连接,webpack内部实现的watch就会监听文件修改,只要有修改webpack就会重新打包编译到内存中,然后webpack-dev-server的依赖中间件webpack-dev-middleware和webpack之间进行交互,每次热更新都会请求一个携带hash值的json文件和一个js,websocket传递的也是hash值,内部机制通过hash值检查进行热更新。

如何提⾼webpack的打包速度/减少打包时间?

  1. 优化 Loader
    对于 Loader 来说,影响打包效率首当其冲必属 Babel 了。因为 Babel 会将代码转为字符串生成 AST,然后对 AST 继续进行转变最后再生成新的代码,项目越大,转换代码越多,效率就越低。
  • 优化 Loader 的文件搜索范围
  • 将 Babel 编译过的文件缓存起来,下次只需要编译更改过的代码文件即可,这样可以大幅度加快打包时间
loader: 'babel-loader?cacheDirectory=true'
  1. HappyPack
    受限于 Node 是单线程运行的,所以 Webpack 在打包的过程中也是单线程的,特别是在执行 Loader 的时候,长时间编译的任务很多,这样就会导致等待的情况。
    HappyPack 可以将 Loader 的同步执行转换为并行的,这样就能充分利用系统资源来加快打包效率了
plugins: [
  new HappyPack({
    id: 'happybabel',
    loaders: ['babel-loader?cacheDirectory'],
    // 开启 4 个线程
    threads: 4
  })
]
  1. DllPlugin
    DllPlugin 可以将特定的类库提前打包然后引入。这种方式可以极大的减少打包类库的次数,只有当类库更新版本才有需要重新打包,并且也实现了将公共代码抽离成单独文件的优化方案。

  2. 代码压缩
    在 Webpack3 中,我们一般使用 UglifyJS 来压缩代码,但是这个是单线程运行的,为了加快效率,我们可以使用 webpack-parallel-uglify-plugin 来并行运行 UglifyJS,从而提高效率

在 Webpack4 中,我们就不需要以上这些操作了,只需要将 mode 设置为 production 就可以默认开启以上功能。代码压缩也是我们必做的性能优化方案,当然我们不止可以压缩 JS 代码,还可以压缩 HTML、CSS 代码,并且在压缩 JS 代码的过程中,我们还可以通过配置实现比如删除 console.log 这类代码的功能。

如何提⾼webpack的构建速度?

  1. 多入口情况下,使用CommonsChunkPlugin提取公共代码
  2. 通过externals配置来提取常用库
  3. 利用DllPluginDllReferencePlugin预编译资源模块。通过DllPlugin对那些我们引用但是绝对不会修改的npm包来进行预编译,再通过DllReferencePlugin将预编译的模块加载进来。
  4. 使用Happypack 实现多线程加速编译
  5. 使用webpack-uglify-parallel来提升uglifyPlugin的压缩速度。 原理上webpack-uglify-parallel采用了多核并行压缩来提升压缩速度
  6. 使用Tree-shakingScope Hoisting剔除多余代码

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

单页应用可以理解为webpack的标准模式直接在entry中指定单页应用的入口即可,这里不再赘述

多页应用的话,可以使用webpack的 AutoWebPlugin来完成简单自动化的构建,但是前提是项目的目录结构必须遵守他预设的规范
多页应用中要注意的是:

  • **每个页面都有公共的代码,可以将这些代码抽离出来,避免重复的加载。**比如,每个页面都引用了同一套css样式表
  • 随着业务的不断扩展,页面可能会不断的追加,所以一定要让入口的配置足够灵活,避免每次添加新页面还需要修改构建配置

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

很多组件库已经提供了现成的解决方案,如Element出品的babel-plugin-component和AntDesign出品的babel-plugin-import 安装以上插件后,在.babelrc配置中或babel-loader的参数中进行设置,即可实现组件按需加载了。

通过import(*)语句来控制加载时机,webpack内置了对于import(*)的解析,会将import(*)中引入的模块作为一个新的入口,再生成一个chunk。 当代码执行到import(*)语句时,会去加载Chunk对应生成的文件import()会返回一个Promise对象,所以为了让浏览器支持,需要事先注入Promise polyfill

treeshaking

为什么需要tree shaking

主要还是为了减少页面的加载时间将无用的代码删除,减少js包的大小,从而减少用户等待的时间,使用户不因为漫长的等待而离开。
那为什么已经有了dec,还要做tree shaking呢,根据作者的意思是,由于js静态语法分析的局限性,从已有代码里去删除代码不如去寻找真正使用的代码来的好

  • tree shaking实现的原理
    其实关于tree shaking的实现原理上文多少都有提到,用一句话解释就是,找到你整个代码里真正使用的代码,打包进去,那么没用的代码自然就剔除了。
    tree shaking得以实现,是依赖es6的module模块的。是es6的模块特性,奠定了tree shaking的实现基础。
    通过 package.json 的 “sideEffects” 属性作为标记,向 compiler 提供提示,表明项目中的哪些文件是 “pure(纯的 ES2015 模块)”,由此可以安全地删除文件中未使用的部分。
    关于es6 module的特性,大概有如下几点:
  1. 必须写在最外层,不能写在函数里
  2. import的语句具有和var一样的提升(hoist)特性。
  3. ES6的模块引入是静态分析的,故而可以在编译时正确判断到底加载了什么代码。
  4. 分析程序流,判断哪些变量未被使用、引用,进而删除此代码。

具体还有哪些特性可以查一下文档。

tree shaking 首先会分析文件项目里具体哪些代码被引入了,哪些没有引入,然后将真正引入的代码打包进去,最后没有使用到的代码自然就不会存在了。

客户端渲染/服务端渲染

客户端渲染SSR(client side rendering)

SPA(single page application) 单页面应用,是前后端分离时提出的一种解决方案。
优点:页面之间切换快;减少了服务器压力;
缺点:首屏打开速度慢,不利于 SEO 搜索引擎优。

服务端渲染SSR (server side rendering)

SSR 的出现一定程度上解决了 SPA 首屏慢的问题,又极大的减少了普通 SPA 对于 SEO 的不利影响。

  • 优点:
  1. 更快的响应时间,不用等待所有 js 都下载完成,浏览器便能显示比较完整的页面;
  2. 更好的SEO,我们可以将 SEO 关键信息直接在后台就渲染成 html,从而保证搜索引擎都能爬取到关键数据。
  • 缺点:
  1. 占用更多的 CUP 和内存资源,更多的服务器端负载。;
  2. 一些常用的浏览器的 api 可能无法正常使用,服务器端和浏览器环境差异带来的问题,比如 window,document,alert等,如果使用的话需要对运行环境加以判断。

客户端渲染和服务器端渲染的最重要的区别就是究竟是谁来完成html文件的完整拼接,如果是在服务器端完成的,然后返回给客户端,就是服务器端渲染,而如果是前端做了更多的工作完成了html的拼接,则就是客户端渲染。

应用场景

比如企业级网站,主要功能是展示而没有复杂的交互,并且需要良好的SEO,则这时我们就需要使用服务器端渲染;而类似后台管理页面,交互性比较强,不需要seo的考虑,那么就可以使用客户端渲染。
另外,具体使用何种渲染方法并不是绝对的,比如现在一些网站采用了首屏服务器端渲染,即对于用户最开始打开的那个页面采用的是服务器端渲染,这样就保证了渲染速度,而其他的页面采用客户端渲染,这样就完成了前后端分离。

其他

ajax/axios区别

axios是通过promise实现对ajax技术的一种封装,就像jQuery实现ajax封装一样。
简单来说: ajax技术实现了网页的局部数据刷新,axios实现了对ajax的封装。
axios是ajax,ajax不止axios。

Axios 是一个基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中。
特点:

  1. 从浏览器中创建 XMLHttpRequests
  2. 从 node.js 创建 http 请求
  3. 支持 Promise API
  4. 拦截请求和响应
  5. 转换请求数据和响应数据
  6. 取消请求
  7. 自动转换 JSON 数据
  8. 客户端支持防御 XSRF

AJAX
AJAX 是与服务器交换数据并更新部分网页的,在不重新加载整个页面的情况下。
Ajax = 异步 JavaScript 和 XML(标准通用标记语言的子集)。

  • 优缺点:
  1. ajax:
  • 本身是针对MVC的编程,不符合现在前端MVVM的浪潮
  • 基于原生的XHR开发,XHR本身的架构不清晰,已经有了fetch的替代方案
  1. axios:
  • 从 node.js 创建 http 请求
  • 支持 Promise API
  • 客户端支持防止CSRF
  • 提供了一些并发请求的接口(重要,方便了很多的操作)

axios拦截器

  • 定义一个拦截器类
// 拦截器
class Interceptor {
	constructor() {
	  this.handlers = [];// 保存拦截器传入的回调
	}
	use(resolveHandler, rejectHandler) {
	  this.handlers.push({
	    resolveHandler,
	    rejectHandler
	  })
	}
}
export default Interceptor
  • 在请求前和返回结果后分别触发请求拦截和响应拦截,使用promise实现传值
/* this代表axios实例指向 */
let promise = Promise.resolve(configs);
// 触发请求拦截器内的所有回调
this.interceptors.request.handlers.forEach(handler => {
	promise = promise.then(handler.resolveHandler, handler.rejectHandler);
})
// 发请求
promise = promise.then(this.dispatch, undefined);
// 触发响应拦截器所有回调
this.interceptors.response.handlers.forEach(handler => {
	promise = promise.then(handler.resolveHandler, handler.rejectHandler);
})

模块化

使用模块化可以给我们带来以下好处

  • 解决命名冲突
  • 提供复用性
  • 提高代码可维护性

立即执行函数

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

AMD/CMD

CommonJS

CommonJS 最早是 Node 在使用,目前也仍然广泛使用,比如在 Webpack 中你就能见到它,当然目前在 Node 中的模块管理已经和 CommonJS 有一些区别了。
exports 和 module.exports 用法相似,但是不能对 exports 直接赋值。因为 var exports = module.exports 这句代码表明了 exports 和 module.exports 享有相同地址,通过改变对象的属性值会对两者都起效,但是如果直接对 exports 赋值就会导致两者不再指向同一个内存地址,修改并不会对 module.exports 起效。

ES Module

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

  1. CommonJS 支持动态导入,也就是 require(${path}/xx.js),后者目前不支持,但是已有提案
  2. CommonJS 是同步导入,因为用于服务端,文件都在本地,同步导入即使卡住主线程影响也不大。而后者是异步导入,因为用于浏览器,需要下载文件,如果也采用同步导入会对渲染有很大影响
  3. CommonJS 在导出时都是值拷贝,就算导出的值变了,导入的值也不会改变,所以如果想更新值,必须重新导入一次。但是 ES Module 采用实时绑定的方式,导入导出的值都指向同一个内存地址,所以导入值会跟随导出值变化
  4. ES Module 会编译成 require/exports 来执行

ES6 Module和CommonJS模块的区别

  • CommonJS是对模块的浅拷⻉,ES6 Module是对模块的引⽤,即ES6 Module只存只读,不能改变其值,具体点就是指针指向不能变,类似const
  • import的接⼝是read-only(只读状态),不能修改其变量值。 即不能修改其变量的指针指向,但可以改变变量内部指针指向,可以对commonJS对重新赋值(改变指针指向),但是对ES6 Module赋值会编译报错。

ES6 Module和CommonJS模块的共同点:

  • CommonJS和ES6 Module都可以对引⼊的对象进⾏赋值,即对对象内部属性的值进⾏改变。

前端模块与组件的区别

面试:其他相关内容/Deno/OS/设计模式/性能优化_第5张图片

  • 组件:最初的目的是代码重用,功能相对单一或者独立。在整个系统的代码层次上位于最底层,被其他代码所依赖,所以说组件化是纵向分层。
  • 模块:最初的目的是将同一类型的代码整合在一起,所以模块的功能相对复杂,但都同属于一个业务。不同模块之间也会存在依赖关系,但大部分都是业务性的互相跳转,从地位上来说它们都是平级的。

JS 面向对象编程

对象字面量/为Object实例添加属性方法

  • 优点:代码简单
  • 缺点: 创建多个对象会产生大量的代码,编写麻烦,且并没有实例与原型的概念。
  • 解决办法:工厂模式。

工厂模式

优缺点

  • 优点:工厂模式解决了对象字面量创建对象代码重复问题,创建相似对象可以使用同一API。
  • 缺点:因为是调用函创建对象,无法识别对象的类型。
  • 解决办法:构造函数

构造函数

优缺点

  • 优点:解决了类似对象创建问题,且可以检测对象类型。
  • 缺点:构造函数方法要在每个实例上新建一次。
  • 解决办法:原型模式。

原型模式

优缺点

  • 优点:与单纯使用构造函数不一样,原型对象中的方法不会在实例中重新创建一次,节约内存。
  • 缺点:使用空构造函数,实例 person1 和 person2 的 name都一样了,我们显然不希望所有实例属性方法都一样,它们还是要有自己独有的属性方法。并且如果原型中对象中有引用类型值,实例中获得的都是该值的引用,意味着一个实例修改了这个值,其他实例中的值都会相应改变。
  • 解决办法:构造函数+原型模式组合使用。

构造函数+原型模式

function Person(name, age) {
    this.name = name;
    this.age = age;
}

Person.prototype = {
    constructor: Person,
    nationality: "China",
    showinfo: function () {
        console.info(this.name);
    }
}

export default Person;

上面代码中用对象字面量的形式重写了原型对象,这样相当于创建了一个新的对象,那么它的constructor属性就会指向Object,这里为了让它继续指向构造函数,显式的写上了constructor: Person。

这种构造函数与原型模式混成的模式,是目前在 JS 中使用最为广泛的一种创建对象的方法。

class(ES6)

class 相对 function 是后出来的,既然 class 出来了,显然是为了解决 function 在处理面向对象设计中的缺陷而来。

class 作为 ES6 中的重大升级之一,有哪些优点呢?

  1. class 写法更加简洁、含义更加明确、代码结构更加清晰。
  2. class 尽管也是函数,却无法直接调用(不存在防御性代码了)。
  3. class 不存在变量提升。
  4. class 不会污染 window 等全局变量(这点很赞啊)。
  5. class 函数体中的代码始终以严格模式执行(新手写的代码可靠性也必须上来了)
  6. 可直接使用 set 和 get 函数。这比 function 要好用多了。
  7. class 内部方法中若涉及到 this,则一定要注意。class 中的 this 永远都不会指向 window。
  8. class 可以从 javascript 中著名的几大类中进行继承:Array、number、string….,显然 function 是做不到的。
  9. class 中有一个对象super,这个对象可以取到父类的方法、构造函数等。
  10. class 中不存在实例方法,class 中定义所有方法都是原型方法。这些方法也都是不可枚举的,使用 for in 这种方式无法遍历到它们。

场景题

一个父元素中不断有div被加入,如何给这些div绑定事件

实现一个搜索框

要实现一个搜索框该怎么做,讨论了一下,首先是防抖\节流,然后是异步请求页面数据显示的问题,面试官提示可能后一个请求会先于前面的请求返回
然后写的时候想复杂了,卡了好一会儿,在面试官的指点下定了一个简单的方案,就是根据当前输入框中的数据与请求结果进行匹配,只显示当前输入框中的返回数据,其余返回结果都抛弃或是中断正在进行的请求等

高效查找数据

后端收到前端的这个搜索词条后,要进行模糊查找,不过现在有100W条数据(总之就是很多),要怎样高效的查找呢?然后这些查找到的数据假设也有很多,但是前端只需要显示那么几条数据,该怎么处理呢?

无限下拉

setTimeout

  • setTimeout的执行时间并不是确定的。在JS中,setTimeout任务被放进事件队列中,只有主线程执行完才会去检查事件队列中的任务是否需要执行,因此setTimeout的实际执行时间可能会比其设定的时间晚一些
  • 刷新频率受屏幕分辨率和屏幕尺寸的影响,因此不同设备的刷新频率可能会不同,而setTimeout只能设置一个固定时间间隔,这个时间不一定和屏幕的刷新时间相同

以上两种情况都会导致setTimeout的执行步调和屏幕的刷新步调不一致。
在setTimeout中对dom进行操作,必须要等到屏幕下次绘制时才能更新到屏幕上,如果两者步调不一致,就可能导致中间某一帧的操作被跨越过去,而直接更新下一帧的元素,从而导致丢帧现象

requestAnimationFrame

与setTimeout相比,requestAnimationFrame最大的优势是由系统来决定回调函数的执行时机

如果屏幕刷新率是60Hz,那么回调函数就每16.7ms被执行一次,如果刷新率是75Hz,那么这个时间间隔就变成了1000/75=13.3ms,换句话说就是,requestAnimationFrame的步伐跟着系统的刷新步伐走。它能保证回调函数在屏幕每一次的刷新间隔中只被执行一次,这样就不会引起丢帧现象

DocumentFragment

DocumentFragment,文档片段接口,表示一个没有父级文件的最小文档对象。它被作为一个轻量版的Document使用,用于存储已排好版的或尚未打理好格式的XML片段。最大的区别是因为DocumentFragment不是真实DOM树的一部分,它的变化不会触发DOM树的(重新渲染) ,且不会导致性能等问题。
可以使用document.createDocumentFragment方法或者构造函数来创建一个空的DocumentFragment

从MDN的说明中,我们得知DocumentFragments是DOM节点,但并不是DOM树的一部分,可以认为是存在内存中的,所以将子元素插入到文档片段时不会引起页面回流。
当append元素到document中时,被append进去的元素的样式表的计算是同步发生的,此时调用 getComputedStyle 可以得到样式的计算值
而append元素到documentFragment 中时,是不会计算元素的样式表,所以documentFragment 性能更优。

分页

工作中,我们经常会遇到列表项。如果列表项的数量比较多,很多情况下我们会采用分页加载的方式,来避免一次性加载大量的数据,造成页面的性能问题。
  但是用户在分页加载浏览了大量数据之后,列表项也会逐渐增多,此时页面可能会存在卡顿的情况。

虚拟列表

虚拟列表是按需显示的一种技术,可以根据用户的滚动,不必渲染所有列表项,而只是渲染可视区域内的一部分列表元素的技术

面试:其他相关内容/Deno/OS/设计模式/性能优化_第6张图片

用数组保存所有列表元素的位置,只渲染可视区内的列表元素,当可视区滚动时,根据滚动的offset大小以及所有列表元素的位置,计算在可视区应该渲染哪些元素。

  1. react-tiny-virtual-list
  2. react-virtualized

其他

前端新趋势

ES2020

  • 新增可选链运算符?. :能够在属性访问、方法调用前检查其是否存在;
  • 新增空值合并操作符?? :用来提供默认值,说明上下文是 null 或 undefined;
  • 新增 BigInt 基础数值类型:可以表示绝对值大于 2^53-1 的整数而无精度损失;

Vue3/vite

低代码

serverless

前端为什么要关注 Serverless? - 阿里巴巴淘系技术的回答 - 知乎
https://www.zhihu.com/question/378776917/answer/1326674674

Serverless 基本概念入门 - Serverless的文章 - 知乎
https://zhuanlan.zhihu.com/p/78250791

WebAssembly

webpack5.0

微前端(架构及发展趋势)

服务端渲染Server Side Render(SSR)

对前后端分离的理解

前端还没有工程化、模块化和可复用化的思维;
高内聚,低耦合;
工程化考量、项目管理的问题;
前端开发组件化/工程化框架;遵循一套体系来进行约束性开发;
包管理器:npm/yarn

RESTful API

用URL名词定位资源,用HTTP动词(GET,POST,DELETE,PUT)描述操作。

怎样用通俗的语言解释REST,以及RESTful? - 覃超的回答 - 知乎
https://www.zhihu.com/question/28557115/answer/48094438

看Url就知道要什么
看http method就知道干什么
看http status code就知道结果如何

前端性能优化相关

回流重回、缓存、网络协议相关、怎么减少请求次数等等

  1. 资源压缩与合并:减少文件体积,提高加载速度

  2. 减少 http 请求

  3. 利用浏览器缓存:提升二次访问的速度

  4. 使用 CDN

利用浏览器缓存也仅仅只能提高二次访问的速度,对于首次访问的加速,可以从网络层面进行优化,最常见的手段就是 CDN(Content Delivery Network,内容分发网络)加速。

通过将静态资源缓存到离用户很近的相同网络运营商的 CDN 节点上,不但能提升用户的访问速度,还能节省服务器的带宽消耗。

CDN 是怎么做到加速的呢?

首先 CDN 服务商会在全国各个网站部署计算节点,CDN 加速将网站的内容缓存在网络边缘,不同地区的用户就会访问到离自己最近的相同网络线路上的 CDN 节点。当请求到达 CDN 节点之后,节点会判断自己的缓存内容是否有效,如果有效,就会立即响应缓存的内容给用户,从而加快响应速度。如果CDN节点的缓存失效,它会根据服务器的配置去我们的内容源服务器获取最先的资源响应给用户,并将内容缓存下来以便响应后续访问的用户。

因此,一个地区只要有一个用户先加载资源,在 CDN 中建立了缓存,该地区其他用户访问相同的资源的时候就可以使用缓存了。

  1. 将 CSS 文件放在头部, JS 文件放在尾部

CSS文件放在头部的原因是为了让用户第一时间看到的页面是有样式的。

另外,JS 文件也不是不可以放在头部,只要给script标签加上 defer 属性就可以异步加载,延迟执行了。

  1. 图片懒加载

  2. 减少重绘和重排

  3. js style动画变成CSS transform动画
    js动画为什么会有性能问题

那你知道为什么修改style会导致浏览器性能问题

怎么评价页面性能

小程序的原理

webAPP的适配

前端四大存储方式

cookie、localStorage、sessionStroage、indexDB

Restful接口规范

MVC/MVP/MVVM

MVC、MVP、MVVM模式的概念与区别

MVC

MVC全名是Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写,一种软件设计典范,是最常见的软件架构之一,业界有着广泛应用。用一种业务逻辑、数据、界面显示分离的方法组织代码,将业务逻辑聚集到一个部件里面,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑。MVC被独特的发展起来用于映射传统的输入、处理和输出功能在一个逻辑的图形化用户界面的结构中。


Controller负责将Model的数据用View显示出来,换句话说就是在Controller里面把Model的数据赋值给View。

  • 视图(View):用户界面(HTML)
  • 控制器(Controller):业务逻辑
  • 模型(Model):数据保存(数据库)
    各部分之间的通信方式如下。
    面试:其他相关内容/Deno/OS/设计模式/性能优化_第7张图片
  1. View 传送指令到 Controller
  2. Controller完成业务逻辑后,要求 Model 改变状态
  3. Model 将新的数据发送到View,用户得到反馈

1)最上面的一层,是直接面向最终用户的"视图层"(View)。它是提供给用户的操作界面,是程序的外壳。
2)最底下的一层,是核心的"数据层"(Model),也就是程序需要操作的数据或信息。
3)中间的一层,就是"控制层"(Controller),它负责根据用户从"视图层"输入的指令,选取"数据层"中的数据,然后对其进行相应的操作,产生最终结果。

这三层是紧密联系在一起的,但又是互相独立的,每一层内部的变化不影响其他层。每一层都对外提供接口(Interface),供上面一层调用。这样一来,软件就可以实现模块化,修改外观或者变更数据都不用修改其他层,大大方便了维护和升级。

MVP

MVP 模式将 Controller 改名为 Presenter,同时改变了通信方向。

面试:其他相关内容/Deno/OS/设计模式/性能优化_第8张图片

  1. 各部分之间的通信,都是双向的。
  2. View 与 Model 不发生联系,都通过 Presenter 传递。
  3. View 非常薄,不部署任何业务逻辑,称为"被动视图"(Passive View),即没有任何主动性,而 Presenter非常厚,所有逻辑都部署在那里。

MVVM

MVVM 模式将 Presenter 改名为 ViewModel,基本上与 MVP 模式完全一致。
面试:其他相关内容/Deno/OS/设计模式/性能优化_第9张图片
唯一的区别是,它采用双向绑定(data-binding):View的变动,自动反映在 ViewModel,反之亦然。Angular 和 Ember 都采用这种模式。

MVC,MVP 和 MVVM 的图示

mvc和mvvm的区别和应用场景

响应式,双向数据绑定:
1.修改View层,Model对应数据发生变化。

2.Model数据变化,不需要查找DOM,直接更新View。

MVVM的实现方式

MVVM的实现原理

(1)发布者-订阅者模式: 一般通过sub, pub的方式实现数据和视图的绑定监听,更新数据方式通常做法是 vm.set(‘property’, value)。

(2)脏值检查: angular.js 是通过脏值检测的方式比对数据是否有变更,来决定是否更新视图,在指定的事件触发时进入脏值检测。

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

面试:其他相关内容/Deno/OS/设计模式/性能优化_第10张图片

  • 实现一个数据监听器Observer,能够对数据对象的所有属性进行监听,如有变动可拿到最新值并通知订阅者(Dep)
  • 实现一个指令解析器Compiler,对每个元素节点的指令进行扫描和解析根据指令模板替换数据,以及绑定相应的更新函数
  • 实现一个Watcher,作为连接Observer和Compiler的桥梁,能够订阅并收到每个属性变动的通知,执行指令绑定的相应回调函数,从而更新视图
  • mvvm入口函数,整合以上三者

理解VUE双向数据绑定原理和实现

Vue中的MVVM

面试:其他相关内容/Deno/OS/设计模式/性能优化_第11张图片
看完这篇关于MVVM的文章,面试通过率提升了80%

Object.defineProperty()
Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。

CDN

这就是CDN回源原理和CDN多级缓存啊!

CDN的核心功能特写
CDN 的核心点有两个,一个是缓存,一个是回源。

这两个概念都非常好理解。对标到上面描述的过程,“缓存”就是说我们把资源 copy 一份到 CDN 服务器上这个过程,“回源”就是说 CDN 发现自己没有这个资源(一般是缓存的数据过期了),转头向根服务器(或者它的上层服务器)去要这个资源的过程。
回源是指浏览器在发送请求报文时,响应该请求报文的是源站点的服务器,而不是各节点上的缓存服务器(比如nginx开启缓存),那么这个过程相对于通过各节点上的缓存服务器来响应的话就称作为回源。回源的请求或流量太多的话,有可能会让源站点的服务器承载着过大的访问压力,进而影响服务的正常访问。

  • 回源比分为回源请求数比例及回源流量比例两种:
  1. 回源请求数比
    统计数据来自所有边缘节点上的请求记录,其中,对于没有缓存或缓存过期(可缓存)的请求以及不可缓存的请求,均计入回源请求中,其他直接命中缓存的,则为命中请求。

  2. 回源流量比
    回源流量是回源请求文件大小产生的流量和请求本身产生的流量 回源流量比=回源流量/回源流量+用户请求访问的流量

缓存

CDN是什么?使用CDN有什么优势? - 阿里巴巴淘系技术的回答 - 知乎
https://www.zhihu.com/question/36514327/answer/1604554133

(1)CDN的加速资源是跟域名绑定的。
(2)通过域名访问资源,首先是通过DNS分查找离用户最近的CDN节点(边缘服务器)的IP
(3)通过IP访问实际资源时,如果CDN上并没有缓存资源,则会到源站请求资源,并缓存到CDN节点上,这样,用户下一次访问时,该CDN节点就会有对应资源的缓存了。
在这里插入图片描述

CDN是什么?使用CDN有什么优势? - 阿里云云栖号的回答 - 知乎
https://www.zhihu.com/question/36514327/answer/193768864

你可能感兴趣的:(基础知识,面试,前端)