关于前端:高级前端一面面试题附答案

45次阅读

共计 15142 个字符,预计需要花费 38 分钟才能阅读完成。

对 CSS 工程化的了解

CSS 工程化是为了解决以下问题:

  1. 宏观设计:CSS 代码如何组织、如何拆分、模块构造怎么设计?
  2. 编码优化:怎么写出更好的 CSS?
  3. 构建:如何解决我的 CSS,能力让它的打包后果最优?
  4. 可维护性:代码写完了,如何最小化它后续的变更老本?如何确保任何一个共事都能轻松接手?

以下三个方向都是时下比拟风行的、普适性十分好的 CSS 工程化实际:

  • 预处理器:Less、Sass 等;
  • 重要的工程化插件:PostCss;
  • Webpack loader 等。

基于这三个方向,能够衍生出一些具备典型意义的子问题,这里咱们一一来看:

(1)预处理器:为什么要用预处理器?它的呈现是为了解决什么问题?

预处理器,其实就是 CSS 世界的“轮子”。预处理器反对咱们写一种相似 CSS、但理论并不是 CSS 的语言,而后把它编译成 CSS 代码:那为什么写 CSS 代码写得好好的,偏偏要转去写“类 CSS”呢?这就和原本用 JS 也能够实现所有性能,但最初却写 React 的 jsx 或者 Vue 的模板语法一样——为了爽!要想晓得有了预处理器有多爽,首先要晓得的是传统 CSS 有多不爽。随着前端业务复杂度的进步,前端工程中对 CSS 提出了以下的诉求:

  1. 宏观设计上:咱们心愿能优化 CSS 文件的目录构造,对现有的 CSS 文件实现复用;
  2. 编码优化上:咱们心愿能写出构造清晰、扼要易懂的 CSS,须要它具备高深莫测的嵌套层级关系,而不是无差别的一铺到底写法;咱们心愿它具备变量特色、计算能力、循环能力等等更强的可编程性,这样咱们能够少写一些无用的代码;
  3. 可维护性上:更强的可编程性意味着更优质的代码构造,实现复用意味着更简略的目录构造和更强的拓展能力,这两点如果能做到,天然会带来更强的可维护性。

这三点是传统 CSS 所做不到的,也正是预处理器所解决掉的问题。预处理器广泛会具备这样的个性:

  • 嵌套代码的能力,通过嵌套来反映不同 css 属性之间的层级关系;
  • 反对定义 css 变量;
  • 提供计算函数;
  • 容许对代码片段进行 extend 和 mixin;
  • 反对循环语句的应用;
  • 反对将 CSS 文件模块化,实现复用。

(2)PostCss:PostCss 是如何工作的?咱们在什么场景下会应用 PostCss?

它和预处理器的不同就在于,预处理器解决的是 类 CSS,而 PostCss 解决的就是 CSS 自身。Babel 能够将高版本的 JS 代码转换为低版本的 JS 代码。PostCss 做的是相似的事件:它能够编译尚未被浏览器广泛支持的先进的 CSS 语法,还能够主动为一些须要额定兼容的语法减少前缀。更强的是,因为 PostCss 有着弱小的插件机制,反对各种各样的扩大,极大地强化了 CSS 的能力。

PostCss 在业务中的应用场景十分多:

  • 进步 CSS 代码的可读性:PostCss 其实能够做相似预处理器能做的工作;
  • 当咱们的 CSS 代码须要适配低版本浏览器时,PostCss 的 Autoprefixer 插件能够帮忙咱们主动减少浏览器前缀;
  • 容许咱们编写面向未来的 CSS:PostCss 可能帮忙咱们编译 CSS next 代码;

(3)Webpack 能解决 CSS 吗?如何实现? Webpack 能解决 CSS 吗:

  • Webpack 在裸奔的状态下,是不能解决 CSS 的,Webpack 自身是一个面向 JavaScript 且只能解决 JavaScript 代码的模块化打包工具;
  • Webpack 在 loader 的辅助下,是能够解决 CSS 的。

如何用 Webpack 实现对 CSS 的解决:

  • Webpack 中操作 CSS 须要应用的两个要害的 loader:css-loader 和 style-loader
  • 留神,答出“用什么”有时候可能还不够,面试官会狐疑你是不是在背答案,所以你还须要理解每个 loader 都做了什么事件:

    • css-loader:导入 CSS 模块,对 CSS 代码进行编译解决;
    • style-loader:创立 style 标签,把 CSS 内容写入标签。

在理论应用中,css-loader 的执行程序肯定要安顿在 style-loader 的后面。因为只有实现了编译过程,才能够对 css 代码进行插入;若提前插入了未编译的代码,那么 webpack 是无奈了解这坨货色的,它会无情报错。

说一下购物车的逻辑?

//vue 中购物车逻辑的实现
1. 购物车信息用一个数组来存储,数组中保留对象,对象中有 id 和 count 属性

2. 在 vuex 中 state 中增加一个数据 cartList 用来保留这个数组

3. 因为商品详情页须要用到退出购物车性能,所以咱们须要提供一个 mutation, 用来将购物车信息退出 cartList 中

4. 退出购物车信息的时候,遵循如下规定:如果购物车中曾经有了该商品信息,则数量累加,如果没有该商品信息,则新增一个对象

5. 在商品详情页,点击退出购物车按钮的时候,调用 vuex 提供的 addToCart 这个 mutation 将以后的商品信息(id count)传给 addTocart  this.$store.commit("addToCart", {id:  , count:})

// js 中购物车逻辑的实现
1. 商品页点击“退出购物车”按钮,触发事件

2. 事件调用购物车“减少商品”的 Js 程序(函数、对象办法)3. 向 Js 程序传递传递“商品 id”、“商品数量”等数据

4. 存储“商品 id”、“商品数量”到浏览器的 localStorage 中

** 展现购物车中的商品 ******

1. 关上购物车页面

2. 从 localStorage 中取出“商品 Id”、“商品数量”等信息。3. 调用服务器端“取得商品详情”的接口失去购物车中的商品信息(参数为商品 Id)4. 将取得的商品信息显示在购物车页面。** 实现购物车中商品的购买 ******

1. 用户对购物车中的商品实现购买流程,产生购物订单

2. 革除 localStorage 中存储的曾经购买的商品信息

备注 1:购物车中商品存储的数据除了“商品 id”、“商品数量”之外,依据产品要求还能够有其余的信息,例如残缺的商品详情(这样就不必掉服务器接口取得详情了)、购物车商品的过期工夫,超过工夫的购物车商品在下次关上网站或者购物车页面时被革除。备注 2:购物车商品除了存储在 localStorage 中,依据产品的需要不同,也能够存储在 sessionStorage、cookie、session 中,或者间接向服务器接口发动申请存储在服务器上。何种状况应用哪种形式存储、有啥区别请本人剖析。

AJAX

题目形容: 利用 XMLHttpRequest 手写 AJAX 实现

实现代码如下:

const getJSON = function (url) {return new Promise((resolve, reject) => {const xhr = new XMLHttpRequest();
    xhr.open("GET", url, false);
    xhr.setRequestHeader("Content-Type", "application/json");
    xhr.onreadystatechange = function () {if (xhr.readyState !== 4) return;
      if (xhr.status === 200 || xhr.status === 304) {resolve(xhr.responseText);
      } else {reject(new Error(xhr.responseText));
      }
    };
    xhr.send();});
};

for…in 和 for…of 的区别

for…of 是 ES6 新增的遍历形式,容许遍历一个含有 iterator 接口的数据结构(数组、对象等)并且返回各项的值,和 ES3 中的 for…in 的区别如下

  • for…of 遍历获取的是对象的键值,for…in 获取的是对象的键名;
  • for… in 会遍历对象的整个原型链,性能十分差不举荐应用,而 for … of 只遍历以后对象不会遍历原型链;
  • 对于数组的遍历,for…in 会返回数组中所有可枚举的属性(包含原型链上可枚举的属性),for…of 只返回数组的下标对应的属性值;

总结: for…in 循环次要是为了遍历对象而生,不适用于遍历数组;for…of 循环能够用来遍历数组、类数组对象,字符串、Set、Map 以及 Generator 对象。

PWA 应用过吗?serviceWorker 的应用原理是啥?

渐进式网络应用(PWA)是谷歌在 2015 年底提出的概念。基本上算是 web 应用程序,但在外观和感觉上与 原生 app相似。反对 PWA 的网站能够提供脱机工作、推送告诉和设施硬件拜访等性能。

Service Worker是浏览器在后盾独立于网页运行的脚本,它关上了通向不须要网页或用户交互的性能的大门。当初,它们已包含如推送告诉和后盾同步等性能。未来,Service Worker将会反对如定期同步或天文围栏等其余性能。本教程探讨的外围性能是拦挡和解决网络申请,包含通过程序来治理缓存中的响应。

陈说输出 URL 回车后的过程

1. 读取缓存:搜寻本身的 DNS 缓存。(如果 DNS 缓存中找到 IP 地址就跳过了接下来查找 IP 地址步骤,间接拜访该 IP 地址。)
2.DNS 解析: 将域名解析成 IP 地址
3.TCP 连贯:TCP 三次握手,繁难形容三次握手
           客户端:服务端你在么?服务端:客户端我在,你要连贯我么?客户端:是的服务端,我要链接。连贯买通,能够开始申请来
4. 发送 HTTP 申请
5. 服务器解决申请并返回 HTTP 报文
6. 浏览器解析渲染页面
7. 断开连接:TCP 四次挥手

对于第六步浏览器解析渲染页面又能够聊聊如果返回的是 html 页面
依据 HTML 解析出 DOM 树
依据 CSS 解析生成 CSS 规定树
联合 DOM 树和 CSS 规定树,生成渲染树
依据渲染树计算每一个节点的信息
依据计算好的信息绘制页面

对媒体查问的了解?

媒体查问由⼀个可选的媒体类型和零个或多个使⽤媒体性能的限度了样式表范畴的表达式组成,例如宽度、⾼度和颜⾊。媒体查问,增加⾃ CSS3,容许内容的出现针对⼀个特定范畴的输出设备⽽进⾏裁剪,⽽不用扭转内容自身,适宜 web ⽹⻚应答不同型号的设施⽽做出对应的响应适配。

媒体查问蕴含⼀个可选的媒体类型和满⾜ CSS3 标准的条件下,蕴含零个或多个表达式,这些表达式形容了媒体特色,最终会被解析为 true 或 false。如果媒体查问中指定的媒体类型匹配展现⽂档所使⽤的设施类型,并且所有的表达式的值都是 true,那么该媒体查问的后果为 true。那么媒体查问内的款式将会⽣效。

<!-- link 元素中的 CSS 媒体查问 --> 
<link rel="stylesheet" media="(max-width: 800px)" href="example.css" /> 
<!-- 样式表中的 CSS 媒体查问 --> 
<style> 
@media (max-width: 600px) {.facet_sidebar {     display: none;} }
</style>

简略来说,应用 @media 查问,能够针对不同的媒体类型定义不同的款式。@media 能够针对不同的屏幕尺寸设置不同的款式,特地是须要设置设计响应式的页面,@media 是十分有用的。当重置浏览器大小的过程中,页面也会依据浏览器的宽度和高度从新渲染页面。

TCP 和 UDP 的区别

UDP TCP
是否连贯 无连贯 面向连贯
是否牢靠 不牢靠传输,不应用流量管制和拥塞管制 牢靠传输(数据程序和正确性),应用流量管制和拥塞管制
连贯对象个数 反对一对一,一对多,多对一和多对多交互通信 只能是一对一通信
传输方式 面向报文 面向字节流
首部开销 首部开销小,仅 8 字节 首部最小 20 字节,最大 60 字节
实用场景 实用于实时利用,例如视频会议、直播 实用于要求牢靠传输的利用,例如文件传输

new 一个函数产生了什么

结构调用:

  • 发明一个全新的对象
  • 这个对象会被执行 [[Prototype]] 连贯,将这个新对象的 [[Prototype]] 链接到这个构造函数.prototype 所指向的对象
  • 这个新对象会绑定到函数调用的 this
  • 如果函数没有返回其余对象,那么 new 表达式中的函数调用会主动返回这个新对象

0.1 + 0.2 === 0.3 嘛?为什么?

JavaScript 应用 Number 类型来示意数字(整数或浮点数),遵循 IEEE 754 规范,通过 64 位来示意一个数字(1 + 11 + 52)

  • 1 符号位,0 示意负数,1 示意正数 s
  • 11 指数位(e)
  • 52 尾数,小数局部(即有效数字)

最大平安数字:Number.MAX_SAFE_INTEGER = Math.pow(2, 53) – 1,转换成整数就是 16 位,所以 0.1 === 0.1,是因为通过 toPrecision(16) 去无效位之后,两者是相等的。

在两数相加时,会先转换成二进制,0.1 和 0.2 转换成二进制的时候尾数会产生有限循环,而后进行对阶运算,JS 引擎对二进制进行截断,所以造成精度失落。

所以总结:精度失落可能呈现在进制转换和对阶运算中

compose

题目形容: 实现一个 compose 函数

// 用法如下:
function fn1(x) {return x + 1;}
function fn2(x) {return x + 2;}
function fn3(x) {return x + 3;}
function fn4(x) {return x + 4;}
const a = compose(fn1, fn2, fn3, fn4);
console.log(a(1)); // 1+4+3+2+1=11

实现代码如下:

function compose(...fn) {if (!fn.length) return (v) => v;
  if (fn.length === 1) return fn[0];
  return fn.reduce((pre, cur) =>
      (...args) =>
        pre(cur(...args))
  );
}

对 JSON 的了解

JSON 是一种基于文本的轻量级的数据交换格局。它能够被任何的编程语言读取和作为数据格式来传递。

在我的项目开发中,应用 JSON 作为前后端数据交换的形式。在前端通过将一个合乎 JSON 格局的数据结构序列化为
JSON 字符串,而后将它传递到后端,后端通过 JSON 格局的字符串解析后生成对应的数据结构,以此来实现前后端数据的一个传递。

因为 JSON 的语法是基于 js 的,因而很容易将 JSON 和 js 中的对象弄混,然而应该留神的是 JSON 和 js 中的对象不是一回事,JSON 中对象格局更加严格,比如说在 JSON 中属性值不能为函数,不能呈现 NaN 这样的属性值等,因而大多数的 js 对象是不合乎 JSON 对象的格局的。

在 js 中提供了两个函数来实现 js 数据结构和 JSON 格局的转换解决,

  • JSON.stringify 函数,通过传入一个合乎 JSON 格局的数据结构,将其转换为一个 JSON 字符串。如果传入的数据结构不合乎 JSON 格局,那么在序列化的时候会对这些值进行对应的非凡解决,使其符合规范。在前端向后端发送数据时,能够调用这个函数将数据对象转化为 JSON 格局的字符串。
  • JSON.parse() 函数,这个函数用来将 JSON 格局的字符串转换为一个 js 数据结构,如果传入的字符串不是规范的 JSON 格局的字符串的话,将会抛出谬误。当从后端接管到 JSON 格局的字符串时,能够通过这个办法来将其解析为一个 js 数据结构,以此来进行数据的拜访。

代码输入后果

async function async1() {console.log("async1 start");
  await async2();
  console.log("async1 end");
}

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

console.log("script start");

setTimeout(function() {console.log("setTimeout");
}, 0);

async1();

new Promise(resolve => {console.log("promise1");
  resolve();}).then(function() {console.log("promise2");
});
console.log('script end')

输入后果如下:

script start
async1 start
async2
promise1
script end
async1 end
promise2
setTimeout

代码执行过程如下:

  1. 结尾定义了 async1 和 async2 两个函数,然而并未执行,执行 script 中的代码,所以打印出 script start;
  2. 遇到定时器 Settimeout,它是一个宏工作,将其退出到宏工作队列;
  3. 之后执行函数 async1,首先打印出 async1 start;
  4. 遇到 await,执行 async2,打印出 async2,并阻断前面代码的执行,将前面的代码退出到微工作队列;
  5. 而后跳出 async1 和 async2,遇到 Promise,打印出 promise1;
  6. 遇到 resolve,将其退出到微工作队列,而后执行前面的 script 代码,打印出 script end;
  7. 之后就该执行微工作队列了,首先打印出 async1 end,而后打印出 promise2;
  8. 执行完微工作队列,就开始执行宏工作队列中的定时器,打印出 setTimeout。

Unicode、UTF-8、UTF-16、UTF-32 的区别?

(1)Unicode

在说 Unicode 之前须要先理解一下 ASCII 码:ASCII 码(American Standard Code for Information Interchange)称为美国规范信息替换码。

  • 它是基于拉丁字母的一套电脑编码零碎。
  • 它定义了一个用于代表常见字符的字典。
  • 它蕴含了 ”A-Z”(蕴含大小写),数据 ”0-9″ 以及一些常见的符号。
  • 它是专门为英语而设计的,有 128 个编码,对其余语言无能为力

ASCII码能够示意的编码无限,要想示意其余语言的编码,还是要应用 Unicode 来示意,能够说 UnicodeASCII 的超集。

Unicode全称 Unicode Translation Format,又叫做对立码、万国码、繁多码。Unicode 是为了解决传统的字符编码方案的局限而产生的,它为每种语言中的每个字符设定了对立并且惟一的二进制编码,以满足跨语言、跨平台进行文本转换、解决的要求。

Unicode的实现形式(也就是编码方式)有很多种,常见的是 UTF-8UTF-16UTF-32USC-2

(2)UTF-8

UTF-8是应用最宽泛的 Unicode 编码方式,它是一种可变长的编码方式,能够是 1—4 个字节不等,它能够齐全兼容 ASCII 码的 128 个字符。

留神: UTF-8 是一种编码方式,Unicode是一个字符汇合。

UTF-8的编码规定:

  • 对于 单字节 的符号,字节的第一位为 0,前面的 7 位为这个字符的 Unicode 编码,因而对于英文字母,它的 Unicode 编码和 ACSII 编码一样。
  • 对于 n 字节 的符号,第一个字节的前 n 位都是 1,第 n + 1 位设为 0,前面字节的前两位一律设为 10,剩下的没有提及的二进制位,全副为这个符号的 Unicode 码。

来看一下具体的 Unicode 编号范畴与对应的 UTF-8 二进制格局:

编码范畴(编号对应的十进制数) 二进制格局
0x00—0x7F(0-127) 0xxxxxxx
0x80—0x7FF(128-2047) 110xxxxx 10xxxxxx
0x800—0xFFFF(2048-65535) 1110xxxx 10xxxxxx 10xxxxxx
0x10000—0x10FFFF(65536 以上) 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

那该如何通过具体的 Unicode 编码,进行具体的 UTF-8 编码呢?步骤如下:

  • 找到该 Unicode 编码的所在的编号范畴,进而找到与之对应的二进制格局
  • Unicode 编码转换为二进制数(去掉最高位的 0)
  • 将二进制数从右往左一次填入二进制格局的 X 中,如果有 X 未填,就设为 0

来看一个理论的例子:
”字的Unicode 编码是:0x9A6C,整数编号是39532(1)首选确定了该字符在第三个范畴内,它的格局是 1110xxxx 10xxxxxx 10xxxxxx(2)39532 对应的二进制数为1001 1010 0110 1100(3)将二进制数填入 X 中,后果是:11101001 10101001 10101100

(3)UTF-16

1. 立体的概念

在理解 UTF-16 之前,先看一下 立体 的概念:Unicode编码中有很多很多的字符,它并不是一次性定义的,而是分区进行定义的,每个区寄存 65536(216)个字符,这称为一个 立体,目前总共有 17 个立体。

最后面的一个立体称为 根本立体 ,它的码点从0 — 216-1,写成 16 进制就是U+0000 — U+FFFF,那剩下的 16 个立体就是 辅助立体,码点范畴是 U+10000—U+10FFFF

2. UTF-16 概念:

UTF-16也是 Unicode 编码集的一种编码模式,把 Unicode 字符集的形象码位映射为 16 位长的整数(即码元)的序列,用于数据存储或传递。Unicode字符的码位须要 1 个或者 2 个 16 位长的码元来示意,因而 UTF-16 也是用变长字节示意的。

3. UTF-16 编码规定:

  • 编号在 U+0000—U+FFFF 的字符(罕用字符集),间接用两个字节示意。
  • 编号在 U+10000—U+10FFFF 之间的字符,须要用四个字节示意。

4. 编码辨认

那么问题来了,当遇到两个字节时,怎么晓得是把它当做一个字符还是和前面的两个字节一起当做一个字符呢?

UTF-16 编码必定也思考到了这个问题,在根本立体内,从 U+D800 — U+DFFF 是一个空段,也就是说这个区间的码点不对应任何的字符,因而这些空段就能够用来映射辅助立体的字符。

辅助立体共有 220 个字符位,因而示意这些字符至多须要 20 个二进制位。UTF-16 将这 20 个二进制位分成两半,前 10 位映射在 U+D800 — U+DBFF,称为 高位 (H),后 10 位映射在 U+DC00 — U+DFFF,称为 低位(L)。这就相当于,将一个辅助立体的字符拆成了两个根本立体的字符来示意。

因而,当遇到两个字节时,发现它的码点在 U+D800 —U+DBFF之间,就能够晓得,它前面的两个字节的码点应该在 U+DC00 — U+DFFF 之间,这四个字节必须放在一起进行解读。

5. 举例说明

以 “𡠀” 字为例,它的 Unicode 码点为 0x21800,该码点超出了根本立体的范畴,因而须要用四个字节来示意,步骤如下:

  • 首先计算超出局部的后果:0x21800 - 0x10000
  • 将下面的计算结果转为 20 位的二进制数,有余 20 位就在后面补 0,后果为:0001000110 0000000000
  • 将失去的两个 10 位二进制数别离对应到两个区间中
  • U+D800 对应的二进制数为 1101100000000000,将 0001000110 填充在它的后 10 个二进制位,失去 1101100001000110,转成 16 进制数为 0xD846。同理,低位为 0xDC00,所以这个字的UTF-16 编码为 0xD846 0xDC00

(4)UTF-32

UTF-32 就是字符所对应编号的整数二进制模式,每个字符占四个字节,这个是间接进行转换的。该编码方式占用的贮存空间较多,所以应用较少。

比方“”字的 Unicode 编号是:U+9A6C,整数编号是39532,间接转化为二进制:1001 1010 0110 1100,这就是它的 UTF-32 编码。

(5)总结

Unicode、UTF-8、UTF-16、UTF-32 有什么区别?

  • Unicode 是编码字符集(字符集),而 UTF-8UTF-16UTF-32 是字符集编码(编码规定);
  • UTF-16 应用变长码元序列的编码方式,相较于定长码元序列的 UTF-32 算法更简单,甚至比同样是变长码元序列的 UTF-8 也更为简单,因为其引入了独特的 代理对 这样的代理机制;
  • UTF-8须要判断每个字节中的结尾标记信息,所以如果某个字节在传送过程中出错了,就会导致前面的字节也会解析出错;而 UTF-16 不会判断结尾标记,即便错也只会错一个字符,所以容错能力教强;
  • 如果字符内容全副英文或英文与其余文字混合,但英文占绝大部分,那么用 UTF-8 就比 UTF-16 节俭了很多空间;而如果字符内容全副是中文这样相似的字符或者混合字符中中文占绝大多数,那么 UTF-16 就占优势了,能够节俭很多空间;

计算属性和 watch 有什么区别? 以及它们的使用场景?

// 区别
  computed 计算属性:依赖其它属性值,并且 computed 的值有缓存,只有它依赖的属性值产生扭转,下一次获取 computed 的值时才会从新计算 computed 的值。watch 侦听器:更多的是察看的作用, 无缓存性, 相似与某些数据的监听回调, 每当监听的数据变动时都会执行回调进行后续操作
// 使用场景
  当须要进行数值计算, 并且依赖与其它数据时, 应该应用 computed, 因为能够利用 computed 的缓存属性, 防止每次获取值时都要从新计算。当须要在数据变动时执行异步或开销较大的操作时, 应该应用 watch, 应用 watch 选项容许执行异步操作(拜访一个 API), 限度执行该操作的频率,并在失去最终后果前,设置中间状态。这些都是计算属性无奈做到的。

代码输入后果

const async1 = async () => {console.log('async1');
  setTimeout(() => {console.log('timer1')
  }, 2000)
  await new Promise(resolve => {console.log('promise1')
  })
  console.log('async1 end')
  return 'async1 success'
} 
console.log('script start');
async1().then(res => console.log(res));
console.log('script end');
Promise.resolve(1)
  .then(2)
  .then(Promise.resolve(3))
  .catch(4)
  .then(res => console.log(res))
setTimeout(() => {console.log('timer2')
}, 1000)

输入后果如下:

script start
async1
promise1
script end
1
timer2
timer1

代码的执行过程如下:

  1. 首先执行同步带吗,打印出 script start;
  2. 遇到定时器 timer1 将其退出宏工作队列;
  3. 之后是执行 Promise,打印出 promise1,因为 Promise 没有返回值,所以前面的代码不会执行;
  4. 而后执行同步代码,打印出 script end;
  5. 继续执行上面的 Promise,.then 和.catch 冀望参数是一个函数,这里传入的是一个数字,因而就会产生值浸透,将 resolve(1)的值传到最初一个 then,间接打印出 1;
  6. 遇到第二个定时器,将其退出到微工作队列,执行微工作队列,按程序顺次执行两个定时器,然而因为定时器工夫的起因,会在两秒后先打印出 timer2,在四秒后打印出 timer1。

函数柯里化

什么叫函数柯里化?其实就是将应用多个参数的函数转换成一系列应用一个参数的函数的技术。还不懂?来举个例子。

function add(a, b, c) {return a + b + c}
add(1, 2, 3)
let addCurry = curry(add)
addCurry(1)(2)(3)

当初就是要实现 curry 这个函数,使函数从一次调用传入多个参数变成屡次调用每次传一个参数。

function curry(fn) {let judge = (...args) => {if (args.length == fn.length) return fn(...args)
        return (...arg) => judge(...args, ...arg)
    }
    return judge
}

ES6 新个性

1.ES6 引入来严格模式
    变量必须申明后在应用
    函数的参数不能有同名属性, 否则报错
    不能应用 with 语句 (说实话我根本没用过)
    不能对只读属性赋值, 否则报错
    不能应用前缀 0 示意八进制数, 否则报错 (说实话我根本没用过)
    不能删除不可删除的数据, 否则报错
    不能删除变量 delete prop, 会报错, 只能删除属性 delete global[prop]
    eval 不会在它的外层作用域引入变量
    eval 和 arguments 不能被从新赋值
    arguments 不会主动反映函数参数的变动
    不能应用 arguments.caller (说实话我根本没用过)
    不能应用 arguments.callee (说实话我根本没用过)
    禁止 this 指向全局对象
    不能应用 fn.caller 和 fn.arguments 获取函数调用的堆栈 (说实话我根本没用过)
    减少了保留字(比方 protected、static 和 interface)2. 对于 let 和 const 新增的变量申明

3. 变量的解构赋值

4. 字符串的扩大
    includes():返回布尔值,示意是否找到了参数字符串。startsWith():返回布尔值,示意参数字符串是否在原字符串的头部。endsWith():返回布尔值,示意参数字符串是否在原字符串的尾部。5. 数值的扩大
    Number.isFinite()用来查看一个数值是否为无限的(finite)。Number.isNaN()用来查看一个值是否为 NaN。6. 函数的扩大
    函数参数指定默认值
7. 数组的扩大
    扩大运算符
8. 对象的扩大
    对象的解构
9. 新增 symbol 数据类型

10.Set 和 Map 数据结构 
    ES6 提供了新的数据结构 Set。它相似于数组,然而成员的值都是惟一的,没有反复的值。Set 自身是一个构造函数,用来生成 Set 数据结构。Map 它相似于对象,也是键值对的汇合,然而“键”的范畴不限于字符串,各种类型的值(包含对象)都能够当作键。11.Proxy
    Proxy 能够了解成,在指标对象之前架设一层“拦挡”,外界对该对象的拜访
    都必须先通过这层拦挡,因而提供了一种机制,能够对外界的拜访进行过滤和改写。Proxy 这个词的原意是代理,用在这里示意由它来“代理”某些操作,能够译为“代理器”。Vue3.0 应用了 proxy
12.Promise
    Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更正当和更弱小。特点是:对象的状态不受外界影响。一旦状态扭转,就不会再变,任何时候都能够失去这个后果。13.async 函数 
    async 函数对 Generator 函数的区别:(1)内置执行器。Generator 函数的执行必须靠执行器,而 async 函数自带执行器。也就是说,async 函数的执行,与一般函数截然不同,只有一行。(2)更好的语义。async 和 await,比起星号和 yield,语义更分明了。async 示意函数里有异步操作,await 示意紧跟在前面的表达式须要期待后果。(3)失常状况下,await 命令前面是一个 Promise 对象。如果不是,会被转成一个立刻 resolve 的 Promise 对象。(4)返回值是 Promise。async 函数的返回值是 Promise 对象,这比 Generator 函数的返回值是 Iterator 对象不便多了。你能够用 then 办法指定下一步的操作。14.Class 
    class 跟 let、const 一样:不存在变量晋升、不能反复申明...
    ES6 的 class 能够看作只是一个语法糖,它的绝大部分性能
    ES5 都能够做到,新的 class 写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。15.Module
    ES6 的模块主动采纳严格模式,不论你有没有在模块头部加上 "use strict";。import 和 export 命令以及 export 和 export default 的区别

call apply bind

题目形容: 手写 call apply bind 实现

实现代码如下:

Function.prototype.myCall = function (context, ...args) {if (!context || context === null) {context = window;}
  // 发明惟一的 key 值  作为咱们结构的 context 外部办法名
  let fn = Symbol();
  context[fn] = this; //this 指向调用 call 的函数
  // 执行函数并返回后果 相当于把本身作为传入的 context 的办法进行调用了
  return context[fn](...args);
};

// apply 原理统一  只是第二个参数是传入的数组
Function.prototype.myApply = function (context, args) {if (!context || context === null) {context = window;}
  // 发明惟一的 key 值  作为咱们结构的 context 外部办法名
  let fn = Symbol();
  context[fn] = this;
  // 执行函数并返回后果
  return context[fn](...args);
};

//bind 实现要简单一点  因为他思考的状况比拟多 还要波及到参数合并(相似函数柯里化)

Function.prototype.myBind = function (context, ...args) {if (!context || context === null) {context = window;}
  // 发明惟一的 key 值  作为咱们结构的 context 外部办法名
  let fn = Symbol();
  context[fn] = this;
  let _this = this;
  //  bind 状况要简单一点
  const result = function (...innerArgs) {
    // 第一种状况 : 若是将 bind 绑定之后的函数当作构造函数,通过 new 操作符应用,则不绑定传入的 this,而是将 this 指向实例化进去的对象
    // 此时因为 new 操作符作用  this 指向 result 实例对象  而 result 又继承自传入的_this 依据原型链常识可得出以下论断
    // this.__proto__ === result.prototype   //this instanceof result =>true
    // this.__proto__.__proto__ === result.prototype.__proto__ === _this.prototype; //this instanceof _this =>true
    if (this instanceof _this === true) {
      // 此时 this 指向指向 result 的实例  这时候不须要扭转 this 指向
      this[fn] = _this;
      this[fn](...[...args, ...innerArgs]); // 这里应用 es6 的办法让 bind 反对参数合并
    } else {
      // 如果只是作为一般函数调用  那就很简略了 间接扭转 this 指向为传入的 context
      context[fn](...[...args, ...innerArgs]);
    }
  };
  // 如果绑定的是构造函数 那么须要继承构造函数原型属性和办法
  // 实现继承的形式: 应用 Object.create
  result.prototype = Object.create(this.prototype);
  return result;
};

// 用法如下

// function Person(name, age) {//   console.log(name); //'我是参数传进来的 name'
//   console.log(age); //'我是参数传进来的 age'
//   console.log(this); // 构造函数 this 指向实例对象
// }
// // 构造函数原型的办法
// Person.prototype.say = function() {//   console.log(123);
// }
// let obj = {
//   objName: '我是 obj 传进来的 name',
//   objAge: '我是 obj 传进来的 age'
// }
// // 一般函数
// function normalFun(name, age) {//   console.log(name);   //'我是参数传进来的 name'
//   console.log(age);   //'我是参数传进来的 age'
//   console.log(this); // 一般函数 this 指向绑定 bind 的第一个参数 也就是例子中的 obj
//   console.log(this.objName); //'我是 obj 传进来的 name'
//   console.log(this.objAge); //'我是 obj 传进来的 age'
// }

// 先测试作为结构函数调用
// let bindFun = Person.myBind(obj, '我是参数传进来的 name')
// let a = new bindFun('我是参数传进来的 age')
// a.say() //123

// 再测试作为一般函数调用
// let bindFun = normalFun.myBind(obj, '我是参数传进来的 name')
//  bindFun('我是参数传进来的 age')

手写题:数组扁平化

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;
}

const a = [1, [2, [3, 4]]];
console.log(flatten(a));

正文完
 0