乐趣区

关于javascript:js手写题汇总面试前必刷

event 模块

实现 node 中回调函数的机制,node 中回调函数其实是外部应用了 观察者模式

观察者模式:定义了对象间一种一对多的依赖关系,当指标对象 Subject 产生扭转时,所有依赖它的对象 Observer 都会失去告诉。

function EventEmitter() {this.events = new Map();
}

// 须要实现的一些办法:// addListener、removeListener、once、removeAllListeners、emit

// 模仿实现 addlistener 办法
const wrapCallback = (fn, once = false) => ({callback: fn, once});
EventEmitter.prototype.addListener = function(type, fn, once = false) {const hanlder = this.events.get(type);
  if (!hanlder) {
    // 没有 type 绑定事件
    this.events.set(type, wrapCallback(fn, once));
  } else if (hanlder && typeof hanlder.callback === 'function') {
    // 目前 type 事件只有一个回调
    this.events.set(type, [hanlder, wrapCallback(fn, once)]);
  } else {
    // 目前 type 事件数 >=2
    hanlder.push(wrapCallback(fn, once));
  }
}
// 模仿实现 removeListener
EventEmitter.prototype.removeListener = function(type, listener) {const hanlder = this.events.get(type);
  if (!hanlder) return;
  if (!Array.isArray(this.events)) {if (hanlder.callback === listener.callback) this.events.delete(type);
    else return;
  }
  for (let i = 0; i < hanlder.length; i++) {const item = hanlder[i];
    if (item.callback === listener.callback) {hanlder.splice(i, 1);
      i--;
      if (hanlder.length === 1) {this.events.set(type, hanlder[0]);
      }
    }
  }
}
// 模仿实现 once 办法
EventEmitter.prototype.once = function(type, listener) {this.addListener(type, listener, true);
}
// 模仿实现 emit 办法
EventEmitter.prototype.emit = function(type, ...args) {const hanlder = this.events.get(type);
  if (!hanlder) return;
  if (Array.isArray(hanlder)) {
    hanlder.forEach(item => {item.callback.apply(this, args);
      if (item.once) {this.removeListener(type, item);
      }
    })
  } else {hanlder.callback.apply(this, args);
    if (hanlder.once) {this.events.delete(type);
    }
  }
  return true;
}
EventEmitter.prototype.removeAllListeners = function(type) {const hanlder = this.events.get(type);
  if (!hanlder) return;
  this.events.delete(type);
}

实现 Event(event bus)

event bus 既是 node 中各个模块的基石,又是前端组件通信的依赖伎俩之一,同时波及了订阅 - 公布设计模式,是十分重要的根底。

简略版:

class EventEmeitter {constructor() {this._events = this._events || new Map(); // 贮存事件 / 回调键值对
    this._maxListeners = this._maxListeners || 10; // 设立监听下限
  }
}


// 触发名为 type 的事件
EventEmeitter.prototype.emit = function(type, ...args) {
  let handler;
  // 从贮存事件键值对的 this._events 中获取对应事件回调函数
  handler = this._events.get(type);
  if (args.length > 0) {handler.apply(this, args);
  } else {handler.call(this);
  }
  return true;
};

// 监听名为 type 的事件
EventEmeitter.prototype.addListener = function(type, fn) {
  // 将 type 事件以及对应的 fn 函数放入 this._events 中贮存
  if (!this._events.get(type)) {this._events.set(type, fn);
  }
};

面试版:

class EventEmeitter {constructor() {this._events = this._events || new Map(); // 贮存事件 / 回调键值对
    this._maxListeners = this._maxListeners || 10; // 设立监听下限
  }
}

// 触发名为 type 的事件
EventEmeitter.prototype.emit = function(type, ...args) {
  let handler;
  // 从贮存事件键值对的 this._events 中获取对应事件回调函数
  handler = this._events.get(type);
  if (args.length > 0) {handler.apply(this, args);
  } else {handler.call(this);
  }
  return true;
};

// 监听名为 type 的事件
EventEmeitter.prototype.addListener = function(type, fn) {
  // 将 type 事件以及对应的 fn 函数放入 this._events 中贮存
  if (!this._events.get(type)) {this._events.set(type, fn);
  }
};

// 触发名为 type 的事件
EventEmeitter.prototype.emit = function(type, ...args) {
  let handler;
  handler = this._events.get(type);
  if (Array.isArray(handler)) {
    // 如果是一个数组阐明有多个监听者, 须要顺次此触发外面的函数
    for (let i = 0; i < handler.length; i++) {if (args.length > 0) {handler[i].apply(this, args);
      } else {handler[i].call(this);
      }
    }
  } else {
    // 单个函数的状况咱们间接触发即可
    if (args.length > 0) {handler.apply(this, args);
    } else {handler.call(this);
    }
  }

  return true;
};

// 监听名为 type 的事件
EventEmeitter.prototype.addListener = function(type, fn) {const handler = this._events.get(type); // 获取对应事件名称的函数清单
  if (!handler) {this._events.set(type, fn);
  } else if (handler && typeof handler === "function") {
    // 如果 handler 是函数阐明只有一个监听者
    this._events.set(type, [handler, fn]); // 多个监听者咱们须要用数组贮存
  } else {handler.push(fn); // 曾经有多个监听者, 那么间接往数组里 push 函数即可
  }
};

EventEmeitter.prototype.removeListener = function(type, fn) {const handler = this._events.get(type); // 获取对应事件名称的函数清单

  // 如果是函数, 阐明只被监听了一次
  if (handler && typeof handler === "function") {this._events.delete(type, fn);
  } else {
    let postion;
    // 如果 handler 是数组, 阐明被监听屡次要找到对应的函数
    for (let i = 0; i < handler.length; i++) {if (handler[i] === fn) {postion = i;} else {postion = -1;}
    }
    // 如果找到匹配的函数, 从数组中革除
    if (postion !== -1) {
      // 找到数组对应的地位, 间接革除此回调
      handler.splice(postion, 1);
      // 如果革除后只有一个函数, 那么勾销数组, 以函数模式保留
      if (handler.length === 1) {this._events.set(type, handler[0]);
      }
    } else {return this;}
  }
};

实现具体过程和思路见实现 event

模板引擎实现

let template = '我是{{name}},年龄{{age}},性别{{sex}}';
let data = {
  name: '姓名',
  age: 18
}
render(template, data); // 我是姓名,年龄 18,性别 undefined

function render(template, data) {const reg = /\{\{(\w+)\}\}/; // 模板字符串正则
  if (reg.test(template)) { // 判断模板里是否有模板字符串
    const name = reg.exec(template)[1]; // 查找以后模板里第一个模板字符串的字段
    template = template.replace(reg, data[name]); // 将第一个模板字符串渲染
    return render(template, data); // 递归的渲染并返回渲染后的构造
  }
  return template; // 如果模板没有模板字符串间接返回
}

实现深拷贝

  • 浅拷贝: 浅拷贝指的是将一个对象的属性值复制到另一个对象,如果有的属性的值为援用类型的话,那么会将这个援用的地址复制给对象,因而两个对象会有同一个援用类型的援用。浅拷贝能够应用 Object.assign 和开展运算符来实现。
  • 深拷贝: 深拷贝绝对浅拷贝而言,如果遇到属性值为援用类型的时候,它新建一个援用类型并将对应的值复制给它,因而对象取得的一个新的援用类型而不是一个原有类型的援用。深拷贝对于一些对象能够应用 JSON 的两个函数来实现,然而因为 JSON 的对象格局比 js 的对象格局更加严格,所以如果属性值里边呈现函数或者 Symbol 类型的值时,会转换失败

(1)JSON.stringify()

  • JSON.parse(JSON.stringify(obj))是目前比拟罕用的深拷贝办法之一,它的原理就是利用 JSON.stringifyjs 对象序列化(JSON 字符串),再应用 JSON.parse 来反序列化 (还原)js 对象。
  • 这个办法能够简略粗犷的实现深拷贝,然而还存在问题,拷贝的对象中如果有函数,undefined,symbol,当应用过 JSON.stringify() 进行解决之后,都会隐没。
let obj1 = {  a: 0,
              b: {c: 0}
            };
let obj2 = JSON.parse(JSON.stringify(obj1));
obj1.a = 1;
obj1.b.c = 1;
console.log(obj1); // {a: 1, b: {c: 1}}
console.log(obj2); // {a: 0, b: {c: 0}}

(2)函数库 lodash 的_.cloneDeep 办法

该函数库也有提供_.cloneDeep 用来做 Deep Copy

var _ = require('lodash');
var obj1 = {
    a: 1,
    b: {f: { g: 1} },
    c: [1, 2, 3]
};
var obj2 = _.cloneDeep(obj1);
console.log(obj1.b.f === obj2.b.f);// false

(3)手写实现深拷贝函数

// 深拷贝的实现
function deepCopy(object) {if (!object || typeof object !== "object") return;

  let newObject = Array.isArray(object) ? [] : {};

  for (let key in object) {if (object.hasOwnProperty(key)) {newObject[key] =
        typeof object[key] === "object" ? deepCopy(object[key]) : object[key];
    }
  }

  return newObject;
}

参考:前端手写面试题具体解答

实现 Ajax

步骤

  • 创立 XMLHttpRequest 实例
  • 收回 HTTP 申请
  • 服务器返回 XML 格局的字符串
  • JS 解析 XML,并更新部分页面
  • 不过随着历史进程的推动,XML 曾经被淘汰,取而代之的是 JSON。

理解了属性和办法之后,依据 AJAX 的步骤,手写最简略的 GET 申请。

实现有并行限度的 Promise 调度器

题目形容:JS 实现一个带并发限度的异步调度器 Scheduler,保障同时运行的工作最多有两个

addTask(1000,"1");
 addTask(500,"2");
 addTask(300,"3");
 addTask(400,"4");
 的输入程序是:2 3 1 4

 整个的残缺执行流程:一开始 1、2 两个工作开始执行
500ms 时,2 工作执行结束,输入 2,工作 3 开始执行
800ms 时,3 工作执行结束,输入 3,工作 4 开始执行
1000ms 时,1 工作执行结束,输入 1,此时只剩下 4 工作在执行
1200ms 时,4 工作执行结束,输入 4 

实现代码如下:

class Scheduler {constructor(limit) {this.queue = [];
    this.maxCount = limit;
    this.runCounts = 0;
  }
  add(time, order) {const promiseCreator = () => {return new Promise((resolve, reject) => {setTimeout(() => {console.log(order);
          resolve();}, time);
      });
    };
    this.queue.push(promiseCreator);
  }
  taskStart() {for (let i = 0; i < this.maxCount; i++) {this.request();
    }
  }
  request() {if (!this.queue || !this.queue.length || this.runCounts >= this.maxCount) {return;}
    this.runCounts++;
    this.queue
      .shift()()
      .then(() => {
        this.runCounts--;
        this.request();});
  }
}
const scheduler = new Scheduler(2);
const addTask = (time, order) => {scheduler.add(time, order);
};
addTask(1000, "1");
addTask(500, "2");
addTask(300, "3");
addTask(400, "4");
scheduler.taskStart();

手写 instanceof 办法

instanceof 运算符用于判断构造函数的 prototype 属性是否呈现在对象的原型链中的任何地位。

实现步骤:

  1. 首先获取类型的原型
  2. 而后取得对象的原型
  3. 而后始终循环判断对象的原型是否等于类型的原型,直到对象原型为 null,因为原型链最终为 null

具体实现:

function myInstanceof(left, right) {let proto = Object.getPrototypeOf(left), // 获取对象的原型
      prototype = right.prototype; // 获取构造函数的 prototype 对象

  // 判断构造函数的 prototype 对象是否在对象的原型链上
  while (true) {if (!proto) return false;
    if (proto === prototype) return true;

    proto = Object.getPrototypeOf(proto);
  }
}

实现 JSON.parse

var json = '{"name":"cxk","age":25}';
var obj = eval("(" + json + ")");

此办法属于黑魔法,极易容易被 xss 攻打,还有一种 new Function 大同小异。

手写 call 函数

call 函数的实现步骤:

  1. 判断调用对象是否为函数,即便咱们是定义在函数的原型上的,然而可能呈现应用 call 等形式调用的状况。
  2. 判断传入上下文对象是否存在,如果不存在,则设置为 window。
  3. 解决传入的参数,截取第一个参数后的所有参数。
  4. 将函数作为上下文对象的一个属性。
  5. 应用上下文对象来调用这个办法,并保留返回后果。
  6. 删除方才新增的属性。
  7. 返回后果。
// call 函数实现
Function.prototype.myCall = function(context) {
  // 判断调用对象
  if (typeof this !== "function") {console.error("type error");
  }
  // 获取参数
  let args = [...arguments].slice(1),
      result = null;
  // 判断 context 是否传入,如果未传入则设置为 window
  context = context || window;
  // 将调用函数设为对象的办法
  context.fn = this;
  // 调用函数
  result = context.fn(...args);
  // 将属性删除
  delete context.fn;
  return result;
};

二分查找

function search(arr, target, start, end) {
  let targetIndex = -1;

  let mid = Math.floor((start + end) / 2);

  if (arr[mid] === target) {
    targetIndex = mid;
    return targetIndex;
  }

  if (start >= end) {return targetIndex;}

  if (arr[mid] < target) {return search(arr, target, mid + 1, end);
  } else {return search(arr, target, start, mid - 1);
  }
}

// const dataArr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
// const position = search(dataArr, 6, 0, dataArr.length - 1);
// if (position !== -1) {//   console.log(` 指标元素在数组中的地位:${position}`);
// } else {//   console.log("指标元素不在数组中");
// }

实现 new 的过程

new 操作符做了这些事:

  • 创立一个全新的对象
  • 这个对象的 __proto__ 要指向构造函数的原型 prototype
  • 执行构造函数,应用 call/apply 扭转 this 的指向
  • 返回值为 object 类型则作为 new 办法的返回值返回,否则返回上述全新对象
function myNew(fn, ...args) {
  // 基于原型链 创立一个新对象
  let newObj = Object.create(fn.prototype);
  // 增加属性到新对象上 并获取 obj 函数的后果
  let res = fn.apply(newObj, args); // 扭转 this 指向

  // 如果执行后果有返回值并且是一个对象, 返回执行的后果, 否则, 返回新创建的对象
  return typeof res === 'object' ? res: newObj;
}
// 用法
function Person(name, age) {
  this.name = name;
  this.age = age;
}
Person.prototype.say = function() {console.log(this.age);
};
let p1 = myNew(Person, "poety", 18);
console.log(p1.name);
console.log(p1);
p1.say();

实现 instanceOf

思路:

  • 步骤 1:先获得以后类的原型,以后实例对象的原型链
  • ​步骤 2:始终循环(执行原型链的查找机制)

    • 获得以后实例对象原型链的原型链(proto = proto.__proto__,沿着原型链始终向上查找)
    • 如果 以后实例的原型链 __proto__ 上找到了以后类的原型prototype,则返回 true
    • 如果 始终找到 Object.prototype.__proto__ == nullObject 的基类 (null) 下面都没找到,则返回 false
// 实例.__ptoto__ === 类.prototype
function _instanceof(example, classFunc) {
    // 因为 instance 要检测的是某对象,须要有一个前置判断条件
    // 根本数据类型间接返回 false
    if(typeof example !== 'object' || example === null) return false;

    let proto = Object.getPrototypeOf(example);
    while(true) {if(proto == null) return false;

        // 在以后实例对象的原型链上,找到了以后类
        if(proto == classFunc.prototype) return true;
        // 沿着原型链__ptoto__一层一层向上查
        proto = Object.getPrototypeof(proto); // 等于 proto.__ptoto__
    }
}

console.log('test', _instanceof(null, Array)) // false
console.log('test', _instanceof([], Array)) // true
console.log('test', _instanceof('', Array)) // false
console.log('test', _instanceof({}, Object)) // true

实现数组元素求和

  • arr=[1,2,3,4,5,6,7,8,9,10],求和
let arr=[1,2,3,4,5,6,7,8,9,10]
let sum = arr.reduce((total,i) => total += i,0);
console.log(sum);
  • arr=[1,2,3,[[4,5],6],7,8,9],求和
var = arr=[1,2,3,[[4,5],6],7,8,9]
let arr= arr.toString().split(',').reduce((total,i) => total += Number(i),0);
console.log(arr);

递归实现:

let arr = [1, 2, 3, 4, 5, 6] 

function add(arr) {if (arr.length == 1) return arr[0] 
    return arr[0] + add(arr.slice(1)) 
}
console.log(add(arr)) // 21

替换 a,b 的值,不能用长期变量

奇妙的利用两个数的和、差:

a = a + b
b = a - b
a = a - b

深克隆(deepclone)

简略版:

const newObj = JSON.parse(JSON.stringify(oldObj));

局限性:

  1. 他无奈实现对函数、RegExp 等非凡对象的克隆
  2. 会摈弃对象的 constructor, 所有的构造函数会指向 Object
  3. 对象有循环援用, 会报错

面试版:

/**
 * deep clone
 * @param  {[type]} parent object 须要进行克隆的对象
 * @return {[type]}        深克隆后的对象
 */
const clone = parent => {
  // 判断类型
  const isType = (obj, type) => {if (typeof obj !== "object") return false;
    const typeString = Object.prototype.toString.call(obj);
    let flag;
    switch (type) {
      case "Array":
        flag = typeString === "[object Array]";
        break;
      case "Date":
        flag = typeString === "[object Date]";
        break;
      case "RegExp":
        flag = typeString === "[object RegExp]";
        break;
      default:
        flag = false;
    }
    return flag;
  };

  // 解决正则
  const getRegExp = re => {
    var flags = "";
    if (re.global) flags += "g";
    if (re.ignoreCase) flags += "i";
    if (re.multiline) flags += "m";
    return flags;
  };
  // 保护两个贮存循环援用的数组
  const parents = [];
  const children = [];

  const _clone = parent => {if (parent === null) return null;
    if (typeof parent !== "object") return parent;

    let child, proto;

    if (isType(parent, "Array")) {
      // 对数组做非凡解决
      child = [];} else if (isType(parent, "RegExp")) {
      // 对正则对象做非凡解决
      child = new RegExp(parent.source, getRegExp(parent));
      if (parent.lastIndex) child.lastIndex = parent.lastIndex;
    } else if (isType(parent, "Date")) {
      // 对 Date 对象做非凡解决
      child = new Date(parent.getTime());
    } else {
      // 解决对象原型
      proto = Object.getPrototypeOf(parent);
      // 利用 Object.create 切断原型链
      child = Object.create(proto);
    }

    // 解决循环援用
    const index = parents.indexOf(parent);

    if (index != -1) {
      // 如果父数组存在本对象, 阐明之前曾经被援用过, 间接返回此对象
      return children[index];
    }
    parents.push(parent);
    children.push(child);

    for (let i in parent) {
      // 递归
      child[i] = _clone(parent[i]);
    }

    return child;
  };
  return _clone(parent);
};

局限性:

  1. 一些非凡状况没有解决: 例如 Buffer 对象、Promise、Set、Map
  2. 另外对于确保没有循环援用的对象,咱们能够省去对循环援用的非凡解决,因为这很耗费工夫

原理详解实现深克隆

手写 Promise.all

1) 外围思路

  1. 接管一个 Promise 实例的数组或具备 Iterator 接口的对象作为参数
  2. 这个办法返回一个新的 promise 对象,
  3. 遍历传入的参数,用 Promise.resolve()将参数 ” 包一层 ”,使其变成一个 promise 对象
  4. 参数所有回调胜利才是胜利,返回值数组与参数程序统一
  5. 参数数组其中一个失败,则触发失败状态,第一个触发失败的 Promise 错误信息作为 Promise.all 的错误信息。

2)实现代码

一般来说,Promise.all 用来解决多个并发申请,也是为了页面数据结构的不便,将一个页面所用到的在不同接口的数据一起申请过去,不过,如果其中一个接口失败了,多个申请也就失败了,页面可能啥也出不来,这就看以后页面的耦合水平了

function promiseAll(promises) {return new Promise(function(resolve, reject) {if(!Array.isArray(promises)){throw new TypeError(`argument must be a array`)
    }
    var resolvedCounter = 0;
    var promiseNum = promises.length;
    var resolvedResult = [];
    for (let i = 0; i < promiseNum; i++) {Promise.resolve(promises[i]).then(value=>{
        resolvedCounter++;
        resolvedResult[i] = value;
        if (resolvedCounter == promiseNum) {return resolve(resolvedResult)
          }
      },error=>{return reject(error)
      })
    }
  })
}
// test
let p1 = new Promise(function (resolve, reject) {setTimeout(function () {resolve(1)
    }, 1000)
})
let p2 = new Promise(function (resolve, reject) {setTimeout(function () {resolve(2)
    }, 2000)
})
let p3 = new Promise(function (resolve, reject) {setTimeout(function () {resolve(3)
    }, 3000)
})
promiseAll([p3, p1, p2]).then(res => {console.log(res) // [3, 1, 2]
})

实现类的继承

类的继承在几年前是重点内容,有 n 种继承形式各有优劣,es6 遍及后越来越不重要,那么多种写法有点『回字有四样写法』的意思,如果还想深刻了解的去看红宝书即可,咱们目前只实现一种最现实的继承形式。

function Parent(name) {this.parent = name}
Parent.prototype.say = function() {console.log(`${this.parent}: 你打篮球的样子像 kunkun`)
}
function Child(name, parent) {
    // 将父类的构造函数绑定在子类上
    Parent.call(this, parent)
    this.child = name
}

/**  1. 这一步不必 Child.prototype =Parent.prototype 的起因是怕共享内存,批改父类原型对象就会影响子类 2. 不必 Child.prototype = new Parent()的起因是会调用 2 次父类的构造方法(另一次是 call),会存在一份多余的父类实例属性 3. Object.create 是创立了父类原型的正本,与父类原型齐全隔离 */
Child.prototype = Object.create(Parent.prototype);
Child.prototype.say = function() {console.log(`${this.parent}好,我是练习时长两年半的 ${this.child}`);
}

// 留神记得把子类的结构指向子类自身
Child.prototype.constructor = Child;

var parent = new Parent('father');
parent.say() // father: 你打篮球的样子像 kunkun

var child = new Child('cxk', 'father');
child.say() // father 好,我是练习时长两年半的 cxk

手写 new 操作符

在调用 new 的过程中会产生以上四件事件:

(1)首先创立了一个新的空对象

(2)设置原型,将对象的原型设置为函数的 prototype 对象。

(3)让函数的 this 指向这个对象,执行构造函数的代码(为这个新对象增加属性)

(4)判断函数的返回值类型,如果是值类型,返回创立的对象。如果是援用类型,就返回这个援用类型的对象。

function objectFactory() {
  let newObject = null;
  let constructor = Array.prototype.shift.call(arguments);
  let result = null;
  // 判断参数是否是一个函数
  if (typeof constructor !== "function") {console.error("type error");
    return;
  }
  // 新建一个空对象,对象的原型为构造函数的 prototype 对象
  newObject = Object.create(constructor.prototype);
  // 将 this 指向新建对象,并执行函数
  result = constructor.apply(newObject, arguments);
  // 判断返回对象
  let flag = result && (typeof result === "object" || typeof result === "function");
  // 判断返回后果
  return flag ? result : newObject;
}
// 应用办法
objectFactory(构造函数, 初始化参数);

手写 Promise

const PENDING = "pending";
const RESOLVED = "resolved";
const REJECTED = "rejected";

function MyPromise(fn) {
  // 保留初始化状态
  var self = this;

  // 初始化状态
  this.state = PENDING;

  // 用于保留 resolve 或者 rejected 传入的值
  this.value = null;

  // 用于保留 resolve 的回调函数
  this.resolvedCallbacks = [];

  // 用于保留 reject 的回调函数
  this.rejectedCallbacks = [];

  // 状态转变为 resolved 办法
  function resolve(value) {
    // 判断传入元素是否为 Promise 值,如果是,则状态扭转必须期待前一个状态扭转后再进行扭转
    if (value instanceof MyPromise) {return value.then(resolve, reject);
    }

    // 保障代码的执行程序为本轮事件循环的开端
    setTimeout(() => {
      // 只有状态为 pending 时能力转变,if (self.state === PENDING) {
        // 批改状态
        self.state = RESOLVED;

        // 设置传入的值
        self.value = value;

        // 执行回调函数
        self.resolvedCallbacks.forEach(callback => {callback(value);
        });
      }
    }, 0);
  }

  // 状态转变为 rejected 办法
  function reject(value) {
    // 保障代码的执行程序为本轮事件循环的开端
    setTimeout(() => {
      // 只有状态为 pending 时能力转变
      if (self.state === PENDING) {
        // 批改状态
        self.state = REJECTED;

        // 设置传入的值
        self.value = value;

        // 执行回调函数
        self.rejectedCallbacks.forEach(callback => {callback(value);
        });
      }
    }, 0);
  }

  // 将两个办法传入函数执行
  try {fn(resolve, reject);
  } catch (e) {
    // 遇到谬误时,捕捉谬误,执行 reject 函数
    reject(e);
  }
}

MyPromise.prototype.then = function(onResolved, onRejected) {
  // 首先判断两个参数是否为函数类型,因为这两个参数是可选参数
  onResolved =
    typeof onResolved === "function"
      ? onResolved
      : function(value) {return value;};

  onRejected =
    typeof onRejected === "function"
      ? onRejected
      : function(error) {throw error;};

  // 如果是期待状态,则将函数退出对应列表中
  if (this.state === PENDING) {this.resolvedCallbacks.push(onResolved);
    this.rejectedCallbacks.push(onRejected);
  }

  // 如果状态曾经凝固,则间接执行对应状态的函数

  if (this.state === RESOLVED) {onResolved(this.value);
  }

  if (this.state === REJECTED) {onRejected(this.value);
  }
};

请实现一个 add 函数,满足以下性能

add(1);             // 1
add(1)(2);      // 3
add(1)(2)(3);// 6
add(1)(2, 3); // 6
add(1, 2)(3); // 6
add(1, 2, 3); // 6
function add(...args) {
  // 在外部申明一个函数,利用闭包的个性保留并收集所有的参数值
  let fn = function(...newArgs) {return add.apply(null, args.concat(newArgs))
  }

  // 利用 toString 隐式转换的个性,当最初执行时隐式转换,并计算最终的值返回
  fn.toString = function() {return args.reduce((total,curr)=> total + curr)
  }

  return fn
}

考点:

  • 应用闭包,同时要对 JavaScript 的作用域链(原型链)有深刻的了解
  • 重写函数的 toSting()办法
// 测试,调用 toString 办法触发求值

add(1).toString();             // 1
add(1)(2).toString();      // 3
add(1)(2)(3).toString();// 6
add(1)(2, 3).toString(); // 6
add(1, 2)(3).toString(); // 6
add(1, 2, 3).toString(); // 6
退出移动版