总结高频手写题目
文章目录
- JS系列
- 一、实现函数节流、防抖方法
- 二、数组去重
- 三、深克隆
- 四、实现instance of
- 五、嵌套数组指定层次展开flat扁平化
- 六、实现 reduce 数组方法
- 七、实现数组map方法
- 八、实现Array.fill()、Array.filter()
- 九、实现Array.find()、Array.finIndex()
- 十、Promise简单实现
- 十一、Ajax请求的原生实现
- 十二、模拟new
- 十三、实现Object.create方法
- 十四、ES5实现继承那些事
- 十五、实现函数的call、apply、bind方法
- 十六、实现一个双向绑定
- 十七、单例模式
- 十八、观察者模式
- 十九、使用setTimeout实现setInterval方法
- 二十、实现jsonp
- 二十一、实现Promise All、Promise.race
- CSS
- 参考资料
JS系列
一、实现函数节流、防抖方法
节流函数
(throttle):规定在一个单位时间内,只能触发一次函数。如果这个单位时间内触发多次函数,只能有一次生效。
//func用户传入的防抖函数;wait是等待时间为1s
const throttle= (func,wait = 1000) => {
//上一次执行该函数的时间
let lastTimer = 0;
return function () {
let now = +new Date();
let args = arguments;
// 将当前时间和上一次执行时间做对比
// 差值大于wait 执行函数
if (now-lastTimer > wait) {
lastTimer = now;
func.apply(this, args)
}
}
};
setInterval(
throttle(()=>{
console.log(1);
},500)
);
适用场景:
- 拖拽场景:固定时间内只执行一次,防止高频词触发位置变动。
- 缩放场景:监控浏览器
resize
。 - 动画场景:避免短时间内多次触发动画引起性能为题。
防抖函数
(debounce):在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时。
//func需要传入的防抖函数
//wait是等待时间
const debounce = (func,wait=50)=>{
let timer = 0;
// 返回函数是用户每次实际调用的防抖函数
return function (...args) {
// 如果已经设定过定时器了就清空上一次的定时器
if(timer) clearTimeout(timer);
// 开启一个新的定时器,延迟执行用户传入的方法
timer = setTimeout(()=>{
func.apply(this,args) //通过this,args获取函数作用域和变量
},wait)
}
};
上面实现了简单的防抖,但是有缺陷,这个防抖只能在最后调用。详细的防抖会有
immediate
选项。表示是否立即调用。
适用场景:
- 表单验证:表单验证需要服务端配合,只执行一段连续的输入事件的最后一次。
- 按钮提交:只执行最后一次提交,防止多次提交按钮。
二、数组去重
给定一个数组:var arr = [1,1,‘true’,‘true’,true,true,0,0,undefined,undefined,null,null,NaN,NaN,‘NaN’,2,{},{}];
- 方法一:双重for + splice
function arrayUnique_1(arr) {
let res = [];
for(let i=0;i<arr.length;i++) {
for (let j=i+1;j<arr.length;j++) {
//当第一个等于第二个,splice方法删除第二个
if(arr[i]===arr[j]){
arr.splice(j,1);
j-- //第二个删除后,这个索引位需要重新判断
}
}
}
return arr
}
console.log(arrayUnique_1(arr)); //NaN和{}没有去重
- 方法二: 利用ES6的 set 去重
function arrayUnique_2(arr) {
return Array.from(new Set(arr))
}
function arrayUnique_3(arr) {
return [...new Set(arr)]
}
console.log(arrayUnique_2(arr)); //{}没有去重
console.log(arrayUnique_3(arr)); //{}没有去重
- 方法三:利用indexof去重
function arrayUnique_4(arr) {
//判断是否是数组
if (!Array.isArray(arr)) {
console.log('type error!');
return
}
var array = [];
for (let i=0;i<arr.length;i++) {
if (array.indexOf(arr[i]) === -1){ //利用indexof 检测元素在数组中第一次出现的位置是否和元素现在的位置相等
array.push(arr[i])
}
}
return array
}
console.log(arrayUnique_4(arr)); //NaN,{}没有去重
- 方法四:利用sort()排序后,判断相邻元素是否相等
function arrayUnique_5(){
if (!Array.isArray(arr)) {
console.log('type error!');
return ;
}
arr = arr.sort();
let array = [arr[0]]
for (let i=1;i<arr.length;i++) {
if(arr[i]!==arr[i-1]){
array.push(arr[i])
}
}
return array
}
console.log(arrayUnique_5(arr)); //NaN,{}没有去重
- 方法五:includes检测数组中是否有某个值
function arrayUnique_6(arr) {
if (!Array.isArray(arr)){
console.log('type error!');
return ;
}
let array = [];
for (let i=0;i<arr.length;i++){
if (!array.includes(arr[i])){ //includes:检测数组是否有某个值
array.push(arr[i]);
}
}
return array
}
console.log(arrayUnique_6(arr)); //{}没有去重
- 方法六:利用filter
//利用filter
function arrayUnique_7(arr) {
return arr.filter(function (item,index,arr) {
// 当前元素,在原始数组中的第一个索引===当前索引值,则返回
return arr.indexOf(item,0) === index; //每次都是从左边开始找
})
}
console.log(arrayUnique_7(arr)); //{}没有去重,NaN直接没了
- 方法七:利用Map
利用Map中不会出现相同key值的特点,创建一个空的Map数据结构,
遍历数组,把数组中的每一个元素作为key存到Map中。
function arrayUnique_8(arr) {
let map = new Map();
let array = new Array();
for(let i=0;i<arr.length;i++){
if(map.has(arr[i])) {
map.set(arr[i],true)
}else{
map.set(arr[i],false);
array.push(arr[i])
}
}
return array;
}
console.log(arrayUnique_8(arr)); //{}没有去重
三、深克隆
- 方法一
const newObj = JSON.parse(JSON.stringify(oldObj));
缺点:无法实现对函数,RegExp等特殊对象的克隆
- 方法二
function deepCopy(obj) {
// 判断是否是简单的数据类型
if(typeof obj === "object"){
let res = obj.constructor == Array ? [] : {};
for(let i in obj){
res[i] = typeof obj[i] == "object" ? deepCopy(obj[i]):obj[i];
}
} else{
// 如果是简单数据类型,则直接赋值
let res = obj;
}
return res;
}
四、实现instance of
- 核心是原型链的向上查找
function myInstanceof(left, right) {
let proto = Object.getPrototypeOf(left);
while(true) {
if(proto == null) return false;
if(proto === right.prototype) return true;
ptoto = Object.getPrototypeOf(ptoto);
}
}
五、嵌套数组指定层次展开flat扁平化
// 实现数组得扁平化
// 就是将多维数组转换为一维数组
let ary = [1,[2,[3,[4,5]]],6]
let str = JSON.stringify(ary)
// arr_flat = ary.flat(Infinity); //不知道要扁平的数组的具体深度,只想完全扁平这个嵌套数组里的成员话,可以使用 Infinity 这个值
// // flat() 方法会按照一个可指定的深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回
// console.log(arr_flat);
// 递归
// 递归的遍历每一项,若为数组则继续遍历,否则concat
// let ary = [1,[2,[3,[4,5]]],6]
// let fn = function(ary) {
// let result = [];
// ary.map(item =>{
// if(Array.isArray(item)){
// result = result.concat(flatten(item));
// }else{
// result.push(item)
// }
// })
// return result
// }
// console.log(fn());
function flatten(arr) {
while(arr.some(item=>Array.isArray(item))) {
arr = [].concat(...arr);
}
return arr;
}
console.log(flatten(ary));
// 和上面的toString一样,join也可以将数组转换为字符串
function flatten(arr) {
return arr.join(',').split(',').map(function(item) {
return parseInt(item);
})
}
console.log(flatten(ary));
六、实现 reduce 数组方法
//reduce()函数接受两个参数,一个函数一个累积变量的初始值。
//函数有四个参数:累计变量初值(默认第一个成员),当前变量值(默认第二个成员),当前位置,数组自身。
//arr.reduce(function(prev, cur, index, arr){}, initialValue)
Array.prototype.myReduce=function(fn,base){
if(typeof fn !== 'function'){
throw new TypeError("arguments[0] is not a function");//TypeError是错误的类型之一:类型错误
}
var initialArr=this;//调用myReduce()函数的当前对象
var arr=initialArr.concat();//目的是返回一个等于初始数组的新数组,后面的操作都基于arr,这样初始数组不会发生改动
var index,newValue;
if(arguments.length==2){
arr.unshift(base);
index = 0; //!!当前位置 指的是当前变量(第二个参数)针对调用该方法的数组位置即initialArr
}else{
index=1;
}
if(arr.length===1){//长度为1 直接返回
newValue=arr[0];
}
while(arr.length>1){
newValue=fn.call(null,arr[0],arr[1],index,initialArr);
index++;
arr.splice(0,2,newValue);//删除前两位 然后把累加值添加到第一位
}
return newValue;
};
七、实现数组map方法
// 模拟实现map
// arr.map(function (currentValue, index, arr) {
// })
// currentValue 必须。 当前元素的值
// index 可选。 当期元素的索引值
// arr 可选。 当期元素属于的数组对象
Array.prototype.newMap = function (fn) { //写法一
var newArr = [];
for (var i = 0; i < this.length; i++) {
newArr.push(fn(this[i], i, this)) //this指向调用newMap方法的数组
}
return newArr;
}
// arr.reduce((previousValue, currentValue, currentIndex, array) => {}, initialValue?)
// reduce若不指定初始值, 那么第一次迭代的 previousValue 为 ar[[0], currentValue 为 arr[1], currentIndex 为 1,
// 若指定初始值, 那么第一次迭代的 previousValue 为 initialValue, currentValue为 arr[0], currentIndex 为0.
Array.prototype.newMap = function (fn, Arg) { 写法二:用数组的reduce方法实现数组的map
var res = [];
this.reduce((prev, curr, index, array) => {
res.push(fn.call(Arg, curr, index, array));
}, 0) //指定初始值initialValue=0,所以从currentIndex=0开始,即第一个开始 不这样会缺第一项,结果为[3,4]
return res;
}
let arr = [1, 2, 3];
let res = arr.newMap((a) => a + 1);
console.log(res); //[2,3,4]
八、实现Array.fill()、Array.filter()
// array.fill(value, start, end)
// value 必需。填充的值。start 可选。开始填充位置。end 可选。停止填充位置 (默认为 array.length)
Array.prototype.myFill = function (value, start = 0, end = this.length) {
for (let i = start; i < end; i++) {
this[i] = value;
}
}
//Array.filter()
Array.prototype.myFilter = function myFilter(fn, context) {
if (typeof fn !== "function") {
throw new TypeError(`${fn} is not a function`);
}
let arr = this;
let temp = [];
for (let i = 0; i < arr.length; i++) {
let result = fn.call(context, arr[i], i, arr);
if (result) temp.push(arr[i]);
}
return temp;
};
九、实现Array.find()、Array.finIndex()
Array.find()
用于找出第一个符合条件的数组成员,参数为一个回调函数
Array.prototype.myFind = function (fn, start = 0, end = this.length) {
for (let i = start; i < end; i++) {
if (fn.call(this, this[i], i, this)) {
return this[i]
}
}
}
Array.findIndex()
Array.prototype.myFindIndex = function (fn, start = 0, end = this.length) {
for (let i = start; i < end; i++) {
if (fn.call(this, this[i], i, this)) {
return i
}
}
return -1
}
十、Promise简单实现
这题再缓缓
十一、Ajax请求的原生实现
// 请熟练掌握
var xhr = new XMLHttpRequest();// 创建XMLHttqRequest
var url = 'https://bbin.com';
xhr.onreadystatechange=function(){// 监听状态码的变化,每次变化 均执行
if(xhr.readyState===4){
if (xhr.status === 200) { // 服务端 状态码
console.log(xhr.responseText); //服务器返回的响应文本
}else{
console.error(xhr.statusText); //状态码的文本描述,如200的statusText是ok
}
}
}
xhr.open('GET', url, true); // 初始化请求参数,还没发送请求 true表示异步
xhr.send(null); // 向服务器发送请求,但是不带有数据发送过去,一般在get方式发送时候多使用这个方式
十二、模拟new
// new操作符的作用:
// 1. 创建了一个全新的对象,这个对象的__proto__要指向构造函数的原型对象
// 2. 执行构造函数
// 3. 返回值为Object类型则作为new方法的返回值返回。否则返回上述全新对象
第一种:
function myNew(fn, ...args) {
let instance = Object.create(fn.prototype);
let res = fn.apply(instance, args);
return typeof res === 'object' ? res: instance;
}
第二种:
function myNew(constructor,params){
var args = [].slice.call(arguments);
var constructor = args.shift();
var obj = new Object();
obj.__proto__ = constructor.prototype;
var res = constructor.apply(obj,args);
return (typeof res === 'object' && typeof res !== null) ? res : obj;
}
十三、实现Object.create方法
// 用于创建一个新对象,被创建的对象继承另一个对象(o)的原型
function createObj(o) {//传入的参数o为返回实例的__porto__,也就是实例构造函数的显示原型
function F() {}//构造函数
F.prototype = o;
return new F();//返回实例
}
十四、ES5实现继承那些事
详细讲解请点击链接☝
十五、实现函数的call、apply、bind方法
详细讲解请点击链接☝,具体实现☟
/* 先将传入的指定执行环境的对象 context 取到
将需要执行的方法(调用call的对象) 作为 context 的一个属性方法fn
处理传入的参数, 并执行 context的属性方法fn, 传入处理好的参数
删除私自定义的 fn 属性
返回执行的结果 */
// 模拟 call 方法
Function.prototype.defineCall = function (context) {
context = context || window;
context.fn = this; //this指向 sayName函数实例
let args = [];
for (let i = 1; i < arguments.length; i++) { //i从1开始
args.push(arguments[i]);
} //或者args = [...arguments].slice(1);
let result = context.fn(args.join(','));
delete context.fn;
return result;
}
let sayName = function (age) {
console.log('current name: ' + this.name, "current age: " + age);
}
let obj1 = {
name: "obj's name"
}
sayName.defineCall(obj1, 22); //this指向 sayName函数实例
// current name: obj's name current age: 22
// 模拟 apply 方法
Function.prototype.defineApply = function (context, arr) {
context = context || window;
context.fn = this;
let result;
if (!arr) { // 第二个参数不传
result = context.fn();
} else { // 第二个参数是数组类型
let args = [];
for (let i = 0; i < arr.length; i++) { //i从0开始
args.push(arr[i]);
}
result = context.fn(args.join(','));
}
delete context.fn;
return result;
}
let obj2 = {
name: ['Tom', 'Johy', 'Joe', 'David']
}
sayName.defineApply(obj2, [3, 4, 5, 6, 7]);
// current name: Tom,Johy,Joe,David current age: 3,4,5,6,7
//用call、apply模拟实现bind
Function.prototype.bind = function (context) {
let self = this; // 保存函数的引用
return function () { // 返回一个新的函数
// console.log(arguments);
// return self.apply(context, arguments);
return self.call(context, arguments);
}
};
let obj = {
name: 'seven'
}
let func = function () {
console.log(this.name)
}.bind(obj);
func('zhangsan', 20);
十六、实现一个双向绑定
十七、单例模式
十八、观察者模式
//ES6 实现观察者模式代码:(观察订阅模式)
const queuedObservers = new Set();
const observe = fn => queuedObservers.add(fn); // 依赖收集 Vue中的dep
const observable = obj => new Proxy(obj, {set});// Proxy和Reflect一一对应
function set(target, key, value, receiver){
const result = Reflect.set(target, key, value, receiver); // 执行set
queuedObservers.forEach(observe => observe()); // 派发更新
return result;
}
// 使用 观察者模式实例
const person = observable({
name: 'Sun',
age: 30
});
function print(){
console.log(`name: ${person.name}, age: ${person.age}`);
}
observe(print);
person.age = 31;
// name: Sun, age: 31
十九、使用setTimeout实现setInterval方法
function mysetinterval(fn,time){
console.log("利用steTimeout实现setInterval");
function interval(){//执行该函数,异步被挂起time时间后在执行,一上来就执行fn
setTimeout(interval,time);//异步
//好,time时间过去,这个异步被执行,而内部执行的函数正是interval,就相当于进了一个循环,递归
fn();//同步
}
setTimeout(interval,time);//interval被延迟time时间执行
}
二十、实现jsonp
var newscript=document.createElement('script');
newscript.src='https://sp0.baidu.com/su?wd=Java&cb=foo';
document.body.appendChild(newscript);
function foo(data) { //callback函数要绑定在window对象上
console.log(data);
}
二十一、实现Promise All、Promise.race
//promise.all()
function myall(proArr) {
return new Promise((resolve, reject) => {
let ret = []
let count = 0
let done = (i, data) => {
ret[i] = data
if(++count === proArr.length) resolve(ret)
}
for (let i = 0; i < proArr.length; i++) {
proArr[i].then(data => done(i,data) , reject)
}
})
}
//promise.race();这么简单得益于promise的状态只能改变一次,即resolve和reject都只被能执行一次
function myrace(proArr) {
return new Promise(function (resolve, reject) {
for(let i=0;i<proArr.length;i++){
proArr[i].then(resolve,reject);
}
})
}
CSS
一、CSS画各种图形
<div class="a"></div>
//等腰三角形
.a{
width: 0px;
height: 0px;
border-bottom: 50px solid red;
border-left: 50px solid transparent;
border-right: 50px solid transparent;
}
//等腰梯形
.a{
width: 50px;
height: 0px;
border-bottom: 50px solid red;
border-left: 50px solid transparent;
border-right: 50px solid transparent;
}
//扇形
.a{
width: 0px;
height: 0px;
border-bottom: 50px solid red;
border-left: 50px solid transparent;
border-right: 50px solid transparent;
border-radius: 50%;
}
//圆
.a{
width: 50px;
height: 50px;
background-color: blue;
border-radius: 25px;//50% 边框半径为宽高的50%
}
半圆
.a{
width: 100px;
height: 50px;
background-color: blue;
border-top-left-radius: 50px;
border-top-right-radius: 50px;
/* border-bottom-left-radius: 50px; */
}
二、三列布局
双飞翼布局是通过新建的div的外边距隔离各列,
而圣杯布局则是通过父容器的内边距来实现各列的间隙。
三、垂直水平居中
每日一更,持续练习!颤抖吧、愚蠢的面试题们。