前端面试题

137 阅读18分钟

原文

1、写一个 mySetInterVal(fn, a, b),每次间隔 a,a+b,a+2b 的时间,然后写一个 myClear,停止上面的 mySetInterVal

function mySetInterVal(fn, a, b) {
    this.time = 0;
    this.handle = -1;
    this.start = () => {
        this.handle = setTimeout(() => {
            fn();
            this.time++;
            this.start();
        }, a + this.time * b);
    }
    this.stop = () => {
        clearTimeout(this.handle);
        this.time = 0;
    }
}

var a = new mySetInterVal(() => {console.log('123')},1000, 2000 );
a.start();
a.stop();

2、合并二维有序数组成一维有序数组,归并排序的思路

function merge(arr1, arr2){
    var result=[];
    while(arr1.length>0 && arr2.length>0){
        if(arr1[0]<arr2[0]){
              /*shift()方法用于把数组的第一个元素从其中删除,并返回第一个元素的值。*/
            result.push(arr1.shift());
        }else{
            result.push(arr2.shift());
        }
    }
    return result.concat(arr1).concat(arr2);
}

function mergeSort(arr){
    let lengthArr = arr.length;
    if(lengthArr === 0){
     return [];
    }
    while(arr.length > 1){
     let arrayItem1 = arr.shift();
     let arrayItem2 = arr.shift();
     let mergeArr = merge(arrayItem1, arrayItem2);
     arr.push(mergeArr);
    }
    return arr[0];
}
let arr1 = [[1,2,3],[4,5,6],[7,8,9],[1,2,3],[4,5,6]];
mergeSort(arr1);

3、斐波那契数列

function fabonacci(num) {
    if (!num) return res;
    var a = 1,
        b = 1,
        n = 1,
        res = [1];
    while (n < num) {
        [a, b] = [b, a + b];
        res.push(a);
        n++;
    }
    return res;
}

4、字符串出现的不重复最长长度

function lenSetStr(str) {
    return [...new Set(str)].length;
}

5、介绍chrome 浏览器的几个版本

6、React 项目中有哪些细节可以优化?实际开发中都做过哪些性能优化

1)对于正常的项目优化,一般都涉及到几个方面,开发过程中上线之后的首屏运行过程的状态

  • 来聊聊上线之后的首屏及运行状态:

    • 首屏优化一般涉及到几个指标FP、FCP、FMP;要有一个良好的体验是尽可能的把FCP提前,需要做一些工程化的处理,去优化资源的加载

    • 方式及分包策略,资源的减少是最有效的加快首屏打开的方式;

    • 对于CSR的应用,FCP的过程一般是首先加载js与css资源,js在本地执行完成,然后加载数据回来,做内容初始化渲染,这中间就有几次的网络反复请求的过程;所以CSR可以考虑使用骨架屏及预渲染(部分结构预渲染)、suspence与lazy做懒加载动态组件的方式

    • 当然还有另外一种方式就是SSR的方式,SSR对于首屏的优化有一定的优势,但是这种瓶颈一般在Node服务端的处理,建议使用stream流的方式来处理,对于体验与node端的内存管理等,都有优势;

    • 不管对于CSR或者SSR,都建议配合使用Service worker,来控制资源的调配及骨架屏秒开的体验

    • react项目上线之后,首先需要保障的是可用性,所以可以通过React.Profiler分析组件的渲染次数及耗时的一些任务,但是Profile记录的是commit阶段的数据,所以对于react的调和阶段就需要结合performance API一起分析;

    • 由于React是父级props改变之后,所有与props不相关子组件在没有添加条件控制的情况之下,也会触发render渲染,这是没有必要的,可以结合React的PureComponent以及React.memo等做浅比较处理,这中间有涉及到不可变数据的处理,当然也可以结合使用ShouldComponentUpdate做深比较处理;

    • 所有的运行状态优化,都是减少不必要的render,React.useMemo与React.useCallback也是可以做很多优化的地方;

    • 在很多应用中,都会涉及到使用redux以及使用context,这两个都可能造成许多不必要的render,所以在使用的时候,也需要谨慎的处理一些数据;

    • 最后就是保证整个应用的可用性,为组件创建错误边界,可以使用componentDidCatch来处理;

  • 实际项目中开发过程中还有很多其他的优化点:

    • 1.保证数据的不可变性
    • 2.使用唯一的键值迭代
    • 3.使用web worker做密集型的任务处理
    • 4.不在render中处理数据
    • 5.不必要的标签,使用React.Fragments

7、react 最新版本解决了什么问题 加了哪些东西

1)React 16.x的三大新特性 Time Slicing, Suspense,hooks

    1. Time Slicing(解决CPU速度问题)使得在执行任务的期间可以随时暂停,跑去干别的事情,这个特性使得react能在性能极其差的机器跑时,仍然保持有良好的性能
    1. Suspense (解决网络IO问题)和lazy配合,实现异步加载组件。 能暂停当前组件的渲染, 当完成某件事以后再继续渲染,解决从react出生到现在都存在的「异步副作用」的问题,而且解决得非
  • 的优雅,使用的是「异步但是同步的写法」,我个人认为,这是最好的解决异步问题的方式
    1. 此外,还提供了一个内置函数 componentDidCatch,当有错误发生时, 我们可以友好地展示 fallback 组件;可以捕捉到它的子元素(包括嵌套子元素)抛出的异常;可以复用错误组件。

2)React16.8

  • 加入hooks,让React函数式组件更加灵活
  • hooks之前,React存在很多问题
      1. 在组件间复用状态逻辑很难
      1. 复杂组件变得难以理解,高阶组件和函数组件的嵌套过深。
      1. class组件的this指向问题
      1. 难以记忆的生命周期
  • hooks很好的解决了上述问题,hooks提供了很多方法
      1. useState 返回有状态值,以及更新这个状态值的函数
      1. useEffect 接受包含命令式,可能有副作用代码的函数。
      1. useContext 接受上下文对象(从React.createContext返回的值)并返回当前上下文值,
      1. useReducer useState的替代方案。接受类型为(state,action) => newState的reducer,并返回与dispatch方法配对的当前状态。
      1. useCallback 返回一个回忆的memoized版本,该版本仅在其中一个输入发生更改时才会更改。纯函数的输入输出确定性
      1. useMemo 纯的一个记忆函数
      1. useRef 返回一个可变的ref对象,其.current属性被初始化为传递的参数,返回的 ref 对象在组件的整个生命周期内保持不变。
      1. useImperativeMethods 自定义使用ref时公开给父组件的实例值
      1. useMutationEffect 更新兄弟组件之前,它在React执行其DOM改变的同一阶段同步触发
      1. useLayoutEffect DOM改变后同步触发。使用它来从DOM读取布局并同步重新渲染

3)React16.9

    1. 重命名 Unsafe 的生命周期方法。新的 UNSAFE_ 前缀将有助于在代码 review 和 debug 期间,使这些有问题的字样更突出
    1. 废弃 javascript: 形式的 URL。以 javascript: 开头的 URL 非常容易遭受攻击,造成安全漏洞。
    1. 废弃 “Factory” 组件。 工厂组件会导致 React 变大且变慢。
    1. act() 也支持异步函数,并且你可以在调用它时使用 await。
    1. 使用 <React.Profiler> 进行性能评估。 在较大的应用中追踪性能回归可能会很方便

4)React16.13.0

    1. 支持在渲染期间调用setState,但仅适用于同一组件
    1. 可检测冲突的样式规则并记录警告
    1. 废弃unstable_createPortal,使用createPortal
    1. 将组件堆栈添加到其开发警告中,使开发人员能够隔离bug并调试其程序,这可以清楚地说明问题所在,并更快地定位和修复错误。

7、说一下 Http 缓存策略,有什么区别,分别解决了什么问题

1)浏览器缓存策略

浏览器每次发起请求时,先在本地缓存中查找结果以及缓存标识,根据缓存标识来判断是否使用本地缓存。如果缓存有效,则使用本地缓存;否则,则向服务器发送请求并携带缓存标识。根据是否需向服务器发起http请求,将缓存过程划分为两个部分:强制缓存和协商缓存,强缓优先于协商缓存。

  • 强缓存,服务器通知浏览器一个缓存时间,在缓存时间内,下次请求直接用缓存,不在时间内,执行比较缓存策略;
  • 协商缓存,让客户端与服务端之间能实现缓存文件是否更新的验证、提高缓存的复用率,将缓存信息中的Etag和Last-Modified通过请求发送给服务端,由服务端校验,返回304状态码时,浏览器直接使用缓存。

HTTP缓存都是从第二次请求开始的:

  • 第一次请求资源时,服务器返回资源,并在response header中回传资源的缓存策略;
  • 第二次请求时,浏览器判断这些请求参数,击中强缓存就直接200,否则就把请求参数加到request header头中传给服务器,看是否击中协商缓存,击中则返回304,否则服务器会返回新的资源。

img

2)强缓存

  • 强缓存命中则直接读取浏览器本地的资源,在network中显示的是from memory 或者 from disk;
  • 控制强缓存的字段有:Cache-Control(http1.1)和Expires(http1.0)
  • Cache-control是一个相对时间,用以表达自上次请求正确的资源之后的多少秒的时间段内缓存有效。
  • Expires是一个绝对时间。用以表达在这个时间点之前发起请求可以直接从浏览器中读取数据,而无需发起请求
  • Cache-Control的优先级比Expires的优先级高。前者的出现是为了解决Expires在浏览器时间被手动更改导致缓存判断错误的问题。如果同时存在则使用Cache-control。

3)强缓存-expires

4)强缓存-cache-control

  • 已知Expires的缺点之后,在HTTP/1.1中,增加了一个字段Cache-control,该字段表示资源缓存的最大有效时间,在该时间内,客户端不需要向服务器发送请求。github.com/lgwebdream/…

5)协商缓存

  • 协商缓存的状态码由服务器决策返回200或者304

  • 当浏览器的强缓存失效的时候或者请求头中设置了不走强缓存,并且在请求头中设置了If-Modified-Since 或者 If-None-Match 的时候,会将这两个属性值到服务端去验证是否命中协商缓存,如果命中了协商缓存,会返回 304 状态,加载浏览器缓存,并且响应头会设置 Last-Modified 或者 ETag 属性。

  • 对比缓存在请求数上和没有缓存是一致的,但如果是 304 的话,返回的仅仅是一个状态码而已,并没有实际的文件内容,因此 在响应体体积上的节省是它的优化点。

  • 协商缓存有 2 组字段(不是两个),控制协商缓存的字段有:Last-Modified/If-Modified-since(http1.0)和Etag/If-None-match(http1.1)

  • Last-Modified/If-Modified-since表示的是服务器的资源最后一次修改的时间;Etag/If-None-match表示的是服务器资源的唯一标识,只要资源变化,Etag就会重新生成。

  • Etag/If-None-match的优先级比Last-Modified/If-Modified-since高。

6)协商缓存-Last-Modified/If-Modified-since

  • 1.服务器通过 Last-Modified 字段告知客户端,资源最后一次被修改的时间,例如 Last-Modified: Mon, 10 Nov 2018 09:10:11 GMT

  • 2.浏览器将这个值和内容一起记录在缓存数据库中。

  • 3.下一次请求相同资源时时,浏览器从自己的缓存中找出“不确定是否过期的”缓存。因此在请求头中将上次的 Last-Modified 的值写入到请求头的 If-Modified-Since 字段

  • 4.服务器会将 If-Modified-Since 的值与 Last-Modified 字段进行对比。如果相等,则表示未修改,响应 304;反之,则表示修改了,响应 200 状态码,并返回数据。

  • 优势特点

    • 1、不存在版本问题,每次请求都会去服务器进行校验。服务器对比最后修改时间如果相同则返回304,不同返回200以及资源内容。
  • 劣势问题

  • 2、只要资源修改,无论内容是否发生实质性的变化,都会将该资源返回客户端。例如周期性重写,这种情况下该资源包含的数据实际上一样的。

  • 3、以时刻作为标识,无法识别一秒内进行多次修改的情况。 如果资源更新的速度是秒以下单位,那么该缓存是不能被使用的,因为它的时间单位最低是秒。

  • 4、某些服务器不能精确的得到文件的最后修改时间。

  • 5、如果文件是通过服务器动态生成的,那么该方法的更新时间永远是生成的时间,尽管文件可能没有变化,所以起不到缓存的作用。

7)协商缓存-Etag/If-None-match

  • 为了解决上述问题,出现了一组新的字段 EtagIf-None-Match
  • Etag 存储的是文件的特殊标识(一般都是 hash 生成的),服务器存储着文件的 Etag 字段。之后的流程和 Last-Modified 一致,只是 Last-Modified 字段和它所表示的更新时间改变成了 Etag 字段和它所表示的文件 hash,把 If-Modified-Since 变成了 If-None-Match。服务器同样进行比较,命中返回 304, 不命中返回新资源和 200。
  • 浏览器在发起请求时,服务器返回在Response header中返回请求资源的唯一标识。在下一次请求时,会将上一次返回的Etag值赋值给If-No-Matched并添加在Request Header中。服务器将浏览器传来的if-no-matched跟自己的本地的资源的ETag做对比,如果匹配,则返回304通知浏览器读取本地缓存,否则返回200和更新后的资源。
  • Etag 的优先级高于 Last-Modified
  • 优势特点
    • 1、可以更加精确的判断资源是否被修改,可以识别一秒内多次修改的情况。
    • 2、不存在版本问题,每次请求都回去服务器进行校验。
  • 劣势问题
    • 1、计算ETag值需要性能损耗。
    • 2、分布式服务器存储的情况下,计算ETag的算法如果不一样,会导致浏览器从一台服务器上获得页面内容后到另外一台服务器上进行验证时现ETag不匹配的情况。

8、介绍防抖节流原理、区别以及应用,并用JavaScript进行实现

1)防抖

  • 原理:在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时

  • 适用场景:

  • 按钮提交场景:防止多次提交按钮,只执行最后提交的一次

  • 搜索框连续场景:防止连续发送请求,只发送最后一次输入

    const debounce = function(fun, delay=500){ let timer = null; return function(...args) { clearTimeout(timer); timer = setTimeout(() => { fun.apply(this, args); }, delay); } }

2)节流

  • 原理:规定在一个单位时间内,只能触发一次函数。如果这个单位时间内触发多次函数,只有一次生效。

  • 适用场景:

  • 拖拽场景:固定时间内只执行一次,防止超高频次触发位置变动

  • 缩放场景:监控浏览器resize

    const throttle = (fun, delay) => { let flag = true; return funcion(...args) { if (!flag) return; flag = false; setTimeout(() => { fun.apply(this, args); flag = true; }, delay); }、 }

9、前端安全、中间人攻击

1)XSS:跨站脚本攻击

攻击者想尽一切办法将可执行的代码注入网页中。

存储型(server端):

  • 场景:见于带有用户保存数据功能的网站,如论坛发帖、商品评论、用户私信等。

  • 攻击步骤:

  • 攻击者将恶意代码提交到目标网站的数据库中

  • 用户打开目标网站时,服务端将恶意代码从数据库中取出来,拼接在HTML中返回给浏览器

  • 用户浏览器在收到响应后解析执行,混在其中的恶意代码也同时被执行

  • 恶意代码窃取用户数据,并发送到指定攻击者的网站,或者冒充用户行为,调用目标网站的接口,执行恶意操作

反射型(server端)

与存储型的区别在于,存储型的恶意代码存储在数据库中,反射型的恶意代码在URL上

  • 场景:通过URL传递参数的功能,如网站功能、跳转等

  • 攻击步骤:

  • 攻击者构造出特殊的URL,其中包含恶意代码

  • 用户打开带有恶意代码的URL时,网站服务端将恶意代码从 URL 中取出,拼接在 HTML 中返回给浏览器。

  • 用户浏览器接收到响应后解析执行,混在其中的恶意代码也被执行。

  • 恶意代码窃取用户数据并发送到攻击者的网站,或者冒充用户的行为,调用目标网站接口执行攻击者指定的操作。

Dom型(浏览器端)

DOM型XSS攻击中,取出和执行恶意代码由浏览器端完成,属于前端 JavaScript 自身的安全漏洞,而其他两种 XSS 都属于服务端的安全漏洞。

  • 场景:通过 URL 传递参数的功能,如网站搜索、跳转等。

  • 攻击步骤:

  • i)攻击者构造出特殊的 URL,其中包含恶意代码。

  • ii)用户打开带有恶意代码的 URL。

  • iii)用户浏览器接收到响应后解析执行,前端 JavaScript 取出 URL 中的恶意代码并执行。

  • iv)恶意代码窃取用户数据并发送到攻击者的网站,或者冒充用户的行为,调用目标网站接口执行攻击者指定的操作。

预防方案:(防止攻击者提交恶意代码,防止浏览器执行恶意代码)

  • i)对数据进行严格的输出编码:如HTML元素的编码,JS编码,CSS编码,URL编码等等
    • 避免拼接 HTML;Vue/React 技术栈,避免使用 v-html / dangerouslySetInnerHTML
  • ii)CSP HTTP Header,即 Content-Security-Policy、X-XSS-Protection
    • 增加攻击难度,配置CSP(本质是建立白名单,由浏览器进行拦截)
    • Content-Security-Policy: default-src 'self' -所有内容均来自站点的同一个源(不包括其子域名)
    • Content-Security-Policy: default-src 'self' *.trusted.com-允许内容来自信任的域名及其子域名 (域名不必须与CSP设置所在的域名相同)
    • Content-Security-Policy: default-src https://yideng.com-该服务器仅允许通过HTTPS方式并仅从yideng.com域名来访问文档
  • iii)输入验证:比如一些常见的数字、URL、电话号码、邮箱地址等等做校验判断
  • iv)开启浏览器XSS防御:Http Only cookie,禁止 JavaScript 读取某些敏感 Cookie,攻击者完成 XSS 注入后也无法窃取此 Cookie。
  • v)验证码

2)CSRF(cross-site request forgery):跨站请求伪造

攻击者诱导受害者进入第三方网站,在第三方网站中,向被攻击网站发送跨站请求。利用受害者在被攻击网站已经获取的注册凭证,绕过后台的用户验证,达到冒充用户对被攻击的网站执行某项操作的目的。

攻击流程举例
  • i)受害者登录 a.com,并保留了登录凭证(Cookie)
  • ii)攻击者引诱受害者访问了b.com
  • iii)b.com 向 a.com 发送了一个请求:a.com/act=xx浏览器会默认携带a.com的Cookie
  • iv)a.com接收到请求后,对请求进行验证,并确认是受害者的凭证,误以为是受害者自己发送的请求
  • v)a.com以受害者的名义执行了act=xx
  • vi)攻击完成,攻击者在受害者不知情的情况下,冒充受害者,让a.com执行了自己定义的操作
攻击类型
  • i)GET型:如在页面的某个 img 中发起一个 get 请求
  • ii)POST型:通过自动提交表单到恶意网站
  • iii)链接型:需要诱导用户点击链接
预防方案:

CSRF通常从第三方网站发起,被攻击的网站无法防止攻击发生,只能通过增强自己网站针对CSRF的防护能力来提升安全性。)

  • i)同源检测:通过Header中的Origin Header 、Referer Header 确定,但不同浏览器可能会有不一样的实现,不能完全保证
  • ii)CSRF Token 校验:将CSRF Token输出到页面中(通常保存在Session中),页面提交的请求携带这个Token,服务器验证Token是否
    正确
  • iii)双重cookie验证:
    • 流程:
      • 步骤1:在用户访问网站页面时,向请求域名注入一个Cookie,内容为随机字符串(例如csrfcookie=v8g9e4ksfhw)
      • 步骤2:在前端向后端发起请求时,取出Cookie,并添加到URL的参数中(接上例POST www.a.com/comment?csr…
      • 步骤3:后端接口验证Cookie中的字段与URL参数中的字段是否一致,不一致则拒绝。
    • 优点:
      • 无需使用Session,适用面更广,易于实施。
      • Token储存于客户端中,不会给服务器带来压力。
      • 相对于Token,实施成本更低,可以在前后端统一拦截校验,而不需要一个个接口和页面添加。
    • 缺点:
      -Cookie中增加了额外的字段。
      -如果有其他漏洞(例如XSS),攻击者可以注入Cookie,那么该防御方式失效。
      -难以做到子域名的隔离。
      -为了确保Cookie传输安全,采用这种防御方式的最好确保用整站HTTPS的方式,如果还没切HTTPS的使用这种方式也会有风险。
  • iv)Samesite Cookie属性:Google起草了一份草案来改进HTTP协议,那就是为Set-Cookie响应头新增Samesite属性,它用来标明这个 Cookie是个“同站 Cookie”,同站Cookie只能作为第一方Cookie,不能作为第三方Cookie,Samesite 有两个属性值,Strict 为任何情况下都不可以作为第三方 Cookie ,Lax 为可以作为第三方 Cookie , 但必须是Get请求

3)iframe安全

说明:

  • i)嵌入第三方 iframe 会有很多不可控的问题,同时当第三方 iframe 出现问题或是被劫持之后,也会诱发安全性问题
  • ii)点击劫持
    • 攻击者将目标网站通过 iframe 嵌套的方式嵌入自己的网页中,并将 iframe 设置为透明,诱导用户点击。
  • iii)禁止自己的 iframe 中的链接外部网站的JS
预防方案:
  • i)为 iframe 设置 sandbox 属性,通过它可以对iframe的行为进行各种限制,充分实现“最小权限“原则
  • ii)服务端设置 X-Frame-Options Header头,拒绝页面被嵌套,X-Frame-Options 是HTTP 响应头中用来告诉浏览器一个页面是否可以嵌入 中 eg.X-Frame-Options: SAMEORIGIN
  • SAMEORIGIN: iframe 页面的地址只能为同源域名下的页面
  • ALLOW-FROM: 可以嵌套在指定来源的 iframe 里
  • DENY: 当前页面不能被嵌套在 iframe 里
  • iii)设置 CSP 即 Content-Sec
  • github.com/lgwebdream/…

    10、对闭包的看法,为什么要用闭包?说一下闭包原理以及应用场景

    1)什么是闭包

    函数执行后返回结果是一个内部函数,如果内部函数持有被执行函数作用域的变量,即形成了闭包。

    可以在内部函数访问到外部函数作用域。使用闭包,一可以读取到函数中的变量,二可以将函数中的变量存储在内存中,保护变量不被污染。而正因闭包会把函数中的变量值存储在内存中,会对内存有消耗,所以不能滥用闭包,否则会影响网页性能,造成内存泄漏。当不需要使用闭包时,要及时释放内存,可将内层函数对象的变量赋值为null。

    2)闭包原理

    函数执行分为两个阶段(预编译阶段和执行阶段)

    • 在预编译阶段,如果发现内部函数使用了外部函数的变量,则会在内存中创建一个“闭包”对象并保存对应的变量值,如果已存在“闭包”,则只需要增加对应的属性值即可。
    • 执行完后,函数执行上下文会被销毁,函数对闭包对象的引用也会被销毁,但其内部函数还持有该“闭包”的引用,所以内部函数可以继续使用“外部函数”中的变量

    利用了函数作用域链的特性,一个函数内部定义的函数会将包含外部函数的活动对象添加到它的作用域链中,函数执行完毕,其执行作用域链销毁,但因内部函数的作用域链仍然在引用这个活动对象,所以其活动对象不会被销毁,直到内部函数被烧毁后才被销毁。

    4)应用场景

    应用场景一:典型应用是模块封装,在各模块规范出现之前,都是用这种方式防止变量污染全局。

    var Yideng = (function(){
        // 这样声明为模块私有变量,外界无法直接访问
        var foo = 0;
    
        function Yideng() {}
        Yideng.prototype.bar = function bar() {
            return foo;
        };
        return Yideng;
    }());
    

    应用场景二:在循环中创建闭包,防止取到意外的值。

    for (var i = 0; i < 3; i++) {
        document.getElementById('id' + i).onfocus = function() {
          alert(i);
        };
    }
    //可用闭包解决
    function makeCallback(num) {
      return function() {
        alert(num);
      };
    }
    for (var i = 0; i < 3; i++) {
        document.getElementById('id' + i).onfocus = makeCallback(i);
    }
    

    11、css伪类和伪元素的区别

    12、有一堆整数,请把他们分成三份,确保每一份和尽可能相等(11,42,23,4,5,6 4 5 6 11 23 42 56 78 90

    function sliceArray (arr, count) {
      // 平均值向上取整
      let average = Math.ceil(arr.reduce((acc, v) => acc + v, 0) / count)
      // 由大到小排列
      arr.sort((n1, n2) => n2 - n1)
      const getArr = () => {
        let sum = 0, newArr = [] // 存相应数据
        arr.map((v, i) => {
          if (sum + v <= average) {
            sum += v
            newArr.push(v)
            arr.splice(i, 1)
          }
        })
        return newArr;
      }
    
      let backArr = new Array(count).fill(0)
      backArr.forEach((x, i) => {
        backArr[i] = i === backArr.length - 1 ? arr : getArr()
      })
      return backArr
    }
    

    15、add(1)(2)(3)

    函数柯里化概念:柯里化(Currying)是把接受多个参数的函数转变为接受一个单一参数的函数,并且返回接受余下的参数且返回结果的新函数的技术。

    function add (a) {
        return function (b) {
            returnnfunction (c) {
                return a + b + c;
            }
        }
    }
    console.log(add(1)(2)(3)); // 6
    
    
    const curry = (fn) => (
        judge = (...args) => {
            args.length === fn.length 
            ? fn(...args)
            : (...arg) => judge(...args, ...arg);
        };
    );
    const add = (a, b, c) => a + b + c;
    const curryAdd = curry(add);
    
    console.log(curryAdd(1)(2)(3)); // 6
    console.log(curryAdd(1, 2)(3)); // 6
    console.log(curryAdd(1)(2, 3)); // 6
    
    
    function add (...args) {
        return args.reduce((a, b) => a + b, 0);
    }
    const curry = (fn) => {
        var args = [];
        return function temp (...arg) {
            if (arg.length) {
                args = [
                    ...args,
                    ...arg
                ];
                return temp;
            } else {
                var val = fn.apply(this, ...args);
                args = [];
                return val;
            }
        }
    }
    

    16、实现链式调用

    链式调用的核心就是在于调用完的方法将自身实例返回。

    1)

    function Class1() {
        console.log('初始化');
    }
    Class1.prototype.method = function(param) {
        console.log(param);
        return this;
    }
    let c1 = new Class1();
    c1.method('第一次调用').method('第二次链式调用').method('第三次链式调用');
    

    2)

    var obj = {
        a: function() {
            console.log("a");
            return this;
        },
        b: function() {
            console.log("b");
            return this;
        },
    };
    obj.a().b();
    

    17、React事件绑定原理