金三银四,磨砺锋芒;剑指大厂,扬帆起航(2020年最全大厂WEB前端面试题精选)上

2020-01-15

金三银四,磨砺锋芒;剑指大厂,扬帆起航(2020年最全大厂WEB前端面试题精选)上

引言

元旦匆匆而过,2020年的春节又接踵而来,大家除了忙的提着裤子加班、年底冲冲冲外,还有着对于明年的迷茫和期待!2019年有多少苦涩心酸,2020年就有更多幸福美好,加油,奥利给!怀着一颗积极向上的心,来面对未来每一天的挑战!

所谓“兵马未动,粮草先行”,我们打响明天的战役也需要精神食粮来做后勤保障才是。在此我整理了多位从业者和我在2019年底至2020年初的一厂面试精选题,希望对磨砺锋芒、奋发向上的小伙伴有所帮助,祝你早日剑指大厂,扬帆起航,奥利给!

CSS

1、讲讲盒模型(蚂蚁金服 2019.03 招行信用卡 2019.04 美团 作业帮)

盒子模型就是 元素在网页中的实际占位,有两种:标准盒子模型和IE盒子模型

标准(W3C)盒子模型:内容content+填充padding+边框border+边界margin宽高指的是 content 的宽高

低版本IE盒子模型:内容(content+padding+border)+ 边界margin,宽高指的是content+padding+border 部分的宽高

/* 标准模型 */
box-sizing:content-box;
 /*IE模型*/
box-sizing:border-box;

2、根据盒模型解释边距重叠(蚂蚁金服 2019.03 图森未来)

父子元素、兄弟元素,当有外边距时,会取其中一个边距的最大值,作为实际的边距。
空元素的有上下边距时,也会取其中更大的一个边距值,作为实际的边距。
这就是边距重叠。

BFC:
概念:块级格式化上下文
原理:

  1. 在BFC这个元素垂直方向的边距会发生重叠
  2. BFC的区域不会与浮动元素的box重叠
  3. BFC在页面上是一个独立的容器,其里外的元素不会互相影响
  4. 计算BFC高度时,浮动元素也会参与计算

3、 宽高比4:3自适应 (蚂蚁金服 2019.03 )

垂直方向的padding: 在css中,padding-top或padding-bottom的百分比值是根据容器的width来计算的。

.wrap{
       position: relative;
       height: 0;  //容器的height设置为0
       width: 100%;
       padding-top: 75%;  //100%*3/4
}
.wrap > *{
       position: absolute;//容器的内容的所有元素absolute,然子元素内容都将被padding挤出容器
       left: 0;
       top: 0;
       width: 100%;
       height: 100%;
}
+ **padding & calc()**: 跟第一种方法原理相同
​```css
padding-top: calc(100%*9/16);
  • padding & 伪元素
  • 视窗单位: 浏览器100vw表示浏览器的视窗宽度
width:100vw;
height:calc(100vw*3/4)

4、css选择器的优先级( 百度前端 搜狐 美团 拼多多 VIVO)

优先级就近原则,同权重情况下样式定义最近者为准

!important>id >class>tag

important比内联优先级高

元素选择符的权值:元素标签(派生选择器):1,class选择符:10,id选择符:100,内联样式权值最大,为1000

  1. !important声明的样式优先级最高,如果冲突再进行计算。
  2. 如果优先级相同,则选择最后出现的样式。
  3. 继承得到的样式的优先级最低。

5、css水平居中 (蚂蚁金服、百度前端、字节跳动)

一、对于行内元素:

text-align:center;

二、对于确定宽度的块级元素:

(1)margin和width实现水平居中

常用(前提:已设置width值):margin-left:auto; margin-right:auto;

(2)绝对定位和margin-left: -(宽度值/2)实现水平居中

固定宽度块级元素水平居中,通过使用绝对定位,以及设置元素margin-left为其宽度的一半

.content{

width: 200px;

position: absolute;

left: 50%;

margin-left: -100px; // 该元素宽度的一半,即100px

background-color: aqua;

}

(3)position:absolute + (left=0+top=0+right=0+bottom=0) + margin:auto

.content{

position: absolute;

width: 200px;

top: 0;

right: 0;

bottom: 0;

left: 0;

margin: auto;

}

三、对于未知宽度的块级元素:

(1)table标签配合margin左右auto实现水平居中

使用table标签(或直接将块级元素设值为display:table),再通过给该标签添加左右margin为auto

(2)inline-block实现水平居中方法

display:inline-block;(或display:inline)和text-align:center;实现水平居中

存在问题:需额外处理inline-block的浏览器兼容性(解决inline-block元素的空白间距)

(3)绝对定位实现水平居中

绝对定位+transform,translateX可以移动本省元素的50%

.content{

position: absolute;

left: 50%;

transform: translateX(-50%); /* 移动元素本身50% */

background: aqua;

}

(4)相对定位实现水平居中

用float或者display把父元素变成行内块状元素

.contentParent{

display: inline-block; /* 把父元素转化为行内块状元素 */

/*float: left; 把父元素转化为行内块状元素 */

position: relative;

left: 50%;

}

/*目标元素*/

.content{

position: relative;

right: 50%;

background-color:aqua;

}

(5)CSS3的flex实现水平居中方法,法一

.contentParent{

display: flex;

flex-direction: column;

}

.content{

align-self:center;

}

(6)CSS3的flex实现水平居中方法,法二

.contentParent{

display: flex;

}

.content{

margin: auto;

}

(7)CSS3的fit-content配合左右margin为auto实现水平居中方法

.content{

width: fit-content;

margin-left: auto;

margin-right: auto;

}

参考链接 https://blog.csdn.net/dengdongxia/article/details/80297116

6、CSS3 的 flexbox(弹性盒布局模型)以及适用场景 (猿辅导)

该布局模型的目的是提供一种更加高效的方式来对容器中的条目进行布局、对齐和分配空间。在传统的布局方式中,block 布局是把块在垂直方向从上到下依次排列的;而 inline 布局则是在水平方向来排列。弹性盒布局并没有这样内在的方向限制,可以由开发人员自由操作。flexbox设置父元素的display属性为flex,则子元素都变成flex item,由此可以控制子元素的排列方式、尺寸、间距等;
试用场景:弹性布局适合于移动前端开发,在Android和ios上也完美支持。

7、如何画一个三角形 (星环科技)

左右边框设置为透明,长度为底部边框的一半。左右边框长度必须设置,不设置则只有底部一条边框,是不能展示的。

{width: 0; height: 0; border-top: 40px solid transparent; border-left: 40px solid transparent; border-right: 40px solid transparent; border-bottom: 40px solid #ff0000;}

8、让一个图片无限旋转(蚂蚁金服 2019.03)

 <img class="circle" src="001.jpg" width="400" height="400"/>

 //infinite 表示动画无限次播放 linear表示动画从头到尾的速度是相同的
 .circle{
         animation: myRotation 5s linear infinite;
     }
@keyframes myRotation {
         from {transform: rotate(0deg);}
         to {transform: rotate(360deg);}
}

9、display 有哪些值?说明他们的作用?

inline默认。此元素会被显示为内联元素,元素前后没有换行符。

block此元素将显示为块级元素,此元素前后会带有换行符。

none此元素不会被显示(隐藏)。

inline-block行内块元素。(CSS2.1 新增的值)

list-item此元素会作为列表显示。

table此元素会作为块级表格来显示(类似table),表格前后带有换行符

10、position 的值?

absolute

生成绝对定位的元素,相对于 static 定位以外的第一个父元素进行定位。

元素的位置通过 "left", "top", "right" 以及 "bottom" 属性进行规定。

fixed

生成固定定位的元素,相对于浏览器窗口进行定位。(老IE不支持)

元素的位置通过 "left", "top", "right" 以及 "bottom" 属性进行规定。

relative

生成相对定位的元素,相对于其正常位置进行定位,不脱离文档流。

因此,"left:20" 会向元素的 LEFT 位置添加 20 像素。

static默认值。

没有定位,元素出现在正常的文档流中(忽略 top, bottom, left, right 或者 z-index 声明)。inherit规定应该从父元素继承 position 属性的值。

11、为什么要初始化 CSS 样式

因为浏览器的兼容问题,不同浏览器对有些标签的默认值是不同的,如果没对CSS初始化往往会出现浏览器之间的页面显示差异。

12、简要介绍一下CSS3的新特性

  1. 在布局方面新增了flex布局;

  2. 在选择器方面新增了例如:first-of-type,nth-child等选择器;

  3. 在盒模型方面添加了box-sizing来改变盒模型,

  4. 在动画方面增加了animation、2d变换、3d变换等。在颜色方面添加透明、rgba等,

  5. 在字体方面允许嵌入字体和设置字体阴影,同时当然也有盒子的阴影,

  6. 媒体查询。为不同设备基于它们的能力定义不同的样式。

    @media screen and (min-width:960px) and (max-width:1200px){
     body{
         background:yellow;
     }
    }

13、元素的显示与隐藏

元素的显示隐藏方法很多,不同方法的在不同的场景下页面效果不一,对页面的性能也有不同的影响。

元素隐藏方法总结:

  1. 如果希望元素不可见、不占据空间、资源会加载、DOM 可访问: display: none

  2. 如果希望元素不可见、不能点击、但占据空间、资源会加载,可以使用: visibility: hidden

  3. 如果希望元素不可见、不占据空间、显隐时可以又transition淡入淡出效果

    div{ 
    
         position: absolute;
    
         visibility: hidden;
    
         opacity: 0;
    
         transition: opacity .5s linear;
    
         background: cyan;
    }
    
    
    div.active{
         visibility: visible;
         opacity: 1;
    }

这里使用visibility: hidden而不是display: none,是因为display: none会影响css3的transition过渡效果。 但是display: none并不会影响cssanimation动画的效果。

  1. 如果希望元素不可见、可以点击、占据空间,可以使用: opacity: 0

  2. 如果希望元素不可见、可以点击、不占据空间,可以使用: opacity: 0; position: absolute;

  3. 如果希望元素不可见、不能点击、占据空间,可以使用: position: relative; z-index: -1;

  4. 如果希望元素不可见、不能点击、不占据空间,可以使用: position: absolute ; z-index: -1;

14、display: nonevisibility: hidden的区别

  1. display: none的元素不占据任何空间,visibility: hidden的元素空间保留;
  2. display: none会影响css3的transition过渡效果,visibility: hidden不会;
  3. display: none隐藏产生重绘 ( repaint ) 和回流 ( relfow ),visibility: hidden只会触发重绘;
  4. 株连性:display: none的节点和子孙节点元素全都不可见,visibility: hidden的节点的子孙节点元素可以设置 visibility: visible显示。visibility: hidden属性值具有继承性,所以子孙元素默认继承了hidden而隐藏,但是当子孙元素重置为visibility: visible就不会被隐藏。

JavaScript部分

1. 谈谈对闭包的理解,闭包的用途,闭包的缺点 (阿里巴巴一面 2019.8)

  • 闭包是指有权访问另外一个函数作用域中的变量的函数
  • 闭包的用途:
  1. 设计私有的方法和变量。
  2. 匿名函数最大的用途是创建闭包,并且还可以构建命名空间,以减少全局变量的使用。从而使用闭包模块化代码,减少全局变量的污染。
  • 闭包的缺点:
  1. 闭包会使得函数中的变量都被保存在内存中,滥用闭包可能导致内存泄漏。解决方法是在函数退出之前,将不使用的局部变量全删了。
  2. 闭包会在父函数外部,改变父函数内部变量的值。

2. 谈谈js的垃圾回收机制

  • JavaScript拥有自动的垃圾回收机制,当一个值,在内存中失去引用时,垃圾回收机制会根据特殊的算法找到它,并将其回收,释放内存。
  • 标记清除算法:
  1. 标记阶段,垃圾回收器会从根对象开始遍历。每一个可以从根对象访问到的对象都会被添加一个标识,于是这个对象就被标识为可到达对象。
  2. 清除阶段,垃圾回收器会对堆内存从头到尾进行线性遍历,如果发现有对象没有被标识为可到达对象,那么就将此对象占用的内存回收,并且将原来标记为可到达对象的标识清除,以便进行下一次垃圾回收操作。
  3. 缺点:垃圾收集后有可能会造成大量的 内存碎片
  • 引用计数算法:
  1. 引用计数的含义是跟踪记录每个值被引用的次数,如果没有引用指向该对象(零引用),对象将被垃圾回收机制回收。
  2. 缺点: 循环引用没法回收。

3. 引起JavaScript内存泄漏的操作有哪些,如何防止内存泄漏?(搜狐一面 2019.12 )

  • 内存泄漏(Memory Leak)是指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。
  • 虽然JavaScript 会自动垃圾收集,但是如果我们的代码写法不当,会让变量一直处于“进入环境”的状态,无法被回收。
  1. 意外的全局变量引起的内存泄漏

  2. 闭包引起的内存泄漏

  3. 未清除 dom 元素的引用的内存泄漏

  4. 循环引用引起的内存泄漏

  5. 被遗忘的计时器或回调引起的内存泄漏

  • 防止内存泄漏的方法:
  1. 及时清除引用。
  2. 使用WeakSet和WeakMap,它们对于值的引用都是不计入垃圾回收机制的,所以名字里面才会有一个"Weak",表示这是弱引用。
const wm = new WeakMap();
const element = document.getElementById('example');
wm.set(element, 'some information');
wm.get(element) // "some information"

先新建一个 Weakmap 实例。然后,将一个 DOM 节点作为键名存入该实例,并将一些附加信息作为键值,一起存放在 WeakMap 里面。这时,WeakMap 里面对element的引用就是弱引用,不会被计入垃圾回收机制。

也就是说,DOM 节点对象的引用计数是1,而不是2。这时,一旦消除对该节点的引用,它占用的内存就会被垃圾回收机制释放。Weakmap 保存的这个键值对,也会自动消失。

4. ajax的用途,ajax请求的五种状态(搜狐一面 2019.10 )

  • AJAX是“Asynchronous JavaScript And XML”的缩写,是一种实现无页面刷新获取服务器数据的混合技术。
  • XMLHttpRequest 对象是浏览器提供的一个API,用来顺畅地向服务器发送请求并解析服务器响应,当然整个过程中,浏览器页面不会被刷新。
  • .open()方法接收三个参数:请求方式(get or post),请求URL地址和是否为异步请求的布尔值。“同步”意味着一旦请求发出,任何后续的JavaScript代码不会再执行,“异步”则是当请求发出后,后续的JavaScript代码会继续执行,当请求成功后,会调用相应的回调函数。
// 该段代码会启动一个针对“example.php”的GET同步请求。
xhr.open("get", "example.php", false)
  • xhr实例的readystatechange事件会监听xhr.readyState属性的变化,有以下五种变化:
readyState 对应常量 描述
0(未初始化) xhr.UNSENT 请求已建立, 但未初始化(此时未调用open方法)
1(初始化) xhr.OPENED 请求已建立, 但未发送 (已调用open方法, 但未调用send方法)
2(发送数据) xhr.HEADERS_RECEIVED 请求已发送 (send方法已调用, 已收到响应头)
3(数据发送中) xhr.LOADING 请求处理中, 因响应内容不全, 这时通过responseBody和responseText获取可能会出现错误
4(完成) xhr.DONE 数据接收完毕, 此时可以通过responseBody和responseText获取完整的响应数据
//promise 实现ajax
function ajax(method, url, data) {
    var request = new XMLHttpRequest();
    return new Promise(function (resolve, reject) {
        request.onreadystatechange = function () {
            if (request.readyState === 4) {
                if (request.status === 200) {
                    resolve(request.responseText);
                } else {
                    reject(request.status);
                }
            }
        };
        request.open(method, url);
        request.send(data);
    });
}
ajax('GET', '/api/categories').then(function (text) {   // 如果AJAX成功,获得响应内容
    log.innerText = text;
}).catch(function (status) { // 如果AJAX失败,获得响应代码
    log.innerText = 'ERROR: ' + status;
});

5.讲一讲js里面的异步操作有哪些?( 蚂蚁金服一面 2019.03)

  1. 回调函数。 ajax典型的异步操作,利用XMLHttpRequest,回调函数获取服务器的数据传给前端。
  2. 事件监听。 当监听事件发生时,先执行回调函数,再对监听事件进行改写
  3. 观察者模式,也叫订阅发布模式。 多个观察者可以订阅同一个主题,主题对象改变时,主题对象就会通知这个观察者。
  4. promise.
  5. es7语法糖async/await
  6. co库的generator函数

6. javascript做类型判断的方法有哪些?

  1. typeof,可以判断原始数据类型:undefined、boolean、string、number、symbol,但是typeof null的类型判断为object。对于引用类型,会判断为function、object两种类型。
  2. instanceof ,instanceof 检测的是原型,判断一个实例是否属于某种类型。
function instanceOf(left,right) {
    let proto = left.__proto__;
    let prototype = right.prototype
    while(true) {
        if(proto === null) return false;
        if(proto === prototype) return true;
        proto = proto.__proto__;
    }
}
  1. Object.prototype.toString
  • toString() 是 Object 的原型方法,调用该方法,默认返回当前对象的类型。
Object.prototype.toString.call('') ;   // [object String]
Object.prototype.toString.call(1) ;    // [object Number]
Object.prototype.toString.call(true) ; // [object Boolean]
Object.prototype.toString.call(Symbol()); //[object Symbol]
Object.prototype.toString.call(undefined) ; // [object Undefined]
Object.prototype.toString.call(null) ; // [object Null]
Object.prototype.toString.call(new Function()) ; // [object Function]
Object.prototype.toString.call(new Date()) ; // [object Date]
Object.prototype.toString.call([]) ; // [object Array]
Object.prototype.toString.call(new RegExp()) ; // [object RegExp]
Object.prototype.toString.call(new Error()) ; // [object Error]
Object.prototype.toString.call(document) ; // [object HTMLDocument]
Object.prototype.toString.call(window) ; //[object global] window 是全局对象 global 的引用

7. 如何阻止事件冒泡和默认事件?

  • 标准的DOM对象中可以使用事件对象的stopPropagation()方法来阻止事件冒泡,但在IE8以下中IE的事件对象通过设置事件对象的cancelBubble属性为true来阻止冒泡;
  • 默认事件的话通过事件对象的preventDefault()方法来阻止,而IE通过设置事件对象的returnValue属性为false来阻止默认事件。
使用 IntersectionObserver
  • IntersectionObserver API为开发者提供了一种可以异步监听目标元素与其祖先或视窗(viewport)处于交叉状态的方式。祖先元素与视窗(viewport)被称为根(root)。
const config = {
    root: null,    // 默认指向浏览器的视口,但可以是任意DOM元素
    rootMargin: '0px',  // 计算交叉时,root边界盒的偏移量
    threshold: 0.5   // 监听对象的交叉区域与边界区域的比率
}
let observer = new IntersectionObserver(fucntion(entries){
    // ...
}, config)

new IntersectionObserver(function(entries, self))
  • 在entries我们得到我们的回调函数作为Array是特殊类型的:IntersectionObserverEntry 首先IntersectionObserverEntry含有三个不同的矩形的信息
  • 此外,IntersectionObserverEntry还提供了isIntersecting,这是一个方便的属性,返回观察元素是否与捕获框架相交,
  • 另外,IntersectionObserverEntry提供了利于计算的遍历属性intersctionRatio:返回intersectionRect 与 boundingClientRect 的比例值.
图片懒加载实现代码
  • 以加载图片为例子,我们需要将img标签中设置一个data-src属性,它指向的是实际上我们需要加载的图像,而img的src指向一张默认的图片,如果为空的话也会向服务器发送请求。
    <img src="default.jpg" data-src="www.example.com/1.jpg">
const images = document.querySelectorAll('[data-src]')
const config = {
    rootMargin: '0px',
    threshold: 0
};
let observer = new IntersectionObserver((entries, self)=>{
    entries.forEach(entry => {
        if(entry.isIntersecting){
         // 加载图像
         preloadImage(entry.target);
         // 解除观察
           self.unobserve(entry.target)
        }
    })
}, config)

images.forEach(image => {
  observer.observe(image);
});

function preloadImage(img) {
  const src = img.dataset.src
  if (!src) { return; }
  img.src = src;
}

参考: 实现图片懒加载

8. 什么是函数节流和函数去抖?( 蚂蚁金服一面 2019.03)

  • 函数去抖 debounce: 当调用函数n秒后,才会执行该动作,若在这n秒内又调用该函数则将取消前一次并重新计算执行时间。
var debounce = function(delay, cb) {
    var timer;
    return function() {
        if (timer) clearTimeout(timer);
        timer = setTimeout(function() {
            cb();
        }, delay);
    }
}
  • 函数节流 throttle: 函数节流的基本思想是函数预先设定一个执行周期,当调用动作的时刻大于等于执行周期则执行该动作,然后进入下一个新周期
var throttle = function(delay, cb) {
    var startTime = Date.now();
    return function() {
        var currTime = Date.now();
        if (currTime - startTime > delay) {
            cb();
            startTime = currTime;
        }
    }
}

9.如何实现对一个DOM元素的深拷贝,包括元素的绑定事件?

//使用cloneNode,但是在元素上绑定的事件不会拷贝
function clone(origin) {
    return Object.assign({},origin);
}
//实现了对原始对象的克隆,但是只能克隆原始对象自身的值,不能克隆她继承的值,如果想要保持继承链,可以采用如下方法:
function clone(origin) {
    let originProto=Object.getPrototypeOf(origin);
    return Object.assign(Object.create(originProto),origin);
}

10. script标签如何异步加载?

  • 默认情况下,浏览器是同步加载 JavaScript 脚本,即渲染引擎遇到 script 标签就会停下来,等到执行完脚本,再继续向下渲染。如果是外部脚本,还必须加入脚本下载的时间。如果脚本体积很大,下载和执行的时间就会很长,因此造成浏览器堵塞,用户会感觉到浏览器“卡死”了,没有任何响应。
<script src="path/to/myModule.js" defer></script>
<script src="path/to/myModule.js" async></script>
  • defer与async的区别是:
  1. defer要等到整个页面在内存中正常渲染结束(DOM 结构完全生成,以及其他脚本执行完成),才会执行;async一旦下载完,渲染引擎就会中断渲染,执行这个脚本以后,再继续渲染。一句话,defer是“渲染完再执行”,async是“下载完就执行”。
  2. 另外,如果有多个defer脚本,会按照它们在页面出现的顺序加载,而多个async脚本是不能保证加载顺序的。

11. 讲一下let、var、const的区别,谈谈如何冻结变量

  • var 没有块级作用域,支持变量提升。
  • let 有块级作用域,不支持变量提升。不允许重复声明,暂存性死区。
  • const 有块级作用域,不支持变量提升,不允许重复声明,暂存性死区。声明一个变量一旦声明就不能改变,改变报错。const保证的变量的内存地址不得改动。如果想要将对象冻结的话,使用Object.freeze()方法
const foo=Object.freeze({});
foo.prop=123;
console.log(foo.prop);//混杂模式undefined,不起作用

12. 谈谈js事件循环机制 ( 蚂蚁金服一面 2019.03)

  • 程序开始执行之后,主程序则开始执行 同步任务,碰到 异步任务 就把它放到任务队列中,等到同步任务全部执行完毕之后,js引擎便去查看任务队列有没有可以执行的异步任务,将异步任务转为同步任务,开始执行,执行完同步任务之后继续查看任务队列,这个过程是一直 循环 的,因此这个过程就是所谓的 事件循环,其中任务队列也被称为事件队列。通过一个任务队列,单线程的js实现了异步任务的执行,给人感觉js好像是多线程的。

13.如何拦截变量属性

  • 使用proxy。new Proxy() 表示生成一个 Proxy 实例,target 参数表示所要拦截的目标对象,handler 参数也是一个对象,用来定制拦截行为。
var proxy = new Proxy({}, {
  get: function(target, property) {
    return 35;
  }
});
let obj = Object.create(proxy);
obj.time // 35

14.箭头函数和普通函数的区别是什么?(阿里巴巴 2019.8.5)

  • 普通函数this:
  1. this总是代表它的直接调用者。
  2. 在默认情况下,没找到直接调用者,this指的是window。
  3. 在严格模式下,没有直接调用者的函数中的this是undefined。
  4. 使用call,apply,bind绑定,this指的是绑定的对象。
  • 箭头函数this:
  1. 在使用=>定义函数的时候,this的指向是 定义时所在的对象,而不是使用时所在的对象;
  2. 不能够用作构造函数,这就是说,不能够使用new命令,否则就会抛出一个错误;
  3. 不能够使用 arguments 对象;
  4. 不能使用 yield 命令;

15.函数柯里化理解 ( 蚂蚁金服一面 2019.03)

  • 只传递给函数一部分参数来调用它,让它返回一个函数去处理剩下的参数。
  • 用途:1.延迟计算;2.参数复用;3.动态生成函数
const curry = (fn, currArgs=[]) => {
    return function() {
        let args = Array.from(arguments);
        [].push.apply(args,currArgs);
        if (args.length < fn.length) {
            return curry.call(this,fn,...args);
        }
        return fn.apply(this,args);
    }
}

16.讲讲对d3的理解,讲讲d3与echarts的区别 (腾讯二面 2019.06)

  • d3正如其名 Data Driven Documents,其本质是将数据与 DOM 绑定,并将数据映射至 DOM 属性上;
  • d3与echarts的区别:
  1. d3通过svg绘制图形,可以自定义事件。svg不依赖分辨率,继续xml绘制图形,可以操作dom。支持事件处理器,复杂度高,会减慢页面的渲染速度。

  2. echarts通过canvas来绘制图形,用户通过配置 options 参数,就可很容易绘制指定图表。canvas依赖分辨率,基于js绘制图形,不支持事件处理,能以png或者jpg的格式保存图片。

框架( react anglar vue等)

Vue

1、vuex原理 (百度前端 字节跳动 腾讯 网易)

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。每一个 Vuex 应用的核心就是 store(仓库)。“store” 基本上就是一个容器,它包含着你的应用中大部分的状态 ( state )。

(1)Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。

(2)改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样使得我们可以方便地跟踪每一个状态的变化。

vuex的store有State、 Getter、Mutation 、Action、 Module五种属性;

  • state 为单一状态树,在state中需要定义我们所需要管理的数组、对象、字符串等等
  • getters 类似vue的计算属性,主要用来过滤一些数据。
  • mutation 更改store中state状态的唯一方法就是提交mutation,store.commit。
  • action actions可以理解为通过将mutations里面处里数据的方法变成可异步的处理数据的方法,简单的说就是异步操作数据。view 层通过 store.dispath 来分发 action。
  • module module其实只是解决了当state中很复杂臃肿的时候,module可以将store分割成模块,每个模块中拥有自己的state、mutation、action和getter。

2. vue数据双向绑定(腾讯 2019.04)

<body>
    <div id="app">
    <input type="text" id="txt">
    <p id="show"></p>
</div>
</body>
<script type="text/javascript">
    var obj = {}
    Object.defineProperty(obj, 'txt', {
        get: function () {
            return obj
        },
        set: function (newValue) {
            document.getElementById('txt').value = newValue
            document.getElementById('show').innerHTML = newValue
        }
    })
    document.addEventListener('keyup', function (e) {
        obj.txt = e.target.value
    })
</script>

3. 父子组件如何通信,兄弟组件如何通信(腾讯 2019.04 依图 猫眼电影 bilibili)

父组件通过props属性与子组件通信

父组件:

<parent>
    <single-voice  ref="singleVoiceRef" :parent-to-child="singleVoiceData"/>
</parent>

data(){
    return {
        singleVoiceData:'来自父组件的数据'
    }
}

// 父组件调用子组件中的方法,将值传递给子组件

CSSmethods:{`
`this.$refs.singleVoiceRef.openSingleVoice(this.singleVoiceData)`
`}`

子组件通过props来接受数据

props: {parentToChild: {type: String,required: true}},methods:{openSingleVoice(SingleVoice) {console.log(SingleVoice)}}

子组件向父组件传值

vue2.0只允许单向数据传递,我们通过出发事件来改变组件的数据

子组件代码:

<template>
    <div @click="open"></div>
</template>

methods: {
   open() {
        this.$emit('showbox','the msg'); //触发showbox方法,'the msg'为向父组件传递的数据
    }
}

父组件代码:

<child @showbox="toshow" :msg="msg"></child> //监听子组件触发的showbox事件,然后调用toshow方法

methods: {
    toshow(msg) {
        this.msg = msg;
    }
}

兄弟组件之间的通信

我们可以实例化一个vue实例,相当于一个第三方

eventVue.$emit(‘function1’,value)
eventVue.$on(‘function1’, (message) => { // 箭头函数接收
})

创建一个公共桥梁 eventVue.js

import Vue from 'vue'
export default new Vue()

兄弟组件内引入 eventVue.js

兄弟组件一

import eventVue from './eventVue.js'
export default {
  methods: {
 // 点击通讯录与员工进行语音聊天
    handleChatStaff(staffInfo) {
      console.log(staffInfo)
      this.staffInfo = staffInfo
      eventVue.$emit('updateChatList', this.staffInfo)
    },
  }
}

兄弟组件二

import eventVue from './eventVue.js'
export default {
 created() {
    this.updateList()
  },
  methods: {
    updateList() {
      eventVue.$on('updateChatList', (message) => { // 与phoneBook组件通信
        console.log(message)
        this.updateChatListEvent()
      })
    },
    // 更新聊天列表
    updateChatListEvent() {},
    }

其他参考地址 :

​ https://www.imooc.com/article/68394?block_id=tuijian_wz

​ https://www.cnblogs.com/zhangruiqi/p/9386437.html

4、vue如何将一个组件打包上传 (腾讯 2019.04)

参考地址:

​ https://www.cnblogs.com/yalong/p/10388384.html

​ https://www.jianshu.com/p/2d47396c775c

5、谈谈你对 Vue 生命周期的理解?(网易)

​ Vue 实例有一个完整的生命周期,也就是从开始创建、初始化数据、编译模版、挂载 Dom -> 渲染、更新 -> 渲染、卸载等一系列过程,我们称这是 Vue 的生命周期。

beforeCreate 组件实例被创建之初,组件的属性生效之前

created 组件实例已经完全创建,属性也绑定,但真实 dom 还没有生成,$el 还不可用

beforeMount 在挂载开始之前被调用:相关的 render 函数首次被调用 mounted el 被新创建的 vm.$el 替换,并挂载到实例上去之后调用该钩子

beforeUpdate 组件数据更新之前调用,发生在虚拟 DOM 打补丁之前

update 组件数据更新之后

activited keep-alive 专属,组件被激活时调用

deactivated keep-alive 专属,组件被销毁时调用

beforeDestory 组件销毁前调用

destoryed 组件销毁后调用

6、父组件可以监听到子组件的生命周期吗?

比如有父组件 Parent 和子组件 Child,如果父组件监听到子组件挂载 mounted 就做一些逻辑处理,可以通过以下写法实现:

// Parent.vue
<Child @mounted="doSomething"/>
    
// Child.vue
mounted() {
  this.$emit("mounted");
}
复制代码

以上需要手动通过 $emit 触发父组件的事件,更简单的方式可以在父组件引用子组件时通过 @hook 来监听即可,如下所示:

//  Parent.vue
<Child @hook:mounted="doSomething" ></Child>

doSomething() {
   console.log('父组件监听到 mounted 钩子函数 ...');
},
    
//  Child.vue
mounted(){
   console.log('子组件触发 mounted 钩子函数 ...');
},    
    
// 以上输出顺序为:
// 子组件触发 mounted 钩子函数 ...
// 父组件监听到 mounted 钩子函数 ...     
复制代码

当然 @hook 方法不仅仅是可以监听 mounted,其它的生命周期事件,例如:created,updated 等都可以监听。

7、Vue 组件间通信有哪几种方式?

Vue 组件间通信是面试常考的知识点之一,这题有点类似于开放题,你回答出越多方法当然越加分,表明你对 Vue 掌握的越熟练。Vue 组件间通信只要指以下 3 类通信:父子组件通信、隔代组件通信、兄弟组件通信,下面我们分别介绍每种通信方式且会说明此种方法可适用于哪类组件间通信。

(1)props / $emit 适用 父子组件通信

这种方法是 Vue 组件的基础,相信大部分同学耳闻能详,所以此处就不举例展开介绍。

(2)ref$parent / $children 适用 父子组件通信

  • ref:如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例
  • $parent / $children:访问父 / 子实例

(3)EventBus ($emit / $on) 适用于 父子、隔代、兄弟组件通信

这种方法通过一个空的 Vue 实例作为中央事件总线(事件中心),用它来触发事件和监听事件,从而实现任何组件间的通信,包括父子、隔代、兄弟组件。

(4)$attrs/$listeners 适用于 隔代组件通信

  • $attrs:包含了父作用域中不被 prop 所识别 (且获取) 的特性绑定 ( class 和 style 除外 )。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 ( class 和 style 除外 ),并且可以通过 v-bind="$attrs" 传入内部组件。通常配合 inheritAttrs 选项一起使用。
  • $listeners:包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on="$listeners" 传入内部组件

(5)provide / inject 适用于 隔代组件通信

祖先组件中通过 provider 来提供变量,然后在子孙组件中通过 inject 来注入变量。 provide / inject API 主要解决了跨级组件间的通信问题,不过它的使用场景,主要是子组件获取上级组件的状态,跨级组件间建立了一种主动提供与依赖注入的关系。

(6)Vuex 适用于 父子、隔代、兄弟组件通信

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。每一个 Vuex 应用的核心就是 store(仓库)。“store” 基本上就是一个容器,它包含着你的应用中大部分的状态 ( state )。

  • Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。
  • 改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样使得我们可以方便地跟踪每一个状态的变化。

8、你有对 Vue 项目进行哪些优化?

(1)代码层面的优化**

  • v-if 和 v-show 区分使用场景
  • computed 和 watch 区分使用场景
  • v-for 遍历必须为 item 添加 key,且避免同时使用 v-if
  • 长列表性能优化
  • 事件的销毁
  • 图片资源懒加载
  • 路由懒加载
  • 第三方插件的按需引入
  • 优化无限列表性能
  • 服务端渲染 SSR or 预渲染

(2)Webpack 层面的优化

  • Webpack 对图片进行压缩
  • 减少 ES6 转为 ES5 的冗余代码
  • 提取公共代码
  • 模板预编译
  • 提取组件的 CSS
  • 优化 SourceMap
  • 构建结果输出分析
  • Vue 项目的编译优化

(3)基础的 Web 技术的优化

  • 开启 gzip 压缩
  • 浏览器缓存
  • CDN 的使用
  • 使用 Chrome Performance 查找性能瓶颈

9、vuejs与angularjs以及react的区别?(天壤智能)

angularjs的区别:

1、Angular.js的学习成本高,比如增加了Dependency Injection特性,而Vue.js本身提供的API都比较简单、直观。

2、在性能上,Angular.js依赖对数据做脏检查,所以watcher越多越慢。

3、Vue.js使用基于依赖追踪的观察并且使用异步队列更新。所有的数据都是独立出发的。

对于庞大的应用来说,这个优化差异还是比较明显的。

与reactjs的区别:

React 依赖Virtual DOM,而Vue.js使用的是DOM模板。React采用的Virtual DOM会对渲染出来的结果做脏检查。
Vue.js在模板中提供了指令,过滤器等,可以非常方便,快捷地操作Virtual DOM

10、 的作用是什么,如何使用?

包裹动态组件时,会缓存不活动的组件实例,主要用于保留组件状态或避免重新渲染;

使用:简单页面时

缓存:

不缓存:

使用:复杂项目时

路由字典中定义{path:’/detail’,meta:{keepAlive:false/true}} 是否缓存

根目录中:

11 、scss是什么?在vue.cli中的安装使用步骤是?有哪几大特性?

css的预编译。

使用步骤:

​ 第一步:用npm 下三个loader(sass-loader、css-loader、node-sass)

​ 第二步:在build目录找到webpack.base.config.js,在那个extends属性中加一个拓展.scss

​ 第三步:还是在同一个文件,配置一个module属性

​ 第四步:然后在组件的style标签加上lang属性 ,例如:lang=”scss”

有哪几大特性:

​ 1、可以用变量,例如($变量名称=值);

​ 2、可以用混合器,例如:定义了字体的混合器

@mixin font-dpr($font-size){
    $font:$font-size/2;
    font-size: $font;
    [data-dpr="2"] & { font-size: $font+2px;}
    [data-dpr="3"] & { font-size: $font+4px;}
}

使用方法如下

.div{

  @include font-dpr(24px);

}

​ 3、可以嵌套

12、vuex原理

  • vuex的store有State、 Getter、Mutation 、Action、 Module五种属性;
  • state 为单一状态树,在state中需要定义我们所需要管理的数组、对象、字符串等等
  • getters 类似vue的计算属性,主要用来过滤一些数据。
  • mutation 更改store中state状态的唯一方法就是提交mutation,store.commit。
  • action actions可以理解为通过将mutations里面处里数据的方法变成可异步的处理数据的方法,简单的说就是异步操作数据。view 层通过 store.dispath 来分发 action。
  • module module其实只是解决了当state中很复杂臃肿的时候,module可以将store分割成模块,每个模块中拥有自己的state、mutation、action和getter。

13、 vue数据双向绑定

<body>
    <div id="app">
    <input type="text" id="txt">
    <p id="show"></p>
</div>
</body>
<script type="text/javascript">
    var obj = {}
    Object.defineProperty(obj, 'txt', {
        get: function () {
            return obj
        },
        set: function (newValue) {
            document.getElementById('txt').value = newValue
            document.getElementById('show').innerHTML = newValue
        }
    })
    document.addEventListener('keyup', function (e) {
        obj.txt = e.target.value
    })
</script>

react

1、为什么选择使用框架而不是原生

框架的好处:

​ 组件化: 其中以 React 的组件化最为彻底,甚至可以到函数级别的原子组件,高度的组件化可以是我们的工程易于维护、易于组合拓展。
​ 天然分层: JQuery 时代的代码大部分情况下是面条代码,耦合严重,现代框架不管是 MVC、MVP还是MVVM 模式都能帮助我们进行分层,代码解耦更易于读写。
​ 生态: 现在主流前端框架都自带生态,不管是数据流管理架构还是 UI 库都有成熟的解决方案。
​ 开发效率: 现代前端框架都默认自动更新DOM,而非我们手动操作,解放了开发者的手动DOM成本,提高开发效率,从根本上解决了UI 与状态同步问题.

虚拟DOM的优劣如何?
优点:

​ 保证性能下限: 虚拟DOM可以经过diff找出最小差异,然后批量进行patch,这种操作虽然比不上手动优化,但是比起粗暴的DOM操作性能要好很多,因此虚拟DOM可以保证性能下限
无需手动操作DOM: 虚拟DOM的diff和patch都是在一次更新中自动进行的,我们无需手动操作DOM,极大提高开发效率
​ 跨平台: 虚拟DOM本质上是JavaScript对象,而DOM与平台强相关,相比之下虚拟DOM可以进行更方便地跨平台操作,例如服务器渲染、移动端开发等等

​ 缺点:

​ 无法进行极致优化: 在一些性能要求极高的应用中虚拟DOM无法进行针对性的极致优化,比如VScode采用直接手动操作DOM的方式进行极端的性能优化

2、为什么虚拟dom会提高性能? (美团 )

虚拟dom相当于在js和真实dom中间加了一个缓存,利用dom diff算法避免了没有必要的dom操作,从而提高性能。虚拟DOM本质上是JavaScript对象,是对真实DOM的抽象,状态变更时,记录新树和旧树的差异,最后把差异更新到真正的dom中

具体实现步骤如下:

1.用 JavaScript 对象结构表示 DOM 树的结构;然后用这个树构建一个真正的 DOM 树,插到文档当中;

2.当状态变更的时候,重新构造一棵新的对象树。然后用新的树和旧的树进行比较,记录两棵树差异;

把2所记录的差异应用到步骤1所构建的真正的DOM树上,视图就更新了

3、react生命周期函数 (旷世面试 2019.06 拍拍贷 天壤智能)

​ 组件加载之前,组件加载完成,以及组件更新数据,组件销毁。触发的一系列的方法 ,这就是组件的生命周期函数

​ 1、初始化阶段:

​ getDefaultProps:获取实例的默认属性

​ getInitialState:获取每个实例的初始化状态

​ componentWillMount:组件即将被装载、渲染到页面上

​ render:组件在这里生成虚拟的DOM节点

​ componentDidMount:组件真正在被装载之后

​ 2、运行中状态:

​ componentWillReceiveProps:组件将要接收到属性的时候调用

​ shouldComponentUpdate:组件接受到新属性或者新状态的时候(可以返回false,接收数据后不更新,阻止render调用,后面的函数不会被继续执行了)

​ componentWillUpdate:组件即将更新不能修改属性和状态

​ render:组件重新描绘

​ componentDidUpdate:组件已经更新

​ 3、销毁阶段:

​ componentWillUnmount:组件即将销毁

必须记住的生命周期函数:

*加载的时候:componentWillMount、 render 、componentDidMount(dom操作)

更新的时候:componentWillUpdate、render、componentDidUpdate

*销毁的时候: componentWillUnmount

4、什么是Diff算法? (美团 )

diff算法作为Virtual DOM的加速器,其算法的改进优化是React整个界面渲染的基础和性能的保障,同时也是React源码中最神秘的,最不可思议的部分

传统diff算法通过循环递归对比差异,算法复杂度为O(n3)。react diff算法制定了三条策略,将算法复杂度从 O(n3)降低到O(n)。

  • WebUI中DOM节点跨节点的操作特别少,可以忽略不计。
  • 拥有相同类的组件会拥有相似的DOM结构。拥有不同类的组件会生成不同的DOM结构。
  • 同一层级的子节点,可以根据唯一的ID来区分。

针对这三个策略,react diff实施的具体策略是:

  1. diff对树进行分层比较,只对比两棵树同级别的节点。跨层级移动节点,将会导致节点删除,重新插入,无法复用。
  2. diff对组件进行类比较,类相同的递归diff子节点,不同的直接销毁重建。diff对同一层级的子节点进行处理时,会根据key进行简要的复用。两棵树中存在相同key的节点时,只会移动节点。

5、React性能优化方式

由于react中性能主要耗费在于update阶段的diff算法,因此性能优化也主要针对diff算法。

1.减少diff算法触发次数

​ A、 setState机制在正常运行时,由于批更新策略,已经降低了update过程的触发次数。
因此,setState优化主要在于非批更新阶段中(timeout/Promise回调),减少setState的触发次数。
常见的业务场景即处理接口回调时,无论数据处理多么复杂,保证最后只调用一次setState。

​ B、父组件的render必然会触发子组件进入update阶段(无论props是否更新)。此时最常用的优化方案即为shouldComponentUpdate方法。最常见的方式为进行this.props和this.state的浅比较来判断组件是否需要更新。或者直接使用PureComponent,原理一致。需要注意的是,父组件的render函数如果写的不规范,将会导致上述的策略失效。

​ C、使用shouldComponentUpdate钩子,根据具体的业务状态,减少不必要的props变化导致的渲染。如一个不用于渲染的props导致的update。
另外, 也要尽量避免在shouldComponentUpdate 中做一些比较复杂的操作, 比如超大数据的pick操作等。

合理设计state,不需要渲染的state,尽量使用实例成员变量。

2、正确使用diff算法

​ 不使用跨层级移动节点的操作。

​ 对于条件渲染多个节点时,尽量采用隐藏等方式切换节点,而不是替换节点。

​ 尽量避免将后面的子节点移动到前面的操作,当节点数量较多时,会产生一定的性能问题。

性能检测工具
React官方提供的:React.addons.Perf

6、你了解React吗?

React是facebook搞出来的一个轻量级的组件库,用于解决前端视图层的一些问题,就是MVC中V层的问题,它内部的Instagram网站就是用React搭建的。

解决了三个问题: 1.组件复用问题, 2.性能问题,3.兼容性问题:

React 会创建一个虚拟 DOM(virtual DOM)。当一个组件中的状态改变时,React 首先会通过 "diffing" 算法来标记虚拟 DOM 中的改变,第二步是调节(reconciliation),会用 diff 的结果来更新 DOM。

优点:

​ 1.只需查看 render 函数就会很容易知道一个组件是如何被渲染的

​ 2.JSX 的引入,使得组件的代码更加可读,也更容易看懂组件的布局,或者组件之间是如何互相引用的

​ 3.支持服务端渲染,这可以改进 SEO 和性能

​ 4.易于测试

​ 5.React 只关注 View 层,所以可以和其它任何框架(如Backbone.js, Angular.js)一起使用

angular

1、React 与 Angular 有何不同

Angular是一个成熟的MVC框架,带有很多特定的特性,比如服务、指令、模板、模块、解析器等等。React是一个非常轻量级的库,它只关注MVC的视图部分。

Angular遵循两个方向的数据流,而React遵循从上到下的单向数据流。React在开发特性时给了开发人员很大的自由,例如,调用API的方式、路由等等。我们不需要包括路由器库,除非我们需要它在我们的项目。

2、angular 核心?

AngularJS是为了克服HTML在构建应用上的不足而设计的。 AngularJS有着诸多特性,最为核心的是:

  • MVC
  • 模块化
  • 自动化双向数据绑定
  • 语义化标签、依赖注入等等

3、简述angular的数据绑定

  Angular 在 scope 模型上设置了一个监听队列,用来监听数据变化并更新 view 。每次绑定一个东西到 view 上时 AngularJS 就会往 $watch 队列里插入一条 $watch ,用来检测它监视的 model 里是否有变化的东西。当浏览器接收到可以被 angular context 处理的事件时, $digest 循环就会触发,遍历所有的 $watch ,最后更新 dom。

4、AngularJS的数据双向绑定是怎么实现的?

  1、每个双向绑定的元素都有一个watcher
  2、在某些事件发生的时候,调用digest脏数据检测。
    这些事件有:表单元素内容变化、Ajax请求响应、点击按钮执行的函数等。
  3、脏数据检测会检测rootscope下所有被watcher的元素。
    $digest函数就是脏数据监测

5、单页应用有哪些优缺点?(网易雷火)

单页 Web 应用 (single-page application 简称为 SPA) 是一种特殊的 Web 应用。它将所有的活动局限于一个Web页面中,仅在该Web页面初始化时加载相应的HTML、JavaScript 和 CSS。一旦页面加载完成了,SPA不会因为用户的操作而进行页面的重新加载或跳转。取而代之的是利用 JavaScript 动态的变换HTML的内容,从而实现UI与用户的交互。由于避免了页面的重新加载,SPA 可以提供较为流畅的用户体验。

1、优点:

1).良好的交互体验

用户不需要重新刷新页面,获取数据也是通过Ajax异步获取,页面显示流畅。

2).良好的前后端工作分离模式

单页Web应用可以和RESTful规约一起使用,通过REST API提供接口数据,并使用Ajax异步获取,这样有助于分离客户端和服务器端工作。更进一步,可以在客户端也可以分解为静态页面和页面交互两个部分。

3).减轻服务器压力

服务器只用出数据就可以,不用管展示逻辑和页面合成,吞吐能力会提高几倍;

4).共用一套后端程序代码
不用修改后端程序代码就可以同时用于Web界面、手机、平板等多种客户端;
2、缺点:

1).SEO难度较高

由于所有的内容都在一个页面中动态替换显示,所以在SEO上其有着天然的弱势,所以如果你的站点对SEO很看重,且要用单页应用,那么就做些静态页面给搜索引擎用吧。

2).前进、后退管理

由于单页Web应用在一个页面中显示所有的内容,所以不能使用浏览器的前进后退功能,所有的页面切换需要自己建立堆栈管理,当然此问题也有解决方案,比如利用URI中的散列+iframe实现。

3).初次加载耗时多

为实现单页Web应用功能及显示效果,需要在加载页面的时候将JavaScript、CSS统一加载,部分页面可以在需要的时候加载。所以必须对JavaScript及CSS代码进行合并压缩处理,如果使用第三方库,建议使用一些大公司的CDN,因此带宽的消耗是必然的。

结语

还有2件事拜托大家
一:求赞 求收藏 求分享 求留言,让更多的人看到这篇内容
二:欢迎添加我的个人微信
备注“资料”, 300多篇原创技术文章,海量的视频资料即可获得
备注“加群”,我会拉你进技术交流群,群里大牛学霸具在,哪怕您做个潜水鱼也会学到很多东西

相关链接

参考资料

11道浏览器原理面试题

这儿有20道大厂面试题等你查收

2020 前端面试 | “HTML + CSS + JS”专题

图解浏览器的工作原理(史上最全)

BAT前端经典面试问题:史上最最最详细的手写Promise教程

webpack 中文网