前端面试总结三

本文详细探讨了Vue.js的核心特性和在前端面试中的重要地位,包括其轻量级、数据绑定、指令系统和插件化的特性。同时,讲解了Webpack的工作原理和在构建模块化项目中的作用,以及在前端项目中如何提高构建速度的各种策略。此外,文章还对比了SGML、XML、HTML的关系,介绍了CDN的运作机制和避免FOUC的方法,并深入讨论了JavaScript中的Null和Undefined的区别。最后,文章阐述了Event Loop机制以及在处理大量数据时的优化策略,以及Promise和Async/Await在异步编程中的应用,最后讲解了基于HTML5 Canvas的图片裁剪和马赛克功能的实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.深入理解Vue
Vue.js是什么?
Vue.js不是一个框架——它只聚焦视图层,是一个构建数据驱动的web界面的库。
特性:
1)轻量
体积小,不依赖其他基础库;
2) 数据绑定
3)指令
类似AngularJS 可以用一些内置的简单指令(v-*),也可以自定义指令,通过对应表达式的值的变化就可以修改对应的Dom;
4)插件化
Vue的核心不包含Router,Ajax表单验证,但可方便地加载对应的插件。

Vue是一套构建用户界面的渐进式框架,也可以理解为是一个视图模板引擎,强调的是状态到界面的映射。倘若用一句话来概括vue,那么我首先想到的便是官方文档中的一句话: Vue.js(读音 /vjuː/,类似于 view) 是一套构建用户界面的渐进式框架。
在具有响应式系统的Vue实例中,DOM状态只是数据状态的一个映射,即 UI=VM(State) ,当等式右边State改变了,页面展示部分UI就会发生相应改变。很多人初次上手Vue时,觉得很好用,原因就是这个。不过,Vue的核心定位并不是一个框架,设计上也没有完全遵循MVVM模式,可以看到在图中只有State和View两部分, Vue的核心功能强调的是状态到界面的映射,对于代码的结构组织并不重视, 所以单纯只使用其核心功能时,它并不是一个框架,而更像一个视图模板引擎,这也是为什么Vue开发者把其命名成读音类似于view的原因。
Vue的核心的功能,是一个视图模板引擎,但这不是说Vue就不能成为一个框架。如下图所示,这里包含了Vue的所有部件,在声明式渲染(视图模板引擎)的基础上,我们可以通过添加组件系统、客户端路由、大规模状态管理来构建一个完整的框架。更重要的是,这些功能相互独立,你可以在核心功能的基础上任意选用其他的部件,不一定要全部整合在一起。可以看到,所说的“渐进式”,其实就是Vue的使用方式,同时也体现了Vue的设计的理念
在这里插入图片描述
Vue.js 自身不是一个全能框架——它只聚焦于视图层。因此它非常容易学习,非常容易与其它库或已有项目整合。另一方面,在与相关工具和支持库一起使用时,Vue.js 也能完美地驱动复杂的单页应用。

渐进式框架
每个框架都不可避免会有自己的一些特点,从而会对使用者有一定的要求,这些要求就是主张,主张有强有弱,它的强势程度会影响在业务开发中的使用方式。渐进式代表的含义是:主张最少。
比如说,Angular,它两个版本都是强主张的,如果你用它,必须接受以下东西:- 必须使用它的模块机制- 必须使用它的依赖注入- 必须使用它的特殊形式定义组件(这一点每个视图框架都有,难以避免)所以Angular是带有比较强的排它性的,如果你的应用不是从头开始,而是要不断考虑是否跟其他东西集成,这些主张会带来一些困扰。
比如React,它也有一定程度的主张,它的主张主要是函数式编程的理念,比如说,你需要知道什么是副作用,什么是纯函数,如何隔离副作用。它的侵入性看似没有Angular那么强,主要因为它是软性侵入。
Vue可能有些方面是不如React,不如Angular,但它是渐进的,没有强主张,你可以在原有大系统的上面,把一两个组件改用它实现,当jQuery用;也可以整个用它全家桶开发,当Angular用;还可以用它的视图,搭配你自己设计的整个下层用。你可以在底层数据逻辑的地方用OO和设计模式的那套理念,也可以函数式,都可以,它只是个轻量视图而已,只做了自己该做的事,没有做不该做的事,仅此而已。渐进式的含义,我的理解是:没有多做职责之外的事。

声明式渲染

<div id=app{
    
    {
    
     message }}input v-model=inputValue/div>var app = new Vue({el: '#app',data: {message: 'Hello Vue!',inputValue}})

vue的这种声明式渲染,也算是响应式的核心原理是使用了js的一个函数 defineProperty ,这个函数可以将对象进行监控,setter和getter都可以进行其他的操作。就是进门出门要登记,这种中间就可以进行其他操作,比如对视图进行刷新,通知对应的watcher等等。
命令式渲染 : 命令我们的程序去做什么,程序就会跟着你的命令去一步一步执行。
声明式渲染 : 我们只需要告诉程序我们想要什么效果,其他的交给程序来做。

自底向上的开发模式
我的理解就是比如我们先写一个基础的页面,把基础的东西写好,再逐一去添加功能和效果,由简单到繁琐的这么一个过程。

组件化应用构建
组件化应用就是把一个应用或者页面进行拆分,各个部分处理各个部分的事情就可以了。

2.什么是webpack
webpack是一个打包模块化javascript的工具,在webpack里一切文件皆模块,通过loader转换文件,通过plugin注入钩子,最后输出由多个模块组合成的文件,webpack专注构建模块化项目。
WebPack可以看做是模块打包机:它做的事情是,分析你的项目结构,找到JavaScript模块以及其它的一些浏览器不能直接运行的拓展语言(Scss,TypeScript等),并将其打包为合适的格式以供浏览器使用。
在这里插入图片描述
几个常见的loader:

  • file-loader:把文件输出到一个文件夹中,在代码中通过相对 URL 去引用输出的文件;
  • url-loader:和 file-loader 类似,但是能在文件很小的情况下以 base64 的方式把文件内容注入到代码中去;
  • source-map-loader:加载额外的 Source Map 文件,以方便断点调试;
  • image-loader:加载并且压缩图片文件;
  • babel-loader:把 ES6 转换成 ES5;
  • css-loader:加载 CSS,支持模块化、压缩、文件导入等特性;
  • style-loader:把 CSS 代码注入到 JavaScript 中,通过 DOM 操作去加载 CSS;
  • eslint-loader:通过 ESLint 检查 JavaScript 代码。

几个常见的plugin:

  • define-plugin:定义环境变量;
  • terser-webpack-plugin:通过TerserPlugin压缩ES6代码;
  • html-webpack-plugin 为html文件中引入的外部资源,可以生成创建html入口文件;
  • mini-css-extract-plugin:分离css文件;
  • clean-webpack-plugin:删除打包文件;
  • happypack:实现多线程加速编译。

webpack与grunt、gulp的不同?:
Webpack与Gulp、Grunt没有什么可比性,它可以看作模块打包机,通过分析你的项目结构,找到JavaScript模块以及其它的一些浏览器不能直接运行的拓展语言(Scss,TypeScript等),并将其转换和打包为合适的格式供浏览器使用。Gulp/Grunt是一种能够优化前端的开发流程的工具,而WebPack是一种模块化的解决方案,不过Webpack的优点使得Webpack在很多场景下可以替代Gulp/Grunt类的工具。

他们的工作方式也有较大区别

Grunt和Gulp的工作方式是:在一个配置文件中,指明对某些文件进行类似编译,组合,压缩等任务的具体步骤,工具之后可以自动替你完成这些任务。

Webpack的工作方式是:把你的项目当做一个整体,通过一个给定的主文件(如:index.js),Webpack将从这个文件开始找到你的项目的所有依赖文件,使用loaders处理它们,最后打包为一个(或多个)浏览器可识别的JavaScript文件。
三者都是前端构建工具,grunt和gulp在早期比较流行,现在webpack相对来说比较主流,不过一些轻量化的任务还是会用gulp来处理,比如单独打包CSS文件等。

grunt和gulp是基于任务和流(Task、Stream)的。类似jQuery,找到一个(或一类)文件,对其做一系列链式操作,更新流上的数据, 整条链式操作构成了一个任务,多个任务就构成了整个web的构建流程。

webpack是基于入口的。webpack会自动地递归解析入口所需要加载的所有资源文件,然后用不同的Loader来处理不同的文件,用Plugin来扩展webpack功能。

所以总结一下:

从构建思路来说
gulp和grunt需要开发者将整个前端构建过程拆分成多个Task,并合理控制所有Task的调用关系
webpack需要开发者找到入口,并需要清楚对于不同的资源应该使用什么Loader做何种解析和加工
对于知识背景来说
gulp更像后端开发者的思路,需要对于整个流程了如指掌 webpack更倾向于前端开发者的思路

webpack有哪些优点:

  • 专注于处理模块化的项目,能做到开箱即用,一步到位;
  • 可通过plugin扩展,完整好用又不失灵活;
  • 使用场景不局限于web开发;
  • 社区庞大活跃,经常引入紧跟时代发展的新特性,能为大多数场景找到已有的开源扩展;
  • 良好的开发体验。

webpack的缺点:
webpack的缺点是只能用于采用模块化开发的项目。

分别介绍bundle,chunk,module是什么:

  • bundle:是由webpack打包出来的文件;
  • chunk:代码块,一个chunk由多个模块组合而成,用于代码的合并和分割;
  • module:是开发中的单个模块,在webpack的世界,一切皆模块,一个模块对应一个文件,webpack会从配置的entry中递归开始找出所有依赖的模块。

分别介绍什么是loader?什么是plugin?:
loader:模块转换器,用于将模块的原内容按照需要转成你想要的内容
plugin:在webpack构建流程中的特定时机注入扩展逻辑,来改变构建结果,是用来自定义webpack打包过程的方式,一个插件是含有apply方法的一个对象,通过这个方法可以参与到整个webpack打包的各个流程(生命周期)。

模块热更新:
模块热更新是webpack的一个功能,他可以使得代码修改过后不用刷新浏览器就可以更新,是高级版的自动刷新浏览器。
devServer中通过hot属性可以控制模块的热替换
1,通过配置文件

const webpack = require('webpack');
const path = require('path');
let env = process.env.NODE_ENV == "development" ? "development" : "production";
const config = {
   
   
  mode: env,
 devServer: {
   
   
     hot:true
 }
}
  plugins: [
     new webpack.HotModuleReplacementPlugin(), //热加载插件
  ],
module.exports = config;

2,通过命令行

 "scripts": {
   
   
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "NODE_ENV=development  webpack-dev-server --config  webpack.develop.config.js --hot",
  },

通过webpack处理长缓存:
浏览器在用户访问页面的时候,为了加快加载速度,会对用户访问的静态资源进行存储,但是每一次代码升级或是更新,都需要浏览器去下载新的代码,最方便和简单的更新方式就是引入新的文件名称。在webpack中可以在output纵输出的文件指定chunkhash,并且分离经常更新的代码和框架代码。通过NameModulesPlugin或是HashedModuleIdsPlugin使再次打包文件名不变。

如何提高webpack的构建速度:
(1)通过externals配置来提取常用库;
(2)利用DllPlugin和DllReferencePlugin预编译资源模块 通过DllPlugin来对那些我们引用但是绝对不会修改的npm包来进行预编译,再通过DllReferencePlugin将预编译的模块加载进来;
(3)使用Happypack 实现多线程加速编译;

要注意的第一点是,它对file-loader和url-loader支持不好,所以这两个loader就不需要换成happypack了,其他loader可以类似地换一下

(4)使用Tree-shaking和Scope Hoisting来剔除多余代码;
(5)使用fast-sass-loader代替sass-loader;
(6)babel-loader开启缓存;
babel-loader在执行的时候,可能会产生一些运行期间重复的公共文件,造成代码体积大冗余,同时也会减慢编译效率
可以加上cacheDirectory参数或使用 transform-runtime 插件试试

// webpack.config.js
use: [{
   
   
    loader: 'babel-loader',
    options: {
   
   
        cacheDirectory: true
}]
// .bablerc
{
   
   
    "presets": [
        "env",
        "react"
    ],
    "plugins": ["transform-runtime"]
}

(7)不需要打包编译的插件库换成全局"script"标签引入的方式;
比如jQuery插件,react, react-dom等,代码量是很多的,打包起来可能会很耗时
可以直接用标签引入,然后在webpack配置里使用 expose-loader 或 externals 或 ProvidePlugin 提供给模块内部使用相应的变量

// @1
use: [{
   
   
    loader: 'expose-loader',
    options: '$'
    }, {
   
   
    loader: 'expose-loader',
    options: 'jQuery'
    }]
// @2
externals: {
   
   
     jquery: 'jQuery'
    },
// @3
new webpack.ProvidePlugin({
   
   
   $: 'jquery',
   jQuery: 'jquery',
   'window.jQuery': 'jquery'
 }),

(8)优化构建时的搜索路径。
在webpack打包时,会有各种各样的路径要去查询搜索,我们可以加上一些配置,让它搜索地更快
比如说,方便改成绝对路径的模块路径就改一下,以纯模块名来引入的可以加上一些目录路径
还可以善于用下resolve alias别名 这个字段来配置
还有exclude等的配置,避免多余查找的文件,比如使用babel别忘了剔除不需要遍历的

3.SGML、XML、HTML之间的区别与联系
SGML是指“标准通用标记语言”(Standard Generalized Markup Language), 是1986年出版发布的一个信息管理方面的国际标准(ISO 8879),它是国际上定义电子文件结构和内容描述的标准,是一种非常复杂的文档的结构,主要用于大量高度结构化数据的防卫区和其他各种工业领域,利于分类和索引。
SGML规定了在文档中嵌入描述标记的标准格式,指定了描述文档结构的标准方法,目前在WEB上使用的HTML格式便是使用固定标签集的一种SGML文档。由于SGML可以支持无数的文档结构类型,并且可以创建与特定的软硬件无关的文档,因此很容易与使用不同计算机系统的用户交换文档。同XML相比,定义的功能很强大,缺点是它不适用于Web数据描述,而且SGML软件价格非常价格昂贵。

HTML相信大家都比较熟悉,即“HyperText Markup Language” (超文本标识语言),它的优点是比较适合web 页面的开发。但它有一个缺点是标记相对少,只有固定的标记集如<p>.<strong>等。缺少SGML的柔性和适应性。不能支持特定领域的标记语言,如对数学、化学、音乐等领域的表示支持较少。

所谓的XML(eXtensible Markup Language), 翻译成中文就是“可扩展标识语言”,在国内很多人理解XML为HTML的简单扩展,这实际上是一种误解。尽管XML同HTML关系非常密切。它由万维网协会(W3C)创建,用来克服 HTML的局限。和 HTML 一样,XML 基于 SGML。XML 是为 Web 设计的。XML实际上是Web上表示结构化信息的一种标准文本格式,它没有复杂的语法和包罗万象的数据定义。

SGML是一种在Web发明之前就早已存在的用标记来描述文档资料的通用语言。但SGML十分庞大且难于学习和使用。鉴于此,人们提出了HTML语言。但近年来,随着Web应用的不断深入,HTML在需求广泛的应用中已显得捉襟见肘,有人建议直接使用SGML作为Web语言。但SGML太庞大了,学用两难尚且不说,就是全面实现SGML的浏览器也非常困难。于是Web标准化组织W3C建议使用一种精简的SGML版本——XML。XML与SGML一样,是一个用来定义其他语言的元语言。与SGML相比,XML规范不到SGML规范的1/10,简单易懂,是一门既无标签集也无语法的新一代标记语言。

4.CDN托管
大型Web应用对速度的追求并没有止步于仅仅利用浏览器缓存,因为浏览器缓存始终只是为了提升二次访问的速度,对于首次访问的加速,我们需要从网络层面进行优化,最常见的手段就是CDN(Content Delivery Network,内容分发网络)加速。通过将静态资源缓存到离用户很近的相同网络运营商的CDN节点上,不但能提升用户的访问速度,还能节省服务器的带宽消耗,降低负载。
不同地区的用户会访问到离自己最近的相同网络线路上的CDN节点,当请求到达CDN节点后,节点会判断自己的内容缓存是否有效,如果有效,则立即响应缓存内容给用户,从而加快响应速度。如果CDN节点的缓存失效,它会根据服务配置去我们的内容源服务器获取最新的资源响应给用户,并将内容缓存下来以便响应给后续访问的用户。因此,一个地区内只要有一个用户先加载资源,在CDN中建立了缓存,该地区的其他后续用户都能因此而受益。
之所以不同地区的用户访问同一个域名却能得到不同CDN节点的IP地址,这要依赖于CDN服务商提供的智能域名解析服务,浏览器发起域名查询时,这种智能DNS服务会根据用户IP计算并返回离它最近的同网络CDN节点IP,引导浏览器与此节点建立连接以获取资源。
  
结合上述两点,为了使用CDN网络缓存,我们至少要对静态资源的部署做两项改变:
  1、将静态资源部署到不同网络线路的服务器中,以加速对应网络中CDN节点无缓存时溯源的速度。
  2、加载静态资源时使用与页面不同的域名,一方面是便于接入为CDN而设置的智能DNS解析服务,另一方面因为静态资源和主页面不同域,这样加载资源的HTTP请求就不会带上主页面中的Cookie等数据,减少了数据传输量,又进一步加快网络访问。

CDN服务基本上已经成为现代大型Web应用的标配,这项技术“几乎”是一种对开发透明的网络性能优化手段,使用它的理由很充分,但是这里既然强调了“几乎透明”而不是“完全透明”,是因为使用CDN服务所需要的两项改变对前端工程产生了一定的影响,就是前端工程必须引入非覆盖式发布的根本原因。

5.什么是 FOUC(无样式内容闪烁)?你如何来避免 FOUC?
如果使用import方法对css进行导入,会导致某些页面在Windows 下的Internet Explorer出现一些奇怪的现象: 以无样式显示页面内容的瞬间闪烁,这种现象称之为文档样式短暂失效(Flash of Unstyled Content),简称为FOUC。

原因:

  • 使用import方法导入样式表;
  • 将样式表放在页面底部;
  • 有几个样式表,放在html结构的不同位置。

原理:
当样式表晚于结构性html加载,当加载到此样式表时,页面将停止之前的渲染。
此样式表被下载和解析后,将重新渲染页面,也就出现了短暂的花屏现象。

解决方法:
使用link标签将样式表放在文档head中

6.JavaScript中Null和Undefined的区别
Null:
null是js中的关键字,表示空值,null可以看作是object的一个特殊的值,如果一个object值为空,表示这个对象不是有效对象。

Undefined:
undefined不是js中的关键字,是一个全局变量,是Global的一个属性,以下情况会返回undefined:
1)使用了一个未定义的变量 var i;
2)使用了已定义但未声明的变量;
3)使用了一个对象属性,但该属性不存在或者未赋值;
4)调用函数时,该提供的参数没有提供:

function func(a){
   
   
   console.log(a);      
}
func();//undefined

5)函数没有返回值时,默认返回undefined

var aa=func();
aa;//undefined

相同点:
都是原始类型的值,保存在栈中变量本地。
区别:
类型不一样:

console.log(typeOf undefined);//undefined
console.log(typeOf null);//object 

转化为值时不一样:undefined为NaN ,null为0

console.log(Number(undefined));//NaN
console.log(Number(10+undefined));//NaN
console.log(Number(null));//0
console.log(Number(10+null));//10

3.undefined=null;//false
undefined
null;//true

何时使用:
null当使用完一个比较大的对象时,需要对其进行释放内存时,设置为null;
var arr=[“aa”,“bb”,“cc”];
arr=null;//释放指向数组的引用

7.ts与js
JavaScript:
JavaScript是一种直译式脚本语言,是一种动态类型、弱类型、基于原型的语言,内置支持类型。
JavaScript已经被广泛用于Web应用开发,常用来为网页添加各式各样的动态功能,为用户提供更流畅美观的浏览效果。

  • 脚本语言:无需编译,是在程序的运行过程中逐行进行解释;
  • 基于对象:不仅可以创建对象,也能使用现有的对象。但是并不支持其它面向对象语言所具有的继承和重载功能;
  • 简单:采用弱类型的变量类型,对使用的数据类型未做出严格的要求,是基于Java基本语句和控制的脚本语言,其设计简单紧凑;
  • 动态性:不需要经过Web服务器就可以对用户的输入做出响应。在访问一个网页时,鼠标在网页中进行鼠标点击或上下移、窗口移动等操作JavaScript都可直接对这些事件给出相应的响应;
  • 跨平台性:不依赖于操作系统,仅需要浏览器的支持。因此一个JavaScript脚本在编写后可以带到任意机器上使用,前提上机器上的浏览器支持JavaScript脚本语言,目前JavaScript已被大多数的浏览器所支持。

TypeScript:
TypeScript是一种由微软开发的自由和开源的编程语言。它是JavaScript的一个超集,而且本质上向这个语言添加了可选的静态类型基于类的面向对象编程
TypeScript 支持为已存在的 JavaScript 库添加类型信息的头文件,扩展了它对于流行的库如 jQuery,MongoDB,Node.js 和 D3.js 的好处。

  • 添加语言特性:类型批注和编译时类型检查、类、接口、模块、lambda函数;
  • 适用于大型应用的开发。

JS和TS主要差异:

  • TypeScript扩展了JavaScript的语法,任何现有的JavaScript程序可以不加改变的在TypeScript下工作;
  • TypeScript 中的数据要求带有明确的类型,JavaScript不要求;
  • TypeScript 引入了 JavaScript 中没有的“类”概念;
  • TypeScript 中引入了模块的概念,可以把声明、数据、函数和类封装在模块中;
  • TypeScript 为函数提供了缺省参数值;
  • TypeScript 通过类型注解提供编译时的静态类型检查。

TypeScript优势:
(1)静态输入
静态类型化是一种功能,可以在开发人员编写脚本时检测错误。查找并修复错误是当今开发团队的迫切需求。有了这项功能,就会允许开发人员编写更健壮的代码并对其进行维护,以便使得代码质量更好、更清晰。
(2)大型的开发项目
有时为了改进开发项目,需要对代码库进行小的增量更改。这些小小的变化可能会产生严重的、意想不到的后果,因此有必要撤销这些变化。使用TypeScript工具来进行重构更变的容易、快捷。
(3)更好的协作
当开发大型项目时,会有许多开发人员,此时乱码和错误的机也会增加。类型安全是一种在编码期间检测错误的功能,而不是在编译项目时检测错误。这为开发团队创建了一个更高效的编码和调试过程。
(4)更强的生产力
干净的 ECMAScript 6 代码,自动完成和动态输入等因素有助于提高开发人员的工作效率。这些功能也有助于编译器创建优化的代码。

参考:TypeScript & JavaScript

8.Async/Await
在很长一段时间里面,FE们不得不依靠回调来处理异步代码。使用回调的结果是,代码变得很纠结,不便于理解与维护,值得庆幸的是Promise带来了.then(),让代码变得井然有序,便于管理。于是我们大量使用,代替了原来的回调方式。但是不存在一种方法可以让当前的执行流程阻塞直到promise完成。JS里面,我们无法直接原地等promise完成,唯一可以用于提前计划promise完成后的执行逻辑的方式就是通过then附加回调函数。 现在随着Async/Await的增加,可以让接口按顺序异步获取数据,用更可读,可维护的方式处理回调。

Async/Await:
Async/Await是一个期待已久的JavaScript特性,让我们更好的理解使用异步函数。它建立在Promises上,并且与所有现有的基于Promise的API兼容。

(1)Async—声明一个异步函数(async function someName(){…})

  • 自动将常规函数转换成Promise,返回值也是一个Promise对象;
  • 只有async函数内部的异步操作执行完,才会执行then方法指定的回调函数;
  • 异步函数内部可以使用await。

(2)Await—暂停异步的功能执行(var result = await someAsyncCall()

  • 放置在Promise调用之前,await强制其他代码等待,直到Promise完成并返回结果;
  • 只能与Promise一起使用,不适用与回调;
  • 只能在async函数内部使用。

async函数完全可以看作多个异步操作,包装成的一个 Promise 对象,而await命令就是内部then命令的语法糖。

如何使用:
(1)async 函数的几种使用形式

//函数声明
async function foo(){
   
   }

//函数表达式
const foo=async function(){
   
   };

//对象的方法
let obj={
   
   async foo(){
   
   }};
obj.foo().then(()=>{
   
   console.log('balabala')});

//箭头函数
const foo=async()=>{
   
   };
//Class的方法
class Storage{
   
   
	constructor(){
   
   
		this.cachePromise=caches.open('avatars');
	}

	async getAvatar(name){
   
   
		const cache=await this.cachePromise;
		return cache.match(`/avatars/${
     
     name}.jpg`);
	}
}

const storage=new Storage();
storage.getAvatar('jake').then();

(2)await的用法则相对简单了许多,他后面需要是一个Promise对象,如果不是则会被转成Promise对象。只要其中一个Promise对象变为reject状态,那么整个async函数都会中断操作。如果状态是resolve,那么他的返回值则会变成then里面的参数,如下。

async function f(){
   
   
	return await 123;
}

f().then(v=>console.log(v))
//123

注意:

  • 怎样容错呢,犹豫await后面的promise运行结果可能是rejected,最好把await放入try{}catch{}中;
  • Await后的异步操作,如果彼此没有依赖关系最好同时触发;
  • Await只能在async函数之中,如果在普通函数中,会报错。

使用场景介绍:
那么什么情况下适合用,什么情况下不适合使用呢?
(1)场景一,我们同时发出三个不互相依赖的请求,如果用Async/Await就显得不明智了

async function getABC(){
   
   
	let A=await getValueA();//getValueA takes 2 seconds to finish
	let B=await getValueB();//getValueB takes 4 seconds to finish
	let C=await getValueC();//getValueC takes 3 seconds to finish

	return A*B*C;
}

上面我们A需要2s,B需要4s,C需要3s,我们如上图所示发请求,就存在彼此依赖的关系,c等b执行完,b等a执行完,从开始到结束需要(2+3+4)9s。

此时我们需要用Promise.all()将异步调用并行执行,而不是一个接一个执行,如下所示:

async function getABC(){
   
   
	let results=await Promise.all([getValueA,getValueB,getValueC]);

	return results.reduce((total,value)=>total*value);
}

这样将会节省我们不少的时间,从原来的的9s缩减到4s

(2)场景二,我曾经遇到过一个场景,一个提交表单的页面,里面有姓名、地址等巴拉巴拉的信息,其中有一项是手机验证码,我们不得不等待手机验证码接口通过,才能发出后续的请求操作,这时候接口之间就存在了彼此依赖的关系,Async跟Await就有了用武之地,让异步请求之间可以按顺序执行。

其中不用Async/Await的写法,我们不得不用.then()的方式,在第一个请求验证码的接口有返回值之后,才能执行后续的的Promise,并且还需要一个then输出结果,如下图:

//获取手机验证码的接口请求
const callPromise=fetch('//xxx.');
callPromise
.then(response=>response.json())
.then(json=>{
   
   
	//对验证码的返回值进行后续的操作,比如提交表单等
	const call2Promise=fetch('//xxx.');
	return call2Promise;
})
.then(response=>response.json())
.then(json=>{
   
   
	//输出执行完之后的结果
	console.log(json.respCode);
})
.catch(err=>{
   
   console.log(err);});

而用Async/Await的方式去写就是下面这样,我们将逻辑分装在一个async函数里。这样我们就可以直接对promise使用await了,也就规避了写then回调。最后我们调用这个async函数,然后按照普通的方式使用返回的promise。

//提交表单的请求
async function postForm(){
   
   
	try{
   
   
		//手机验证码是否通过,拿到返回的json
		const response1=await fetch('//xxx.');
		const json1=await response1.json();

		let json2;
		//对第一个请求的返回数据进行判断,满足条件的话请求第二个接口,并返回数据
		if(json1.resCode===200){
   
   
			const response2=await fetch('//xxx.');
			json2=await response2.json();
		}
		return json2;
	}catch(e){
   
   
		console.log(e);
	}
}

//执行postForm
postForm().then((json)=>console.log(json));

参考:浅谈Async/Await

9.为什么POST比GET安全?
如果不通过SSL加密,GET和POST方法都会把数据以明文方式发送到服务器上,安全性相差无几。

说POST比GET安全,有如下几点理由:

  • GET请求的数据会被浏览器作为URL的一部分而保存;
  • 服务器端的访问日志等一般都会记下URL;
  • HTTP头部的referer里会记下URL里请求数据

总之一般人都认为URL里不会包含敏感数据的,所以最好不要把敏感数据放到URL里,这种场合自然不适合使用GET。

10.垃圾回收机制
标记清除:
JavaScript 中最常用的垃圾收集方式是标记清除(mark-and-sweep)。
这个算法把“对象是否不再需要”简化定义为“对象是否可以获得”。

该算法假定设置一个叫做根(root)的对象(在Javascript里,根是全局对象)。垃圾回收器将定期从根开始(在JS中就是全局对象)扫描内存中的对象。凡是能从根部到达的对象,都是还需要使用的。那些无法由根部出发触及到的对象被标记为不再使用,稍后进行回收。

此算法可以分为两个阶段,一个是标记阶段(mark),一个是清除阶段(sweep)。

  • 标记阶段:垃圾回收器会从根对象开始遍历。每一个可以从根对象访问到的对象都会被添加一个标识,于是这个对象就被标识为可到达对象。
  • 清除阶段:垃圾回收器会对堆内存从头到尾进行线性遍历,如果发现有对象没有被标识为可到达对象,那么就将此对象占用的内存回收,并且将原来标记为可到达对象的标识清除,以便进行下一次垃圾回收操作。

何时开始垃圾回收?
通常来说,在使用标记清除算法时,未引用对象并不会被立即回收。取而代之的做法是,垃圾对象将一直累计到内存耗尽为止。当内存耗尽时,程序将会被挂起,垃圾回收开始执行。

标记清除算法缺陷:

  • 那些无法从根对象查询到的对象都将被清除;
  • 垃圾收集后有可能会造成大量的内存碎片,垃圾收集后内存中存在三个内存碎片,假设一个方格代表1个单位的内存,如果有一个对象需要占用3个内存单位的话,那么就会导致Mutator一直处于暂停状态,而Collector一直在尝试进行垃圾收集,直到Out of Memory。

引用计数算法:
此算法把“对象是否不再需要”简化定义为“对象有没有其他对象引用到它”。如果没有引用指向该对象(零引用),对象将被垃圾回收机制回收。

引用计数的含义是跟踪记录每个值被引用的次数。当声明了一个变量并将一个引用类型值赋给该变量时,则这个值的引用次数就是1。如果同一个值又被赋给另一个变量,则该值的引用次数加1。相反,如果包含对这个值引用的变量又取得了另外一个值,则这个值的引用次数减1。当这个值的引用次数变成0时,则说明没有办法再访问这个值了,因而就可以将其占用的内存空间回收回来。这样,当垃圾收集器下次再运行时,它就会释放那些引用次数为零的值所占用的内存。

引用计数缺陷:
该算法有个限制:无法处理循环引用。如果两个对象被创建,并互相引用,形成了一个循环。它们被调用之后会离开函数作用域,所以它们已经没有用了,可以被回收了。然而,引用计数算法考虑到它们互相都有至少一次引用,所以它们不会被回收。

参考:前端面试查漏补缺–(二) 垃圾回收机制

11.前端加密
前端加密的几种做法:

  • JavaScript 加密后传输(具体可以参考后面的常见加密方法);
  • 浏览器插件内进行加密传输 (这个用得不是很多,这里暂不细究);
  • Https 传输。

加密算法:
加密(Encrypt)是将目标文本转换成具有不同长度的、可逆的密文。也就是说加密算法是可逆的,而且其加密后生成的密文长度和明文本身的长度有关。所以如果被保护数据在以后需要被还原成明文,则需要使用加密。

对称加密:
对称加密采用了对称密码编码技术,它的特点是文件加密和解密使用相同的密钥加密.也就是加密和解密都是用同一个密钥,这种方法在密码学中叫做对称加密算法。

常见的对称加密算法有DES、3DES、Blowfish、IDEA、RC4、RC5、RC6和AES。

注意: 因为前端的透明性,对于登录密码等敏感信息,就不要使用JavaScript来进行对称加密. 因为别人可以从前端得到密匙后,可以直接对信息进行解密!

非对称加密:
非对称加密算法需要两个密钥:公钥(publickey)和私钥(privatekey)。 公钥与私钥是一对,如果用公钥对数据进行加密,只有用对应的私钥才能解密;如果用私钥对数据进行加密,那么只有用对应的公钥才能解密。 因为加密和解密使用的是两个不同的密钥,所以这种算法叫作非对称加密算法。

非对称加密算法实现机密信息交换的基本过程是:甲方生成一对密钥并将其中的一把作为公钥向其它方公开;得到该公钥的乙方使用该密钥对机密信息进行加密后再发送给甲方;甲方再用自己保存的另一把专用密钥对加密后的信息进行解密。甲方只能用其专用密钥解密由其公钥加密后的任何信息。
常见的非对称加密算法有:RSA、ECC(移动设备用)、Diffie-Hellman、El Gamal、DSA(数字签名用)。

哈希加密算法:
哈希(Hash)是将目标文本转换成具有固定长度的字符串(或叫做消息摘要)。 当输入发生改变时,产生的哈希值也是完全不同的。从数学角度上讲,一个哈希算法是一个多对一的映射关系,对于目标文本 T,算法 H 可以将其唯一映射为 R,并且对于所有的 T,R 具有相同的长度,所以 H 不存在逆映射,也就是说哈希算法是不可逆的
基于哈希算法的特性,其适用于该场景:被保护数据仅仅用作比较验证且不需要还原成明文形式。比较常用的哈希算法是 MD5 和 SHA1 。

我们比较熟悉的使用哈希存储数据的例子是:当我们登录某个已注册网站时,在忘记密码的情况下需要重置密码,此时网站会给你发一个随机的密码或者一个邮箱激活链接,而不是将之前的密码发给你,这就是因为哈希算法是不可逆的。

对于简单的哈希算法的攻击方法主要有:寻找碰撞法和穷举法。所以,为了保证数据的安全,可以在哈希算法的基础上进一步的加密,常见的方法有:加盐、慢哈希、密钥哈希、XOR 等。

加盐(Adding Salt):
加盐加密是一种对系统登录口令的加密方式,它实现的方式是将每一个口令同一个叫做“盐”(salt)的 n 位随机数相关联。

使用salt加密,它的基本想法是这样的:

1.用户注册时,在密码上撒一些盐。生成一种味道,记住味道。
2.用户再次登陆时,在输入的密码上撒盐,闻一闻,判断是否和原来的味道相同,相同就让你吃饭。

由于验证密码时和最初散列密码时使用相同的盐值,所以salt的存储在数据库。并且这个值是由系统随机产生的,而非硬编码。这就保证了所要保护对象的机密性。

注册时:

(1)用户注册,系统随机产生salt值。
(2)将salt值和密码连接起来,生产Hash值。
(3)将Hash值和salt值分别存储在数据库中。
在这里插入图片描述
登陆时:

(1)系统根据用户名找到与之对应的密码Hash。
(2)将用户输入密码和salt值进行散列。
(3)判断生成的Hash值是否和数据库中Hash相同。
在这里插入图片描述
使用加盐加密时需要注意以下两点:
(1)短盐值(Short Slat)
如果盐值太短,攻击者可以预先制作针对所有可能的盐值的查询表。例如,如果盐值只有三个 ASCII 字符,那么只有 95x95x95=857,375 种可能性,加大了被攻击的可能性。还有,不要使用可预测的盐值,比如用户名,因为针对某系统用户名是唯一的且被经常用于其他服务。

(2)盐值复用(Salt Reuse)
在项目开发中,有时会遇到将盐值写死在程序里或者只有第一次是随机生成的,之后都会被重复使用,这种加盐方法是不起作用的。以登录密码为例,如果两个用户有相同的密码,那么他们就会有相同的哈希值,攻击者就可以使用反向查表法对每个哈希值进行字典攻击,使得该哈希值更容易被破解。

所以正确的加盐方法如下:
(1)盐值应该使用加密的安全伪随机数生成器( Cryptographically Secure Pseudo-Random Number Generator,CSPRNG )产生,比如 C 语言的 rand() 函数,这样生成的随机数高度随机、完全不可预测;
(2)盐值混入目标文本中,一起使用标准的加密函数进行加密;
(3)盐值要足够长(经验表明:盐值至少要跟哈希函数的输出一样长)且永不重复;
(4)盐值最好由服务端提供,前端取值使用。

慢哈希函数(Slow Hash Function):
慢哈希函数是将哈希函数变得非常慢,使得攻击方法也变得很慢,慢到足以令攻击者放弃,而往往由此带来的延迟也不会引起用户的注意。降低攻击效率用到了密钥扩展( key stretching)的技术,而密钥扩展的实现使用了一种 CPU 密集型哈希函数( CPU-intensive hash function)。

密钥哈希:
密钥哈希是将密钥添加到哈希加密,这样只有知道密钥的人才可以进行验证。目前有两种实现方式:使用 ASE 算法对哈希值加密、使用密钥哈希算法 HMAC 将密钥包含到哈希字符串中。为了保证密钥的安全,需要将其存储在外部系统(比如一个物理上隔离的服务端)。
即使选择了密钥哈希,在其基础上进行加盐或者密钥扩展处理也是很有必要。目前密钥哈希用于服务端比较多,例如来应对常见的 SQL 注入攻击。

XOR:
XOR 指的是逻辑运算中的 “异或运算”。两个值相同时,返回 false,否则返回 true,用来判断两个值是否不同。

XOR 运算有一个特性:如果对一个值连续做两次 XOR,会返回这个值本身。这也是其可以用于信息加密的根本。

message XOR key // cipherText
cipherText XOR key // message

目标文本 message,key 是密钥,第一次执行 XOR 会得到加密文本;在加密文本上再用 key 做一次 XOR 就会还原目标文本 message。为了保证 XOR 的安全,需要满足以下两点:
(1)key 的长度大于等于 message ;
(2)key 必须是一次性的,且每次都要随机产生。

下面以登录密码加密为例介绍下 XOR 的使用:
第一步:使用 MD5 算法,计算密码的哈希;

const message = md5(password);

第二步:生成一个随机 key 值;
第三步:进行 XOR 运算,求出加密后的 message。

function getXOR(message, key) 

const arr = [];

//假设 key 是32位的

for (let i = 0; i < 32; i++) {
   
   
  const  m = parseInt(message.substr(i, 1), 16);
  const k = parseInt(key.substr(i, 1), 16);
  arr.push((m ^ k).toString(16));
}

return arr.join('');

}

如上所示,使用 XOR 和一次性的密钥 key 对密码进行加密处理,只要 key 没有泄露,目标文本就不会被破解。

参考:前端面试查漏补缺–(八) 前端加密

12.前端软件架构模式MVC/MVP/MVVM
MVC,MVP和MVVM都是常见的软件架构设计模式(Architectural Pattern),它通过分离关注点来改进代码的组织方式。不同于设计模式(Design Pattern),只是为了解决一类问题而总结出的抽象方法,一种架构模式往往使用了多种设计模式。
要了解MVC、MVP和MVVM,就要知道它们的相同点和不同点。不同部分是C(Controller)、P(Presenter)、VM(View-Model),而相同的部分则是MV(Model-View)。

MVC模式:
MVC模式(Model–view–controller)是软件工程中的一种软件架构模式,把软件系统分为三个基本部分:模型(Model)、视图(View)和控制器(Controller)。

  • 模型(Model) - Model层用于封装和应用程序的业务逻辑相关的数据以及对数据的处理方法。一旦数据发生变化,模型将通知有关的视图。
  • 视图(View) - View作为视图层,主要负责数据的展示,并且响应用户操作。
  • 控制器(Controller)- 控制器是模型和视图之间的纽带,接收View传来的用户事件并且传递给Model,同时利用从Model传来的最新模型控制更新View。
    在这里插入图片描述
    数据关系:
  • View 接受用户交互请求;
  • View 将请求转交给Controller;
  • Controller 操作Model进行数据更新;
  • 数据更新之后,Model通知View更新数据变化.PS: 还有一种是View作为Observer监听Model中的任意更新,一旦有更新事件发出,View会自动触发更新以展示最新的Model状态.这种方式提升了整体效率,简化了Controller的功能,不过也导致了View与Model之间的紧耦合。
  • View 更新变化数据。

方式:
所有方式都是单向通信。

结构实现:

  • View :使用组合(Composite)模式;
  • View和Controller:使用策略(Strategy)模式;
  • Model和 View:使用观察者(Observer)模式同步信息。

缺点:

  • View层过重: View强依赖于Model的,并且可以直接访问Model.所以不可避免的View还要包括一些业务逻辑.导致view过重,后期修改比较困难,且复用程度低.
  • View层与Controller层也是耦合紧密: View与Controller虽然看似是相互分离,但却是联系紧密.经常View和Controller一一对应的,捆绑起来作为一个组件使用.解耦程度不足.

MVP模式:
MVP(Model-View-Presenter)是MVC模式的改良.MVP与MVC有着一个重大的区别:在MVP中View并不直接使用Model,它们之间的通信是通过Presenter (MVC中的Controller)来进行的,所有的交互都发生在Presenter内部,而在MVC中View会直接从Model中读取数据而不是通过 Controller。

  • Model - Model层依然是主要与业务相关的数据和对应处理数据的方法。
  • View - View依然负责显示,但MVP中的View并不能直接使用Model。
  • Presenter - Presenter作为View和Model之间的“中间人”,且MVP中的View并不能直接使用Model,而是通过为Presenter提供接口,让Presenter去更新Model,再通过观察者模式更新View。
    在这里插入图片描述
    数据关系:
  • View 接收用户交互请求;
  • View 将请求转交给 Presenter;
  • Presenter 操作Model进行数据更新;
  • Model 通知Presenter数据发生变化;
  • Presenter 更新View数据。

方式:
各部分之间都是双向通信

结构实现:

  • View :使用 组合(Composite)模式;
  • View和Presenter:使用 中介者(Mediator)模式;
  • Model和Presenter:使用 命令(Command)模式同步信息。

MVC和MVP关系:

  • MVP:是MVC模式的变种。
  • 项目开发中,UI是容易变化的,且是多样的,一样的数据会有N种显示方式;业务逻辑也是比较容易变化的。为了使得应用具有较大的弹性,我们期望将UI、逻辑(UI的逻辑和业务逻辑)和数据隔离开来,而MVP是一个很好的选择。
  • Presenter代替了Controller,它比Controller担当更多的任务,也更加复杂。Presenter处理事件,执行相应的逻辑,这些逻辑映射到Model操作Model。那些处理UI如何工作的代码基本上都位于Presenter。
  • MVC中的Model和View使用Observer模式进行沟通;MPV中的Presenter和View则使用Mediator模式进行通信;Presenter操作Model则使用Command模式来进行。基本设计和MVC相同:Model存储数据,View对Model的表现,Presenter协调两者之间的通信。在MVP 中 View 接收到事件,然后会将它们传递到 Presenter, 如何具体处理这些事件,将由Presenter来完成。

MVP的优点:

  • Model与View完全分离,修改互不影响
  • 更高效地使用,因为所有的逻辑交互都发生在一个地方—Presenter内部
  • 一个Preseter可用于多个View,而不需要改变Presenter的逻辑(因为View的变化总是比Model的变化频繁)。
  • 更便于测试。把逻辑放在Presenter中,就可以脱离用户接口来测试逻辑(单元测试)

MVP的缺点:

Presenter中除了业务逻辑以外,还有大量的View->Model,Model->View的手动同步逻辑,造成Presenter比较笨重,一旦视图需要变更,那么Presenter也需要变更,维护起来比较困难。

MVVM模式:
MVVM是Model-View-ViewModel的简写。由Microsoft提出,并经由Martin Fowler布道传播。在 MVVM 中,不需要Presenter手动地同步View和Model.View 是通过数据驱动的,Model一旦改变就会相应的刷新对应的 View,View 如果改变,也会改变对应的Model。这种方式就可以在业务处理中只关心数据的流转,而无需直接和页面打交道。ViewModel 只关心数据和业务的处理,不关心 View 如何处理数据,在这种情况下,View 和 Model 都可以独立出来,任何一方改变了也不一定需要改变另一方,并且可以将一些可复用的逻辑放在一个 ViewModel 中,让多个 View 复用这个 ViewModel。

  • Model - Model层仅仅关注数据本身,不关心任何行为(格式化数据由View负责),这里可以把它理解为一个类似json的数据对象。
  • View - MVVM中的View通过使用模板语法来声明式的将数据渲染进DOM,当ViewModel对Model进行更新的时候,会通过数据绑定更新到View。
  • ViewModel - 类似与Presenter. ViewModel会对View 层的声明进行处理.当 ViewModel 中数据变化,View 层会进行更新;如果是双向绑定,一旦View对绑定的数据进行操作,则ViewModel 中的数据也会进行自动更新。
    在这里插入图片描述
    数据关系:
  • View 接收用户交互请求;
  • View 将请求转交给ViewModel;
  • ViewModel 操作Model数据更新;
  • Model 更新完数据,通知ViewModel数据发生变化;
  • ViewModel 更新View数据。

方式:
双向绑定。View/Model的变动,自动反映在 ViewModel,反之亦然。

实现数据绑定的方式:

  • 数据劫持 (Vue);
  • 发布-订阅模式 (Knockout、Backbone);
  • 脏值检查 (旧版Angular)。

使用:

  • 可以兼容你当下使用的 MVC/MVP 框架。
  • 增加你的应用的可测试性。
  • 配合一个绑定机制效果最好。

MVVM优点:
MVVM模式和MVC模式一样,主要目的是分离视图(View)和模型(Model),有几大优点:

(1)低耦合。View可以独立于Model变化和修改,一个ViewModel可以绑定到不同的”View”上,当

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值