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()
05. Array.prototype.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()
08. Function.prototype.apply()
09. Function.prototype.call()
10. Function.prototype.bind()
11. debounce
12. 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 操作
15. 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. 深拷贝
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
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;
});
/{[^}]+}/g, 首先先匹配大括号{ , 匹配字符,到 } 停止匹配。- ^}, 即不匹配 }
- 一个以上
- 最后匹配得出 ["{a.b}", "{a.c}", "{a.d}"]
- for 循环就完事了