- 算法题
- 涉及js原理的题以及ajax请求
- 业务场景题: 实现一个具有某种功能的组件
- 其他(进阶,对计算机综合知识的考察,考的相对较少):实现订阅发布者模式;分别用面向对象编程,面向过程编程,函数式编程实现把大象放进冰箱等等
优先掌握
- instanceof (考察对原型链的理解)
- new (对创建对象实例过程的理解)
- call&apply&bind (对this指向的理解)
- 手写promise (对异步的理解)
- 手写原生ajax (对ajax原理和http请求方式的理解,重点是get和post请求的实现)
- 其他:数组,字符串的api的实现,难度相对较低。只要了解数组,字符串的常用方法的用法,现场就能写出来个大概。(ps:笔者认为数组的reduce方法比较难,这块有余力可以单独看一些,即使面试没让你实现reduce,写其他题时用上它也是很加分的)
1、手写instanceof
判断原型的
判断一个实例是否是其父类或者祖先类型的实例。
[] instanceof Array //=> true
法一: 通过Object.prototype.toString
javascript
Object.prototype.myInstanceOf = function(type){
let result = Object.prototype.toString.call(this)
let reg = /\[object ([\s\S]{5})\]/
return result.match(reg)[1] === type
}
let a =[]
a.myInstanceOf('Array') // true
法二:通过原型链查找 通过whild 循环 依次查找是不是和要检索的原型相同
javascript
function instanceOf(target,type){
while(target){
if(Object.getPrototypeOf(target) === type.prototype){
return true
}
target = Object.getPrototypeOf(target)
}
return false
}
let abc = []
instanceOf(abc,Array)
2、实现数组的map方法
明确: 数组map 有两个参数,第一个是回调函数(参数分别为item, index , array), 第二个参数可选是回调函数中的this对象,注意一定这个时候需要是普通函数,箭头函数这个值设置没有用,this为window
巩固一下for...in 和 for...of 区别
for...in 一般在for循环的时候代替复杂的for循环三段式写法,对象数组都可以用,对象是key 数组为索引, 千万注意for...in 会把当前变量原型链上的属性都检索出来,要搭配Object.hasOwnProperty使用, 类似使用Obejct.keys
for...of 是es6中出现的 for item of array 只能迭代数组或者具有迭代器的 不能用于对象。 item是value值。类似Object.values
javascript
Array.prototype.myMap=function(fn, thisValue){
let res = []
thisValue = thisValue || window;
let arr = this
for(let i in arr){
if(arr.hasOwnProperty(i)){
res.push(fn.call(thisValue,arr[i],i,arr))
}
}
return res
}
let aa = [1,2,3,4]
let b = aa.myMap(function(item, index ,array){
console.log(item, index ,array,this)
return item + 1
},{a:1})
3、reduce实现数组的map方法
reduce(( )=> , init ). reduce 第一个参数返回的值 第二个参数是每一项的值 , 函数三个参数分别是index索引 第四个参数是整个array数组
javascript
Array.prototype.myMap = function(fn,thisValue){
var res = [];
thisValue = thisValue||[];
this.reduce(function(pre,cur,index,arr){
return res.push(fn.call(thisValue,cur,index,arr));
},[]);
return res;
}
let aa = [1,2,3,4]
aa.myMap(function(item, index ,array){
console.log(item, index ,array,this)
return item + 1
},{a:1})
4、手写数组的reduce方法
reduce 工作: 需要两个参数, 第一个是回调函数(从初始项开始累加值, item , index , array),第二个是初始依赖项。 (累加器) 注意 回调函数的第一个可能是initValue或者undefined
javascript
Array.prototype.myReduce = function (cb, initValue) {
let _arr = this;
let num = initValue == undefined ? _arr[0] : initValue;
let i = initValue == undefined ? 1 : 0;
for (i; i < _arr.length; i++) {
num = cb(num, _arr[i], i, _arr);
}
return num;
};
let arr = [1, 2, 3, 4];
arr.myReduce(function (num, item, index, arr) {
return num + item;
});
5、数组扁平化
javascript
// 法一利用递归 push/cancat
Array.prototype.myFlat = function () {
let _arr = this;
let array = [];
for (let item of _arr) {
if (Array.isArray(item)) {
array.push(...item.myFlat(count));
// array = array.concat(flatten(arr[i])); //concat 并不会改变原数组
} else {
array.push(item);
}
}
return array;
};
let aa = [1, 2, [3, 4, [5]]];
console.log(aa.myFlat());
// 法二利用递归
let a = [1,[2,3,[4,[5]]]];
a.flat(Infinity); // [1,2,3,4,5] a是4维数组
6、函数柯里化
柯里化定义,当传入足够多参数的时候, 执行原函数。 也就是满足参数与原函数的参数数量了就执行
javascript
function curry(fn, len = fn.length, ...args) {
let _this = this;
return function (...params) {
let _args = [...args, ...params];
if (_args.length >= len) {
fn.call(_this, ..._args);
} else {
return curry.call(_this, fn, len, ..._args);
}
};
}
// 验证
let _fn = curry(function test(a, b, c, d, e, f) {
console.log(a, b, c, d, e, f);
});
_fn(1,2,3,4)(5)(6)
lodash 也提供了 curry 方法
7、实现深浅拷贝
浅拷贝:只拷贝一层,更深层的对象级别的只拷贝引用
深拷贝:拷贝多层,每一级别的数据都会拷贝。这样更改拷贝值就不影响另外的对象
实现浅拷贝:[...] , Object.assign 数组返回新数组的方法比如slice
实现深拷贝JSON.parse(JSON.stringfy(data))
遍历实现深拷贝
javascript
function deepClone(newObj,oldObj){
for(let k in oldObj){
let item = oldObj[k]
if(Array.isArray(item)){
newObj[k] = [];
deepClone(newObj[k],item)
}else if(item instanceof Object){
newObj[k] = {};
deepClone(newObj[k],item)
}else{
newObj[k] = item;
}
}
}
8、手写call、apply、bind
9、手动实现new 操作 new一个对象 (继承构造函数的属性和方法)
- 创建一个空对象 Object.create(null)
- 将对象指针隐式指向构造函数的prototype (原型继承)
- 使用call改变this指向
- 判断构造函数执行返回的是不是对象,不是对象返回{},是返回新的值
javascript
function create() {
let args = [...arguments];
let fn = args.shift();
// 创造一个空对象,隐式原型指向构造函数的原型(继承构造函数原型伤的属性方法)
let obj = Object.create(fn.prototype);
// 调用构造函数,绑定this为新对象
fn.apply(obj, args);
return typeof obj === "object" ? obj : {};
}
function test(name, age) {
this.name = name;
this.age = age;
}
let t = create(test, "lisi", 123);
console.log(t); // {name:'lisi', age:'124'}
10、手写Promise (常见promise.all, promise.race)
javascript
// Promise/A+ 规范规定的三种状态
const STATUS = {
PENDING: 'pending',
FULFILLED: 'fulfilled',
REJECTED: 'rejected'
}
class MyPromise {
// 构造函数接收一个执行回调
constructor(executor) {
this._status = STATUS.PENDING // Promise初始状态
this._value = undefined // then回调的值
this._resolveQueue = [] // resolve时触发的成功队列
this._rejectQueue = [] // reject时触发的失败队列
// 使用箭头函数固定this(resolve函数在executor中触发,不然找不到this)
const resolve = value => {
const run = () => {
// Promise/A+ 规范规定的Promise状态只能从pending触发,变成fulfilled
if (this._status === STATUS.PENDING) {
this._status = STATUS.FULFILLED // 更改状态
this._value = value // 储存当前值,用于then回调
// 执行resolve回调
while (this._resolveQueue.length) {
const callback = this._resolveQueue.shift()
callback(value)
}
}
}
//把resolve执行回调的操作封装成一个函数,放进setTimeout里,以实现promise异步调用的特性(规范上是微任务,这里是宏任务)
setTimeout(run)
}
// 同 resolve
const reject = value => {
const run = () => {
if (this._status === STATUS.PENDING) {
this._status = STATUS.REJECTED
this._value = value
while (this._rejectQueue.length) {
const callback = this._rejectQueue.shift()
callback(value)
}
}
}
setTimeout(run)
}
// new Promise()时立即执行executor,并传入resolve和reject
executor(resolve, reject)
}
// then方法,接收一个成功的回调和一个失败的回调
function then(onFulfilled, onRejected) {
// 根据规范,如果then的参数不是function,则忽略它, 让值继续往下传递,链式调用继续往下执行
typeof onFulfilled !== 'function' ? onFulfilled = value => value : null
typeof onRejected !== 'function' ? onRejected = error => error : null
// then 返回一个新的promise
return new MyPromise((resolve, reject) => {
const resolveFn = value => {
try {
const x = onFulfilled(value)
// 分类讨论返回值,如果是Promise,那么等待Promise状态变更,否则直接resolve
x instanceof MyPromise ? x.then(resolve, reject) : resolve(x)
} catch (error) {
reject(error)
}
}
}
}
const rejectFn = error => {
try {
const x = onRejected(error)
x instanceof MyPromise ? x.then(resolve, reject) : resolve(x)
} catch (error) {
reject(error)
}
}
switch (this._status) {
case STATUS.PENDING:
this._resolveQueue.push(resolveFn)
this._rejectQueue.push(rejectFn)
break;
case STATUS.FULFILLED:
resolveFn(this._value)
break;
case STATUS.REJECTED:
rejectFn(this._value)
break;
}
})
}
catch (rejectFn) {
return this.then(undefined, rejectFn)
}
// promise.finally方法
finally(callback) {
return this.then(value => MyPromise.resolve(callback()).then(() => value), error => {
MyPromise.resolve(callback()).then(() => error)
})
}
// 静态resolve方法
static resolve(value) {
return value instanceof MyPromise ? value : new MyPromise(resolve => resolve(value))
}
// 静态reject方法
static reject(error) {
return new MyPromise((resolve, reject) => reject(error))
}
// 静态all方法
static all(promiseArr) {
let count = 0
let result = []
return new MyPromise((resolve, reject) => {
if (!promiseArr.length) {
return resolve(result)
}
promiseArr.forEach((p, i) => {
MyPromise.resolve(p).then(value => {
count++
result[i] = value
if (count === promiseArr.length) {
resolve(result)
}
}, error => {
reject(error)
})
})
})
}
// 静态race方法
static race(promiseArr) {
return new MyPromise((resolve, reject) => {
promiseArr.forEach(p => {
MyPromise.resolve(p).then(value => {
resolve(value)
}, error => {
reject(error)
})
})
})
}
}
11、手写原生AJAX
- 创建 XMLHttpRequest 实例
- 发出 HTTP 请求
- 服务器返回 XML 格式的字符串
- JS 解析 XML,并更新局部页面不过随着历史进程的推进,XML 已经被淘汰,取而代之的是 JSON。
javascript
// 1.0
function ajax() {
let xhr = new XMLHttpRequest() //实例化,以调用方法
xhr.open('get', 'https://www.google.com') //参数2,url。参数三:异步
xhr.onreadystatechange = () => { //每当 readyState 属性改变时,就会调用该函数。
if (xhr.readyState === 4) { //XMLHttpRequest 代理当前所处状态。
if (xhr.status >= 200 && xhr.status < 300) { //200-300请求成功
let string = request.responseText
//JSON.parse() 方法用来解析JSON字符串,构造由字符串描述的JavaScript值或对象
let object = JSON.parse(string)
}
}
}
request.send() //用于实际发出 HTTP 请求。不带参数为GET请求
}
// promise 版本
function ajax(url) {
const p = new Promise((resolve, reject) => {
let xhr = new XMLHttpRequest()
xhr.open('get', url)
xhr.onreadystatechange = () => {
if (xhr.readyState == 4) {
if (xhr.status >= 200 && xhr.status <= 300) {
resolve(JSON.parse(xhr.responseText))
} else {
reject('请求出错')
}
}
}
xhr.send() //发送hppt请求
})
return p
}
let url = '/data.json'
ajax(url).then(res => console.log(res))
.catch(reason => console.log(reason))
12、手写防抖节流函数
plain
函数节流与函数防抖都是为了限制函数的执行频次,是一种性能优化的方案,比如应用于window对象的resize、scroll事件,拖拽时的mousemove事件,文字输入、自动完成的keyup事件。
防抖: n秒内只触发一次,多次执行清掉上次的定时器重新开始定时器
节流:连续触发事件但是在 n 秒中只执行一次函数
javascript
// 防抖
function debounce(fn, delay) {
if (typeof fn !== "function") {
throw new TypeError("fn不是函数");
}
let timer; // 维护一个 timer
return function () {
var _this = this; // 取debounce执行作用域的this(原函数挂载到的对象)
var args = arguments;
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(function () {
fn.apply(_this, args); // 用apply指向调用debounce的对象,相当于_this.fn(args);
}, delay);
};
}
let app = document.querySelector("#app");
app.addEventListener(
"keyup",
debounce(() => console.log(11), 2000)
);
// 节流
function throttle(fn, delay) {
let timer = null;
return function () {
var _this = this; // 取debounce执行作用域的this(原函数挂载到的对象)
var args = arguments;
if (timer) return;
timer = setTimeout(function () {
fn.apply(_this, args);
timer = null;
}, delay);
};
}
let app = document.querySelector("#app");
app.addEventListener(
"keyup",
throttle(() => console.log(11), 9000)
);
13、手写Promise加载图片
javascript
function promiseImg(src){
return new Promise((resolve,rejected)=>{
$.ajax({
url,
success(data) {
resolve(data)
},
error(err) {
reject(err)
}
})
})
}
const url1 = './data1.json'
const url2 = './data2.json'
const url3 = './data3.json'
getData(url1).then(data1 => {
console.log(data1)
return getData(url2)
}).then(data2 => {
console.log(data2)
return getData(url3)
}).then(data3 =>
console.log(data3)
).catch(err =>
console.error(err)
)
14、实现一秒打印一个数
javascript
function log(){
let i=0
window.setInterval(function(){
console.log(i++)
},1000)
}
log()
15、创建10个标签,点击弹出相应序号
javascript
function createLabel(el) {
let a = ''
for (let i = 0; i <= 10; i++) {
a += `<a onclick="alert(${i})">${i}-</a><br/>`
}
document.querySelector(el).innerHTML=a
}
createLabel("#app");
16、 手写一个睡眠函数 sleep
js
function sleep(milliseconds) {
const start = performance.now();
while (performance.now() - start < milliseconds) {
// 这里是空循环,直到达到指定的延迟时间为止
}
}
// 使用示例
console.log("开始");
sleep(3000); // 等待 3 秒
console.log("结束");
17、 数据结构题
将下面的数据结构进行
js
const o = {
a: 1,
b: [1, { c: true }, [3]],
d: { e: 2, f: 3 },
g: null,
};
把上面的数据结构改成下面的结构
js
result = {
a:1,
'b[0]':1,
'b[1].c': true,
'b[2][0]': 3,
'd.e':2,
'd.f': 3
}
解题思路主要是递归, 循环遍历去处理 key , key 处理好之后, 在实现就很简单了
js
let result = {}
const trans = (o, keylink = '')=> {
if(typeof o === 'object'){
// 对象进入
if(Array.isArray(o)){
// 处理数组
o.map((el,index)=>{
trans(el, keylink + '[' + index + ']')
})
}else if(o !== null){
// 处理对象
Object.entries(o).map(([k, v])=>{
trans(v, keylink + (keylink ? '.': '') + k)
})
}
}else{
console.log(o, keylink) // 打印值为非对象和key的拼接值, 也就是最终要的结果
// 在这里进行收集
result[keylink] = o
}
return result
}