Git Product home page Git Product logo

blog's Issues

【面经】美团 二面5:用 promise 实现一个请求超时功能

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

关键词:promise.then 与 setTimeout 并行

抓住 promise 的状态只能由 pending -> fulfilled,或者 pending -> rejected,并且只能进行一次改变

function promiseWithTimeout(url, timeout = 3000) {
  return new Promise((resolve, reject) => {
    fetch(url).then(data => data.json()).then(data => resolve(data)); // fetch 先得到结果就 resolve
    setTimeout(() => reject(Error('time is out!')), timeout); // 时间到了还没 fetch 到就 reject
  });
}

promiseWithTimeout('http://localhost:8080/data.json')
  .then(data => console.log(data))
  .catch(err => console.error(err));

// server.js 测试
const express = require('express');
const cors = require('cors');
const app = express();

app.use(cors({ origin: '*' }));

app.use('/data.json', (req, res) => {
  setTimeout(() => res.end(JSON.stringify({ a: 1 })), Math.floor(Math.random() * 6 * 1000));
});

app.listen(8080, () => console.log('the app is running at http://localhost:8080'));

【面经】字节跳动 笔试4:格式化数字。正则和非正则实现

格式化数字

要求:使用正则和非正则两种方式实现

评论区大神们,欢迎附上正则版~

// 法1:toLocaleString
// 浏览器提供该 api 对数字进行格式化,不过对于小数的处理使用的是四舍五入
let num = 1234567;
console.log(num.toLocaleString('en-US')); // 1,234,567
num = 12345.6785;
console.log(num.toLocaleString('en-US')); // 12,345.679

// 法2:数组操作
function format(num) {
  if (typeof num !== 'number') return num;

  const [ first, last ] = (num + '').split('.');
  const flen = first.length;
  const tmp = first.split('').reverse().reduce((res, n, i) => {
    if ((i + 1) % 3 === 0  && i !== flen - 1) res.push(n, ',');
    else res.push(n);
    return res;
  }, []).reverse().join('');

  return last ? tmp + '.' + last : tmp;
}

// 法3:递归
function format(num) {
  if (typeof num !== 'number') return num;

  const [first, last] = (num + '').split('.');

  function run(s) {
    if (s.length <= 3) return s;
    return run(s.slice(0, -3)) + ',' + s.slice(-3);
  }

  return last ? run(first) + '.' + last : run(first);
}

console.log(format(123456)); // 123,456
console.log(format(1234567)); // 1,234,567
console.log(format(12345.6785)); // 12,345.6785
console.log(format(123456.78)); // 123,456.78

【面经】字节跳动 二面9:实现简易版 MVVM

实现简易版 MVVM

关键词:Object.defineProperty

class Observer {
  constructor(data) { this.data = data; }

  observe(prop, cbObj) { // 调用了 observe 才会对属性 prop 进行观察
    if (!prop || typeof prop !== 'string') return;
    this.defineReactive(this.data, prop, this.data[prop], cbObj);

    // 由于 new Observer() 出的对象没有 data.a 属性,必须通过 data.data.a 调用,熟悉 vue 的应该知道这一点
    // 为了方便我们直接调用 data.a,把 this.data 的属性代理到 this 上
    this.proxyData(prop, this.data);
  }

  defineReactive(obj, key, value, { getCallback, setCallback }) { // 抽离的对属性进行监控的方法
    Object.defineProperty(obj, key, {
      get() {
        getCallback(value);
        return value;
      },
      set(newValue) {
        if (newValue !== value) {
          setCallback(newValue, value);
          value = newValue;
        }
      }
    });
  }

  proxyData(key, data) {
    Object.defineProperty(this, key, {
      get() { return data[key]; }, // 对调用 data.a 进行劫持,返回内部的 data[key]
      set(newValue) { data[key] = newValue; } // 对 data.a = 2; 进行劫持,实际上的在操作内部的 data[key] = 2;
    });
  }
}

// 测试
const data = new Observer({ a: 1, b: 2 });

const callbackObj = {
  getCallback(value) { console.log(`get: value: ${value}`); },
  setCallback(newValue, oldValue) { console.log(`set: newValue: ${newValue}, oldValue: ${oldValue}`); }
}
data.observe("a", callbackObj);
data.a; // console: 1
data.a = 2; // console: 2 1
data.b; // undefined 因为没有进行 data.observe('b', ...)

【面经】字节跳动 笔试1:实现一个类可以完成事件 on,once,trigger,off

手撸一个事件机制

关键词:发布-订阅模式

其实核心就是维护一个对象,对象的 key 存的是事件 type,对应的 value 为触发相应 type 的回调函数,即 listeners,然后 trigger 时遍历通知,即 forEach 进行回调执行。

class EventTarget {
  constructor() {
    this.listeners = {}; // 储存事件的对象
  }

  on(type, callback) {
    if (!this.listeners[type]) this.listeners[type] = []; // 如果是第一次监听该事件,则初始化数组
      this.listeners[type].push(callback);
  }

  once(type, callback) {
   if (!this.listeners[type]) this.listeners[type] = [];
      callback._once = true; // once 只触发一次,触发后 off 即可
      this.listeners[type].push(callback);
  }

  off(type, callback) {
    const listeners = this.listeners[type];
    if (Array.isArray(listeners)) {
      // filter 返回新的数组,会每次对 this.listeners[type] 分配新的空间
      // this.listeners[type] = listeners.filter(l => l !== callback);
      const index = listeners.indexOf(callback); // 根据 type 取消对应的回调
      this.listeners[type].splice(index, 1); // 用 splice 要好些,直接操作原数组

      if (this.listeners[type].length === 0) delete this.listeners[type]; // 如果回调为空,删除对该事件的监听
    }
  }

  trigger(event) {
    const { type } = event; // type 为必传属性
    if (!type) throw new Error('没有要触发的事件!');

    const listeners = this.listeners[type]; // 判断是否之前对该事件进行监听了
    if (!listeners) throw new Error(`没有对象监听 ${type} 事件!`);

    if (!event.target) event.target = this;

    listeners.forEach(l => {
      l(event);
      if (l._once) this.off(type, l); // 如果通过 once 监听,执行一次后取消
    });
  }
}

// 测试
function handleMessage(event) { console.log(`message received: ${ event.message }`); }

function handleMessage2(event) { console.log(`message2 received: ${ event.message }`); }

const target = new EventTarget();

target.on('message', handleMessage);
target.on('message', handleMessage2);
target.trigger({ type: 'message', message: 'hello custom event' }); // 打印 message,message2

target.off('message', handleMessage);
target.trigger({ type: 'message', message: 'off the event' }); // 只打印 message2

target.once('words', handleMessage);
target.trigger({ type: 'words', message: 'hello2 once event' }); // 打印 words
target.trigger({ type: 'words', message: 'hello2 once event' }); // 报错:没有对象监听 words 事件!

【面经】字节跳动 笔试2:格式化发布时间

格式化发布时间

找准每段时间的变换点即可

function format(date, testMode) {
  const MIN = 60 * 1000;
  const HOUR = 60 * MIN;
  const DAY = HOUR * 24;
  const WEEK = DAY * 7;
  const time = testMode ? date : Date.now() - date;

  if (time < MIN) return '刚刚';

  if (time < HOUR) return Math.floor(time / MIN) + '分前';

  if (time < DAY) return Math.floor(time / HOUR) + '小时前';

  if (time < WEEK) return Math.floor(time / DAY) + '天前';

  return new Date(testMode ? Date.now() - date : date).toLocaleString();
}

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

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

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

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

// 正常使用
console.log(format(1554111847534)); // 发布时的时间戳

【面经】字节跳动 三面8:顺序发送 4 个请求 a,b,c,d,要求按照顺序输出

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

利用数组缓存,再按照标志依次输出

function getData(urls) {
  // 感谢 @jinxin0112 的指正
  return new Promise((resolve, reject) => {
    const res = [], len = urls.length;
    urls.forEach((url, i) => {
      fetch('http://localhost:8080' + url).then(data => data.json())
        .then(data => {
          res[i] = { data, printed: false }; // 将数据放入缓存数组
          let flag = true;
          for (let j = 0; j < len && flag; j += 1) {
            if (res[j]) { // 如果标志为 j 的有返回值,则继续
              if (!res[j].printed) {
                console.log(res[j].data);
                res[j].printed = true;
                j === len - 1 && resolve(res.map(o => o.data))
              }
            } else { // 无返回值,则跳出
              flag = false;
            }
          }
        }, reject);
    });
  })
}

const listPromise = getData(['/data.json', '/data2.json', '/data3.json', '/data4.json']);
listPromise.then(res => console.log(res));

// server.js 测试:模拟各个请求延迟返回
const express = require('express');
const cors = require('cors');
const app = express();

const getTime = () => Math.floor(Math.random() * 4 * 1000);

app.use(cors({ origin: '*' }));

app.use('/data.json', (req, res) => {
  setTimeout(() => res.end(JSON.stringify({ a: 1 })), getTime());
});

app.use('/data2.json', (req, res) => {
  setTimeout(() => res.end(JSON.stringify({ b: 2 })), getTime());
});

app.use('/data3.json', (req, res) => {
  setTimeout(() => res.end(JSON.stringify({ c: 3 })), getTime());
});

app.use('/data4.json', (req, res) => {
  setTimeout(() => res.end(JSON.stringify({ d: 4 })), getTime());
});

app.listen(8080, () => console.log('the app is running at http://localhost:8080'));

【面经】字节跳动 一面7(3):深度克隆 js 各种数据类型

深度克隆 js 各种数据类型。附加题:实现对象中嵌套数组,数组中嵌套对象

JS 中基本类型

5 种原始类型:Boolean, Number, String, Null, Undefined(暂不考虑 ES6 的 Symbol)还有 Object

// 这里把数组也考虑了进来
function deepClone(someObj) {
  const { toString } = Object.prototype;
  const isObject = o => toString.call(o).slice(8, -1) === 'Object';

  function _deepClone(source) {
    if (Array.isArray(source)) { // 处理数组
      return source.reduce((res, item) => {
        res.push(_deepClone(item));
        return res;
      }, []);
    }

    if (isObject(source)) { // 处理对象
      return Object.keys(source).reduce((res, key) => {
        res[key] = _deepClone(source[key]);
        return res;
      }, {});
    }

    return source; // 处理其他类型
  }

  return _deepClone(someObj);
}

// 测试
console.log(deepClone([1, 2, [3], [4, 5, [6, 7]]]));
console.log(deepClone({ a: 1, b: { c: 3, d: 7, e: { y: 8, z: 4 } } }));
console.log(deepClone(12));
console.log(deepClone(true));
console.log(deepClone(null));
console.log(deepClone(undefined));
console.log(deepClone([1, { a: 9, b: 8 }, [3], [4, 5, [6, 7]]]));
console.log(deepClone({ a: 1, b: { c: [4, 5, [6]], d: 7, e: { y: 8, z: 4 } } }));

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.