关于javascript:阿里前端常考面试题集锦

2次阅读

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

如何防止 ajax 数据申请从新获取

一般而言,ajax 申请的数据都放在 redux 中存取。

代码输入后果

async function async1 () {await async2();
  console.log('async1');
  return 'async1 success'
}
async function async2 () {return new Promise((resolve, reject) => {console.log('async2')
    reject('error')
  })
}
async1().then(res => console.log(res))

输入后果如下:

async2
Uncaught (in promise) error

能够看到,如果 async 函数中抛出了谬误,就会终止谬误后果,不会持续向下执行。

如果想要让谬误不足之处前面的代码执行,能够应用 catch 来捕捉:

async function async1 () {await Promise.reject('error!!!').catch(e => console.log(e))
  console.log('async1');
  return Promise.resolve('async1 success')
}
async1().then(res => console.log(res))
console.log('script start')

这样的输入后果就是:

script start
error!!!
async1
async1 success

Nginx 的概念及其工作原理

Nginx 是一款轻量级的 Web 服务器,也能够用于反向代理、负载平衡和 HTTP 缓存等。Nginx 应用异步事件驱动的办法来解决申请,是一款面向性能设计的 HTTP 服务器。

传统的 Web 服务器如 Apache 是 process-based 模型的,而 Nginx 是基于 event-driven 模型的。正是这个次要的区别带给了 Nginx 在性能上的劣势。

Nginx 架构的最顶层是一个 master process,这个 master process 用于产生其余的 worker process,这一点和 Apache 十分像,然而 Nginx 的 worker process 能够同时解决大量的 HTTP 申请,而每个 Apache process 只能解决一个。

如何对我的项目中的图片进行优化?

  1. 不必图片。很多时候会应用到很多润饰类图片,其实这类润饰图片齐全能够用 CSS 去代替。
  2. 对于挪动端来说,屏幕宽度就那么点,齐全没有必要去加载原图节约带宽。个别图片都用 CDN 加载,能够计算出适配屏幕的宽度,而后去申请相应裁剪好的图片。
  3. 小图应用 base64 格局
  4. 将多个图标文件整合到一张图片中(雪碧图)
  5. 抉择正确的图片格式:

    • 对于可能显示 WebP 格局的浏览器尽量应用 WebP 格局。因为 WebP 格局具备更好的图像数据压缩算法,能带来更小的图片体积,而且领有肉眼辨认无差别的图像品质,毛病就是兼容性并不好
    • 小图应用 PNG,其实对于大部分图标这类图片,齐全能够应用 SVG 代替
    • 照片应用 JPEG

Virtual Dom 的劣势在哪里?

Virtual Dom 的劣势」其实这道题目面试官更想听到的答案不是上来就说「间接操作 / 频繁操作 DOM 的性能差」,如果 DOM 操作的性能如此不堪,那么 jQuery 也不至于活到明天。所以面试官更想听到 VDOM 想解决的问题以及为什么频繁的 DOM 操作会性能差。

首先咱们须要晓得:

DOM 引擎、JS 引擎 互相独立,但又工作在同一线程(主线程)JS 代码调用 DOM API 必须 挂起 JS 引擎、转换传入参数数据、激活 DOM 引擎,DOM 重绘后再转换可能有的返回值,最初激活 JS 引擎并继续执行若有频繁的 DOM API 调用,且浏览器厂商不做“批量解决”优化,引擎间切换的单位代价将迅速积攒若其中有强制重绘的 DOM API 调用,从新计算布局、从新绘制图像会引起更大的性能耗费。

其次是 VDOM 和实在 DOM 的区别和优化:

  1. 虚构 DOM 不会立马进行排版与重绘操作
  2. 虚构 DOM 进行频繁批改,而后一次性比拟并批改实在 DOM 中须要改的局部,最初在实在 DOM 中进行排版与重绘,缩小过多 DOM 节点排版与重绘损耗
  3. 虚构 DOM 无效升高大面积实在 DOM 的重绘与排版,因为最终与实在 DOM 比拟差别,能够只渲染部分

为什么须要浏览器缓存?

对于浏览器的缓存,次要针对的是前端的动态资源,最好的成果就是,在发动申请之后,拉取相应的动态资源,并保留在本地。如果服务器的动态资源没有更新,那么在下次申请的时候,就间接从本地读取即可,如果服务器的动态资源曾经更新,那么咱们再次申请的时候,就到服务器拉取新的资源,并保留在本地。这样就大大的缩小了申请的次数,进步了网站的性能。这就要用到浏览器的缓存策略了。

所谓的 浏览器缓存 指的是浏览器将用户申请过的动态资源,存储到电脑本地磁盘中,当浏览器再次拜访时,就能够间接从本地加载,不须要再去服务端申请了。

应用浏览器缓存,有以下长处:

  • 缩小了服务器的累赘,进步了网站的性能
  • 放慢了客户端网页的加载速度
  • 缩小了多余网络数据传输

常见的浏览器内核比拟

  • Trident: 这种浏览器内核是 IE 浏览器用的内核,因为在晚期 IE 占有大量的市场份额,所以这种内核比拟风行,以前有很多网页也是依据这个内核的规范来编写的,然而实际上这个内核对真正的网页规范反对不是很好。然而因为 IE 的高市场占有率,微软也很长时间没有更新 Trident 内核,就导致了 Trident 内核和 W3C 规范脱节。还有就是 Trident 内核的大量 Bug 等平安问题没有失去解决,加上一些专家学者公开本人认为 IE 浏览器不平安的观点,使很多用户开始转向其余浏览器。
  • Gecko: 这是 Firefox 和 Flock 所采纳的内核,这个内核的长处就是功能强大、丰盛,能够反对很多简单网页成果和浏览器扩大接口,然而代价是也不言而喻就是要耗费很多的资源,比方内存。
  • Presto: Opera 已经采纳的就是 Presto 内核,Presto 内核被称为公认的浏览网页速度最快的内核,这得益于它在开发时的天生劣势,在解决 JS 脚本等脚本语言时,会比其余的内核快 3 倍左右,毛病就是为了达到很快的速度而丢掉了一部分网页兼容性。
  • Webkit: Webkit 是 Safari 采纳的内核,它的长处就是网页浏览速度较快,尽管不迭 Presto 然而也胜于 Gecko 和 Trident,毛病是对于网页代码的容错性不高,也就是说对网页代码的兼容性较低,会使一些编写不规范的网页无奈正确显示。WebKit 前身是 KDE 小组的 KHTML 引擎,能够说 WebKit 是 KHTML 的一个开源的分支。
  • Blink: 谷歌在 Chromium Blog 上发表博客,称将与苹果的开源浏览器外围 Webkit 各奔前程,在 Chromium 我的项目中研发 Blink 渲染引擎(即浏览器外围),内置于 Chrome 浏览器之中。其实 Blink 引擎就是 Webkit 的一个分支,就像 webkit 是 KHTML 的分支一样。Blink 引擎当初是谷歌公司与 Opera Software 独特研发,下面提到过的,Opera 弃用了本人的 Presto 内核,退出 Google 营垒,追随谷歌一起研发 Blink。

什么是 CSRF 攻打?

(1)概念

CSRF 攻打指的是 跨站申请伪造攻打,攻击者诱导用户进入一个第三方网站,而后该网站向被攻打网站发送跨站申请。如果用户在被攻打网站中保留了登录状态,那么攻击者就能够利用这个登录状态,绕过后盾的用户验证,假冒用户向服务器执行一些操作。

CSRF 攻打的 实质是利用 cookie 会在同源申请中携带发送给服务器的特点,以此来实现用户的假冒。

(2)攻打类型

常见的 CSRF 攻打有三种:

  • GET 类型的 CSRF 攻打,比方在网站中的一个 img 标签里构建一个申请,当用户关上这个网站的时候就会主动发动提交。
  • POST 类型的 CSRF 攻打,比方构建一个表单,而后暗藏它,当用户进入页面时,主动提交这个表单。
  • 链接类型的 CSRF 攻打,比方在 a 标签的 href 属性里构建一个申请,而后诱导用户去点击。

代码输入后果

const promise1 = new Promise((resolve, reject) => {console.log('promise1')
  resolve('resolve1')
})
const promise2 = promise1.then(res => {console.log(res)
})
console.log('1', promise1);
console.log('2', promise2);

输入后果如下:

promise1
1 Promise{<resolved>: resolve1}
2 Promise{<pending>}
resolve1

须要留神的是,间接打印 promise1,会打印出它的状态值和参数。

代码执行过程如下:

  1. script 是一个宏工作,依照程序执行这些代码;
  2. 首先进入 Promise,执行该构造函数中的代码,打印promise1
  3. 碰到 resolve 函数, 将 promise1 的状态扭转为resolved, 并将后果保留下来;
  4. 碰到 promise1.then 这个微工作,将它放入微工作队列;
  5. promise2是一个新的状态为 pendingPromise
  6. 执行同步代码 1,同时打印出 promise1 的状态是resolved
  7. 执行同步代码 2,同时打印出 promise2 的状态是pending
  8. 宏工作执行结束,查找微工作队列,发现 promise1.then 这个微工作且状态为resolved,执行它。

IndexedDB 有哪些特点?

IndexedDB 具备以下特点:

  • 键值对贮存:IndexedDB 外部采纳对象仓库(object store)存放数据。所有类型的数据都能够间接存入,包含 JavaScript 对象。对象仓库中,数据以 ” 键值对 ” 的模式保留,每一个数据记录都有对应的主键,主键是举世无双的,不能有反复,否则会抛出一个谬误。
  • 异步:IndexedDB 操作时不会锁死浏览器,用户仍然能够进行其余操作,这与 LocalStorage 造成比照,后者的操作是同步的。异步设计是为了避免大量数据的读写,拖慢网页的体现。
  • 反对事务:IndexedDB 反对事务(transaction),这意味着一系列操作步骤之中,只有有一步失败,整个事务就都勾销,数据库回滚到事务产生之前的状态,不存在只改写一部分数据的状况。
  • 同源限度: IndexedDB 受到同源限度,每一个数据库对应创立它的域名。网页只能拜访本身域名下的数据库,而不能拜访跨域的数据库。
  • 贮存空间大:IndexedDB 的贮存空间比 LocalStorage 大得多,一般来说不少于 250MB,甚至没有下限。
  • 反对二进制贮存:IndexedDB 不仅能够贮存字符串,还能够贮存二进制数据(ArrayBuffer 对象和 Blob 对象)。

实现节流函数和防抖函数

函数防抖的实现:

function debounce(fn, wait) {
  var timer = null;

  return function() {
    var context = this,
      args = [...arguments];

    // 如果此时存在定时器的话,则勾销之前的定时器从新记时
    if (timer) {clearTimeout(timer);
      timer = null;
    }

    // 设置定时器,使事件间隔指定事件后执行
    timer = setTimeout(() => {fn.apply(context, args);
    }, wait);
  };
}

函数节流的实现:

// 工夫戳版
function throttle(fn, delay) {var preTime = Date.now();

  return function() {
    var context = this,
      args = [...arguments],
      nowTime = Date.now();

    // 如果两次工夫距离超过了指定工夫,则执行函数。if (nowTime - preTime >= delay) {preTime = Date.now();
      return fn.apply(context, args);
    }
  };
}

// 定时器版
function throttle (fun, wait){
  let timeout = null
  return function(){
    let context = this
    let args = [...arguments]
    if(!timeout){timeout = setTimeout(() => {fun.apply(context, args)
        timeout = null 
      }, wait)
    }
  }
}

浏览器的渲染过程

浏览器渲染次要有以下步骤:

  • 首先解析收到的文档,依据文档定义构建一棵 DOM 树,DOM 树是由 DOM 元素及属性节点组成的。
  • 而后对 CSS 进行解析,生成 CSSOM 规定树。
  • 依据 DOM 树和 CSSOM 规定树构建渲染树。渲染树的节点被称为渲染对象,渲染对象是一个蕴含有色彩和大小等属性的矩形,渲染对象和 DOM 元素绝对应,但这种对应关系不是一对一的,不可见的 DOM 元素不会被插入渲染树。还有一些 DOM 元素对应几个可见对象,它们个别是一些具备简单构造的元素,无奈用一个矩形来形容。
  • 当渲染对象被创立并增加到树中,它们并没有地位和大小,所以当浏览器生成渲染树当前,就会依据渲染树来进行布局(也能够叫做回流)。这一阶段浏览器要做的事件是要弄清楚各个节点在页面中的确切地位和大小。通常这一行为也被称为“主动重排”。
  • 布局阶段完结后是绘制阶段,遍历渲染树并调用渲染对象的 paint 办法将它们的内容显示在屏幕上,绘制应用 UI 根底组件。

大抵过程如图所示:

留神: 这个过程是逐渐实现的,为了更好的用户体验,渲染引擎将会尽可能早的将内容出现到屏幕上,并不会等到所有的 html 都解析实现之后再去构建和布局 render 树。它是解析完一部分内容就显示一部分内容,同时,可能还在通过网络下载其余内容。

点击刷新按钮或者按 F5、按 Ctrl+F5(强制刷新)、地址栏回车有什么区别?

  • 点击刷新按钮或者按 F5: 浏览器间接对本地的缓存文件过期,然而会带上 If-Modifed-Since,If-None-Match,这就意味着服务器会对文件查看新鲜度,返回后果可能是 304,也有可能是 200。
  • 用户按 Ctrl+F5(强制刷新): 浏览器不仅会对本地文件过期,而且不会带上 If-Modifed-Since,If-None-Match,相当于之前素来没有申请过,返回后果是 200。
  • 地址栏回车:浏览器发动申请,依照失常流程,本地查看是否过期,而后服务器查看新鲜度,最初返回内容。

什么是中间人攻打?如何防备中间人攻打?

两头⼈ (Man-in-the-middle attack, MITM) 是指攻击者与通信的两端别离创立独⽴的分割, 并替换其所收到的数据, 使通信的两端认为他们正在通过⼀个私密的连贯与对⽅直接对话, 但事实上整个会话都被攻击者齐全管制。在两头⼈攻打中,攻击者能够拦挡通信双⽅的通话并插⼊新的内容。

攻打过程如下:

  • 客户端发送申请到服务端,申请被两头⼈截获
  • 服务器向客户端发送公钥
  • 两头⼈截获公钥,保留在⾃⼰⼿上。而后⾃⼰⽣成⼀个 伪造的 公钥,发给客户端
  • 客户端收到伪造的公钥后,⽣成加密 hash 值发给服务器
  • 两头⼈取得加密 hash 值,⽤⾃⼰的私钥解密取得真秘钥, 同时⽣成假的加密 hash 值,发给服务器
  • 服务器⽤私钥解密取得假密钥, 而后加密数据传输给客户端

手写公布订阅

class EventListener {listeners = {};
    on(name, fn) {(this.listeners[name] || (this.listeners[name] = [])).push(fn)
    }
    once(name, fn) {let tem = (...args) => {this.removeListener(name, fn)
            fn(...args)
        }
        fn.fn = tem
        this.on(name, tem)
    }
    removeListener(name, fn) {if (this.listeners[name]) {this.listeners[name] = this.listeners[name].filter(listener => (listener != fn && listener != fn.fn))
        }
    }
    removeAllListeners(name) {if (name && this.listeners[name]) delete this.listeners[name]
        this.listeners = {}}
    emit(name, ...args) {if (this.listeners[name]) {this.listeners[name].forEach(fn => fn.call(this, ...args))
        }
    }
}

instanceof

题目形容: 手写 instanceof 操作符实现

实现代码如下:

function myInstanceof(left, right) {while (true) {if (left === null) {return false;}
    if (left.__proto__ === right.prototype) {return true;}
    left = left.__proto__;
  }
}

ES6 之前应用 prototype 实现继承

Object.create() 会创立一个“新”对象,而后将此对象外部的 [[Prototype]] 关联到你指定的对象(Foo.prototype)。Object.create(null) 创立一个空 [[Prototype]] 链接的对象,这个对象无奈进行委托。

function Foo(name) {this.name = name;}

Foo.prototype.myName = function () {return this.name;}

// 继承属性,通过借用结构函数调用
function Bar(name, label) {Foo.call(this, name);
  this.label = label;
}

// 继承办法,创立备份
Bar.prototype = Object.create(Foo.prototype);

// 必须设置回正确的构造函数,要不然在会产生判断类型出错
Bar.prototype.constructor = Bar;

 // 必须在上一步之后
Bar.prototype.myLabel = function () {return this.label;}

var a = new Bar("a", "obj a");

a.myName(); // "a"
a.myLabel(); // "obj a"

介绍下 promise 的个性、优缺点,外部是如何实现的,入手实现 Promise

1)Promise 根本个性

  • 1、Promise 有三种状态:pending(进行中)、fulfilled(已胜利)、rejected(已失败)
  • 2、Promise 对象承受一个回调函数作为参数, 该回调函数承受两个参数,别离是胜利时的回调 resolve 和失败时的回调 reject;另外 resolve 的参数除了正常值以外,还可能是一个 Promise 对象的实例;reject 的参数通常是一个 Error 对象的实例。
  • 3、then 办法返回一个新的 Promise 实例,并接管两个参数 onResolved(fulfilled 状态的回调);onRejected(rejected 状态的回调,该参数可选)
  • 4、catch 办法返回一个新的 Promise 实例
  • 5、finally 办法不论 Promise 状态如何都会执行,该办法的回调函数不承受任何参数
  • 6、Promise.all()办法将多个多个 Promise 实例,包装成一个新的 Promise 实例,该办法承受一个由 Promise 对象组成的数组作为参数 (Promise.all() 办法的参数能够不是数组,但必须具备 Iterator 接口,且返回的每个成员都是 Promise 实例),留神参数中只有有一个实例触发 catch 办法,都会触发 Promise.all()办法返回的新的实例的 catch 办法,如果参数中的某个实例自身调用了 catch 办法,将不会触发 Promise.all()办法返回的新实例的 catch 办法
  • 7、Promise.race()办法的参数与 Promise.all 办法一样,参数中的实例只有有一个率先扭转状态就会将该实例的状态传给 Promise.race()办法,并将返回值作为 Promise.race()办法产生的 Promise 实例的返回值
  • 8、Promise.resolve()将现有对象转为 Promise 对象,如果该办法的参数为一个 Promise 对象,Promise.resolve()将不做任何解决;如果参数 thenable 对象 (即具备 then 办法),Promise.resolve() 将该对象转为 Promise 对象并立刻执行 then 办法;如果参数是一个原始值,或者是一个不具备 then 办法的对象,则 Promise.resolve 办法返回一个新的 Promise 对象,状态为 fulfilled,其参数将会作为 then 办法中 onResolved 回调函数的参数,如果 Promise.resolve 办法不带参数,会间接返回一个 fulfilled 状态的 Promise 对象。须要留神的是,立刻 resolve()的 Promise 对象,是在本轮“事件循环”(event loop)的完结时执行,而不是在下一轮“事件循环”的开始时。
  • 9、Promise.reject()同样返回一个新的 Promise 对象,状态为 rejected,无论传入任何参数都将作为 reject()的参数

2)Promise 长处

  • ①对立异步 API

    • Promise 的一个重要长处是它将逐步被用作浏览器的异步 API,对立当初各种各样的 API,以及不兼容的模式和手法。
  • ②Promise 与事件比照

    • 和事件相比拟,Promise 更适宜解决一次性的后果。在后果计算出来之前或之后注册回调函数都是能够的,都能够拿到正确的值。Promise 的这个长处很天然。然而,不能应用 Promise 解决屡次触发的事件。链式解决是 Promise 的又一长处,然而事件却不能这样链式解决。
  • ③Promise 与回调比照

    • 解决了回调天堂的问题,将异步操作以同步操作的流程表达出来。
  • ④Promise 带来的额定益处是蕴含了更好的错误处理形式(蕴含了异样解决),并且写起来很轻松(因为能够重用一些同步的工具,比方 Array.prototype.map())。

3)Promise 毛病

  • 1、无奈勾销 Promise,一旦新建它就会立刻执行,无奈中途勾销。
  • 2、如果不设置回调函数,Promise 外部抛出的谬误,不会反馈到内部。
  • 3、当处于 Pending 状态时,无奈得悉目前停顿到哪一个阶段(刚刚开始还是行将实现)。
  • 4、Promise 真正执行回调的时候,定义 Promise 那局部实际上曾经走完了,所以 Promise 的报错堆栈上下文不太敌对。

4)简略代码实现
最简略的 Promise 实现有 7 个次要属性, state(状态), value(胜利返回值), reason(错误信息), resolve 办法, reject 办法, then 办法

class Promise{constructor(executor) {
    this.state = 'pending';
    this.value = undefined;
    this.reason = undefined;
    let resolve = value => {if (this.state === 'pending') {
        this.state = 'fulfilled';
        this.value = value;
      }
    };
    let reject = reason => {if (this.state === 'pending') {
        this.state = 'rejected';
        this.reason = reason;
      }
    };
    try {
      // 立刻执行函数
      executor(resolve, reject);
    } catch (err) {reject(err);
    }
  }
  then(onFulfilled, onRejected) {if (this.state === 'fulfilled') {let x = onFulfilled(this.value);
    };
    if (this.state === 'rejected') {let x = onRejected(this.reason);
    };
  }
}

5)面试够用版

function myPromise(constructor){ let self=this;
  self.status="pending" // 定义状态扭转前的初始状态 
  self.value=undefined;// 定义状态为 resolved 的时候的状态 
  self.reason=undefined;// 定义状态为 rejected 的时候的状态 
  function resolve(value){
    // 两个 ==="pending",保障了了状态的扭转是不不可逆的 
    if(self.status==="pending"){
      self.value=value;
      self.status="resolved"; 
    }
  }
  function reject(reason){
     // 两个 ==="pending",保障了了状态的扭转是不不可逆的
     if(self.status==="pending"){
        self.reason=reason;
        self.status="rejected"; 
      }
  }
  // 捕捉结构异样 
  try{constructor(resolve,reject);
  }catch(e){reject(e);
    } 
}
myPromise.prototype.then=function(onFullfilled,onRejected){ 
  let self=this;
  switch(self.status){case "resolved": onFullfilled(self.value); break;
    case "rejected": onRejected(self.reason); break;
    default: 
  }
}

// 测试
var p=new myPromise(function(resolve,reject){resolve(1)}); 
p.then(function(x){console.log(x)})
// 输入 1 

6)大厂专供版

const PENDING = "pending"; 
const FULFILLED = "fulfilled"; 
const REJECTED = "rejected";
const resolvePromise = (promise, x, resolve, reject) => {if (x === promise) {
    // If promise and x refer to the same object, reject promise with a TypeError as the reason.
    reject(new TypeError('循环援用'))
  }
  // if x is an object or function,
  if (x !== null && typeof x === 'object' || typeof x === 'function') {
    // If both resolvePromise and rejectPromise are called, or multiple calls to the same argument are made, the first call takes precedence, and any further calls are ignored.
    let called
    try { // If retrieving the property x.then results in a thrown exception e, reject promise with e as the reason.
      let then = x.then // Let then be x.then
      // If then is a function, call it with x as this
      if (typeof then === 'function') {// If/when resolvePromise is called with a value y, run [[Resolve]](promise, y)
        // If/when rejectPromise is called with a reason r, reject promise with r.
        then.call(x, y => {if (called) return
          called = true
          resolvePromise(promise, y, resolve, reject)
        }, r => {if (called) return
          called = true
          reject(r)
        })
      } else {
        // If then is not a function, fulfill promise with x.
        resolve(x)
      }
    } catch (e) {if (called) return
      called = true
      reject(e)
    }
  } else {
    // If x is not an object or function, fulfill promise with x
    resolve(x)
  }
}
function Promise(excutor) {
  let that = this; // 缓存以后 promise 实例例对象
  that.status = PENDING; // 初始状态
  that.value = undefined; // fulfilled 状态时 返回的信息
  that.reason = undefined; // rejected 状态时 回绝的起因 
  that.onFulfilledCallbacks = []; // 存储 fulfilled 状态对应的 onFulfilled 函数
  that.onRejectedCallbacks = []; // 存储 rejected 状态对应的 onRejected 函数
  function resolve(value) { // value 胜利态时接管的终值
    if(value instanceof Promise) {return value.then(resolve, reject);
    }
    // 实际中要确保 onFulfilled 和 onRejected ⽅办法异步执⾏行行,且应该在 then ⽅办法被调⽤用的那⼀一轮事件循环之后的新执⾏行行栈中执⾏行行。setTimeout(() => {
      // 调⽤用 resolve 回调对应 onFulfilled 函数
      if (that.status === PENDING) {// 只能由 pending 状态 => fulfilled 状态 (防止调⽤用屡次 resolve reject)
        that.status = FULFILLED;
        that.value = value;
        that.onFulfilledCallbacks.forEach(cb => cb(that.value));
      }
    });
  }
  function reject(reason) { // reason 失败态时接管的拒因
    setTimeout(() => {
      // 调⽤用 reject 回调对应 onRejected 函数
      if (that.status === PENDING) {// 只能由 pending 状态 => rejected 状态 (防止调⽤用屡次 resolve reject)
        that.status = REJECTED;
        that.reason = reason;
        that.onRejectedCallbacks.forEach(cb => cb(that.reason));
      }
    });
  }

  // 捕捉在 excutor 执⾏行行器器中抛出的异样
  // new Promise((resolve, reject) => {//     throw new Error('error in excutor')
  // })
  try {excutor(resolve, reject);
  } catch (e) {reject(e);
  }
}
Promise.prototype.then = function(onFulfilled, onRejected) {
  const that = this;
  let newPromise;
  // 解决理参数默认值 保障参数后续可能持续执⾏行行
  onFulfilled = typeof onFulfilled === "function" ? onFulfilled : value => value;
  onRejected = typeof onRejected === "function" ? onRejected : reason => {throw reason;};
  if (that.status === FULFILLED) { // 胜利态
    return newPromise = new Promise((resolve, reject) => {setTimeout(() => {
        try{let x = onFulfilled(that.value);
          resolvePromise(newPromise, x, resolve, reject); // 新的 promise resolve 上⼀一个 onFulfilled 的返回值
        } catch(e) {reject(e); // 捕捉前⾯面 onFulfilled 中抛出的异样 then(onFulfilled, onRejected);
        }
      });
    })
  }
  if (that.status === REJECTED) { // 失败态
    return newPromise = new Promise((resolve, reject) => {setTimeout(() => {
        try {let x = onRejected(that.reason);
          resolvePromise(newPromise, x, resolve, reject);
        } catch(e) {reject(e);
        }
      });
    });
  }
  if (that.status === PENDING) { // 期待态
// 当异步调⽤用 resolve/rejected 时 将 onFulfilled/onRejected 收集暂存到汇合中
    return newPromise = new Promise((resolve, reject) => {that.onFulfilledCallbacks.push((value) => {
        try {let x = onFulfilled(value);
          resolvePromise(newPromise, x, resolve, reject);
        } catch(e) {reject(e);
        }
      });
      that.onRejectedCallbacks.push((reason) => {
        try {let x = onRejected(reason);
          resolvePromise(newPromise, x, resolve, reject);
        } catch(e) {reject(e);
        }
      });
    });
  }
};

渲染过程中遇到 JS 文件如何解决?

JavaScript 的加载、解析与执行会阻塞文档的解析,也就是说,在构建 DOM 时,HTML 解析器若遇到了 JavaScript,那么它会暂停文档的解析,将控制权移交给 JavaScript 引擎,等 JavaScript 引擎运行结束,浏览器再从中断的中央复原持续解析文档。也就是说,如果想要首屏渲染的越快,就越不应该在首屏就加载 JS 文件,这也是都倡议将 script 标签放在 body 标签底部的起因。当然在当下,并不是说 script 标签必须放在底部,因为你能够给 script 标签增加 defer 或者 async 属性。

为什么 0.1 + 0.2 != 0.3,请详述理由

因为 JS 采纳 IEEE 754 双精度版本(64 位),并且只有采纳 IEEE 754 的语言都有该问题。

咱们都晓得计算机示意十进制是采纳二进制示意的,所以 0.1 在二进制示意为

// (0011) 示意循环
0.1 = 2^-4 * 1.10011(0011)

那么如何失去这个二进制的呢,咱们能够来演算下

小数算二进制和整数不同。乘法计算时,只计算小数位,整数位用作每一位的二进制,并且失去的第一位为最高位。所以咱们得出 0.1 = 2^-4 * 1.10011(0011),那么 0.2 的演算也根本如上所示,只须要去掉第一步乘法,所以得出 0.2 = 2^-3 * 1.10011(0011)

回来持续说 IEEE 754 双精度。六十四位中符号位占一位,整数位占十一位,其余五十二位都为小数位。因为 0.10.2 都是有限循环的二进制了,所以在小数位开端处须要判断是否进位(就和十进制的四舍五入一样)。

所以 2^-4 * 1.10011...001 进位后就变成了 2^-4 * 1.10011(0011 * 12 次)010。那么把这两个二进制加起来会得出 2^-2 * 1.0011(0011 * 11 次)0100 , 这个值算成十进制就是 0.30000000000000004

上面说一下原生解决办法,如下代码所示

parseFloat((0.1 + 0.2).toFixed(10))
正文完
 0