阅读 1115

【面试系列】前端高频手写面试题汇总

防抖 debounce

当持续触发事件时,如果在设定的时间范围内频繁触发某个事件,则每次都会清空之前的计时,重新从0开始计时,直到在设定的时间范围内,没有事件触发,才会执行事件处理函数。以最后一次触发为准。

function debounce (fn, time, ...args) {
  var timer;
  return function () {
    var _this = this;
    if (timer) {
      clearTimeout(timer);
    }
    timer = setTimeout (function() {
      fn.call(_this, ...args);
    }, time);
  }
}
// 测试例子
function handleTouchMove(str) {
  console.log(str);
}
// 没有加防抖
document.addEventListener('touchmove', function() { handleTouchMove(111) }, false);
// 加防抖
document.addEventListener('touchmove', debounce(handleTouchMove, 1000, 222), false);
复制代码

触发touchmove事件,运行结果如下:

节流 throttle

多次触发,以第一次触发为准,当持续触发事件时,保证在设置的时间范围内只调用一次事件处理函数。

function throttle (fn, time, ...args) {
  var timer = null;
  return function() {
    var _this = this;
    if (!timer) {
      timer = setTimeout(function() {
        fn.call(_this, ...args);
        timer = null;
      }, time);
    }
  }
}
// 测试例子
function handleTouchMove(str) {
  console.log(str);
}
// 没有加节流
document.addEventListener('touchmove', function() {handleTouchMove(`111---${new Date().getTime()}`)}, false);
// 加节流
document.addEventListener('touchmove', throttle(handleTouchMove, 1000, `222---${new Date().getTime()}`), false);
复制代码

触发touchmove事件,运行结果如下:

深拷贝, 不考虑symbol和循环引用的情况

// 方法一
function deepClone1 (obj) {
  if (typeof obj !== 'object' || obj == null) {
    return obj;
  }
  if (obj instanceof Date) {
    return new Date(obj);
  }
  if (obj instanceof RegExp) {
    return new RegExp(obj);
  }
  const res = Array.isArray(obj) ? [] : {};
  Object.keys(obj).forEach(key => {
    if(typeof obj[key] === 'object') {
      res[key] = deepClone1(obj[key]);
    } else {
      res[key] = obj[key];
    }
  });
  return res;
}
// 方法二
function deepClone2 (obj) {
  if(typeof obj !== 'object' || obj == null) {
    return obj
  }
  if(obj instanceof Date) {
    return new Date(obj);
  }
  if(obj instanceof RegExp) {
    return new RegExp(obj);
  }
  const res = Object.create(Object.getPrototypeOf(obj));
  const keys = Object.getOwnPropertyNames(obj);
  keys.forEach(key => {
    const value = Object.getOwnPropertyDescriptor(obj, key);
    Object.defineProperty(res, key, value);
  })
  return res;
}
// 测试例子
let obj = {
  age: 18,
  favoriteAnimal: ['长颈鹿', '天鹅'],
  obj: {
    name: 'kele',
    age: 18
  },
};
let cloneObj1 = deepClone1(obj);
console.log("cloneObj1:", cloneObj1); 
let cloneObj2 = deepClone2(obj);
console.log("cloneObj2:", cloneObj2);
复制代码

深拷贝, 考虑symbol和循环引用的情况

function deepClone3 (obj, map = new Map()) {
  if (typeof obj !== 'object' || obj == null) {
    return obj;
  }
  if (map.has(obj)) {
    return map.get(obj);
  }
  const res = Array.isArray(obj) ? [] : {};
  map.set(obj, res);
  Reflect.ownKeys(obj).forEach(k => {
    if (typeof obj[k] === 'object' && obj != null) {
      res[k] = deepClone3(obj[k], map);
    } else {
      res[k] = obj[k];
    }
  })
  return res;
}
// 测试例子
let objx ={};
objx.repeat = objx;
let objy = {
  [Symbol('name')]: 'litokele',
  gender: Symbol('male'),
  age: 18,
  favoriteAnime: ['xxx1', 'xxx2'],
  obj: {
    [Symbol('test')]: 'test',
    name: 'kele',
    age: 18
  },
  repeat: objx
}
let cloneObj3 = deepClone3(objy);
console.log(cloneObj3);
复制代码

数组去重

// 方法一
function uniq1 (arr) {
  return [...new Set(arr)];
}
// 方法二
function uniq2 (arr) {
  return Array.from(new Set(arr));
}
// 方法三
function uniq3 (arr) {
  return arr.reduce((prev, cur) => {
    if (!prev.includes(cur)) {
        prev.push(cur);
    }
    return prev;
  }, [])
}
// 方法四
function uniq4 (arr) {
  const res = [];
  const map = {};
  arr.forEach(item => {
    if(!map[item]) {
      res.push(item);
      map[item] = true;
    }
  })
  return res;
}
//  测试例子
const arr = [1,2,3,1,5,3,9,6,5];
uniq1(arr); // => [1, 2, 3, 5, 9, 6]
uniq2(arr); // => [1, 2, 3, 5, 9, 6]
uniq3(arr); // => [1, 2, 3, 5, 9, 6]
uniq4(arr); // => [1, 2, 3, 5, 9, 6]
复制代码

数组乱序

function confuseArr (arr) {
  const res = [];
  while(arr.length) {
    res.push(arr.splice(Math.floor(Math.random() * (arr.length - 1)), 1)[0]);
  }
  return res;
}
// 测试例子
confuseArr([1,2,3,4,5,6,7]);
复制代码

call函数实现

Function.prototype._call = function (context) {
    if (typeof this !== 'function') {
        throw new TypeError('not funciton')
    }
    const args = [...arguments].slice(1);
    context = context || window;
    context.fn = this;
    const res = context.fn(...args);
    delete context.fn
    return res;
}

// 测试例子
var value = "this is a window value";
var foo = {
  value: "this is a foo value"
};
function bar(...num) {
  console.log(`${this.value} is ${num}`);
}
bar(); // => this is a window value is 
bar._call(foo, 111); // => this is a foo value is 111
bar._call(null, 111, 222); // => this is a window value is 111,222
复制代码

apply函数实现

Function.prototype._apply = function(context) {
    if (typeof this !== 'function') {
        throw new TypeError('not funciton')
    }
    context = context || window;
    context.fn = this;
    let res;
    if(arguments[1]) {
        res = context.fn(...arguments[1]);
    } else {
        res = context.fn();
    }
    delete context.fn;
    return res;
}

// 测试例子
var value = "this is a window value";
var foo = {
  value: "this is a foo value"
};
function bar(...num) {
  console.log(`${this.value} is ${num}`);
}
bar(); // => this is a window value is 
bar._apply(foo, [111]); // => this is a foo value is 111
bar._apply(null, [111, 222]); // => this is a window value is 111,222
复制代码

bind函数实现

Function.prototype._bind = function() {
  if (typeof this !== 'function') {
    throw new TypeError('Error')
  }
  const _this = this;
  const context = [...arguments].shift();
  const args = [...arguments].slice(1);
  return function F() {
    if (this instanceof F) {
        return new _this(...args, ...arguments);
    } else {
        return _this.apply(context, args.concat(...arguments));
    }
  }
}
// 测试例子
var value = "this is a window value";
var foo = {
  value: "this is a foo value"
};
function bar(num) {
  console.log(`${this.value} is ${num}`);
}
bar(222); // => this is a window value is 222
var bindFoo = bar._bind(foo, 111);
bindFoo(); // => this is a foo value is 111
复制代码

实现一个自执行器 (实现async/await)

参考:async/await 源码实现

function _asyncToGenerator(fn) {
  return function() {
    var self = this,
    args = arguments;
    // 将返回值promise化
    return new Promise(function(resolve, reject) {
      // 获取迭代器实例
      var gen = fn.apply(self, args);
      // 执行下一步
      function _next(value) {
        asyncGeneratorStep(gen, resolve, reject, _next, _throw, 'next', value);
      }
      // 抛出异常
      function _throw(err) {
        asyncGeneratorStep(gen, resolve, reject, _next, _throw, 'throw', err);
      }
      // 第一次触发
      _next(undefined);
    });
  };
}
function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
  try {
    var info = gen[key](arg);
    var value = info.value;
  } catch (error) {
    reject(error);
    return;
  }
  if (info.done) {
    // 迭代器完成,将返回值(return)保存起来
    resolve(value);
  } else {
    // -- 这行代码就是精髓 --
    // 将所有值promise化
    // 比如 yield 1
    // const a = Promise.resolve(1) a 是一个 promise
    // const b = Promise.resolve(a) b 是一个 promise
    // 可以做到统一 promise 输出
    // 当 promise 执行完之后再执行下一步
    // 递归调用 next 函数,直到 done == true
    Promise.resolve(value).then(_next, _throw);
  }
}
const asyncFunc = _asyncToGenerator(function* () {
  console.log(1);
  yield new Promise(resolve => {
    setTimeout(() => {
      resolve();
      console.log('sleep 1s');
    }, 1000);
  });
  console.log(2);
  const a = yield Promise.resolve('a');
  console.log(3);
  const b = yield Promise.resolve('b');
  const c = yield Promise.resolve('c');
  return [a, b, c];
})
asyncFunc().then(res => {
  console.log(res)
});
// 运行结果
// 1
// sleep 1s
// 2
// 3
// ["a", "b", "c"]
复制代码

promise实现(包含all()、race()、finally()等方法实现)

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

class Promise {
  constructor(executor) {
    // Promise存在三个状态(state)pending、fulfilled、rejected
    // pending(等待态)为初始态,并可以转化为fulfilled(成功态)和rejected(失败态)
    // 成功时,不可转为其他状态,且必须有一个不可改变的值(value)
    // 失败时,不可转为其他状态,且必须有一个不可改变的原因(reason)
    this.state = 'pending';
    this.value = undefined;
    this.reason = undefined;
    this.onResolvedCallbacks = [];
    this.onRejectedCallbacks = [];
    let resolve = value => {
      if (this.state === 'pending') {
        this.state = 'fulfilled';
        this.value = value;
        this.onResolvedCallbacks.forEach(fn=>fn());
      }
    };
    let reject = reason => {
      if (this.state === 'pending') {
        this.state = 'rejected';
        this.reason = reason;
        this.onRejectedCallbacks.forEach(fn=>fn());
      }
    };
    try {
      executor(resolve, reject);
    } catch (err) {
      reject(err);
    }
  }
  // Promise有一个叫做then的方法,里面有两个参数:onFulfilled,onRejected,成功有成功的值,失败有失败的原因
  // onFulfilled,onRejected如果他们是函数,则必须分别在fulfilled,rejected后被调用,value或reason依次作为他们的第一个参数
  then(onFulfilled,onRejected) {
    // onFulfilled如果不是函数,就忽略onFulfilled,直接返回value
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
    // onRejected如果不是函数,就忽略onRejected,直接扔出错误
    onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err };
    // 为了解决链式调用, 默认在第一个then里返回一个新的promise -> promise2, 将这个promise2返回的值传递到下一个then中
    // 声明返回的promise2
    const promise2 = new Promise((resolve, reject) => {
      // 当我们在第一个then中return了一个参数(参数未知,需判断)。这个return出来的新的promise就是onFulfilled()或onRejected()的值
      // onFulfilled()或onRejected()的值,即第一个then返回的值, 第一个then返回的值,叫做x,判断x的函数叫做resolvePromise
      if (this.state === 'fulfilled') {
        // onFulfilled或onRejected不能同步被调用,必须异步调用。我们就用setTimeout解决异步问题
        setTimeout(() => {
          try {
            let x = onFulfilled(this.value);
            // resolvePromise函数,处理自己return的promise和默认的promise2的关系
            resolvePromise(promise2, x, resolve, reject);
          } catch (e) {
            reject(e);
          }
        }, 0);
      };
      if (this.state === 'rejected') {
        // onFulfilled或onRejected不能同步被调用,必须异步调用。我们就用setTimeout解决异步问题
        setTimeout(() => {
          try {
            let x = onRejected(this.reason);
            resolvePromise(promise2, x, resolve, reject);
          } catch (e) {
            reject(e);
          }
        }, 0);
      };
      if (this.state === 'pending') {
        this.onResolvedCallbacks.push(() => {
          setTimeout(() => {
            try {
              let x = onFulfilled(this.value);
              resolvePromise(promise2, x, resolve, reject);
            } catch (e) {
              reject(e);
            }
          }, 0);
        });
        this.onRejectedCallbacks.push(() => {
          setTimeout(() => {
            try {
              let x = onRejected(this.reason);
              resolvePromise(promise2, x, resolve, reject);
            } catch (e) {
              reject(e);
            }
          }, 0)
        });
      };
    });
    // 返回promise,完成链式
    return promise2;
  }
  catch(fn){
    return this.then(null,fn);
  }
}
  
// 首先,要看x是不是promise。
// 如果是promise,则取它的结果,作为新的promise2成功的结果
// 如果是普通值,直接作为promise2成功的结果
// 所以要比较x和promise2
// resolvePromise的参数有promise2(默认返回的promise)、x(我们自己return的对象)、resolve、reject
// resolve和reject是promise2的
// x 是普通值 直接resolve(x) x 是对象或者函数(包括promise),let then = x.then
function resolvePromise(promise2, x, resolve, reject) {
  // 循环引用报错
  if (x === promise2) {
    // reject报错
    return reject(new TypeError('Chaining cycle detected for promise'));
  }
  // 防止多次调用
  let called;
  if (x != null && (typeof x === 'object' || typeof x === 'function')) {
    try {
      // A+规定,声明then = x的then方法
      let then = x.then;
      if (typeof then === 'function') {
        // 就让then执行 第一个参数是this   后面是成功的回调 和 失败的回调
        then.call(x, y => {
          if(called)return;
          called = true;
          // resolve的结果依旧是promise 那就继续解析
          resolvePromise(promise2, y, resolve, reject);
        }, err => {
          if(called)return;
          called = true;
          reject(err);
        })
      } else {
        resolve(x);
      }
    } catch (e) {
      // 也属于失败
      if(called)return;
      called = true;
      // 取then出错了那就不要在继续执行了
      reject(e); 
    }
  } else {
    resolve(x);
  }
}
// resolve方法
Promise.resolve = function(val){
  return new Promise((resolve,reject)=>{
    resolve(val)
  });
}
// reject方法
Promise.reject = function(val){
  return new Promise((resolve,reject)=>{
    reject(val)
  });
}
// race方法 
Promise.race = function(promises){
  return new Promise((resolve,reject)=>{
    for(let i=0;i<promises.length;i++){
      promises[i].then(resolve,reject)
    };
  })
}
// all方法(获取所有的promise,都执行then,把结果放到数组,一起返回)
Promise.all = function(promises){
  let arr = [];
  let i = 0;
  function processData(index,data){
    arr[index] = data;
    i++;
    if(i == promises.length){
      resolve(arr);
    };
  };
  return new Promise((resolve,reject)=>{
    for(let i=0;i<promises.length;i++){
      promises[i].then(data=>{
        processData(i,data);
      },reject);
    };
  });
}
// finally方法
Promise.prototype.finally = function(cb) {
  let P = this.constructor;
  return this.then(value => {
    P.resolve(cb()).then(() => value);
  }, reason => {
    P.resolve(cb()).then(() => {throw reason})
  })
}
// retry方法
Promise.retry = function(fn, times, delay) {
  return new Promise((resolve, reject) => {
    var error;
    var tryFun = function() {
      if (times === 0) {
        reject(error);
      }
      fn().then(res => {
        resolve(res);
      }).catch(e => {
        times--;
        error = e;
        setTimeout(() => {
          tryFun();
        }, delay);
      })
    };
    tryFun();
  })
}
复制代码

写一个函数,可以控制最大并发数

class requestQueue {
  constructor(max) {
    this.taskQueue = [];
    this.max = max || 10;
    setTimeout(() => {
      this.next();
    }, 0)
  }
  addTask(task) {
    this.taskQueue.push(task);
  }
  next() {
    const len = this.taskQueue.length;
    if(!len) {
      return;
    }
    const min = Math.min(len, this.max);
    for (let i = 0 ; i < min; i++) {
      this.max --;
      var task = this.taskQueue.shift();
      task().then(res => {
        console.log(res);
      }).catch((err) => {
        console.log(err);
      }).finally(() => {
        this.max ++;
        this.next();
      })
    }
  }
}
// 测试例子
const request = new requestQueue();
for(let i = 0; i < 20; i++) {
  request.addTask(function() {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve(i);
      }, 2000)
    })
  })
}
复制代码

jsonp 的实现

// 动态的加载js文件
function addScript(src) {
  const script = document.createElement('script');
  script.src = src;
  script.type = "text/javascript";
  document.body.appendChild(script);
}
addScript("http://xxx.xxx.com/xxx.js?callback=handleRes");
// 设置一个全局的callback函数来接收回调结果
function handleRes(res) {
  console.log(res);
}
// 接口返回的数据格式
handleRes({a: 1, b: 2});
复制代码

简单实现 EventEmitter

发布者和订阅者是松耦合的,互不关心对方,他们关注的对象是事件本身。

发布者通过事件调度中心提供publish方法进行事件发布操作,他们并不关心有没有人订阅。

订阅者通过事件调度中心提供subscribe方法进行事件订阅操作,他们并不关心有没有人发布事件,但是只要是自己订阅的事件发生了,他们就作出响应。

class EventEmitter {
  constructor () {
    this.events = {};
  }
  // 订阅事件
  on (type, cb) {
    if (!this.events[type]) {
      this.events[type] = [];
    }
    this.events[type].push(cb);
  }
  // 发布事件
  emit(type, ...args) {
    if (this.events[type]) {
      this.events[type].forEach(cb => {
        cb(...args);
      });
    }
  }
  // 只执行一次
  once(type, cb) {
    const _this = this;
    function one() {
      cb.call(_this, arguments);
      _this.off(type, one);
    }
    this.on(type, one);
  }
  // 移除事件
  off(type, cb) {
    if (this.events[type]) {
      this.events[type] = this.events[type].filter(item => item != cb );
    }
  }
}
// 测试例子
var myEmitter = new EventEmitter();
myEmitter.on('study', function(data) {
  console.log(`学习${data}`);
});
myEmitter.on('eat', function(data) {
  console.log(`吃${data}`);
});
myEmitter.once('relax', function() {
  console.log('relax');
})
myEmitter.emit('study', 'javascript'); // => 学习javascript
myEmitter.emit('eat', '苹果'); // => 吃苹果
myEmitter.emit('relax'); // => relax
myEmitter.emit('relax'); // => undefined
复制代码

简单实现观察者模式

观察者的Observer和目标对象Subject耦合度相对较高,没有事件调度中心作为中间者,目标对象Subject和观察者Observer都要实现约定的成员方法。

观察者需要实现update方法,在目标对象通知更新时被调用。

目标对象需要维护观察者列表,在自身状态改变时,通过notify方法遍历观察者列表,通知所有观察者,目标对象的主动性很强,自己收集和维护观察者,并在状态变化时主动通知观察者更新

class Observer {
    constructor(cb) {
        if(!cb || typeof cb !== 'function') {
            throw new Error('Observer构造器必须传入函数类型');
            return;
        }
        this.cb = cb;
    }
    // 被目标对象通知时执行
    update() {
        this.cb();
    }
}

class Subject {
    // 维护观察者列表
    constructor(observer) {
        this.observerList = [];
    }
    // 添加一个观察者
    addObserver(observer) {
        this.observerList.push(observer);
    }
    // 通知所有观察者
    notify() {
        this.observerList.forEach(observer => {
            observer.update();
        })
    }
}

// 测试例子
const cbFun = function() {
    console.log('update');
}
const observer = new Observer(cbFun);
const subject = new Subject();
subject.addObserver(observer);
subject.notify(); // update
复制代码

实现 instanceof

function _instanceof (obj, cunstructor) {
  while (true) {
    if (obj.__proto__ == null) {
      return false;
    }
    if (obj.__proto__ === cunstructor.prototype) {
      return true;
    }
    obj = obj.__proto__;
  }
}
// 测试例子
_instanceof({a: 1, b:2}, Array) // => false
_instanceof({a: 1, b:2}, Object) // => true
复制代码

实现 new 操作符

// 方法一
function _new1(fn, ...args) {
  const obj = new Object();
  obj.__proto__ = fn.prototype;
  const res = fn.call(obj, ...args);
  if ((typeof res === 'object' || typeof res === 'function') && res != null) {
    return res;
  }
  return obj;
}
// 方法二
function _new2(fn, ...args) {
  const obj = Object.create(fn.prototype);
  const res = fn.call(obj, ...args);
  if ((typeof res === 'object' || typeof res === 'function') && res != null) {
    return res;
  }
  return obj;
}
// 测试例子
function A(a, b) {
  this.a = a;
  this.b = b;
}
const a = _new1(A, 1, 2);
console.log(a); // => {a: 1, b: 2}
const b = _new2(A, 2, 3);
console.log(b); // => {a: 2, b: 3}
复制代码

实现Object.create

function createObject(o) {
    function F() {};
    F.prototype = o;
    return new F();
}
复制代码

实现数组的flat方法

function _flat(arr, depth) {
  if(!Array.isArray(arr) || depth <= 0) {
    return arr;
  }
  return arr.reduce((prev, cur) => {
    if (Array.isArray(cur)) {
      return prev.concat(_flat(cur, depth - 1))
    } else {
      return prev.concat(cur);
    }
  }, []);
}
// 测试例子
_flat([1,2,3,[4,5,[6,7]]], 1); // => [1, 2, 3, 4, 5, [6, 7]]
_flat([1,2,3,[4,5,[6,7]]], 2); // => [1, 2, 3, 4, 5, 6, 7]
复制代码

实现数组的filter方法

Array.prototype._filter = function(fn) {
    if (typeof fn !== "function") {
        throw Error('参数必须是一个函数');
    }
    const res = [];
    for (let i = 0, len = this.length; i < len; i++) {
        fn(this[i]) && res.push(this[i]);
    }
    return res;
}

// 测试例子
const arr = [1,2,3,4,5];
arr._filter(item => item > 3);
复制代码

实现数组的map方法

Array.prototype._map = function(fn) {
   if (typeof fn !== "function") {
        throw Error('参数必须是一个函数');
    }
    const res = [];
    for (let i = 0, len = this.length; i < len; i++) {
        res.push(fn(this[i]));
    }
    return res;
}
// 测试例子
const arr = [1, 2, 3, 4, 5];
arr._map(function(item) {
    return item * 2;
});
// [2, 4, 6, 8, 10]
复制代码

实现柯理化函数currying

function currying(fn) {
  const args = Array.prototype.slice.call(arguments, 1);
  return function() {
    return fn.apply(null, args.concat(Array.prototype.slice.call(arguments)));
  }
}
// 测试例子
function add(num1, num2) {
  return num1+num2;
}
var curryingAdd = currying(add, 5);
curryingAdd(3);
复制代码

URL参数字典实现

function getURLParams (href = window.location.href) {
  const result = {};
  let param = null;
  const reg = /[?&](.*?)=([^&#]*)/g;
  param = reg.exec(href);
  while (param) {
    try {
      result[param[1]] = decodeURIComponent(param[2]);
    } catch (e) {
      try {
        result[param[1]] = unescape(param[2]);
      } catch (escapeErr) {
        result[param[1]] = param[2];
      }
    }
    param = reg.exec(href);
  }
  return result;
}
getURLParams('https://www.xx.cn/api?keyword=&level1=&local_batch_id=&elective=&local_province_id=33');
// { keyword: "", level1: "", local_batch_id: "", elective: "", local_province_id: "33" }
getURLParams('https://www.xx.cn/api?keyword=&level1=&local_batch_id=&elective=800&local_province_id=33');
// { keyword: "", level1: "", local_batch_id: "", elective: "800", local_province_id: "33" }
getURLParams('https://www.xx.cn/api?keyword=&level1=&local_batch_id=&elective=800,700&local_province_id=33');
// { keyword: "", level1: "", local_batch_id: "", elective: "800,700", local_province_id: "33" }
复制代码

使用setTimeout模拟setInterval

function _setInterval(fn, time) {
    function _inner() {
        fn();
        setTimeout(_inner, time);
    }
    _inner();
}
// 测试例子
_setInterval(function() {
    console.log(1);
}, 1000);
复制代码

实现ajax

function _ajax (url, method = "GET", data = null) {
  var xhr = new XMLHttpRequest();
  xhr.onreadystatechange = function() {
    if(xhr.readyState === 4 && xhr.status === 200) {
      success(JSON.parse(xhr.responseTxt));
    } else {
      fail(xhr.responseTxt) 
    }
  }
  xhr.open(method, url, true);
  if(method.toLowerCase() == 'post') {
    xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencode');
  }
  xhr.send(data);
}
function success(data) {
  console.log(data);
}
function fail(err) {
  console.log(err);
}
复制代码

使用promise对象实现ajax

function _ajax (url, method = "GET", data = null) {
  return new Promise((resolve, reject) => {
    var xhr = new XMLHttpRequest();
    xhr.onreadystatechange = function() {
      if (xhr.readyState === 4 && xhr.status === 200) {
        resolve(JSON.parse(xhr.responseTxt));
      } else {
        reject(xhr.responseText);
      }
    }
    xhr.open(method, url, true);
    if (method.toLowerCase() === "post") {
      xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencode');
    }
    xhr.send(data);
  })
}
复制代码

实现sleep函数

// 方法一 - promise
function sleep(time) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve();
    }, time)
  })
}
// 测试例子
sleep(1000).then(() => {
  console.log(1);
})
// 方法二 - Generator
function * sleep(time) {
  yield new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve();
    }, time);
  })
}
// 测试例子
sleep(1000).next().value.then(() => {
  console.log(2);
});
// 方法三 - async/await
function sleep(time) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve();
    }, time)
  })
}
async function sleepDoSomething(time) {
  let res = await sleep(time);
  console.log(3);
  return res;
}
// 测试例子
sleepDoSomething(1000);
复制代码

手动实现vue双向数据绑定

const Vue = (function() {
    let uid = 0;
    // 用于储存订阅者并发布消息
    class Dep {
        constructor() {
            // 设置id,用于区分新Watcher和只改变属性值后新产生的Watcher
            this.id = uid++;
            // 储存订阅者的数组
            this.subs = [];
        }
        // 触发target上的Watcher中的addDep方法,参数为dep的实例本身
        depend() {
            Dep.target.addDep(this);
        }
        // 添加订阅者
        addSub(sub) {
            this.subs.push(sub);
        }
        notify() {
            // 通知所有的订阅者(Watcher),触发订阅者的相应逻辑处理
            this.subs.forEach(sub => {
                sub.update();
            })
        }
    }
    // 为Dep类设置一个静态属性,默认为null,工作时指向当前的Watcher
    Dep.target = null;
    // 监听者,监听对象属性值的变化
    class Observer {
        constructor(value) {
            this.value = value;
            this.walk(value);
        }
        // 遍历属性值并监听
        walk(value) {
            Object.keys(value).forEach(key => this.convert(key, value[key]));
        }
        // 执行监听的具体方法
        convert(key, val) {
            defineReactive(this.value, key, val);
        }
    }
    function defineReactive(obj, key, val) {
        const dep = new Dep();
        // 给当前属性的值添加监听
        let chlidOb = observe(val);
        Object.defineProperty(obj, key, {
            enumerable: true,
            configurable: true,
            get: () => {
                // 如果Dep类存在target属性,将其添加到dep实例的subs数组中
                // target指向一个Watcher实例,每个Watcher都是一个订阅者
                // Watcher实例在实例化过程中,会读取data中的某个属性,从而触发当前get方法
                if (Dep.target) {
                    dep.depend();
                }
                return val;
            },
            set: newVal => {
                if (val === newVal) return;
                val = newVal;
                // 对新值进行监听
                chlidOb = observe(newVal);
                // 通知所有订阅者,数值被改变了
                dep.notify();
            }    
        })
    }
    function observe(value) {
        // 当值不存在,或者不是复杂数据类型时,不再需要继续深入监听
        if (!value || typeof value !== 'object') {
            return;
        }
        return new Observer(value);
    }
    class Watcher {
        constructor(vm, expOrFn, cb) {
            this.depIds = {}; // hash储存订阅者的id,避免重复的订阅者
            this.vm = vm; // 被订阅的数据一定来自于当前Vue实例
            this.cb = cb; // 当数据更新时想要做的事情
            this.expOrFn = expOrFn; // 被订阅的数据
            this.val = this.get(); // 维护更新之前的数据
        }
        // 对外暴露的接口,用于在订阅的数据被更新时,由订阅者管理员(Dep)调用
        update() {
            this.run();
        }
        addDep(dep) {
            // 如果在depIds的hash中没有当前的id,可以判断是新Watcher,因此可以添加到dep的数组中储存
            // 此判断是避免同id的Watcher被多次储存
            if (!this.depIds.hasOwnProperty(dep.id)) {
                dep.addSub(this);
                this.depIds[dep.id] = dep;
            }
        }
        run() {
            const val = this.get();
            console.log(val);
            if (val !== this.val) {
                this.val = val;
                this.cb.call(this.vm, val);
            }
        }
        get() {
            // 当前订阅者(Watcher)读取被订阅数据的最新更新后的值时,通知订阅者管理员收集当前订阅者
            Dep.target = this;
            const val = this.vm._data[this.expOrFn];
            // 置空,用于下一个Watcher使用
            Dep.target = null;
            console.log(Dep.target, 2);
            return val;
        }
    }
    class Vue {
        constructor(options = {}) {
            // 简化了$options的处理
            this.$options = options;
            // 简化了对data的处理
            let data = (this._data = this.$options.data);
            // 将所有data最外层属性代理到Vue实例上
            Object.keys(data).forEach(key => this._proxy(key));
            // 监听数据
            observe(data);
        }
        // 对外暴露调用订阅者的接口,内部主要在指令中使用订阅者
        $watch(expOrFn, cb) {
            new Watcher(this, expOrFn, cb);
        }
        _proxy(key) {
            Object.defineProperty(this, key, {
                configurable: true,
                enumerable: true,
                get: () => this._data[key],
                set: val => {
                    this._data[key] = val;
                },
            });
        }
    }
    return Vue;
})();
// 测试例子
let demo = new Vue({
    data: {
        text: '111',
    },
});

const domp = document.createElement('p');
domp.setAttribute('id', 'p');
const domInput = document.createElement('input');
domInput.type = 'text';
domInput.value = demo.data;
domInput.setAttribute('id', 'input');
document.body.appendChild(domp);
document.body.appendChild(domInput);
const p = document.getElementById('p');
const input = document.getElementById('input');

input.addEventListener('keyup', function(e) {
    demo.text = e.target.value;
});

demo.$watch('text', str => p.innerHTML = str);
复制代码

使用 JavaScript Proxy 实现简单的数据绑定

const domp = document.createElement('p');
domp.setAttribute('id', 'p');
const domInput = document.createElement('input');
domInput.setAttribute('type', 'text');
domInput.setAttribute('id', 'input');
document.body.appendChild(domp);
document.body.appendChild(domInput);

const p = document.getElementById('p');
const input = document.getElementById('input');
const inputObj = {
    inputTxt: 5 
};
const proxy = new Proxy(inputObj, {
    get: function(target, key, receiver) {
        return Reflect.get(target, key, receiver);
    },
    set: function(target, key, value, receiver) {
        if (key === 'inputTxt') {
            input.value = value;
            p.innerHTML = value;
        }
        return Reflect.set(target, key, value, receiver);
    }
});
// 显示初始化的值
input.value = proxy.inputTxt;
p.innerHTML = proxy.inputTxt;
// 根据输入框的值变化
input.addEventListener('input', function(e) {
    proxy.inputTxt = e.target.value;
})
复制代码

将一个同步callback包装成promise形式

function promisify(original) {
    return function (...args) {
        return new Promise((resolve, reject) => {
            args.push(function callback(err, ...values) {
                if (err) {
                    return reject(err);
                }
                return resolve(...values)
            });
            original.call(this, ...args);
        });
    };
}
复制代码

将下划线命名转换为驼峰命名

//  例如:将 "wo_ai_xue_qian_duan" 转换为 "woAiXueQianDuan"

const str = "wo_ai_xue_qian_duan";
// 方法一
str.replace(/_./g, function(i) {
    console.log(i);
    return i.slice(1).toUpperCase();
});
// 输出:"woAiXueQianDuan"

// 方法二
function formatStr(str) {
    const strArr = str.split('_');
    let res;
    strArr.forEach((item, index) => {
        if (index === 0) {
            res = item
        } else {
            res += item[0].toUpperCase() + item.slice(1);
        }
    });
    return res;
}
formatStr(str);

// 方法三
function formatStr2(str) {
    return str.split('_').reduce((prev, cur) => {
        return prev += cur[0].toUpperCase() + cur.slice(1);
    });
}
formatStr2(str);
复制代码

如有问题,请指正,谢谢!