求职季节之你必须要懂的原生js(上)


(来自掘金) 寒冬求职之季你必须要懂的原生JS(上)

1. 基本类型有哪几种?null是对象吗?基本数据类型和复杂数据类型存储有啥区别?

  • 基本数据类型有6种:undefined、boolean、null、number、string、symbol(es6新增类型)。
  • 虽然 typeof null 返回的是对象,但是null不是对象,而是基本类型种的一种。
  • 基本数据类型存储在栈内存,存储的是值。
  • 复杂数据类型存储的在堆内存,存储的是地址。当我们把对象赋值给另一个变量的时候,复制的是地址,指向同一块内存空间,当其中一个对象改变时,另一个对象也会改变。

2. typeof是否正确判断类型?instanceof呢?instanceof的实现原理是什么?

  • typeof 能正确判断基本数据类型,但除了null之外, typeof null返回的是对象
  • 但是对于对象,typeof不能正确的判断其类型,type 一个函数 输出的是 function,其余的对象都返回object
  • instanceof 能正确判断复杂类型但不能判断基本类型
  • instanceof是通过原型链判断的,A instanceof B , 在A的原型链中层层查找,是否有原型等于B.prototype,如果一直站到A的原型链的顶端(null即 Object.prototype.proto),仍然不等于B.prototype,那么返回false,否则返回true
  • instanceof 的实现代码:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // L instanceof R
    function instance_of(L, R) { // L 为左边表达式 R 为右表达式
    var O = R.prototype; // 取R的显示原型
    L = L._proto_; // 取L的隐式原型
    while (true) { // 遇到return 语句则跳出循环
    if (L === null) // 已经找到顶层
    return false;
    if (L === O) // 当 O 严格等于 L 时,返回true
    return true;
    L = L._proto_; // 继续向上一层原型链查找
    }
    }

3. for of,for in 和forEach,map的区别

  • for…of循环:具有iterator接口,就可以使用for…of循环遍历他的成员(属性值)。for…of 循环可以使用的范围包括数组、Set和Map结构、某些类似数组的对象、generator对象,以及字符串。for…of 循环调用遍历器接口,数组的遍历器只返回具有数字索引的属性。对于普通的对象,for…of 结构不能直接使用,会报错,必须部署了Iterator接口后才能使用。可以中断循环。
  • for…in 循环:遍历对象自身和继承的可枚举的属性,不能直接获取属性值。可以中断循环。
  • forEach只能遍历数组,不能中断,没有返回值(或认为是undefined)。
  • map:只能遍历数组,不能中断,返回值修改后的数组。
    PS:Object.keys():返回给定对象的所有可枚举属性的字符串数组。
  • 关于forEach是否会改变原数组的问题:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    let arry = [1, 2, 3, 4];
    arry.forEach((item) => {
    item*=10;
    });
    console.log(arry);// [1, 2, 3, 4]
    arry.forEach((item) => {
    arry[1] = 10;
    });
    console.log(arry); // [1, 10, 3, 4]
    let arry2 = [
    {name: 'Vue'},
    {age: 10}
    ];
    arry2.forEach((item) => {
    item.name = 20;
    });
    console.log(arrr2); // [{name: 20}, {age: 10, name: 20}

4.如何判断一个变量是不是数组?

  • 使用Arrar.isArray()判断,如果返回true,说明是数组
  • 使用instanceof Array 判断,如果返回true,说明是数组
  • 使用Object.prototype.toString.call判断,如果值是[objec Array],说明是数组
  • 通过constructor来判断,如果是数组,那么arr.constructor ==== Array,(不准确,因为我们可指定 obj.constructor = Array)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    function fn() {
    console.log(Array.isArray(arguments)); // false, 因为arguments是类数组
    console.log(Array.isArray([1, 2, 3])); // true
    console.log(arguments instanceof Array); // false
    console.log([1, 2, 3] instanceof Array); // true
    console.log(Object.prototype.toString.call(arguments)); // [object arguments]
    console.log(arguments.constructor === Array); // false
    fn(1, 2, 3, 4);
    }

5.类数组和数组的区别?

类数组:是一个普通的对象,而真实的数组是Array类型。

  • 拥有length属性,其他属性(索引)为非负整数(对象中的索引会被当做字符串来处理);
  • 不具有数组的方法;
    常见的类数组有:函数的参数arguments,DOM对象列表(比如通过document.querySelectorAll得到的列表),jQuery对象¥(“div”)
    类数组可以转换为数组:
    1
    2
    3
    4
    5
    6
    // 第一种转换方法
    Object.prototype.slice.call(arrayLike, start);
    // 第二种方法
    [...arrayLike]
    // 第三种方法
    Array.from(arrayLike);

PS:任何定义了遍历器(Irerator)接口的对象,都可以用扩展运算符转为真正的数组
Array.from方法用于将两类对象转为真正的数组:类似数组的对象(array-like-object)和可遍历(iterator)对象。


6.== 和 === 有什么区别?

=== 不需要类型转换,只有类型相等并且值相等时才返回true

== 如果两者类型不同,首先进行类型转换,具体流程如下:

  1. 首先判断两者类型是否相同,如果相等,判断值是否相等。
  2. 如果类型不同进行类型转换
  3. 判断比较的是否是null 或undefined,如果是,返回true
  4. 判断两者类型事是否为string和number,如果是,将字符串转换成number
  5. 判断其中一方是否是boolean,如果是,将boolean转为number再进行判断
  6. 判断其中一方是否是object且另一方为string、number或者symbol,如果是,将object转换为原始类型再判断
    1
    2
    3
    4
    5
    6
    let person1 = {
    age:25
    }
    let person2 = person1;
    person2.age = 10;
    console.log(person1 === person2);

思考:[] == ![];
我们来分析一下:[] == ![] 是true还是false?

  1. 首先我们需要知道!运算符优先级高于 ==
  2. ![] 引用类型转换成布尔值都是true,因此,![] 是false
  3. 根据上面的第5条,false转换为number是0,因此右边表达式的计算结果是0
  4. 再根据上面的第6条,有一方是number,那么将object也转换为Number(空数组转换为number是0,如果数组中只有一个数字,转换为number就是这个数字,其他情况均为NaN)
  5. 0 == 0;为true

7. ES6中的class和ES5的类有什么区别?

  1. ES6 class 内部所有定义的方法都是不可枚举的
  2. ES6 class 必须使用new调用
  3. ES6 class 不存在变量提升
  4. ES6 class 即默认为严格模式
  5. ES6 class 子类必须在父类的构造器函数中调用super(),这样才有this对象;ES5中类的继承关系是相反的,现有子类的this,然后用父类的方法应用在this上。

8.数组中的哪下API会改变原数组?

修改原数组的API有:
splice/reverse/fill/copyWithin/sort/push/pop/unshift/shift

不修改原数组的API有:
slice/map/forEach/every/filter/reduce/entries/find/some


9.let,const以及var的区别是什么?

  • let 和 const 定义的变量不会出现变量提升,而var 定义的变量会提升
  • let 和 const 是JS中的块级作用域
  • let 和 const 不允许重复声明(会抛出错误)
  • let 和 const 定义的变量,如果在定义语句之前使用会抛出错误(形成了暂时性死区),而var 不会
  • const 声明一个只读的常量。一旦声明,常量的值就不能改变(如果声明是一个对象,那么不能改变的是对象的引用地址)

10.在JS中什么是变量提升,什么是暂时性四区?

变量提升就是在变量声明之前就可以使用,值为undefined
在代码块中,使用let/const 命令声明变量之前,该变量都是不可用的。这在语法上称为“暂时性死区”。暂时性死区也意味着typeof不再是一个百分之百安全的操作

1
2
3
typeof x; // referenceError(暂时性死区)
let x;
typeof y; // 值是undefined,不会抛错

暂时性死区的本质就是,只要一进入当前作用域,所要使用的变量就已经存在了,但是不可获取,只有等到声明变量的那一行代码出现,才可以获取和使用该变量。


11.如何正确的判断this?箭头函数的this是什么?

this的绑定有四种规则:默认绑定、隐式绑定、显式绑定,new绑定

  1. 函数是否在new中调用(new绑定),如果是,那么this绑定的是新创建的对象。
  2. 函数使用通过call,apply调用,或者使用了bind(即硬绑定),如果是,那么this绑定的就是指定的对象
  3. 函数是否在某个上下文对象中调用(隐式绑定),如果是的话,this绑定的就是那个上线文对象。一般是obj.foo()
  4. 如果以上都不是,那么使用默认绑定。如果在严格模式下,则绑定到undefined,否则绑定到全局对象。
  5. 如果把null或者undefined作为this的绑定对象传入call、appley或者bind,这些值在调用时会被忽略,实际应用的是默认绑定规则。
  6. 箭头函数没有this,她的this继承于上一层代码块的this

测试下你已经get的知识点(浏览器执行环境)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var number = 5;
var obj = {
number: 3,
fn1: (function(){
var number;
this.number *= 2
number = number * 2;
number = 3;
return function () {
var num = this.number;
this.number *= 2;
console.log(num);
number *= 3;
console.log(number);
}
})()
}
var fn1 = obj.fn1;
fn1.call(null);
obj.fn1();
console.log(window.number);


12.词法作用域和this的区别

  • 词法作用域是由你写代码时将变量和块作用域写在哪里来决定的
  • this是在被调用的时候被绑定的,this指向什么?完全取决于函数的调用位置

13.谈谈你对JS执行上下文栈和作用域链的理解

执行上下文就是当前JS代码被解析和执行时所在的环境,JS执行上下文栈可以认为是一个存储函数调用的栈结构,遵循先进后出的原则。

  • Javascript 执行在单线程上,所有的代码都是排队执行
  • 一开始浏览器执行全局的代码时,首先创建全局的执行上下文,压入执行栈的顶部。
  • 每当进入一个函数的执行就会创建函数的执行上下文,并且把它压入执行栈的顶部。当前函数执行完成后,当前函数的执行上下文出栈,并等待垃圾回收。
  • 浏览器的JS执行引擎总是访问栈顶的执行上下文
  • 全局上下文只有唯一的一个,它在浏览器关闭时出栈。
    作用域链:无论是LHS还是RHS查询,都会在当前的作用域开始查找,如果没有找到,就会继续向上级作用域链查找目标标识符,每次上升一个作用域,一直到全局作用域为止。

14.什么是闭包?闭包的作用是什么?闭包有哪些使用场景?

闭包是指有权访问另一个函数作用域中的变量的函数,创建闭包最常用的方式就是在一个函数内部创建另一个函数。
闭包的作用有:

  1. 封装私有变量
  2. 模仿块级作用域(es5中没有块就作用域)
  3. 实现JS的模块

15.call、apply有什么区别?call,apply和bind的内部是如何实现的?

call和apply的功能相同,区别在于传参的方式不一样:

  • fn.call(obj,arg1,arg2,…)调用一个函数,具有一个指定的this值和分别提供的指定的参数(参数的列表)
  • fn.apply(obj, [argsArray])调用一个函数,具有一个指定的this值,一个作为一个数组(或类数组对象)提供的参数。

    call核心:

  • 将函数设为传入参数的属性
  • 指定this到函数并传入给定参数执行函数
  • 如果不传入参数或者参数为null,默认指向为window/global
  • 删除参数上的函数
    call方法的实现:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    Object.prootype.call = function (context) {
    if (!context) {
    context = typeof window === 'undefined' : global ? window;
    }
    contex.fn = this; // this的指向是当前函数function的实例
    let rest = [...argument].slice(1);
    let result = context.fn(...rest);
    delete context.fn;
    return result;
    }
    // 测试代码
    var foo = {
    name: 'Selina';
    }
    var name = 'Chirs';
    function bar(job, age) {
    console.log(this.name);
    console.log(job, age);
    }
    bar.call(foo, 'programmer', 20);
    // Selina programmer 20
    bar.call(null, 'teacher', 25);
    // 浏览器环境:Chirs teacher 25 // node 环境:undefined teacher 25

apply

apply的实现和call很类似,但是需要注意他们的参数是不一样的,apply的第二个参数是数组或类数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
Object.prototype.apply = function (context, rest) {
if (!context) {
context = typeof window === 'undefined' ? global : window;
}
constext.fn = this;
let result;
if (rest === undefined || rest === null) {
result = context.fn(rest);
} else if (type rest === 'object') {
result = context.fn(...rest)
}
delete context.fn;
return result;
}
// 测试代码
var foo = {
name: 'Selina';
}
var name = 'Chirs';
function bar(job, age) {
console.log(this.name);
console.log(job, age);
}
bar.apply(foo, ['programmer', 20]);
// Selina programmer 20
bar.call(null, 'teacher', 25);
// 浏览器环境:Chirs teacher 25 // node 环境:undefined teacher 25

bind

bind和call/apply 有一个很重要的的区别,一个函数被call/apply的时候,会直接调用,但是bind会创建一个新函数。当这个新函数被调用时,bind()的第一个参数作为它运行时的this,之后的一序列参数将会在传递的实参前传入作为它的参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Function.prototype.bind = function (context) {
if(typeof this !== 'function') {
throw new TypeError('not a function');
}
let self = this;
let args = [...arguments].slice(1);
function Fn() {}
Fn.prototype = this.prototype;
let bound = function () {
let res = [...args, ...arguments];
context = this instanceof Fn ? this : context || this;
return self.apply(context, res)
}
// 原型链
bound.prototype = new Fn();
return bound;
}

var name = 'jack';
function person(age, job, gender) {
console.log(this.name, age, job, gender);
}
var Yve = {name: 'Yvette'};
let result = person.bind(Yve, 22, 'enginner')('female')


16.new的原理是什么?通过new的方式创建对象和通过字面量创建有啥区别?

new:

  1. 创建一个新对象
  2. 这个新对象会被执行[[原型]]连接
  3. 将构造函数的作用域值赋值给新对象,即this指向这个新对象
  4. 如果函数没有返回其他对象,那么new 表达式中的函数调用会自动返回这个新对象
    1
    2
    3
    4
    5
    6
    7
    8
    9
    function new(func) {
    let target = {};
    target._proto_ = func.prototype;
    let res = func.call(target);
    if (typeof (res) == 'object' || typeof (res) == "function") {
    return res;
    }
    return target;
    }

字面量创建对象,不会调用Object构造函数,简洁且性能更好;
new Object() 方式创建对象本质上是方法的调用,涉及到在proto链中遍历该方法,当找到该方法后,又会生产方法调用必须的堆栈信息,方法调用结束后,还有释放该堆栈,性能不如字面量的方式。


17.谈谈你对原型的理解

在JavaScript中,每当定义一个对象(函数也是对象)时候,对象中都会包含一些预定义的属性。其中每个函数对象都有一个prototype属性,这个属性指向函数的原型对象。使用原型对象的好处就是所有对象实例共享共享它所包含的属性和方法。


18.什么是原型链?【原型链解决是什么问题?】

原型链解决的主要是集成问题
每个对象拥有一个原型对象,通过proto指针指向其原型对象,并从中集成方法和属性,同时原型对象也可能拥有原型,这样一层一层,最终指向null。这种关系称为原型链,同过原型链可以拥有定义在其他对象的中的属性和方法

1
p._proo_ === Parent.prototype


19.prototype 和 proto 区别是什么?

prototype 是构造函数的属性
proto是每个实例都有的属性,可以访问[[prototype]]属性
实例的proto与其构造函数的prototype指向的是同一个对象

1
2
3
4
5
6
7
8
function Student(name) {
this.name = name;
}
Student.prototype.setAge = function() {
this.age = 20;
}
let Jack = new Student('jack');
console.log(Jack._proto_ === Student.prototype);


20.使用ES5实现一个继承?

组合继承(最常用的方式)

1
2
3
4
5
6
7
8
9
10
11
12
13
function SuperType() {
this.name = '';
this.colors = ['red', 'yellow', 'blue'];
}
SuperType.prototype.sayName = function () {
console.log(this.name)
}
function SubType (name, age) {
SuperType.call(this, age);
this.age = age;
}
SubType.prototype = new SuperType();
SubType.prototype.construction = SubType;

21.什么是深拷贝?深拷贝和浅拷贝的区别是什么?

浅拷贝是值值复制第一层对象,但是当对象的属性是引用类型时,实质是复制的其引用,当引用指向的值改变时也会跟着变化。

深拷贝复制变量值了,对于非基本类型的变量,则递归至基本类型变量后,在复制。深拷贝的对象和原来的对象是隔离的,互不影响,对一个对象的修改并不会影响另一个对象。
实现一个深拷贝:

1
2
3
4
5
6
7
8
9
10
11
12
13
function deepClone(obj) {
if (obj === undefined) return null;
if (obj instanceof RegExp) return new RegExp(obj);
if (obj instanceof Date) return new Date(obj);
if (typeof obj !== 'object') {
return obj; // 不是复杂数据类型的话直接返回
}
let t = new obj.constuctor();
for (let key in obj) {
t[key] = deepClone(obj(key));
}
return t;
}


22.防抖和节流的区别是什么?防抖和节流的实现?

防抖和节流的作用都是防止函数的多次调用。区别在于,假设一个用户一直触发这个函数且每次触发函数的时间小于设置的时间,防抖的情况下只会调用一次,而节流的情况是每个一定时间调用一次函数。

防抖(debounce): n秒内函数只执行一次,如果n秒内高频事件再次被触发,则重新计算时间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
function debounce(func, wait, immediate=true) {
let timeout, context, args;
const later = () => settimeout(() => {
timeout = null; // 延时函数执行完毕,清空计时器
// 延迟执行的情况下,函数会在延迟函数中执行
// 使用到之前的的缓存参数和执行上下文
if (!immediate) {
func.appy(context, args);
context = args = null;
}
} , wait);
let debounced = function (...params) {
if (!timeout) {
timeout = later();
if(immediate) {
// 立即执行
func.apply(this, params);
} else {
// 闭包
constext = this;
args = params;
}
} else {
clearTimeout(timeout);
timeout = later();
}
}
debounced.cancel = function (params) {
clearTimeout(timeout);
timeout = null
};
return debounced;
}

防抖的应用场景:

  • 每次resize/scroll触发统计事件
  • 文本输入的验证(连续输入文字后发送AJAX请求进行验证,验证一次就好)

    节流(throttle): 高频事件在规定的时间内只会执行一次,执行一次后,只有大于设定的执行周期后才会执行第二次。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
function throttle(func, wait, options) {
var timeout, context, args, result;
var previous = 0;
if (!options) options = {};
var later = function () {
previous = options.leading === false ? 0: Date.now() || new Date().getTime();
timeout = null;
result = func.apply(context, args);
if (!timeout) context = args = null;
}
var throttled = function() {
var now = Date.now() || new Date().getTime();
if (!previous && options.leading === false) previous = now;
var remaining = wait - (now - previous);
context = this;
args = arguments;
if (remaining <= 0 || remaining > wait) {
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
previous = now;
result = func.apply(context, args);
} else if(!timeout && options.trailing !== false) {
// 判断是否设置了定时器和trailing
timeout = setTimeout(later, remaining);
}
return result;
}
throttled.cancel = function () {
clearTimeout(timeout);
previous = 0;
timeout = context = args = null;
}
return throttled;
}

函数节流的应用场景有:

  • DOM 元素的拖拽功能的实现(mousemove)
  • 设计游戏的mousedown/keydown时间(单位时间内只能发送一颗子弹)
  • 计算鼠标移动的距离(mousemove)
  • canvas模拟画板功能(mousemove)
  • 搜索联想
  • 监听滚动事件是否到页面底部自动加载更多:给scroll加了debounce后,只能用户停止滚动后,才能判断是否到了页面底部;如果是throttle的话,只要页面滚动就会间隔一段时间判断一次

23.取数组中的最大值(ES5、5S6)

1
2
3
4
5
6
7
8
// es5
Math.max.apply(null, [14, 3, 77, 301]);
// es6
Math.max(...[14, 3, 77, 301])
// reduce
[14, 3, 77, 301].reduce((accumulator, currentValue) => {
return accumulator = accumulator > currentVale ? accumulator : currentValue;
});

24.ES6新的特性有哪些?

  1. 新增了块级作用域(let 和 const)
  2. 提供了定义类的语法糖
  3. 新增了一种基本数据类型(Symbol)
  4. 新增了变量的结构赋值
  5. 函数参数允许设置默认值,引入了rest参数,新增了箭头函数
  6. 数组新增了一些API, 如isArray/from/of方法;数组实例新增了entries(), keys()和values()等方法
  7. 对象好数组新增了扩展运算符
  8. ES6新增了模块化(import/export)
  9. ES6新增了Set和Map数据结构
  10. ES6 原生提供了Proxy构造函数,用来生成Proxy实例
  11. ES6 新增了生成器(Generator)和遍历器(iterator)

25.setTimeout倒计时为什么会出现误差?

setTimeout()只是将事件加入了“任务队列”,必须等待当前执行栈执行完,主线程才会去执行它指定的回调函数。要是当前代码消耗时间很长,也有可能要等很久,所以并不能保证回调函数一定会在setTimeout()指定的时间执行。所以,setTimeout()的第二个参数表示的是最少时间,并非确切时。

HTML5标准规定了setTimeout()的第二参数的最小值不得小于4毫秒,如果低于这个值,则默认是4毫秒。在此之前,老版本的浏览器都将最短时间设为10毫秒。另外,对于那些DOM的变动(尤其是涉及页面重新渲染的部分),通常是间隔16毫秒执行,这时使用requestAnimationFrame()的效果要好于setTimeout()


26.为什么0.1 + 0.2 != 0.3

因为在进制转换和进阶运算的过程中出现精度损失。


27.promise有几种状态,Promise有什么优缺点?

promise有三种状态:fulfilled,rejected,pending
Promise的优点:

  1. 一旦状态改变就不会变,任何时候都可以得到这个结果。
  2. 可以将异步操作以同步操作表达出来,避免了层层嵌套的回调函数
    Promise的缺点:
  3. 无法取消Promise
  4. 当处于pending状态时,无法得知目前进展到哪一个阶段

28.Promise构造函数是同步执行还是异步执行,then中方法呢?promise如何实现then处理?

promise的构造函数是同步执行的。then中的方法是异步执行的。
promise的then实现

29. Promise和setTimeout的区别?

Promise是微服务,setTimeout是宏服务,同一个事件循环中,promise.then总是先于setTimeout执行。

30. 如何实现Promise.all?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
Promise.all = function (promises) {
return new Promise((resolve, reject) => {
let index = 0;
let result = [];
if (promises.length === 0) {
resolve(result);
} else {
function processValue(i, data) {
result[i] = data;
if (++index === promises.length) {
resolve(result);
}
}
for (let i = 0; i < promises.length; i++) {
Promise.resolve(promises[i].then((data) => {
processValue(i, data);
},(err) => {
reject(err);
return;
}));
}
}
});
}
```
---
### 31.如何实现Promise.finally?
不管成功还是失败,都会走到finally中,并且finally之后,还可以继续then。并且将值原封不动的传递给后面的then
```javascript
Promise.prototype.finally = function (callback) {
return this.then((value) => {
return Promise.resolve(callback()).then(() => {
return value;
});
}, (err) => {
return Promise.resolve(callback()).then(() => {
throw err;
})
});
}
```
### 32.什么是函数的柯里化?实现sum(1)(2)(3) 返回的结果是1,2,3的和
```javascript
function sum (a) {
return function (b){
return function (c) {
return a + b + c;
}
}
}
sum(1)(2)(3);

引申:实现一个curry函数,将普通函数进行柯里化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function cuury(fn, args = []) {
return function() {
let rest = [...args, ...arguments];
if (rest.length < fn.length) {
return curry.call(this, fn, rest);
} else {
return fn.apply(this, rest)
}
}
}
// test
function sum(a, b, c) {
return a + b + c;
}
let sumFn = curry(sum);
console.log(sumFn(1)(2)(3)); // 6
console.log(sumFn(1)(2)(3)); // 6