# 手写防抖函数

function debounce(fn, delay) {
  let timer = null;
  return function() {
    const context = this;
    const args = arguments;
    if (timer) clearTimeout(timer);
    timer = setTimeout(function() {
      fn.apply(context, args);
    }, delay);
  };
}

# 手写节流函数

function throttle(fn, interval) {
  let last = 0;
  return function() {
    const now = Date.now();
    const context = this;
    const args = arguments;
    if (now - last >= interval) {
      fn.apply(context, args);
    }
  };
}

# 手写加强版节流

防抖有时触发的太频繁,可能会迟迟没有响应,我们需要将防抖和节流组合下避免这种问题:

function throttle(fn, delay) {
  let last = 0, timer = null;
  return function() {
    const context = this;
    const args = arguments;
    const now = Date.now();
    if (now - last < delay) {
      clearTimeout(timer);
      timer = setTimeout(function() {
        last = now;
        fn.apply(context, args);
      }, delay);
    }else {
      last = now;
      fn.apply(context, args);
    }
  }
}

# 手写懒加载

let num = 0;
const imgs = document.getElementsByTagName('img');
const viewHeight = window.innerHeight || document.documentElement.clientHeight;

function lazyload() {
  for (let i = num, len = imgs.length; i < len; i++) {
    const distance = viewHeight - imgs[i].getBoundingClientRect().top
    if (distance >=0 ) {
      imgs[i].src = imgs[i].getAttribute('data-src');
      num = i + 1;
    }
  }
}

function throttle(fn, interval) {
  let last = 0;
  return function() {
    const context = this;
    const args = arguments;
    const now = Date.now();
    if (now - last >= interval) {
      fn.apply(context, args);
    }
  }
}

window.addEventListener('scroll', throttle(lazyload, 300), false);

# 手写jsonp

# 手写ajax

export default function ajax({ url = '', method = 'GET', data = {}, success = null, fail = null }) {
	const xhr = new XMLHttpRequest();

	const isGet = /^(GET|DELETE|HEAD|OPTIONS)$/i.test(method);

	if (isGet) {
		url = url + '?' + toQuery(data);
		data = null;
	} else {
		data = JSON.stringify(data);
	}

	xhr.open(method, url);
	xhr.onreadystatechange = function() {
		if (/^2\d{2}$/.test(xhr.status) && xhr.readyState === 4) {
			typeof success === 'function' ? success(xhr.responseText) : '';
		}else {
      typeof fail === 'function' ? fail(xhr.responseText) : ''
    }
	};
	xhr.send(data);
}

function toQuery(obj) {
	let str = '';
	for (let key in obj) {
		str += '&' + key + '=' + obj[key];
	}
	return str.slice(1);
}

# 你认为ajax的意义是什么?

局部刷新

# ajax、$.ajax、axios、fetch对比?

ajax是基于XMLHttpRequest的

$.ajax是对ajax的封装,基于回调函数

axios是基于promise对ajax的封装

Fetch是es6中新增的通信方案,它不是ajax,它本身实现数据通信就是基于promise管理的。

# 手写call函数

es5

~function(proto) {
  proto.myCall = function(context) {
    var args = [].splice.call(arguments, 1);
    context = Object(context) || window;
    context.fn = this;
    var res = eval('context.fn(' + args + ')');
    delete context.fn;
    return res;
  };
}(Function.prototype);

es6

(function(proto) {
  proto.myCall = function(context = Object(context) || window, ...args) {
    context.$fn = this
    const res = context.$fn(...args)
    delete context.$fn
    return res
  }
})(Function.prototype)

# 手写apply函数

es5

~function(proto) {
  proto.myApply = function(context) {
    var args = [].splice.call(arguments, 1);
    context = Object(context) || window;
    context.fn = this;
    var res = eval('context.fn(' + args + ')');
    delete context.fn;
    return res;
  };
}(Function.prototype);

es6

(function(proto) {
  proto.myApply = function(context = Object(context) || window, ...args) {
    context.$fn = this
    const res = context.$fn(...args)
    delete context.$fn()
    return res
  }
})(Function.prototype)

# 手写bind函数

方法一:

(function(proto){
  function bind(context) {
    if (!context) context = window
    var args = [].slice.call(arguments, 1)
    var _this = this
    return function() {
      var arg = [].slice.call(arguments, 0)
      _this.apply(context, args.conca t(arg))
    }
  }
  proto.myBind = bind
})(Function.prototype)

方法二:

(function(proto){
  function bind(context = window, ...args) {
    return (...innerArgs) => {
      this.call(context, ...args, ...innerArgs)
    }
  }
  proto.myBind = bind
})(Function.prototype)

# 手写new

function new1(func) {
  const o = Object.create(func.prototype);
  const obj = func.call(this);
  return typeof obj === 'function' ? obj : o;
}

# 手写instanceof

# 手写拷贝

数组浅拷贝:

let arr = [1, 2, 3];
let arr1 = [...arr];
let arr2 = arr.concat([]);
let arr3 = arr.slice();

对象浅拷贝:

let obj = {
    0: 'math',
    score: {
        english: 90
    },
    reg: /1/,
    time: new Date,
    friends: ['tom'],
    say: function () {},
    tag: Symbol('tag'),
    [Symbol.toStringTag]: 'object'
}

let obj1 = {
  ...obj
}
let obj2 = Object.assign({}, obj)
// 上面两种包含了原始对象的Symbol属性的处理

// 不支持对 Symbol 属性的处理
let objCopy = {}
for (let key in obj) {
  objCopy[key] = obj[key]
}

极简版的浅克隆(处理的情况比较少):

function shallowClone(obj) {
  if (typeof obj !== 'object' || typeof obj === null) return obj;
  return Array.isArray(obj) ? [...obj] : {...obj};
}

相对完善的浅克隆:

['Boolean', 'Number', 'String', 'Function', 'Array', 'Date', 'RegExp', 'Object', 'Error', 'Symbol', 'BigInt']
.forEach(name => {
  class2type[`[object ${name}]`] = name.toLowerCase();
})

const toType = function (obj) {
  if (obj == null) return obj + '';
  return typeof obj === 'object' || typeof obj === 'function' ? class2type[toString.call(obj)] || 'object' :
    typeof obj;
}

function shallowClone(obj) {
  const type = toType(obj), Ctor = obj.constructor;

  // Symbol/BigInt
  if (/^(symbol|bigint)$/i.test(type)) return Object(obj);

  // 正则和日期
  if (/^(regexp|date)$/i.test(type)) return new Ctor(obj);

  // 错误对象的处理
  if (/^error$/i.test(type)) return new Ctor(obj.message);

  // 函数
  if (/^function$/i.test(type)) return function() {
    return obj.call(obj, ...arguments)
  };

  // 数组对象
  if (/^(object|array)$/i.test(type)) {
    let keys = [...Object.keys(obj), ...Object.getOwnPropertySymbols(obj)]
    const res = new Ctor();

    keys.forEach(key => {
      res[key] = obj[key]
    })
    return res;
  }

  return obj;
}

简版的深克隆:

function isObject (obj) {
  return (typeof obj === 'object') && obj !== null
}
function deepClone (obj) {
  if (!isObject(obj)) {
    return obj
  }

  const newObj = Array.isArray(obj) ? [] : {}
  Reflect.ownKeys(obj).forEach(key => {
    const value = obj[key]
    newObj[key] = isObject(value) ? deepClone(value) : value
  })
  return newObj
}

相对完善些的深克隆:

function deepClone(obj, cache = new Set()) {
  const type = toType(obj),
    Ctor = obj.constructor;

  if (!/^(object|array)$/i.test(type)) return shallowClone(obj);

  // 避免套娃
  if(cache.has(obj)) return obj;
  cache.add(obj)

  let res = new Ctor();
  Reflect.ownKeys(obj).forEach(key => {
    res[key] = deepClone(obj[key], cache)
  })
  return res;
}

# 实现柯里化函数

# 手写一个观察者模式

# 手动实现EventEmitter(发布订阅模式)

# 手动实现 Object.assign

# js格式化数字(每三位加逗号)

# 手写数组去重的方法?

# 基于JS实现Ajax请求并发请求的控制

在平时开发中,我们都是通过promise对ajax请求进行封装。这里我们先说一下,通过promise来实现串行和并行操作。

  • 串行:请求是异步的,需要等到上一个请求成功才能执行下一个请求。
  • 并行:同时发送多个请求,注意HTTP请求可以同时进行。但是因为js是单线程的,所以js的处理操作是一步一步来的。

# 串行

const delay = function(interval) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(interval)
    }, interval)
  })
}

delay(1000).then(res=> {
  console.log(res)
  return delay(2000)
}).then(res=> {
  console.log(res)
  return delay(3000)
}).then(res=> {
  console.log(res)
  return delay(4000)
}).then(res => {
  console.log(res)
})

串行会从上到下依次执行。

# 并行

等到所有请求都成功后我们再做某些事情,我们可以使用promise.all来进行并行请求

const delay = function(interval) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(interval)
    }, interval)
  })
}

const fetchRequest = () => delay(1000)

const tasks = [
  () => delay(1000),
  () => delay(2000),
  () => delay(3000),
  () => delay(4000)
]

Promise.all(tasks.map(task => task())).then(res => {
  console.log(res) // 四秒后输出 [1000, 2000, 3000, 4000]
}).catch(err => {
  console.log(err)
})

如果tasks里面的请求由有很多,在瞬间需要发出这么多的请求可能会带来一些问题。这个时候我们需要使用promise.all做并发限制。

方法一: 方法二:

function delay(ms) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(ms)
    }, ms);
  })
}

const tasks = [
  () => delay(4000),
  () => delay(3000),
  () => delay(2000),
  () => delay(1000)
]

function createRequest(tasks, pool, cb) {
  if (pool && typeof pool === 'function') {
    pool = 5
    cb = pool
  } else {
    pool = pool || 5
  }

  if (typeof cb !== 'function') cb = function() {}

  class TaskQueue {
    running = 0
    queue = []
    results = []

    pushTask(task) {
      this.queue.push(task)
      this.next()
    }
    next() {
      while(this.running < pool && this.queue.length) {
        this.running++
        const task = this.queue.shift()
        task().then(result => {
          this.results.push(result)
        }).finally(() => {
          this.running--
          this.next()
        })
      }

      if (!this.running) cb(this.results)
    }
  }

  let TQ = new TaskQueue
  tasks.forEach(task => TQ.pushTask(task))
}

createRequest(tasks, 2, (res) => {
  console.log(res) // [3000, 4000, 1000, 2000]
})

从运行结果可以看出,使用上述方法实现的方式是乱序的。。

# 手写merge合并

const options = {
  url: '',
  method: 'get',
  headers: {
    'Content-type': 'application/json'
  },
  data: null,
  arr: [1, 2],
  config: {
    xhr: {
      async: true,
      cache: true
    }
  }
}

const params = {
  url: 'www.baidu.com',
  headers: {
    'token': '123'
  },
  data: {
    name: 'f'
  },
  arr: [3, 4],
  config: {
    xhr: {
      cache: true
    }
  }
}


function isObject(obj) {
  return typeof obj === 'object' && obj !== null
}

function merge(options, params) {
  for (let key in params) {
    const isA = isObject(options[key]),
      isB = isObject(params[key]);
    if (isA && !isB) {
      throw new TypeError(`${key} in params must be object`)
    }
    if (isA && isB) {
      options[key] = merge(options[key], params[key])
    }else {
      options[key] = params[key]
    }
  }
  return options
}

# 手写AOP切片编程

APO(面向切片编程)的主要作用是把一些跟核心业务逻辑模块无关的功能抽离出来,这些跟业务逻辑无关的功能通常包括日志统计、安全控制、异常处理等。把这些功能抽离出来后再通过动态植入的方式参入业务逻辑模块中。

例子:

Function.prototype.before = function (cb) {
  if (typeof cb !== 'function') throw new TypeError('cb must be function')
  const self = this;
  return function () {
    cb.call(this, ...arguments);
    return self.call(this, ...arguments);
  }
}
Function.prototype.after = function (cb) {
  if (typeof cb !== 'function') throw new TypeError('cb must be function')
  const self = this;
  return function () {
    const res = self.call(this, ...arguments);
    cb.call(this, ...arguments);
    return res;
  }
}
const func = () => {
  console.log('func')
}

func.before(() => {
  console.log('before')
}).after(() => {
  console.log('after')
})();

# 手写getUrlParam

方法一:

function getUrlParam(name) {
  let res = '';
  const str = location.search
  if (!str) return res;

  const arr = str.split('?')[1].split('&')
  arr.forEach(item => {
    const arr2 = item.split('=')
    if (arr2[0] === name) {
      res = arr2[1]
    }
  })
  return res
}

方法二:

function getUrlParam(name){
  var reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)');    
  var result =  window.location.search.substr(1).match(reg);
  return result ? decodeURIComponent(result[2]) : null;  
}