是什么
通常面试的时候,面试官会提一些JS手写的面试题,主要是为了考察面试者JS 基础的掌握程度。
以下是整理收集的手写面试题,方便大家学习与巩固 JS 基础知识
数组去重
const _arr = [1, 2, 3, 4, 5, 4]
function uniqueArr () {
return [...new Set(_arr)]
}
console.log(uniqueArr())
数组扁平化
const _arr = [1, 2, [1, [2, 3, [4, 5, [6]]]]]
// 递归实现
function flatter (arr) {
if (!arr.length) return
while (arr.some(item => Array.isArray(item))) {
arr = [].concat(...arr)
}
return arr
}
// reducer实现
function flatter (arr) {
if (!arr.length) return
return arr.reduce(
(prev, cur) =>
Array.isArray(cur) ? [...prev, ...flatter(cur)] : [...prev, cur],
[]
)
}
console.log(flatter(_arr)) // [1,2,3,4,5,6]
AJAX
/**
* tips: httpPut 发送 ajax 请求
*/
const httpPut = (url, data, callback, err = console.error) => {
const request = new XMLHttpRequest()
request.open("PUT", url, true)
request.setRequestHeader("Content-type", "application/json; charset=utf-8")
request.onload = () => callback(request)
request.onerror = () => err(request)
request.send(data)
}
const data = JSON.stringify({
id: 1,
title: "foo",
body: "bar",
userId: 1
})
httpPut("https://jsonplaceholder.typicode.com/posts/1", data, (request) => {
console.log(request.responseText)
})
/*
* tips: ajax get
*/
const httpGet = (url, callback, err = console.error) => {
const request = new XMLHttpRequest()
request.open("GET", url, true)
request.onload = () => callback(request.responseText)
request.onerror = () => err(request)
request.send()
}
httpGet("https://jsonplaceholder.typicode.com/posts/1", console.log)
/*
* tips: ajax delete
*/
const httpDelete = (url, callback, err = console.error) => {
const request = new XMLHttpRequest()
request.open("DELETE", url, true)
request.onload = () => callback(request)
request.onerror = () => err(request)
request.send()
}
httpDelete("https://jsonplaceholder.typicode.com/posts/1", (request) => {
console.log(request.responseText)
})
/**
* tips: ajax post
*/
const httpPost = (url, data, callback, err = console.error) => {
const request = new XMLHttpRequest()
request.open("POST", url, true)
request.setRequestHeader("Content-type", "application/json; charset=utf-8")
request.onload = () => callback(request.responseText)
request.onerror = () => err(request)
request.send(data)
}
const newPost = {
userId: 1,
id: 1337,
title: "Foo",
body: "bar bar bar"
}
const data = JSON.stringify(newPost)
httpPost("https://jsonplaceholder.typicode.com/posts", data, console.log)
发布订阅
/**
* tips: publish–subscribe
*/
const createEventHub = () => ({
hub: Object.create(null),
emit(event, data) {
;(this.hub[event] || []).forEach((handler) => handler(data))
},
on(event, handler) {
if (!this.hub[event]) this.hub[event] = []
this.hub[event].push(handler)
},
off(event, handler) {
const i = (this.hub[event] || []).findIndex((h) => h === handler)
if (i > -1) this.hub[event].splice(i, 1)
if (this.hub[event].length === 0) delete this.hub[event]
}
})
const handler = (data) => console.log(data)
const hub = createEventHub()
let increment = 0
// Subscribe: listen for different types of events
hub.on("message", handler)
hub.on("message", () => console.log("Message event fired"))
hub.on("increment", () => increment++)
// Publish: emit events to invoke all handlers subscribed to them, passing the data to them as an argument
hub.emit("message", "hello world") // logs 'hello world' and 'Message event fired'
hub.emit("message", { hello: "world" }) // logs the object and 'Message event fired'
hub.emit("increment") // `increment` variable is now 1
// Unsubscribe: stop a specific handler from listening to the 'message' event
hub.off("message", handler)
new 操作符
function myNew (fn, ...args) {
var obj = Object.create(fn.prototype) // 复制原型链
var res = fn.call(obj, ...args) // 获取作用域,修改原型链,绑定参数
if (res && (typeof res === 'object' || typeof res === 'function')) {
return res // 存在返回
}
return obj // 不存在返回对象复制品
}
function Person (name, age) {
this.name = name
this.age = age
}
Person.prototype.say = function () {
console.log(this.age)
}
// wxh
// Person { name: 'wxh', age: 333 }
// 333
let p1 = myNew(Person, 'wxh', 333)
console.log(p1.name)
console.log(p1)
p1.say()
call apply bind
Function.prototype.myCall = function (context, ...args) {
if (!context || context === null) {
context = window;
}
// 创造唯一的key值 作为我们构造的context内部方法名
let fn = Symbol();
context[fn] = this; //this指向调用call的函数
// 执行函数并返回结果 相当于把自身作为传入的context的方法进行调用了
return context[fn](...args);
};
// apply原理一致 只是第二个参数是传入的数组
Function.prototype.myApply = function (context, args) {
if (!context || context === null) {
context = window;
}
// 创造唯一的key值 作为我们构造的context内部方法名
let fn = Symbol();
context[fn] = this;
// 执行函数并返回结果
return context[fn](...args);
};
//bind实现要复杂一点 因为他考虑的情况比较多 还要涉及到参数合并(类似函数柯里化)
Function.prototype.myBind = function (context, ...args) {
if (!context || context === null) {
context = window;
}
// 创造唯一的key值 作为我们构造的context内部方法名
let fn = Symbol();
context[fn] = this;
let _this = this;
// bind情况要复杂一点
const result = function (...innerArgs) {
// 第一种情况 :若是将 bind 绑定之后的函数当作构造函数,通过 new 操作符使用,则不绑定传入的 this,而是将 this 指向实例化出来的对象
// 此时由于new操作符作用 this指向result实例对象 而result又继承自传入的_this 根据原型链知识可得出以下结论
// this.__proto__ === result.prototype //this instanceof result =>true
// this.__proto__.__proto__ === result.prototype.__proto__ === _this.prototype; //this instanceof _this =>true
if (this instanceof _this === true) {
// 此时this指向指向result的实例 这时候不需要改变this指向
this[fn] = _this;
this[fn](...[...args, ...innerArgs]); //这里使用es6的方法让bind支持参数合并
delete this[fn];
} else {
// 如果只是作为普通函数调用 那就很简单了 直接改变this指向为传入的context
context[fn](...[...args, ...innerArgs]);
delete context[fn];
}
};
// 如果绑定的是构造函数 那么需要继承构造函数原型属性和方法
// 实现继承的方式: 使用Object.create
result.prototype = Object.create(this.prototype);
return result;
};
//用法如下
// function Person(name, age) {
// console.log(name); //'我是参数传进来的name'
// console.log(age); //'我是参数传进来的age'
// console.log(this); //构造函数this指向实例对象
// }
// // 构造函数原型的方法
// Person.prototype.say = function() {
// console.log(123);
// }
// let obj = {
// objName: '我是obj传进来的name',
// objAge: '我是obj传进来的age'
// }
// // 普通函数
// function normalFun(name, age) {
// console.log(name); //'我是参数传进来的name'
// console.log(age); //'我是参数传进来的age'
// console.log(this); //普通函数this指向绑定bind的第一个参数 也就是例子中的obj
// console.log(this.objName); //'我是obj传进来的name'
// console.log(this.objAge); //'我是obj传进来的age'
// }
// 先测试作为构造函数调用
// let bindFun = Person.myBind(obj, '我是参数传进来的name')
// let a = new bindFun('我是参数传进来的age')
// a.say() //123
// 再测试作为普通函数调用
// let bindFun = normalFun.myBind(obj, '我是参数传进来的name')
// bindFun('我是参数传进来的age')
深拷贝
// 判断是否是对象
function isObject (val) {
return typeof val === 'object' && val !== null
}
function deepClone (obj, hash = new WeakMap()) {
if (!isObject(obj)) return obj
if (hash.has(obj)) {
return hash.get(obj)
}
let target = Array.isArray(obj) ? [] : {}
hash.set(obj, target) // 存入 WeakMap 中
// 递归调用
Reflect.ownKeys(obj).forEach(item => {
if (isObject(obj[item])) {
target[item] = deepClone(obj[item], hash) // 递归调用
} else {
target[item] = obj[item]
}
})
return target
}
var obj1 = {
a: 1,
b: { a: 2 }
}
var obj2 = deepClone(obj1)
console.log(obj1)
instance 操作符
function myInstanceof(left, right) {
while (true) {
if (left === null) {
return false;
}
if (left.__proto__ === right.prototype) {
return true;
}
left = left.__proto__;
}
}
实现防抖与节流
// 防抖
function debounce(fn, delay = 300) {
//默认300毫秒
let timer;
return function () {
const args = arguments;
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(() => {
fn.apply(this, args); // 改变this指向为调用debounce所指的对象
}, delay);
};
}
window.addEventListener(
"scroll",
debounce(() => {
console.log(111);
}, 1000)
);
// 节流
// 设置一个标志
function throttle(fn, delay) {
let flag = true;
return () => {
if (!flag) return;
flag = false;
timer = setTimeout(() => {
fn();
flag = true;
}, delay);
};
}
window.addEventListener(
"scroll",
throttle(() => {
console.log(111);
}, 1000)
);
将虚拟 Dom 转化为真实 Dom
const VDom = {
tag: 'DIV',
attrs: {
id: 'app'
},
children: [
{
tag: 'SPAN',
children: [{ tag: 'A', children: [], text: 'hello' }],
text: 'hello'
},
{
tag: 'SPAN',
children: [
{ tag: 'A', children: [], text: 'hello' },
{ tag: 'A', children: [], text: 'hello' }
]
}
],
text: 'hello'
}
function _render (vnode) {
if (typeof vnode === 'number') {
vnode = String(vnode)
}
if (typeof vnode === 'string') {
return document.createTextNode(vnode)
}
const dom = document.createElement(vnode.tag)
if (vnode.attrs) {
// 遍历属性
Object.keys(vnode.attrs).forEach(key => {
const value = vnode.attrs[key]
dom.setAttribute(key, value)
})
}
if (vnode.text) {
dom.innerText = vnode.text
}
vnode.children.forEach(child => dom.appendChild(_render(child)))
return dom
}
console.log(_render(VDom))