手写题

47 阅读11分钟

实现 new 操作符

/**
 * 用法:创建一个实例化对象
 * 思路:
 * 1、判断传入的 constructor 是否是函数
 * 2、创建一个空对象 obj
 * 3、将空对象的隐式原型设置为构造函数的 prototype 属性
 * 4、使用 obj 调用构造函数,并掺入参数,获取函数返回值
 * 5、判断这个返回值 如果返回的是 Object || Function 类型,就返回该对对象,否则返回创建的对象
 * 
 * @param {Function} constructor 构造函数
 * @param  {...any} args 构造函数参数数组
 * @returns 
 */function myNew(constructor, ...args) {
  // 判断传入的 constructor 是否是函数
  if (typeof constructor !== 'function') {
    return new TypeError('constructor 必须是一个函数!')
  }
​
  // 创建一个空对象 obj
  // 将空对象的隐式原型设置为构造函数的 prototype 属性
  const obj = Object.create(constructor.prototype)
​
  // 使用 obj 调用构造函数,并掺入参数,获取函数返回值
  const result = constructor.apply(obj, args)
  
  // 判断这个返回值 如果返回的是 Object || Function 类型,就返回该对对象,否则返回创建的对象
  const flag = result && (typeof result === 'object' || typeof result === 'function')
  return flag ? result : obj
}
​
const p = myNew(Person, "ps", 18);

实现 instanceof

/**
 * 原理:
 * 我们拿到 instanceof 左侧对象的原型链
 * 再拿到 instanceof 右侧构造函数的显式原型 prototype
 * 如果原型链中存在右侧构造函数的显式原型 prototype,instanceof 返回 true,否则返回 false
 */function myInstanceOf(obj, constructor) {
  let objProptype = Object.getPrototypeOf(obj)
  while (true) {
    if (!objProptype) return false
    if (objProptype === constructor.prototype) return true
    
    objProptype = Object.getPrototypeOf(objProptype)
  }
}
​
const obj = {}
console.log(myInstanceOf(obj, Object));

实现 Object.create()

/**
 * 用法:创建一个新的对象,将传入的对象原型指向新对象并返回
 * 思路:
 *  1、创建一个 空对象,将 prototype 添加到 空对象的隐式原型属性上
 *  1、将原型写入到一个函数里面,然后 new 函数并返回实例对象
 */function myCreate(prototype) {
  // 方法 1:
  // const obj = {}
  // obj.__proto__ = prototype
  // return obj
​
  // 方法 2:
  function F() {}
  F.prototype = prototype
  
  return new F()
}
​
// 测试代码
function Person(name) {
  this.name = name
}
​
const obj = Object.create(Person.prototype)
console.log(obj, myCreate(Person.prototype));

防抖 debounce

函数在 n 秒后执行,如果多次触发,重新计时,保证函数在 n 秒后执行。

非立即执行

function debounce(fn, delay = 500) {
  let timer = null
  // 使用闭包记录 timer 变量
  return function() {
    if (timer) {
      clearTimeout(timer)
    }
    timer = setTimeout(() => {
      fn.apply(this, arguments)
      timer = null
    }, delay)
  }
}

立即执行

function debounce(fn, delay = 500) {
  let timer = null
  // 使用闭包记录 timer 变量
  return function () {
    if (timer) {
      clearTimeout(timer)
    } else {
      fn.apply(this, arguments)
    }
    timer = setTimeout(() => {
      timer = null
    }, delay)
  }
}

完整版

function debounce(fn, delay = 500, immediate = false) {
  let timer = null
  // 使用闭包记录 timer 变量
  return function() {
    if (timer) {
      clearTimeout(timer)
    } else {
      immediate && fn.apply(this, arguments)
    }
    timer = setTimeout(() => {
      !immediate && fn.apply(this, arguments)
      timer = null
    }, delay)
  }
}
​
// 测试代码
function getData(e) {
  console.log('getData', e);
}
const fn = debounce(getData)
​
window.addEventListener('resize', e => {
  fn(e)
})

节流 throttle

在 n 秒内 函数只能执行一次,如果 n 秒内多次触发,则忽略执行。

非立即执行

function throttle(fn, delay = 500) {
  let timer = null
  return function () {
    if (!timer) {
      timer = setTimeout(() => {
        fn.apply(this, arguments)
        timer = null
      }, delay)
    }
  }
}

立即执行

function throttle(fn, delay = 500) {
  let timer = null
  return function () {
    if (!timer) {
      fn.apply(this, arguments)
      timer = setTimeout(() => {
        timer = null
      }, delay)
    }
  }
}

完整版

function throttle(fn, delay = 500, immediate = false) {
  let timer = null
  return function () {
    if (!timer) {
      immediate && fn.apply(this, arguments)
      timer = setTimeout(() => {
        !immediate && fn.apply(this, arguments)
        timer = null
      }, delay)
    }
  }
}

function getData(e) {
  console.log('getData', e);
}
const fn = throttle(getData, 3000, false)

window.addEventListener('resize', e => {
  fn(e)
})

浅拷贝

/**
 * 原理:如果拷贝的是基本数据类型,拷贝的就是基本数据类型的值,如果是引用数据类型,拷贝的就是内存地址。
 */

function shadowClone(target) {
  if (!target || typeof target !== 'object') {
    // 对于基本数据类型直接返回
    return target
  }

  // 创建一个新对象
  const obj = Array.isArray(target) ? [] : {}

  // 遍历 target 上的所有属性,并添加到 obj 上
  // in 性能低,因为会去遍历原型
  for (const key in target) {
    // 判断是否是自身属性
    if (target.hasOwnProperty(key)) {
      obj[key] = target[key]
    }
  }
  return obj
}

const obj = {
  a: 1, 
  b: {
    c: 0
  }
}
const obj2 = shadowClone(obj)
obj2.a = 2
obj2.b.c = 2
console.log(obj2, obj);

深拷贝

/** 
 * 思路:
 *  1、判断是否为对象
 *  2、判段对象是否在 map 中 如果存在就不需要操作
 *  3、将 target 放入 map 中 避免重复引用
 *  4、for in 遍历对象 拿到 key 判断 key 是否在 obj 中
 */
function deepClone(target, map = new Map()) {
  // 判断数据类型,基本数据类型,直接返回
  if (!target || typeof target !== 'object') {
    return target
  }

  if (map.get(target)) {
    return target
  }

  // 创建对象
  const obj = Array.isArray(target) ? [] : {}

  map.set(target, true)

  for (const key in target) {
    if (target.hasOwnProperty(key)) {
      obj[key] = deepClone(target[key], map)
    }
  }
  return obj
}

// 测试代码
var obj = {
  a: 1,
  b: {
    c: [3, 5, { e: 'eee', children: undefined }]
  }
}
obj.b.c[2].children = obj
const obj2 = deepClone(obj)
console.log(obj2, obj);

call

/** 
 * 用法:call 方法用于调用一个函数,并指定函数内部 this 的指向,传入一个对象
 * 思路:
 *  1、判断 this 是否指向一个函数  只有函数才可以执行
 *  2、获取传入的 context 上下文 也就是我们要指向的 如果不存在就指向 window
 *  3、将当前 this 也就是外部需要执行的函数 绑定到 context 上 然后执行获取 result 传入 ...args 确保参数位置正确
 *  4、删除 context 对象的 fn 属性 并将 result 返回
 */

Function.prototype.myCall = function(context, ...args) {
  if (typeof this !== 'function') {
    return new TypeError('type error')
  }
  context = context || window

  // 缓存this
  const key = Symbol()
  context[key] = this

  const result = context[key](...args)

  delete context[key]

  return result
}

function fn(name, age) {
  console.log(this, name, age);
  return name
}

const obj = {}

console.log(fn.call(obj, 'ps', 19));
console.log(fn.myCall(obj, 'ps', 19));

apply

Function.prototype.myApply = function(context, args = []) {
  if (typeof this !== 'function') {
    return new TypeError('type error')
  }
  context = context || window

  // 缓存this
  const key = Symbol()
  context[key] = this

  const result = context[key](...args)

  delete context[key]

  return result
}

function fn(name, age) {
  console.log(this, name, age);
  return name
}

const obj = {}

console.log(fn.apply(obj, ['ps', 19]));
console.log(fn.myApply(obj, ['ps', 19]));

bind

Function.prototype.myBind = function (context, ...args) {
  if (typeof this !== "function") {
    return new TypeError("type error");
  }

  context = context || window;
  // 缓存this
  const key = Symbol();
  context[key] = this;
  
  return function () {
    const result = context[key](...args);

    delete context[key];

    return result;
  };
};

function fn(name, age) {
  console.log(this, name, age);
  return name;
}

const obj = {};

fn.bind(obj, "ps", 19)();
fn.myBind(obj, "ps", 19)();

函数柯里化 curry

function curry(fn, args = []) {
  // 1. 获取函数形参数量
  const length = fn.length
​
  // 2. 返回一个函数
  return function() {
    // 3. 收集参数
    args.push(...arguments)
​
    if (args.length >= length) {
      // 当收集到的参数列表大于函数的形参数量时,需要调用函数并返回结果
      return fn.apply(this, args)
    } else {
      // 递归返回函数
      return curry(fn, args)
    }
  }
}
​
// 测试代码
function sum(a, b, c) {
  return a * b * c
}
​
const sumCurry = curry(sum)
sumCurry(2)
sumCurry(2)
sumCurry(3)
​
console.log(sumCurry(10))

实现 flat 数组扁平化

// 递归实现// 判断当前项是否为数组 如果是数组递归调用 不是就push到新数组
function flat(arr, result = []) {
  for (let i = 0; i < arr.length; i++) {
    const item = arr[i]
    if (Array.isArray(item)) {
      flat(item, result)
    } else {
      result.push(item)
    }
  }
​
  return result
}
​
Array.prototype.flatten = function() {
  const arr = this
  return [].concat(...arr.map(item => Array.isArray(item) ? item.flatten() : item))
}
// 栈实现// 新建一个栈来存储数据 每次从栈中取出一个数据 判断是否为数组 
// 如果是 就将该数组放入到栈中 修改了栈的长度 开始下一次循环 
// 如果不是 就放入新数组
function flatten(arr = []) {
  if (!Array.isArray(arr)) {
    throw new TypeError('不是一个数组')
  }
​
  const stack = [...arr]
  const result = []
​
  while (stack.length) {
    const current = stack.pop()
    if (Array.isArray(current)) {
      stack.push(...current)
    } else {
      result.push(current)
    }
  }
  return result
}

实现 map

Array.prototype.myMap = function(cb) {
  cb = typeof cb !== 'function' ? () => {} : cb
  
  const arr = this
  const result = []
​
  for (let i = 0; i < arr.length; i++) {
    const item = arr[i]
    result.push(cb(item, i, arr))
  }
​
  return result
}
​
const arr1 = [1, 2].myMap()
console.log(arr1);

实现 forEach

Array.prototype.myForEach = function(cb) {
  cb = typeof cb !== 'function' ? () => {} : cb
​
  const arr = this
​
  for (let i = 0; i < arr.length; i++) {
    const item = arr[i]
    cb(item, i, arr)
  }
}

实现 filter

Array.prototype.myFilter = function(cb) {
  cb = typeof cb !== 'function' ? () => {} : cb
​
  const arr = this
  const result = []
​
  for (let i = 0; i < arr.length; i++) {
    const item = arr[i]
    const flag = cb(item, i, arr)
    flag && result.push(item)
  }
​
  return result
}

实现 find

Array.prototype.myFind = function(cb) {
  cb = typeof cb !== 'function' ? () => {} : cb
  const arr = this

  for (let i = 0; i < arr.length; i++) {
    const item = arr[i]
    const flag = cb(item, i, arr)
    if (flag) return item
  }
}

实现 reduce

Array.prototype.myReduce = function(cb, initValue) {
  cb = typeof cb !== 'function' ? () => {} : cb
  const arr = this
​
  let prev
​
  // 判断是否设置了初始值,设置了就使用
  // 没设置就用数组第一项
  if (initValue !== undefined) {
    prev = initValue
  } else {
    prev = arr.shift()
  }
​
  for (let i = 0; i < arr.length; i++) {
    const next = arr[i]
    prev = cb(prev, next, i, arr)
  }
​
  return prev
}

实现 push

Array.prototype.myPush = function(...rest) {
  const arr = this
​
  // 方法1:
  for (let i = 0; i < rest.length; i++) {
    arr[arr.length] = rest[i]
  }
​
  // 方法2:
  // while (rest.length) {
  //   arr[arr.length] = rest.shift()
  // }
​
  return arr.length
}

数组转树

let source = [
  {
    id: 1,
    parentId: 0,
    name: 'body',
  },
  {
    id: 2,
    parentId: 1,
    name: 'title',
  },
  {
    id: 3,
    parentId: 2,
    name: 'div',
  },
  {
    id: 4,
    parentId: 0,
    name: 'html',
  },
  {
    id: 5,
    parentId: 4,
    name: 'div',
  },
  {
    id: 6,
    parentId: 5,
    name: 'span',
  },
  {
    id: 7,
    parentId: 5,
    name: 'img',
  },
]
[
  // 转为
  ({
    id: 1,
    parentId: 0,
    name: 'body',
    children: [
      {
        id: 2,
        parentId: 1,
        name: 'title',
        children: [{ id: 3, parentId: 2, name: 'div' }],
      },
    ],
  },
  {
    id: 4,
    parentId: 0,
    name: 'html',
    children: [
      {
        id: 5,
        parentId: 4,
        name: 'div',
        children: [{ id: 7, parentId: 5, name: 'img' }],
      },
    ],
  })
]

用 map 对所有的 id 进行缓存,判断每一项的 parentId 是否在 map 中, 如果存在则是子元素,如果不存在则是根元素。

function arrToTree(arr = []) {
  const map = {}
  for (const item of arr) {
    map[item.id] = item
  }
​
  const result = []
​
  for (let i = 0; i < arr.length; i++) {
    const item = arr[i]
    const node = map[item.parentId]
    if (node) {
      node.children = node.children || []
      node.children.push(item)
    } else {
      result.push(item)
    }
  }
​
  return result
}

树转数组

let source1 = [
  {
    id: 1,
    parentId: 0,
    name: "body",
    children: [
      {
        id: 2,
        parentId: 1,
        name: "title",
        children: [{ id: 3, parentId: 2, name: "div" }],
      },
    ],
  },
  {
    id: 4,
    parentId: 0,
    name: "html",
    children: [
      {
        id: 5,
        parentId: 4,
        name: "div",
        children: [{ id: 7, parentId: 5, name: "img" }],
      },
    ],
  },
];
[
  // 转为
  ({ id: 1, parentId: 0, name: 'body' },
  { id: 4, parentId: 0, name: 'html' },
  { id: 2, parentId: 1, name: 'title' },
  { id: 5, parentId: 4, name: 'div' },
  { id: 3, parentId: 2, name: 'div' },
  { id: 7, parentId: 5, name: 'img' })
]
// 递归实现
function treeToArr(arr = [], result = []) {
  for (const { children, ...rest } of arr) {
    result.push(rest);
    if (Array.isArray(children)) {
      treeToArr(children, result);
    }
  }
  return result;
}
// 栈实现
function treeToArr(arr = []) {
  const stack = [...arr]
  const result = []

  while (stack.length) {
    // 从栈顶弹出
    const current = stack.shift()
    
    if (current.children && current.children.length) {
      // 有 children,将 children 全部展开并添加到栈中
      stack.unshift(...current.children)

      // 删除 children 属性
      delete current.children
    }

    result.push(current)
  }

  return result
}

实现每隔一秒打印 1,2,3,4

// let + setTimeout
// 使用 let 块级作用域 
for (let i = 1; i < 5; i++) {
  setTimeout(() => {
    console.log(i);
  }, i * 1000)
}
​
// setInterval
let i = 1
const timer = setInterval(() => {
  if (i >= 4) {
    clearInterval(timer)
  }
  console.log(i);
  i++
}, 1000)

使用 setTimeout 实现一个 setInterval

function setInterval(cd = () => {}, delay = 1000) {
  setTimeout(() => {
    cd()
    setInterval(cd, delay)
  }, delay)
}
setInterval(() => {
  console.log('ps 你好!');
}, 1000)

实现斐波那契数列

递归方式(基础班)

// 斐波那契数列 1, 2, 3, 5, 8, 13, 21
function fbnq(n) {
  if (n === 1 || n === 2) return n
  return fbnq(n - 2) + fbnq(n - 1)
}
​
console.log(fbnq(7)) // 21

递归优化版

// 斐波那契数列 1, 2, 3, 5, 8, 13, 21
function fbnq(n, map = {}) {
  if (n === 1 || n === 2) return n
​
  if (map[n]) {
    return map[n]
  } else {
    const value = fbnq(n - 2, map) + fbnq(n - 1, map)
    map[n] = value
    return value
  }
}
​
console.log(fbnq(100))

将数字每千分位用逗号隔开

// 方法一
function format(num) {
  if (typeof num !== 'number') {
    throw new Error(`${num} is not a number!`)
  }
​
  let decimal
​
  if (!Number.isInteger(num)) {
    decimal = String(num).split('.')[1]
    num = String(num).split('.')[0]
  }
  let strNum = String(num)
  const list = strNum.split('')
  const len = list.length
​
  // 整数
  // 从后往前每三个加一个逗号
  for (let i = len - 3; i > 0; i -= 3) {
    list.splice(i, 0, ',')
  }
  const result = decimal ? list.join('') + '.' + decimal : list.join('')
  return result
}
// 10224 --> 10,224
console.log(format(10922423113.97)); // 10,922,423,113.97// 方法二
function format(num) {
  if (typeof num !== 'number') {
    throw new Error(`${num} is not a number!`)
  }
​
  return num.toLocaleString()
}
// 10224 --> 10,224
console.log(format(10922423113.97)); // 10,922,423,113.97

发布-订阅模式

class EventEmitter {
  // events: { type: [{ cb, once: false }] }
  constructor() {
    this.events = {}
  }
​
  on(type, cb = () => {}, once = false) {
    if (!this.events[type]) {
      this.events[type] = []
    }
    this.events[type].push({ cb, once })
  }
​
  emit(type, data) {
    (this.events[type] || []).forEach(item => {
      item.cb(data)
      if (item.once) {
        this.off(type, item.cb);
      }
    })
  }
​
  off(type, cb = () => {}) {
    this.events[type] = this.events[type].filter(item => item.cb !== cb)
  }
​
  once(type, cb = () => {}) {
    this.on(type, cb, true)
  }
}
​
const eventEmitter = new EventEmitter()
​
function fn(val) {
  console.log(`name: `+ val.name);
}
eventEmitter.on('event1', fn)
​
eventEmitter.emit('event1', { name: 'ps', age: 18 })
​
eventEmitter.off('event1', fn)
​
eventEmitter.once('event1', fn)
​
eventEmitter.emit('event1', { name: 'ps', age: 18 })

实现 Promise

class MyPromise {
  // 定义三种状态
  static PENGING = "pending";
  static FULFILLED = "fulfilled";
  static REJECT = "reject";
​
  constructor(executor) {
    // 初始化状态
    this.status = MyPromise.PENGING;
    // 初始化值
    this.value = null;
    this.reason = null;
    // 成功的回调队列
    this.onFulfilledCallback = [];
    // 失败的回调队列
    this.onRejectedCallback = [];
    // 执行并绑定this
    executor(this.reslove.bind(this), this.reject.bind(this));
  }
​
  reslove(value) {
    // 状态一旦变更就不会再发生变化。
    if (this.status !== MyPromise.PENGING) return;
    this.value = value;
    this.status = MyPromise.FULFILLED;
    this.onFulfilledCallback.length && this.onFulfilledCallback.shift()();
  }
​
  reject(reason) {
    // 状态一旦变更就不会再发生变化。
    if (this.status !== MyPromise.PENGING) return;
    this.reason = reason;
    this.status = MyPromise.REJECT;
    this.onRejectedCallback.length && this.onRejectedCallback.shift()();
  }
​
  then(onFulfilled, onRejected) {
    onFulfilled = typeof onFulfilled !== "function" ? () => {} : onFulfilled;
    onRejected = typeof onRejected !== "function" ? () => {} : onRejected;
    return new MyPromise(() => {
      if (this.status === MyPromise.PENGING) {
        this.onFulfilledCallback.push(() =>
          setTimeout(() => onFulfilled(this.value))
        );
        this.onRejectedCallback.push(() =>
          setTimeout(() => onRejected(this.reason))
        );
      } else if (this.status === MyPromise.FULFILLED) {
        onFulfilled(this.value);
      } else if (this.status === MyPromise.REJECT) {
        onRejected(this.reason);
      }
    });
  }
}
​
const p1 = new MyPromise((resolve, reject) => {
  resolve("p1成功");
  console.log("p1-console");
  setTimeout(() => {
    resolve("p1成功-setTimeout");
    reject("p1-失败-setTimeout");
    console.log("p1-console-setTimeout");
  }, 100);
});
​
p1.then(
  (value) => {
    console.log("value: ", value);
  },
  (reason) => {
    console.log("reason: ", reason);
  }
);

1. 快速排序

利用二分法 + 递归的原理

function quickSort(arr) {
  // 退出递归的条件:数组中只剩下一个元素时 返回数组
  if (arr.length <= 1) return arr;
​
  const middleIndex = Math.floor(arr.length / 2);
  const middle = arr[middleIndex];
​
  const left = [];
  const right = [];
  const center = [];
​
  for (let i = 0; i < arr.length; i++) {
    if (arr[i] < middle) {
      left.push(arr[i]);
    } else if (arr[i] > middle) {
      right.push(arr[i]);
    } else {
      center.push(arr[i]);
    }
  }
​
  return quickSort(left).concat(center, quickSort(right));
}
​
const arr = [3, 1, 4, 9, 10, 1, 3, 2, 5, 9, 6];
console.log(quickSort(arr));

2. 插入排序

以下是插入排序的基本思想步骤:

  1. 从第一个元素开始,该元素可以认为已经被排序;
  2. 取出下一个元素,在已经排序的元素序列中从后向前扫描;
  3. 如果该元素(已排序)大于新元素,将该元素移到下一位置;
  4. 重复步骤 3,直到找到已排序的元素小于或者等于新元素的位置;
  5. 将新元素插入到该位置后;
  6. 重复步骤 2~5,直到所有元素均排序完毕。
function insertSort(arr) {
  for (let i = 1; i < arr.length; i++) {
    let j = i - 1;
    const current = arr[i];
    // 必须使用变量将当前值保存起来,不然后面数组下标变化,导致出现问题。
    while (j >= 0 && arr[j] > current) {
      arr[j + 1] = arr[j];
      j--;
    }
    arr[j + 1] = current;
  }
  return arr;
}
​
const arr = [2, 1, 4, 3, 8, 9, 5, 7, 10, 2, 33];
console.log(insertSort(arr));

3. 冒泡排序

function bubbleSort(arr) {
  // 不易理解
  // for (let i = 0; i < arr.length - 1; i++) {
  //   for (let j = i + 1; j < arr.length; j++) {
  //     if (arr[i] > arr[j]) {
  //       [arr[i], arr[j]] = [arr[j], arr[i]]
  //     }
  //   }
  // }
​
  // 原数组:[4, 3, 2, 1]
  // [4, 3, 2, 1] i = 0, j = 0 --> [3, 4, 2, 1]
  //              i = 0, j = 1 --> [3, 2, 4, 1]
  //              i = 0, j = 2 --> [3, 2, 1, 4]
  // [3, 2, 1, 4] i = 1  j = 0, 1  --> [2, 3, 1, 4], [2, 1, 3, 4]
  // [2, 1, 3, 4] i = 2  j = 0  --> [1, 2, 3, 4]
  // [1, 2, 3, 4] i = 3         --> [1, 2, 3, 4]
  // 外层遍历控制行,内层遍历控制列
  for (let i = 0; i < arr.length - 1; i++) {
    for (let j = 0; j < arr.length - 1 - i; j++) {
      if (arr[j] > arr[j + 1]) {
        [arr[j], arr[j + 1]] = [arr[j + 1], arr[j]];
      }
    }
  }
  return arr;
}
​
const arr = [2, 1, 4, 3, 8, 9, 5, 7, 10, 2, 33, 8];
console.log(bubbleSort(arr));

4. 选择排序

// 思想:查找最小值,记录索引。将未排序列最小值与已排序列的后一位进行交换。
function selectionSort(arr) {
  let minIndex;
  for (let i = 0; i < arr.length; i++) {
    minIndex = i;
    for (let j = i + 1; j < arr.length; j++) {
      if (arr[j] < arr[minIndex]) {
        minIndex = j;
      }
    }
    [arr[i], arr[minIndex]] = [arr[minIndex], arr[i]];
  }
  return arr;
}
​
const arr = [2, 1, 4, 3, 8, 9, 5, 7, 10, 2, 33, 8];
console.log(selectionSort(arr));

按需求合并两个数组

['A1', 'A2', 'B1', 'B2', 'C1', 'C2', 'D1', 'D2'] ['A', 'B', 'C', 'D']

合并为 ['A1', 'A2', 'A', 'B1', 'B2', 'B', 'C1', 'C2', 'C', 'D1', 'D2', 'D']

const a1 = ['A1', 'A2', 'B1', 'B2', 'C1', 'C2', 'D1', 'D2']
const a2 = ['A', 'B', 'C', 'D']
​
function concat(a1, a2) {
  return a1.concat(a2).sort((a, b) => a.charCodeAt() - b.charCodeAt())
}

改造下面的代码,使之输出 0 - 9

for (var i = 0; i < 10; i++) {
  setTimeout(() => {
    console.log(i);
  }, 1000);
}
​
// 方法1
for (let i = 0; i < 10; i++) {
  setTimeout(() => {
    console.log(i);
  }, 1000);
}
​
// 方法2
for (var i = 0; i < 10; i++) {
  ((i) => {
    setTimeout(() => {
      console.log(i);
    }, 1000);
  })(i)
}