# 头条

# 笔试

# 格式化发布时间

类似于微信朋友圈里面的发布时间,其中发布时间有几种特殊情况:

  • 小于 1 min,则显示 刚刚
  • 小于 1 h,则显示 x 分钟前
  • 小于 1 天,则显示 x 小时前
  • 小于 1 个星期,则显示 x 天前
  • 否则显示具体时间。
declare function i18n(s: string): string

function format(date: number): string {
  const MIN = 60 * 1000
	const HOUR = 60 * MIN
  const DAY = 24 * HOUR
  const WEEK = 7 * DAY
  
  if(date < MIN) return i18n('刚刚')
  if(date < HOUR) return Math.floor(date / MIN) + i18n('分钟前')
  if(date < DAY) return Math.floor(date / HOUR) + i18n('小时前')
  if(date < WEEK) return Math.floor(date / DAY) + i18n('天前')
  return new Date(Date.now() - date).toLocaleString()
}

console.log(format(60 * 1000 - 1)); // 59 秒 999
console.log(format(60 * 1000)); // 1 分

console.log(format(60 * 1000 * 59 + 59 * 1000 + 999)); // 59 分 59 秒 999
console.log(format(60 * 1000 * 60)); // 1 小时

console.log(format(60 * 1000 * 60 * 23 + 60 * 1000 * 59 + 59 * 1000 + 999)); // 23 小时 59 分 59 秒 999
console.log(format(60 * 1000 * 60 * 24)); // 1 天

console.log(format(60 * 1000 * 60 * 24 * 6)); // 6 天
console.log(format(60 * 1000 * 60 * 24 * 7)); // 7 天

console.log(format(1554111847534)); // 发布时的时间戳

# 格式化数字

为数字添加都好,使用正则和非正则实现

// 方法1:使用内置api
// 缺点:小数部分会保留三位小数
var num = 12345
console.log(num.toLocaleString()) // '12,345'
num = 12345.6789
console.log(num.toLocaleString()) // '12,345.679'

// 方法2:使用数组
function format(num: number): string {
  if(typeof num !== 'number') return num
  
  const [first, last] = `${num}`.split('.')
  const len = first.length
	const temp = first.split('').reverse().reduce((pre, cur, index) => {
    if((i + 1) % 3 === 0 && i !== flen - 1) pre.push(cur, ',')
    else pre.push(cur)
  }, []).reverse().join('')
  
  return last ? `${temp}.${last}` : temp
}

# 一面

# 实现 js ES5 数据类型的深拷贝

function deepClone<D>(data: D): D {
  const isObj = (v: D) => Object.prototype.toString.call(v).slice(8, -1) === 'Object'
  
  function _deepClone(val: any): D {
    if(Array.isArray(val)) {
			const source = val as any[]
      return source.reduce((res, item) => {
        res.push(_deepClone(item))
        return res
      }, [])
    }
    
    if(isObj(val)) {
      const source = val as object
      return Object.keys(source).reduce((res, key) => {
        res[key] = _deepClone(source[key])
        return res
      }, {})
    }
    
    return val
  }
  
  return _deepClone(data)
}

# 二面

# 实现简易 MVVM

interface Handlers {
    get(val): void
    set(newVal, oldVal): void
}

class Observer<T extends object = {}> {
    private data: T

    constructor(data: T) {
        this.data = data
        this.proxyData()
    }

    private proxyData(): void {
        const keys = Object.keys(this.data)
        for (const key of keys) {
            Object.defineProperty(this, key, {
                get: () => {
                    return this.data[key]
                },
                set: (val) => {
                    this.data[key] = val
                }
            })
        }
    }

    observe(key: keyof T, handlers: Handlers): void {
        const value = this.data[key]

        Object.defineProperty(this.data, key, {
            get: () => {
                handlers.get(value)
                return value
            },
            set: (val) => {
                if (val === value) return
                handlers.set(val, value)
                this.data[key] = val
            }
        })
    }
}

# 三面

# 顺序发送4个请求a,b,c,d,要求按顺序输出

function getData(urls: string) {
  return new Promise<object[]>((resolve, reject) => {
    const len = urls.length
    const res: object[] = []
    
    urls.forEach((url, index) => {
      fetch(url)
        .then(res => res.json())
      	.then(data => {
        	res[index] = { data, printed: false }
        	let flag = true // 用来判断所有请求是否都已经跑完
          for(let i = 0; i < len && flag; i++) {
            if(res[i]) { 
              if(!res[i].printed) { // 循环内,所以去掉已经打印的
                console.log(res[i].data)
                res[i].printed = true
                i === len - 1 && resolve(res.map(v => v.data)) // 当最后一个打印完成后,结束promise
              }
            } else {
              flag = false
            }
          }
      	}, reject)
    })
  })
}

# 美团

# 二面

# 用 promise 实现一个请求超时功能

实现关键在于 promise 的状态改变是单向且唯一的,即只能由 pending -> fulfilled 或者 pending -> rejected,所以可以使用 resolve + setTimeout 来实现。

function promiseWithTimeout(url, delay = 3000) {
  return new Promise((resolve, reject) => {
    setTimeout(function () {
      reject(Error('time is out!'))
    }, delay)
    fetch(url).then(data => resolve(data.json()))
  })
}