本文共 14477 字,大约阅读时间需要 48 分钟。
关系:typescript是基于javascript基础之上的编程语言,是javascript的超集。
语言类型:javascript是弱类型语言,typescript是强类型语言。
TS需要编译,JS基本直接被浏览器解析执行。
主要区别就是 typescript 扩展的功能:
interface 除了可用于对类的一部分行为进行抽象以外,也常用于对「对象的形状(Shape)」进行描述。
type 用于创建类型别名,可以作用于基本类型(原始值),联合类型,元组以及其它任何你需要手写的类型。
都可以描述一个对象或函数
都允许扩展(extends)
参考:
总结:
回答:
async 关键字声明的函数表示是异步函数,函数执行返回的是一个 promise。
async 内部原理就是将函数执行包裹在一个promise中,并返回这个promise。
当函数执行完毕,变更promise的状态。
如果函数有返回值,就作为promise的值返回。
await 用于等待一个异步函数执行完成。
相当于生成器函数(Generator)中 的yield,它会等待后面的异步函数执行完,并返回其处理结果。
其他官方答案:
Promise解决了回调地狱的问题,但是如果遇到复杂的业务,代码里面会包含大量的 then 函数,使得代码依然不是太容易阅读。
基于这个原因,ES7 引入了 async/await,这是 JavaScript 异步编程的一个重大改进,提供了在不阻塞主线程的情况下使用同步代码实现异步访问资源的能力,并且使得代码逻辑更加清晰,而且还支持 try-catch 来捕获异常,非常符合人的线性思维。
async/await,这种方式能够彻底告别执行器和生成器,实现更加直观简洁的代码。根据 MDN 定义,async 是一个通过异步执行并隐式返回 Promise 作为结果的函数。可以说async 是Generator函数的语法糖,并对Generator函数进行了改进
他的重点是自带了执行器,相当于把我们要额外做的(写执行器/依赖co模块)都封装了在内部
注意:
// try catch 只能捕获同步异常 和 async 中的 await Promise 的异常// try catch 不能直接捕获 Promise 的异常async function e() { throw new Error('异常')}function syncFn() { try { const p = e() // 尝试注释 promise 的 catch 方法 p.catch(err => { console.log('Promise catch:', err.message) }) } catch (err) { console.log('try catch:', err.message) }}syncFn()async function asyncFn() { try { const p = await e() } catch (err) { console.log('try catch:', err.message) }}asyncFn()
回答:
JS执行时将数据存储在堆栈内存中,在执行栈执行任务。
JS任务开始处理,将代码逐行压入执行栈。
任务处理完,清空执行栈,压入下一个任务的代码。
任务存储在任务队列中。
按照进入队列的顺序,依次压入执行栈处理。
JS会先执行同步任务,然后执行微任务,最终执行宏任务。
整个过程就是事件循环 Event Loop机制。
内容可以分为:执行代码、收集和处理任务、以及执行队列中的任务。
其他官方答案:
主线程从"任务队列"中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为Event Loop(事件循环)。
用两个队列来处理异步任务。
以setTimeout为代表的任务放到被称为macrotask(宏任务),放到Macrotask queue中。
而以Promise 为代表的任务放到Microtask queue(微任务队列)中。
eventloop对这两个队列的处理逻辑也不一样。
执行过程如下:
JavaScript引擎首先从macrotask queue中取出第一个任务,
执行完毕后,将microtask queue中的所有任务取出,按顺序全部执行(全部执行不仅指开始执行时队列里的microtask,在这一步执行过程中产生的新的microtask,也要在这里执行)
然后再从macrotask queue中取下一个, 执行完毕后,再次将microtask queue中的全部取出; 循环往复,直到两个queue中的任务都取完。
换句话说,一次eventloop循环会处理一个macrotask和所有这次循环中产生的microtask
promise 定义时传入的函数称为 执行器函数,它是同步的,是立即执行的。
回答:
防抖是优化高频率执行js代码的一种手段。
多用于频繁的触发一个事件,但只需要保证执行一次即可,之前的触发都可以忽略的场景。
例如:
实现方式可以分为两种:
回答:
回答:
入口文件gulpfile.js
,通过导出函数成员的方式定义任务。
任务接收的第一个参数是一个函数,用于标记任务结果。
当任务完成之后,需要调用这个函数或其他方式标记任务已经完成。
// foo任务exports.foo = done => { // ...任务内容 done() // 标记任务完成}
也可以通过返回一个promise标记任务状态。
resolve表示成功,reject表示失败。
其他官方答案:
gulp 是基于Node开发环境运行的,所以要先确认好是否有Node开发环境
安装好Node以后,运行npm init创建package.json文件
安装gulp以及你的任务中要使用的依赖
创建并编写gulpfile.js文件
运行程序及打包
构建过程大多数是将文件读出来,进行转换,最后写入到另外的位置。
gulp的构建有三个核心概念,分别是读取流、转换流和写入流。
我们通过读取流把需要转换的文件读取出来,然后通过转换流的转换逻辑,转换成我们想要的结果,再通过写入流去写入到指定的文件位置。
这样的一个过程就完成了我们日常在构建当中所需要的工作。
gulp的官方定义就是基于流的构建系统。
gulp希望实现一个构建管道的概念,这样的话,我们在后续去做一些扩展插件的时候就可以有一个很统一的方式。
回答:
当开发者根据package.json安装依赖时,文件中声明的依赖版本号不是具体的,而是向新兼容下载的(下载主版本最新的版本)。
package-lock.json 是在 npm install
时候生成一份文件,用来记录当前状态下实际安装的各个npm package的具体来源和版本号。
当项目中其他开发者或一个新的环境,或新的下载源,重新安装依赖时,目录下包含这个文件,就会锁定安装时依赖的来源和版本号,保证每个开发者安装的依赖都是一样的。
解决了package.json的缺点:原来package.json文件只能锁定大版本,也就是版本号的第一位,并不能锁定后面的小版本,你每次npm install都是拉取的该大版本下的最新的版本,为了稳定性考虑我们几乎是不敢随意升级依赖包的,这将导致多出来很多工作量,测试/适配等,所以package-lock.json文件出来了,当你每次安装一个依赖的时候就锁定在你安装的这个版本。
回答:
其他官方答案(没有分层级):
entry:打包的入口文件,一个字符串或者一个对象
output:配置打包的结果,一个对象 fileName:定义输出文件名,一个字符串 path:定义输出文件路径,一个字符串 module:定义对模块的处理逻辑,一个对象 loaders:定义一系列的加载器,一个数组 test:正则表达式,用于匹配到的文件 loader/loaders:字符串或者数组,处理匹配到的文件。如果只需要用到一个模块加载器则使用 loader:string,如果要使用多个模块加载器,则使用loaders:array include:字符串或者数组,指包含的文件夹 exclude:字符串或者数组,指排除的文件夹 resolve:影响对模块的解析,一个对象 extensions:自动补全识别后缀,是一个数组 plugins:定义插件,一个数组
回答:
css-loader 用于将js文件中导入的css文件内容转换成js代码,用于 style-loader或其他插件插入到页面中。
原理:将css文件转换称一个js模块(将css代码push到一个数组当中)
可以通过 style-loader 将css-loader 转换的结果通过 style 标签的形式,追加到页面上
官方答案:
{ test: /.css$/, loader: 'css-loader', exclude: /(node_modules|bower_components)/}
css-loader只是帮我们解析了css文件里面的css代码,
默认webpack是只解析js代码的,所以想要应用样式我们要把解析完的css代码拿出来加入到 style标签中。实现原理:
const postcss = require('postcss');const Tokenizer = require('css-selector-tokenizer');const loaderUtils = require('loader-utils');// 插件,用来提取urlfunction createPlugin(options) { return function(css) { const { importItems, urlItems } = options; // 捕获导入,如果多个就执行多次 css.walkAtRules(/^import$/, function(rule) { // 拿到每个导入 const values = Tokenizer.parseValues(rule.params); // console.log(JSON.stringify(values)); // {"type":"values","nodes":[{"type":"value","nodes":[{"type":"string","value":"./base.css","stringType":"'"}]}]} // 找到url const url = values.nodes[0].nodes[0]; // 第一层的第一个的第一个 importItems.push(url.value); }); // 遍历规则,拿到图片地址 css.walkDecls(decl => { // 把value 就是 值 7.5px solid red // 通过Tokenizer.parseValues,把值变成了树结构 const values = Tokenizer.parseValues(decl.value); values.nodes.forEach(value => { value.nodes.forEach(item => { /* { type: 'url', stringType: "'", url: './bg.jpg', after: ' ' } { type: 'item', name: 'center', after: ' ' } { type: 'item', name: 'no-repeat' } */ if (item.type === 'url') { const url = item.url; item.url = `_CSS_URL_${ urlItems.length}_`; urlItems.push(url); // ['./bg.jpg'] } }); }); decl.value = Tokenizer.stringifyValues(values); // 转回字符串 }); return css; };}// css-loader是用来处理,解析@import "base.css"; url('./assets/logo.jpg')module.exports = function loader(source) { const callback = this.async(); // 开始处理 const options = { importItems: [], urlItems: [] }; // 插件转化,然后把url路径都转化成require('./bg.jpg'); // ... const pipeline = postcss([createPlugin(options)]); // 1rem 75px pipeline // .process("background: url('./bg.jpg') center no-repeat;") .process(source) .then(result => { // 拿到导入路径,拼接 const importCss = options.importItems .map(imp => { // stringifyRequest 可以把绝对路径转化成相对路径 return `require(${ loaderUtils.stringifyRequest(this, imp)})`; // 拼接 }) .join('\n'); // 拿到一个个import let cssString = JSON.stringify(result.css); // 包裹后就是"xxx" 双引号 cssString = cssString.replace(/@import\s+?["'][^'"]+?["'];/g, ''); cssString = cssString.replace(/_CSS_URL_(\d+?)_/g, function( matched, group1 ) { // 索引拿到,然后拿到这个,替换掉原来的_CSS_URL_0_哪些 const imgURL = options.urlItems[+group1]; // console.log('图片路径', imgURL); // "background: url('"+require('./bg.jpg')+"') center no-repeat;" return `"+require('${ imgURL}').default+"`; }); // url('_CSS_URL_1_') // console.log(JSON.stringify(options)); // console.log(result.css); callback( null, ` ${ importCss} module.exports = ${ cssString} ` ); });};
回答:
loader是文件加载器,能够加载资源文件,并对这些文件进行一些处理,诸如编译、压缩等,最终一起打包到指定的文件中
什么是plugin
在webpack运行的生命周期中会广播出许多事件,plugin可以监听这些事件,在合适的时机通过webpack提供的API改变输出结果。
loader和plugin的区别
对于loader,它是一个转换器,将A文件进行编译形成B文件,这里操作的是文件,比如将A.scss转换为A.css,单纯的文件转换过程
plugin是一个扩展器,它丰富了webpack本身,针对是loader结束后,webpack打包的整个过程,它并不直接操作文件,而是基于事件机制工作,会监听webpack打包过程中的某些节点,执行广泛的任务
回答:
Rollup的优势:
Rollup的缺点:
iife
格式,只能使用 amd
格式。Parcel 的优点:
Parcel的出现,是由于当时webpack使用上过于繁琐,文档不是很清晰。
而现在 webpack 已经越来越完善,开发者随着使用越来越熟悉。
并且 webpack 有更好的生态,扩展也会更丰富,出现问题很容易去解决
webpack 几乎可以实现Rollup和parcel 的全部功能。
并且不存在Rollup的那些缺点,并且随着发展,Rollup中的绝大多数优势几乎已经被抹平。
但webpack过于大而全,总体来说:
应用开发使用 webpack
库/框架开发使用 rollup
快速构建使用 parcel
其他官方答案:
webpack
为处理资源管理和分割代码而生,可以包含任何类型的文件。灵活,插件多。
rollup
用标准化的格式(es6)来写代码,通过减少死代码尽可能地缩小包体积。
parcel
超快的打包速度,多线程在多核上并发编译,不用任何配置。
对比
- 配置
webpack和rollup都需要配config文件,指明entry, output, plugin,transformations。二者的细微区别在于:
rollup 有对import/export所做的node polyfills,webpack没有 rollup支持相对路径,而webpack没有,所以得使用path.resolve/path.join。parcel则是完全开箱可用的,不用配置。
- 入口文件
webpack只支持js文件作为入口文件,如果要以其他格式的文件作为入口,比如html文件为入口,如要加第三方Plugin。
rollup可以用html作为入口文件,但也需要plugin,比如rollup-plugin-html-entry。
parcel可以用index.html作为入口文件,而且它会通过看index.html的script tag里包含的什么自己找到要打包生成哪些js文件。
- transformations
transformations指的是把其他文件转化成js文件的过程,需要经过transformation才能够被打包。
webpack使用Loaders来处理。
rollup使用plugins来处理。
parcel会自动去转换,当找到配置文件比如.babelrc, .postcssrc后就会自动转。
- 摇树优化
摇树优化是webpack的一大特性。需要1,用import/export语法,2,在package.json中加副作用的入口,3,加上支持去除死代码的缩小器(uglifyjsplugin)。
rollup会统计引入的代码并排除掉那些没有被用到的。这使您可以在现有工具和模块的基础上构建,而无需添加额外的依赖项或膨胀项目的大小。
parcel不支持摇树优化。
- dev server
webpack用webpack-dev-server。
rollup用rollup-plugin-serve和rollup-plugin-livereload共同作用。
parcel内置的有dev server。
- 热更新
webpack的 wepack-dev-server支持hot模式。
rollup不支持hmr。
parcel有内置的hmr。
- 代码分割
webpack通过在entry中手动设置,使用CommonsChunkPlugin,和模块内的内联函数动态引入来做代码分割。
rollup有实验性的代码分割特性。它是用es模块在浏览器中的模块加载机制本身来分割代码的。需要把experimentalCodeSplitting 和 experimentalDynamicImport 设为true。
parcel支持0配置的代码分割。主要是通过动态improt。
回答:
Babel 有两种并行的配置文件格式,可以一起使用,也可以分开使用。
babel.config.js - 项目范围的配置
.babelrc - 相对文件的配置
一般有了babel.config.js 就不会在去执行.babelrc的设置。
回答:
Tree shaking 是一个术语,通常用于描述移除Javascript上下文中的未引用代码(dead-code)的行为。
它依赖于ES6中的import和export语句,用来检测代码模块是否被导出、导入,且被Javascript文件使用。
在webpack中就是将多个JS文件打包为单个文件时,自动删除未引用的代码。以使最终文件具有简洁的结构和最小化大小。
webpack 通过usedExports: true
表示在输出结果中模块只导出外部使用了的成员。
minimize: true
开启代码压缩优化,删除注释、删除没有用到的代码、删除空白、替换变量名为简短的名称等。
官方答案:
tree shaking 是一个术语,通常用于描述移除 JavaScript 上下文中的未引用代码(dead-code)。它依赖于 ES2015 模块系统中的静态结构特性,例如 import 和 export。这个术语和概念实际上是兴起于 ES2015 模块打包工具 rollup。
新的 webpack 4 正式版本,扩展了这个检测能力,通过 package.json 的 “sideEffects” 属性作为标记,向 compiler 提供提示,表明项目中的哪些文件是 “pure(纯的 ES2015 模块)”,由此可以安全地删除文件中未使用的部分。 tree shaking的概念在1990年就提出了,但是直到ES6的ES6-style模块出现以后才真正被利用起来。这是因为tree shaking只能在静态modules下工作。ECMAScript 6 模块加载是静态的,因此整个依赖树可以被静态地推导出解析语法树。所以在ES6中使用tree shaking是非常容易的。而且,tree shaking不仅支持import/export级别,还支持statement(声明)级别。
回答:
eventbus 原理是创建一个事件中心,注册一些事件,并提供触发事件的方法。
Vue中,创建一个公共实例作为事件中心,在组件中引入这个实例。
通过 o n 注 册 自 定 义 事 件 , 在 需 要 触 发 地 方 使 用 on注册自定义事件,在需要触发地方使用 on注册自定义事件,在需要触发地方使用emit触发事件。
官方答案:
EventBus是消息传递的一种方式,基于一个消息中心,订阅和发布消息的模式,称为发布订阅者模式。
on(‘name’, fn)订阅消息,name:订阅的消息名称, fn: 订阅的消息
emit(‘name’, args)发布消息, name:发布的消息名称 , args:发布的消息
实现
class Bus { constructor () { this.callbacks = { } } $on(name,fn) { this.callbacks[name] = this.callbacks[name] || [] this.callbacks[name].push(fn) } $emit(name,args) { if(this.callbacks[name]){ //存在遍历所有callback this.callbacks[name].forEach(cb => cb(args)) } }}
使用
const EventBus = new EventBusClass()EventBus.on('fn1', function(msg) { alert(`订阅的消息是:${ msg}`);});EventBus.emit('fn1', '你好,世界!');
回答:
vue-loader加载器可以解析处理vue单文件组件(SFC)。
它可以将vue组件解析的每个部分使用对应的loader,例如sytle标签使用sass,template标签使用pub。
具体过程:
type
去执行对应的rules。官方答案:
vue-loader就是将
*.vue
文件变成*.bundle.js
,然后放入浏览器运行。而在这个过程当中,其实调用了三个内部loader(lib/style-compiler、lib/template-compiler和lib/selector)和多个外部loader(babel-loader、vue-style-loader、css-loader等等)。
JS部分:selector(参数type=script) 的处理结果是将
*.vue
中的 script 抽出来之后交给babel-loader去处理,最后生成可用的 JavaScript。HTML部分:selector (参数type=template) 的处理结果是将
*.vue
中的 template 抽出来之后交给 template-compiler 处理,最终输出成可用的 HTML。Style部分:selector (参数type=style) 的处理结果是将
*.vue
中的 style 抽出来之后交给 style-compiler 处理成设置好的样式(less、sass、css), 然后交给对应的 loader 处理生成 module, 之后通过 vue-style-loader或者style-loader 将 css 放在<style>
里面,最后注入到 HTML 中。
回答:
使用let 和 const 声明的变量,会绑定当前代码块的块级作用域,不受外部的影响,并且在执行声明语句之前不允许以任何形式访问变量(仍然进行了变量提升,只不过增加了限制)。
目的是明确变量的使用区域(绑定的块级作用域)和使用顺序(声明后使用)。
官方答案:
暂时性死区是ECMAScript与作用域相关的一个新语义模块, 在ES2015(又叫ES6)中引入。
https://sinaad.github.io/xfe/2016/02/26/temporal-dead-zone-tdz-demystified/
转载地址:http://cfzp.baihongyu.com/