JS 基础题

🧐 思考一下

看看你都会吗?

01. 数组扁平化

数组扁平化是指将一个多维数组变为一个一维数组

const arr = [1, [2, [3, [4, 5]]], 6];
// => [1, 2, 3, 4, 5, 6]

map()

const res = arr
  .join()
  .split(',')
  .map(Number);

flat()

const res1 = flat(Infinity);

正则(会变成字符串)

// 使用正则, 但是这样会变成数据类型会变成字符串
const res2 = JSON.stringify(arr)
  .replace(/\[|\]/g, '')
  .split(',');

正则 (优化版)

const value = JSON.stringify(arr).replace(/\[|\]/g, '');
const res3 = JSON.parse(`[${value}]`);

递归调用

let res4 = [];
function recursive(arr) {
  for (let i = 0; i < arr.length; i++) {
    if (Array.isArray(arr[i])) {
      recursive(arr[i]);
    } else {
      res4.push(arr[i]);
    }
  }
  return res4;
}

recursive(arr);

reduce()

function flatter(arr) {
  return arr.reduce((pre, cur) => {
    return pre.concat(Array.isArray(cur) ? flatter(cur) : cur);
  }, []);
}

flatter(arr);

02. 数组去重

const arr = [1, 1, '1', 17, true, true, false, false, 'true', 'a', {}, {}];
// => [1, '1', 17, true, false, 'true', 'a', {}, {}]

Set()

  • Array.from(),方法从一个类数组或可迭代对象创建一个新的浅拷贝的数组实例;

  • Set 对象允许存储任何类型的唯一值,无论是原始值还是对象引用;

const res = Array.from(new Set(arr));

indexOf() || includes()

function removal1(arr) {
  let result = [];
  for (let i = 0; i < arr.length; i++) {
    result.indexOf(arr[i]) === -1 ? result.push(arr[i]) : '';

    // 用includes也是一样
    // !result.includes(arr[i]) ? result.push(arr[i]) : '';
  }
  return result;
}

removal1(arr);

filter()

Array.prototype.indexOf() 返回在数组中可以找到一个给定元素的第一个索引。

利用 indexOf 始终返回的是查找到的第一个索引的特性。

const res = arr.filter((item, index) => arr.indexOf(item) === index);

Map()

function removal2(arr) {
  let map = new Map(),
    result = [];
  for (let i = 0; i < arr.length; i++) {
    if (!map.has(arr[i])) {
      map.set(arr[i], '');
      result.push(arr[i]);
    }
  }
  return result;
}

removal2(arr);

📝

这里只能用 Map(), 用 WeakMap(), 会报 Invalid value used as weak map key 的错误

双重 for + splice()

function removal3(arr) {
  let length = arr.length;
  for (let i = 0; i < length; i++) {
    for (let j = i + 1; j < length; j++) {
      if (arr[i] === arr[j]) {
        arr.splice(j, 1);
        length--;
        j--;
      }
    }
  }
  return arr;
}

removal3(arr);

03. 类数组转换成数组

类数组是具有length属性,但不具有数组原型上的方法。常见的类数组有 arguments、DOM 操作方法返回的结果。

Array.from()

Array.from(document.querySelectorAll('div'));

Array.prototype.slice.call()

Array.prototype.slice.call(document.querySelectorAll('all'));

扩展运算符

[...document.querySelectorAll('div')];

concat()

Array.prototype.concat.apply([], document.querySelectorAll('div'));

04. Array.prototype.filter()

请看手写代码系列 -> filter

05. Array.prototype.map()

请看手写代码系列 -> map

06. Array.prototype.forEach()

forEach 跟 map 类似,唯一不同的是 forEach 是没有返回值。

Array.prototype.forEach = function(callback) {
  if (typeof callback !== 'function') throw new TypeError(`expect function, but got ${typeof callback}`);
  if (!Array.isArray(this)) throw new TypeError(`expect Array, but got ${typeof this}`);

  const arr = this;
  for (let i = 0; i < arr.length; i++) {
    callback(arr[i], i, arr);
  }
};

07. Array.prototype.reduce()

请看手写代码系列 -> reduce

08. Function.prototype.apply()

请看手写代码系列 -> apply

09. Function.prototype.call()

请看手写代码系列 -> call

10. Function.prototype.bind()

请看手写代码系列 -> bind

11. debounce

请看手写代码系列 -> debounce

12. throttle

请看手写代码系列 -> throttle

13. 函数柯里化

指的是将一个接受多个参数的函数变成接受一个参数,返回一个参数的固定形式,这样便于再次调用,例如 f(1)(2)

经典面试题:实现 add(1)(2)(3)(4) = 10 、add(1)(1, 2, 3)(2) = 9

// 初级版
function add() {
  const args = [...arguments];
  function fn() {
    args.push(...arguments);
    return fn;
  }
  fn.toString = function() {
    return args.reduce((pre, cur) => pre + cur, 0);
  };
  return fn;
}
// 高阶版
function add() {
  let data = [].concat(Array.prototype.slice.call(arguments));

  // 使用闭包
  function tmp() {
    data = data.concat(Array.prototype.slice.call(arguments));
    return tmp;
  }
  tmp.valueOf = function() {
    return data.reduce((source, item) => source + item, 0);
  };
  tmp.toString = function() {
    return data.reduce((source, item) => source + item, 0);
  };
  return tmp;
}

14. 模拟 new 操作

请看手写代码系列 -> new

15. instanceof

请看手写代码系列 -> instanceof

16. 原型继承

这里只写了寄生组合继承,中间还有几个演变过来的继承,有缺陷就不过多展示了

function Person(name, age) {
  this.name = name;
  this.age = age;
}

function Student(...args) {
  Person.apply(this, args);
  this.eat = 'eating';
}

Student.prototype = Object.create(Person.prototype);
Student.prototype.constructor = Student;

17. Object.is

Object.is , 判断两个值是否为同一个值。

解决的主要是以下两个问题:

+0 === -0; // true
NaN === NaN; // false
function is(x, y) {
  // +0 === -0
  if (x === y) {
    return x !== 0 || 1 / x === 1 / y;
  } else {
    // NaN !== NaN
    return x !== x && y !== y;
  }
}

is(+0, -0); // false
is(NaN, NaN); // true

18. Object.assign()

Object.assign() 方法用于将可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。

Object.defineProperty(Object, 'assign', {
  value: function assign(target, ...args) {
    if (target === null || target === undefined) {
      throw new TypeError('Cannot convert null or undefined to object');
    }
    // 目标对象需要统一是引用数据类型,若不是会自动转换
    let to = Object(target);
    for (let i = 0; i < args.length; i++) {
      const nextSource = args[i];
      if (nextSource !== null && nextSource !== undefined) {
        // 使用for...in和hasOwnProperty双重判断,确保只拿到本身的属性、方法(不包含继承的)
        for (const key in nextSource) {
          if (Object.prototype.hasOwnProperty.call(nextSource, key)) {
            to[key] = nextSource[key];
          }
        }
      }
    }
    return to;
  },
  // 不可枚举
  enumerable: false,
  writable: true,
  configurable: true
});

19. 深拷贝

请看手写代码系列 -> clone

20. Promise

// 定义常量
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';

// resolve 和 reject 最终都会调用该函数
var final = function(status, value) {
  const promise = this;
  if (promise._status !== PENDING) return;

  // 所有的执行都是异步调用,保证then是先执行的
  setTimeout(function() {
    let st, queue, fn;
    promise._status = status;
    st = promise._status === FULFILLED;
    queue = promise[st ? '_resolves' : '_rejects'];

    while ((fn = queue.shift())) {
      value = fn.call(promise, value) || value;
    }

    promise[st ? '_value' : '_reason'] = value;
    promise._resolves = promise._rejects = undefined;
  });
};

// 参数是一个函数,内部提供两个函数作为该函数的参数,分别是resolve和reject
var Promise = function(resolver) {
  if (typeof resolver !== 'function') {
    throw new TypeError('You must pass a resolver function as the first argument to the promise constructor');
  }
  // 如果不是promise实例,就new一个
  if (!(this instanceof Promise)) return new Promise(resolver);

  const promise = this;
  promise._status = PENDING;
  // promise._value;
  // promise._reason;

  // 存储状态
  promise._resolves = [];
  promise._rejects = [];

  const resolve = function(value) {
    // 由于apply的参数是数组
    final.apply(promise, [FULFILLED].concat([value]));
  };

  const reject = function(reason) {
    final.apply(promise, [REJECTED].concat([reason]));
  };

  resolver(resolve, reject);
};

Promise.prototype.then = function(onFulfilled, onRejected) {
  const promise = this;
  // 每次返回一个promise,保证是可thenable的
  return new Promise((resolve, reject) => {
    function handle(value) {
      // 这一步很关键,只有这样才可以将值传递给下一个resolve
      const ret = (typeof onFulfilled === 'function' && onFulfilled(value)) || value;

      // 判断是不是promise对象
      if (ret && typeof ret['then'] === 'function') {
        ret.then(
          value => {
            resolve(value);
          },
          reason => {
            reject(reason);
          }
        );
      } else {
        resolve(ret);
      }
    }

    function errorBack(reason) {
      reason = (typeof onRejected === 'function' && onRejected(reason)) || reason;
      reject(reason);
    }

    if (promise._status === PENDING) {
      promise._resolves.push(handle);
      promise._rejects.push(errorBack);
    } else if (promise._status === FULFILLED) {
      // 状态改变后的then操作,立刻执行
      handle(promise._value);
    } else if (promise._status === REJECTED) {
      errorBack(promise._reason);
    }
  });
};

测试一下~~~

const getData100 = function() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve('1000ms');
    }, 1000);
  });
};
const getData200 = function() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve('2000ms');
    }, 2000);
  });
};
const getData300 = function() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      reject('reject');
    }, 3000);
  });
};

getData100()
  .then(data => {
    console.log(data); // 1000ms
    return getData200();
  })
  .then(data => {
    console.log(data); // 2000ms
    return getData300();
  })
  .then(
    data => {
      console.log(data);
    },
    error => {
      console.log(error); // reject
    }
  );

21. Promise.all

Promise.all 是支持链式调用的,本质上就是返回了一个 Promise 实例,通过 resolve 和 reject 来改变实例状态。

Promise.all = function(promises) {
  if (!Array.isArray(promises)) {
    throw new TypeError('You must pass an array to all');
  }

  return new Promise((resolve, reject) => {
    const len = promises.length;
    let count = len,
      result = [];

    // 这里与race中的函数相比,多了一层嵌套,要传入index
    function resolver(index) {
      return function(value) {
        resolveAll(index, value);
      };
    }

    function resolveAll(index, value) {
      result[index] = value;
      if (--count === 0) {
        resolve(result);
      }
    }

    function rejecter(reason) {
      reject(reason);
    }

    for (let i = 0; i < len; i++) {
      promises[i].then(resolver(i), rejecter);
    }
  });
};

或者也可以这样写

Promise.all = function(promises) {
  if (!Array.isArray(promises)) {
    throw new TypeError('You must pass an array to all');
  }
  return new Promise((resolve, reject) => {
    const len = promises.length;
    let resolveCount = 0,
      result = new Array(len);

    promises.forEach((obj, index) => {
      Promise.resolve(obj).then(
        value => {
          resolveCount++;
          result[index] = value;
          if (resolveCount === len) {
            resolve(result);
          }
        },
        reason => reject(reason)
      );
    });
  });
};

测试一下~~~

Promise.all([getData100(), getData200()]).then(data => {
  console.log(data);
});

22. Promise.any()

Promise.any() 接收一个 Promise 可迭代对象,只要其中的一个 promise 成功,就返回那个已经成功的 promise 。如果可迭代对象中没有一个 promise 成功(即所有的 promises 都失败/拒绝),就返回一个失败的 promise 和 AggregateError 类型的实例,它是 Error 的一个子类,用于把单一的错误集合在一起。

本质上,这个方法和 Promise.all()是相反的。

Promise.all = function(promises) {
  if (!Array.isArray(promises)) {
    throw new TypeError('You must pass an array to all');
  }
  return new Promise((resolve, reject) => {
    let len = promises.length;
    let count = 0;
    let errors = [];
    promises.forEach((promise, index) => {
      Promise.resolve(promise).then(
        value => {
          resolve(value);
        },
        err => {
          errors[index] = err;
          if (++count === len) {
            reject(new AggregateError('No Promise in Promise.any was resolved', errors));
          }
        }
      );
    });
  });
};

22. Promise.race

Promise.race = function(promises) {
  if (!Array.isArray(promises)) {
    throw new TypeError('You must pass an array to race');
  }

  return new Promise((resolve, reject) => {
    function resolver(value) {
      resolve(value);
    }

    function rejecter(reason) {
      reject(reason);
    }

    for (let i = 0; i < promises.length; i++) {
      promises[i].then(resolver, rejecter);
    }
  });
};
Promise.race = function(promises) {
  if (!Array.isArray(promises)) {
    throw new TypeError('must be array');
  }
  return new Promise((resolve, reject) => {
    promises.forEach(obj => {
      Promise.resolve(obj).then(
        value => resolve(value),
        reason => reject(reason)
      );
    });
  });
};

测试一下~~~

Promise.race([getData100(), getData200()]).then(data => {
  console.log(data);
});

23. Promise.finally 实现

Promise.prototype.finally = function(callback) {
  return this.then(
    value => Promise.resolve(callback()).then(() => value),
    reason => Promise.resolve(callback()).then(() => throw reason)
  );
};

23. Promise.catch

Promise.prototype.catch = function(callback) {
  return this.then(null, callback);
};

23. Promise 并行限制

题目:JS 实现一个带并发限制的异步调度器 Scheduler, 保证同时运行的任务最多有两个。完善下面代码的 Scheduler 类,使以下程序能够正常输出:

class Scheduler {
  add(promiseCreator) {...}
  // ...
}

const timeout = time => new Promise(resolve => {
  setTimeout(resolve, time);
});

const scheduler = new Scheduler();
const addTask = (time, order) => {
  scheduler.add(() => timeout(time).then(() => console.log(order)));
}

addTask(1000, '1');
addTask(500, '2');
addTask(300, '3');
addTask(400, '4');

// output: 2 3 1 4

解:

const maxCount = 2;
let queue = [],
  runCount = 0;

class Scheduler {
  add(promiseCreator) {
    queue.push(promiseCreator);
    this.exec();
  }

  exec() {
    if (!queue.length || runCount >= maxCount) return;
    runCount++;
    queue
      .shift()()
      .then(() => {
        runCount--;
        this.exec();
      });
  }
}

24. JSONP

JSONP 是 JSON with Padding 的略称。它是一个非官方的协议,它允许在服务器端集成 Script tags 返回至客户端,通过 javascript callback 的形式实现跨域访问(这仅仅是 JSONP 简单的实现形式)

script 标签不遵循同源协议,可以用来进行跨域请求,优点就是兼容性好但仅限于 GET 请求。

function jsonp({ url, params, callbackName }) {
  function generateUrl() {
    let dataSrc = '';
    for (const key in params) {
      if (Object.prototype.hasOwnProperty.call(params, key)) {
        dataSrc += `${key}=${params[key]}&`;
      }
    }
    dataSrc += `callback=${callbackName}`;
    return `${url}?${dataSrc}`;
  }

  return new Promise((resolve, reject) => {
    const scriptEle = document.createElement('script');
    scriptEle.src = generateUrl();
    document.body.appendChild(scriptEle);
    window[callbackName] = function(value) {
      resolve(value);
      document.body.removeChild(scriptEle);
    };
  });
}

// 测试一下
jsonp({
  url: 'http://www.geonames.org/postalCodeLookupJSON',
  params: {
    postalcode: 10504,
    country: 'US'
  },
  callbackName: 'result'
}).then(data => {
  console.log(data);
});

http://www.geonames.org/postalCodeLookupJSON?postalcode=10504&country=US&callback=?

25. Ajax

function getJSON(url) {
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest();
    xhr.open('GET', url); // 第三个参数是async,表示是否需要异步执行,默认为true
    xhr.setRequestHeader('Accept', 'application/json');
    xhr.onreadystatechange = function() {
      if (xhr.readyState !== 4) return;
      const repText = xhr.responseText;
      if (xhr.status === 200 || xhr.status === 304) {
        resolve(repText);
      } else {
        reject(new Error(repText));
      }
    };
    xhr.send();
  });
}

getJSON('https://www.baidu.com').then(value => console.log(data));

26. Event

请看手写代码系列 -> Event

27. 字符串解析

var a = {
  b: 123,
  c: '456',
  e: '789'
};
var str = `a{a.b}aa{a.c}aa {a.d}aaaa`;
// => 'a123aa456aa {a.d}aaaa'

解:

str.replace(/{[^}]+}/g, item => {
  const key = item.replace(/[{}]/g, '').split('.')[1];
  return a[key] || item;
});
  1. /{[^}]+}/g, 首先先匹配大括号{ , 匹配字符,到 } 停止匹配。
  2. ^}, 即不匹配 }
    • 一个以上
  3. 最后匹配得出  ["{a.b}", "{a.c}", "{a.d}"]
  4. for 循环就完事了
Last Updated:
Contributors: kk