TypeScript和webpack问题浅析

709 阅读11分钟

never 类型是什么?

它用来描述那些永远无法出现的值。

never类型是所有类型的子类型,可以赋值给任何类型的值

never类型外,任何类型都不是never的子类型,也不能赋值给never类型的变量

在函数表达式或者箭头函数中,如果函数没有声明返回类型,且没有return语句,后者return语句返回的表达式无法到达函数底部,则该函数的返回类型推断为never类型

function infiniteLoop(): never {
  while (true) {}
}

interface 和type声明的区别

interface是接口

type是类型别名

同名合并:interface 支持,type 不支持。

type test = {
  title: string;
};

type test = {
  ts: string;
};
interface demo {
  title: string;
}

interface demo {
  ts: string;
}

let a: demo = {
  title: "ade",
  ts: "ts",
};

对象、函数两者都适用,但是 type 可以用于基础类型、联合类型、元祖。

type Name = string;
type NameResolver = () => string;
type nameArray = [string, number];

type可以计算属性,生成映射类型,interface不可以

type Keys = "firstName" | "lastName"

type DudeType = {
  [key in Keys]: string
}

const test: DudeType = {
  firstName: "ade",
  lastName: "kang"
}

// 报错
interface DudeType2 {
 [key in keys]: string
}

总结

不同点:

  • 扩展语法: interface使用extends,type使用‘&’
  • 同名合并:interface 支持,type 不支持。
  • 描述类型:对象、函数两者都适用,但是 type 可以用于基础类型、联合类型、元祖。
  • 计算属性:type 支持计算属性,生成映射类型,;interface 不支持。

相同点:

  • 两者都可以用来描述对象或函数的类型
  • 两者都可以实现继承

TypeScript 比起 JavaScript 有什么优点?

  • 静态输入

静态类型化是一种功能,可以在开发人员编写脚本时检测错误。查找并修复错误是当今开发团队的迫切需求。有了这项功能,就会允许开发人员编写更健壮的代码并对其进行维护,以便使得代码质量更好、更清晰。

  • 大型的开发项目

有时为了改进开发项目,需要对代码库进行小的增量更改。这些小小的变化可能会产生严重的、意想不到的后果,因此有必要撤销这些变化。使用TypeScript工具来进行重构更变的容易、快捷。

  • 更好的协作

当发开大型项目时,会有许多开发人员,此时乱码和错误的机也会增加。类型安全是一种在编码期间检测错误的功能,而不是在编译项目时检测错误。这为开发团队创建了一个更高效的编码和调试过程。

  • 更强的生产力

干净的 ECMAScript 6 代码,自动完成和动态输入等因素有助于提高开发人员的工作效率。这些功能也有助于编译器创建优化的代码。

Webpack

推荐文章:关于webpack的面试题总结

*有哪些常见 loader 和 plugin,你用过哪些?

loader:

  • file-loader:把文件输出到一个文件夹中,在代码中通过相对URL引用输入的文件。
  • url-loader:和file类似,但能在文件很小的情况下以base64方式把文件内容注入到代码中。
  • image-loader:加载并压缩图片文件。
  • babel-loader:把ES6转换为ES5。
  • scss-loader:自动将scss转换为CSS
  • css-loader:加载CSS,支持模块化、压缩、文件导入等特性。
  • eslint-loader:用于检查常见的 JavaScript 代码错误,也可以进行"代码规范"检查,在企业开发中项目负责人会定制一套 ESLint 规则,然后应用到所编写的项目上,从而实现辅助编码规范的执行,有效控制项目代码的质量。在编译打包时如果语法有错或者有不符合规范的语法就会报错, 并且会提示相关错误信息
  • style-loader:把CSS代码注入到JavaScript中,通过DOM操作去加载CSS。
  • eslint-loader:通过ESlint检查JavaScript代码。

plugin:

  • define-plugin:定义环境变量。
  • HtmlWebpackPlugin:会在打包结束之后自动创建一个index.html, 并将打包好的JS自动引入到这个文件中。
  • commons-chunk-plugin:提取公共代码。
  • mini-css-extract-plugin:是一个专门用于将打包的CSS内容提取到单独文件的插件。前面我们通过style-loader打包的CSS都是直接插入到head中的。
  • optimize-css-assets-webpack-plugin:压缩css代码
  • uglifyjs-webpack-plugin:缩小(压缩优化)js文件

loader 和 plugin 的区别是什么?

  • loader是一个转换器,将a文件进行编译输出b文件,这里是操作文件。单纯的文件转换。
  • plugin是一个扩展器,它丰富了webpack本身,针对是loader结束后,webpack打包的整个过程,它并不直接操作文件,而是基于事件机制工作,会监听webpack打包过程中的某些节点,执行任务。

*如何按需加载代码?

import()

import("./math").then(math => {
  console.log(math.add(16, 26));
});

React中React.lazy()

const OtherComponent = React.lazy(() => import('./OtherComponent'));

function MyComponent() {
  return (
    <div>
      <OtherComponent />
    </div>
  );
}

*如何提高构建速度?

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

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

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

转义出的文件过大怎么办?

  • 压缩代码。删除多余的代码、注释、简化代码的写法等等方式。可以利用webpack的UglifyJsPluginParallelUglifyPlugin来压缩JS文件, 利用cssnano(css-loader?minimize)来压缩css
  • 利用CDN加速。在构建过程中,将引用的静态资源路径修改为CDN上对应的路径。可以利用webpack对于output参数和各loader的publicPath参数来修改资源路径
  • 删除死代码(Tree Shaking)。将代码中永远不会走到的片段删除掉。可以通过在启动webpack时追加参数--optimize-minimize来实现
  • 提取公共代码。

*什么是 XSS?如何预防?

XSS,即 Cross Site Script,中译是跨站脚本攻击;XSS 攻击是指攻击者在网站上注入恶意的客户端代码,通过恶意脚本对客户端网页进行篡改,从而在用户浏览网页时,对用户浏览器进行控制或者获取用户隐私数据的一种攻击方式。

XSS攻击可以分为3类:反射型(非持久型)、存储型(持久型)、基于DOM。

反射型

反射型 XSS 只是简单地把用户输入的数据 “反射” 给浏览器,这种攻击方式往往需要攻击者诱使用户点击一个恶意链接,或者提交一个表单,或者进入一个恶意网站时,注入脚本进入被攻击者的网站。

存储型

存储型 XSS 会把用户输入的数据 "存储" 在服务器端,当浏览器请求数据时,脚本从服务器上传回并执行。这种 XSS 攻击具有很强的稳定性。

比较常见的一个场景是攻击者在社区或论坛上写下一篇包含恶意 JavaScript 代码的文章或评论,文章或评论发表后,所有访问该文章或评论的用户,都会在他们的浏览器中执行这段恶意的 JavaScript 代码。

基于DOM

基于 DOM 的 XSS 攻击是指通过恶意脚本修改页面的 DOM 结构,是纯粹发生在客户端的攻击。

防范

  • 对富文本编辑器,过滤script等不安全标签
  • 对用户输入进行转义,比如把<script></script> 转义为 &lt;script&gt;&lt;/script&gt;
  • 代码需要动态展示内容时用innerText 取代 innerHTML ,使用v-text取代 v-html,尽量少做HTML字符串拼接,禁止使用eval执行JS。
  • 服务端Set Cookie时 带上HttpOnly字段,阻止JavaScript获取Cookie
  • 对于上传图片的场景,禁止使用用户填写的图片地址。特别是Markdown编辑器。

*什么是 CSRF?如何预防?

Cross-site request forgery, 跨站点请求伪造。攻击者诱导受害者进入第三方网站,在第三方网站中,向被攻击网站发送跨站请求。攻击者借助受害者的 Cookie 骗取服务器的信任,可以在受害者毫不知情的情况下以受害者名义伪造请求发送给受攻击服务器,从而在并未授权的情况下执行在权限保护之下的操作。

一个典型的CSRF攻击有着如下的流程:

  • 受害者登录a.com,并保留了登录凭证(Cookie)。
  • 攻击者引诱受害者访问了b.com
  • a.com接收到请求后,对请求进行验证,并确认是受害者的凭证,误以为是受害者自己发送的请求。
  • a.com以受害者的名义执行了act=xx。
  • 攻击完成,攻击者在受害者不知情的情况下,冒充受害者,让a.com执行了自己定义的操作。

防御

Referer Check

根据 HTTP 协议,在 HTTP 头中有一个字段叫 Referer,它记录了该 HTTP 请求的来源地址。通过 Referer Check,可以检查请求是否来自合法的"源"。

无需存储的CSRF Token

用户访问并登录b.com 时,服务器会写入用于登录的Cookie。同时服务端会做两件事

  1. 在返回的HTML上埋点

  2. 在Cookie里写入该csrftoken值

<form>
	<input type="hiden" csrftoken="afe3f94jjfwr" >
  <input type="text" name="username" value="">
  <input type="password" name="password" value="">
</form>
Set-Cookie: csrftoken=afe3f94jjfwr

当用户提交表单或者发送Ajax请求时,在请求参数或者请求头带上之前埋入的csrftoken。请求到服务器后服务端会从用户的请求参数里拿出token和请求自带的cookie里的token做比对,如果都存在且一致,则请求通过。

因为这个token是埋在b.com里,攻击者从第三方网站伪造请求时得不到这个token,所以无法在请求参数里带上该token,请求会失败。


参考文章

typeScript interface和type区别

TypeScript VS JavaScript 深度对比

关于webpack的面试题总结 - 知乎 (zhihu.com)

【基本功】 前端安全系列之二:如何防止CSRF攻击? - 知乎 (zhihu.com)

浅说 XSS 和 CSRF · Issue #68 · dwqs/blog (github.com)