实现单例模式

核心要点: 用闭包和Proxy属性拦截

function proxy(func) {let instance;let handler = {constructor(target, args) {if(!instance) {instance = Reflect.constructor(fun, args);}return instance;}}return new Proxy(func, handler);
}

实现每隔一秒打印 1,2,3,4

// 使用闭包实现
for (var i = 0; i < 5; i++) {(function(i) {setTimeout(function() {console.log(i);}, i * 1000);})(i);
}
// 使用 let 块级作用域
for (let i = 0; i < 5; i++) {setTimeout(function() {console.log(i);}, i * 1000);
}

实现防抖函数(debounce)

防抖函数原理:把触发非常频繁的事件合并成一次去执行 在指定时间内只执行一次回调函数,如果在指定的时间内又触发了该事件,则回调函数的执行时间会基于此刻重新开始计算

防抖动和节流本质是不一样的。防抖动是将多次执行变为最后一次执行,节流是将多次执行变成每隔一段时间执行

eg. 像百度搜索,就应该用防抖,当我连续不断输入时,不会发送请求;当我一段时间内不输入了,才会发送一次请求;如果小于这段时间继续输入的话,时间会重新计算,也不会发送请求。

手写简化版:

// func是用户传入需要防抖的函数
// wait是等待时间
const debounce = (func, wait = 50) => {// 缓存一个定时器idlet timer = 0// 这里返回的函数是每次用户实际调用的防抖函数// 如果已经设定过定时器了就清空上一次的定时器// 开始一个新的定时器,延迟执行用户传入的方法return function(...args) {if (timer) clearTimeout(timer)timer = setTimeout(() => {func.apply(this, args)}, wait)}
}

适用场景:

  • 文本输入的验证,连续输入文字后发送 AJAX 请求进行验证,验证一次就好
  • 按钮提交场景:防止多次提交按钮,只执行最后提交的一次
  • 服务端验证场景:表单验证需要服务端配合,只执行一段连续的输入事件的最后一次,还有搜索联想词功能类似

Array.prototype.map()

Array.prototype.map = function(callback, thisArg) {if (this == undefined) {throw new TypeError('this is null or not defined');}if (typeof callback !== 'function') {throw new TypeError(callback + ' is not a function');}const res = [];// 同理const O = Object(this);const len = O.length >>> 0;for (let i = 0; i < len; i++) {if (i in O) {// 调用回调函数并传入新数组res[i] = callback.call(thisArg, O[i], i, this);}}return res;
}

实现数组元素求和

  • arr=[1,2,3,4,5,6,7,8,9,10],求和
let arr=[1,2,3,4,5,6,7,8,9,10]
let sum = arr.reduce( (total,i) => total += i,0);
console.log(sum);
  • arr=[1,2,3,[[4,5],6],7,8,9],求和
var = arr=[1,2,3,[[4,5],6],7,8,9]
let arr= arr.toString().split(',').reduce( (total,i) => total += Number(i),0);
console.log(arr);

递归实现:

let arr = [1, 2, 3, 4, 5, 6] function add(arr) {if (arr.length == 1) return arr[0] return arr[0] + add(arr.slice(1))
}
console.log(add(arr)) // 21

手写 new 操作符

在调用 new 的过程中会发生以上四件事情:

(1)首先创建了一个新的空对象

(2)设置原型,将对象的原型设置为函数的 prototype 对象。

(3)让函数的 this 指向这个对象,执行构造函数的代码(为这个新对象添加属性)

(4)判断函数的返回值类型,如果是值类型,返回创建的对象。如果是引用类型,就返回这个引用类型的对象。

function objectFactory() {let newObject = null;let constructor = Array.prototype.shift.call(arguments);let result = null;// 判断参数是否是一个函数if (typeof constructor !== "function") {console.error("type error");return;}// 新建一个空对象,对象的原型为构造函数的 prototype 对象newObject = Object.create(constructor.prototype);// 将 this 指向新建对象,并执行函数result = constructor.apply(newObject, arguments);// 判断返回对象let flag = result && (typeof result === "object" || typeof result === "function");// 判断返回结果return flag ? result : newObject;
}
// 使用方法
objectFactory(构造函数, 初始化参数);

参考 前端进阶面试题详细解答

实现类数组转化为数组

类数组转换为数组的方法有这样几种:

  • 通过 call 调用数组的 slice 方法来实现转换
Array.prototype.slice.call(arrayLike);
  • 通过 call 调用数组的 splice 方法来实现转换
Array.prototype.splice.call(arrayLike, 0);
  • 通过 apply 调用数组的 concat 方法来实现转换
Array.prototype.concat.apply([], arrayLike);
  • 通过 Array.from 方法来实现转换
Array.from(arrayLike);

手写 Object.create

思路:将传入的对象作为原型

function create(obj) {function F() {}F.prototype = objreturn new F()
}

查找字符串中出现最多的字符和个数

例: abbcccddddd -> 字符最多的是d,出现了5次

let str = "abcabcabcbbccccc";
let num = 0;
let char = '';// 使其按照一定的次序排列
str = str.split('').sort().join('');
// "aaabbbbbcccccccc"// 定义正则表达式
let re = /(\w)\1+/g;
str.replace(re,($0,$1) => {if(num < $0.length){num = $0.length;char = $1;        }
});
console.log(`字符最多的是${char},出现了${num}次`);

实现一个迷你版的vue

入口

// js/vue.js
class Vue {constructor (options) {// 1. 通过属性保存选项的数据this.$options = options || {}this.$data = options.data || {}this.$el = typeof options.el === 'string' ? document.querySelector(options.el) : options.el// 2. 把data中的成员转换成getter和setter,注入到vue实例中this._proxyData(this.$data)// 3. 调用observer对象,监听数据的变化new Observer(this.$data)// 4. 调用compiler对象,解析指令和差值表达式new Compiler(this)}_proxyData (data) {// 遍历data中的所有属性Object.keys(data).forEach(key => {// 把data的属性注入到vue实例中Object.defineProperty(this, key, {enumerable: true,configurable: true,get () {return data[key]},set (newValue) {if (newValue === data[key]) {return}data[key] = newValue}})})}
}

实现Dep

class Dep {constructor () {// 存储所有的观察者this.subs = []}// 添加观察者addSub (sub) {if (sub && sub.update) {this.subs.push(sub)}}// 发送通知notify () {this.subs.forEach(sub => {sub.update()})}
}

实现watcher

class Watcher {constructor (vm, key, cb) {this.vm = vm// data中的属性名称this.key = key// 回调函数负责更新视图this.cb = cb// 把watcher对象记录到Dep类的静态属性targetDep.target = this// 触发get方法,在get方法中会调用addSubthis.oldValue = vm[key]Dep.target = null}// 当数据发生变化的时候更新视图update () {let newValue = this.vm[this.key]if (this.oldValue === newValue) {return}this.cb(newValue)}
}

实现compiler

class Compiler {constructor (vm) {this.el = vm.$elthis.vm = vmthis.compile(this.el)}// 编译模板,处理文本节点和元素节点compile (el) {let childNodes = el.childNodesArray.from(childNodes).forEach(node => {// 处理文本节点if (this.isTextNode(node)) {this.compileText(node)} else if (this.isElementNode(node)) {// 处理元素节点this.compileElement(node)}// 判断node节点,是否有子节点,如果有子节点,要递归调用compileif (node.childNodes && node.childNodes.length) {this.compile(node)}})}// 编译元素节点,处理指令compileElement (node) {// console.log(node.attributes)// 遍历所有的属性节点Array.from(node.attributes).forEach(attr => {// 判断是否是指令let attrName = attr.nameif (this.isDirective(attrName)) {// v-text --> textattrName = attrName.substr(2)let key = attr.valuethis.update(node, key, attrName)}})}update (node, key, attrName) {let updateFn = this[attrName + 'Updater']updateFn && updateFn.call(this, node, this.vm[key], key)}// 处理 v-text 指令textUpdater (node, value, key) {node.textContent = valuenew Watcher(this.vm, key, (newValue) => {node.textContent = newValue})}// v-modelmodelUpdater (node, value, key) {node.value = valuenew Watcher(this.vm, key, (newValue) => {node.value = newValue})// 双向绑定node.addEventListener('input', () => {this.vm[key] = node.value})}// 编译文本节点,处理差值表达式compileText (node) {// console.dir(node)// {{  msg }}let reg = /\{\{(.+?)\}\}/let value = node.textContentif (reg.test(value)) {let key = RegExp.$1.trim()node.textContent = value.replace(reg, this.vm[key])// 创建watcher对象,当数据改变更新视图new Watcher(this.vm, key, (newValue) => {node.textContent = newValue})}}// 判断元素属性是否是指令isDirective (attrName) {return attrName.startsWith('v-')}// 判断节点是否是文本节点isTextNode (node) {return node.nodeType === 3}// 判断节点是否是元素节点isElementNode (node) {return node.nodeType === 1}
}

实现Observer

class Observer {constructor (data) {this.walk(data)}walk (data) {// 1. 判断data是否是对象if (!data || typeof data !== 'object') {return}// 2. 遍历data对象的所有属性Object.keys(data).forEach(key => {this.defineReactive(data, key, data[key])})}defineReactive (obj, key, val) {let that = this// 负责收集依赖,并发送通知let dep = new Dep()// 如果val是对象,把val内部的属性转换成响应式数据this.walk(val)Object.defineProperty(obj, key, {enumerable: true,configurable: true,get () {// 收集依赖Dep.target && dep.addSub(Dep.target)return val},set (newValue) {if (newValue === val) {return}val = newValuethat.walk(newValue)// 发送通知dep.notify()}})}
}

使用

<!DOCTYPE html>
<html lang="cn">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>Mini Vue</title>
</head>
<body><div id="app"><h1>差值表达式</h1><h3>{{ msg }}</h3><h3>{{ count }}</h3><h1>v-text</h1><div v-text="msg"></div><h1>v-model</h1><input type="text" v-model="msg"><input type="text" v-model="count"></div><script src="./js/dep.js"></script><script src="./js/watcher.js"></script><script src="./js/compiler.js"></script><script src="./js/observer.js"></script><script src="./js/vue.js"></script><script>let vm = new Vue({el: '#app',data: {msg: 'Hello Vue',count: 100,person: { name: 'zs' }}})console.log(vm.msg)// vm.msg = { test: 'Hello' }vm.test = 'abc'</script>
</body>
</html>

实现一个函数判断数据类型

function getType(obj) {if (obj === null) return String(obj);return typeof obj === 'object' ? Object.prototype.toString.call(obj).replace('[object ', '').replace(']', '').toLowerCase(): typeof obj;
}// 调用
getType(null); // -> null
getType(undefined); // -> undefined
getType({}); // -> object
getType([]); // -> array
getType(123); // -> number
getType(true); // -> boolean
getType('123'); // -> string
getType(/123/); // -> regexp
getType(new Date()); // -> date

对象数组如何去重

根据每个对象的某一个具体属性来进行去重

const responseList = [{ id: 1, a: 1 },{ id: 2, a: 2 },{ id: 3, a: 3 },{ id: 1, a: 4 },
];
const result = responseList.reduce((acc, cur) => {const ids = acc.map(item => item.id);return ids.includes(cur.id) ? acc : [...acc, cur];
}, []);
console.log(result); // -> [ { id: 1, a: 1}, {id: 2, a: 2}, {id: 3, a: 3} ]

Array.prototype.reduce()

Array.prototype.reduce = function(callback, initialValue) {if (this == undefined) {throw new TypeError('this is null or not defined');}if (typeof callback !== 'function') {throw new TypeError(callbackfn + ' is not a function');}const O = Object(this);const len = this.length >>> 0;let accumulator = initialValue;let k = 0;// 如果第二个参数为undefined的情况下// 则数组的第一个有效值作为累加器的初始值if (accumulator === undefined) {while (k < len && !(k in O)) {k++;}// 如果超出数组界限还没有找到累加器的初始值,则TypeErrorif (k >= len) {throw new TypeError('Reduce of empty array with no initial value');}accumulator = O[k++];}while (k < len) {if (k in O) {accumulator = callback.call(undefined, accumulator, O[k], k, O);}k++;}return accumulator;
}

实现findIndex方法

var users = [{id: 1, name: '张三'},{id: 2, name: '张三'},{id: 3, name: '张三'},{id: 4, name: '张三'}
]Array.prototype.myFindIndex = function (callback) {// var callback = function (item, index) { return item.id === 4 }for (var i = 0; i < this.length; i++) {if (callback(this[i], i)) {// 这里返回return i}}
}var ret = users.myFind(function (item, index) {return item.id === 2
})console.log(ret)

手写节流函数

函数节流是指规定一个单位时间,在这个单位时间内,只能有一次触发事件的回调函数执行,如果在同一个单位时间内某事件被触发多次,只有一次能生效。节流可以使用在 scroll 函数的事件监听上,通过事件节流来降低事件调用的频率。

// 函数节流的实现;
function throttle(fn, delay) {let curTime = Date.now();return function() {let context = this,args = arguments,nowTime = Date.now();// 如果两次时间间隔超过了指定时间,则执行函数。if (nowTime - curTime >= delay) {curTime = Date.now();return fn.apply(context, args);}};
}

实现apply方法

思路: 利用this的上下文特性。apply其实就是改一下参数的问题

Function.prototype.myApply = function(context = window, args) {// this-->func  context--> obj  args--> 传递过来的参数// 在context上加一个唯一值不影响context上的属性let key = Symbol('key')context[key] = this; // context为调用的上下文,this此处为函数,将这个函数作为context的方法// let args = [...arguments].slice(1)   //第一个参数为obj所以删除,伪数组转为数组let result = context[key](...args); // 这里和call传参不一样// 清除定义的this 不删除会导致context属性越来越多delete context[key]; // 返回结果return result;
}
// 使用
function f(a,b){console.log(a,b)console.log(this.name)
}
let obj={name:'张三'
}
f.myApply(obj,[1,2])  //arguments[1]

模板引擎实现

let template = '我是{{name}},年龄{{age}},性别{{sex}}';
let data = {name: '姓名',age: 18
}
render(template, data); // 我是姓名,年龄18,性别undefined
function render(template, data) {const reg = /\{\{(\w+)\}\}/; // 模板字符串正则if (reg.test(template)) { // 判断模板里是否有模板字符串const name = reg.exec(template)[1]; // 查找当前模板里第一个模板字符串的字段template = template.replace(reg, data[name]); // 将第一个模板字符串渲染return render(template, data); // 递归的渲染并返回渲染后的结构}return template; // 如果模板没有模板字符串直接返回
}

数组中的数据根据key去重

给定一个任意数组,实现一个通用函数,让数组中的数据根据 key 排重:

const dedup = (data, getKey = () => {} ) => {// todo
}
let data = [{ id: 1, v: 1 },{ id: 2, v: 2 },{ id: 1, v: 1 },
];// 以 id 作为排重 key,执行函数得到结果
// data = [
//   { id: 1, v: 1 },
//   { id: 2, v: 2 },
// ];

实现

const dedup = (data, getKey = () => { }) => {const dateMap = data.reduce((pre, cur) => {const key = getKey(cur)if (!pre[key]) {pre[key] = cur}return pre}, {})return Object.values(dateMap)
}

使用

let data = [{ id: 1, v: 1 },{ id: 2, v: 2 },{ id: 1, v: 1 },
];
console.log(dedup(data, (item) => item.id))// 以 id 作为排重 key,执行函数得到结果
// data = [
//   { id: 1, v: 1 },
//   { id: 2, v: 2 },
// ];

实现instanceOf

// 模拟 instanceof
function instance_of(L, R) {//L 表示左表达式,R 表示右表达式var O = R.prototype; // 取 R 的显示原型L = L.__proto__; // 取 L 的隐式原型while (true) {if (L === null) return false;if (O === L)// 这里重点:当 O 严格等于 L 时,返回 truereturn true;L = L.__proto__;}
}

图片懒加载

可以给img标签统一自定义属性data-src='default.png',当检测到图片出现在窗口之后再补充src属性,此时才会进行图片资源加载。

function lazyload() {const imgs = document.getElementsByTagName('img');const len = imgs.length;// 视口的高度const viewHeight = document.documentElement.clientHeight;// 滚动条高度const scrollHeight = document.documentElement.scrollTop || document.body.scrollTop;for (let i = 0; i < len; i++) {const offsetHeight = imgs[i].offsetTop;if (offsetHeight < viewHeight + scrollHeight) {const src = imgs[i].dataset.src;imgs[i].src = src;}}
}// 可以使用节流优化一下
window.addEventListener('scroll', lazyload);

高级前端常考手写面试题(必备)相关推荐

  1. 前端常考手写面试题汇总

    实现一个函数判断数据类型 function getType(obj) {if (obj === null) return String(obj);return typeof obj === 'obje ...

  2. 社招前端常考手写面试题总结

    手写 Promise const PENDING = "pending"; const RESOLVED = "resolved"; const REJECTE ...

  3. 前端工程师常考手写面试题指南

    实现 add(1)(2)(3) 函数柯里化概念: 柯里化(Currying)是把接受多个参数的函数转变为接受一个单一参数的函数,并且返回接受余下的参数且返回结果的新函数的技术. 1)粗暴版 funct ...

  4. 高级前端必会手写面试题及答案

    循环打印红黄绿 下面来看一道比较典型的问题,通过这个问题来对比几种异步编程方法:红灯 3s 亮一次,绿灯 1s 亮一次,黄灯 2s 亮一次:如何让三个灯不断交替重复亮灯? 三个亮灯函数: functi ...

  5. 社招前端必会手写面试题集锦

    查找字符串中出现最多的字符和个数 例: abbcccddddd -> 字符最多的是d,出现了5次 let str = "abcabcabcbbccccc"; let num ...

  6. 百度前端必会手写面试题整理

    请实现一个 add 函数,满足以下功能 add(1); // 1 add(1)(2); // 3 add(1)(2)(3):// 6 add(1)(2, 3); // 6 add(1, 2)(3); ...

  7. 【2022前端面试】CSS手写面试题汇总(加紧收藏)

    [2022前端面试]CSS手写面试题汇总(加紧收藏) 更新时间:2022年3月3日 把答案一起写上,但是希望大家在看之前思考一下,如果有好的建议,跪求改正! 本文致力于建设前端面试题库,欢迎兄弟们投稿 ...

  8. 前端面试高频手写代码题

    前端面试高频手写代码题 一.实现一个解析URL参数的方法 方法一:String和Array的相关API 方法二: Web API 提供的 URL 方法三:正则表达式+string.replace方法 ...

  9. 高级前端常见手写面试题指南

    Function.prototype.call 于call唯一不同的是,call()方法接受的是一个参数列表 Function.prototype.call = function(context = ...

最新文章

  1. GIS开发随笔(2)——关于建立GIS数据库的几个问题
  2. 新版vue-cli搭建多页应用2
  3. 盘点丨MIT教授Poggio:过去23年,机器学习取得了哪些进步
  4. faster rcnn在自己的数据集上训练
  5. 转:java网络编程-HTTP编程
  6. spring-service.xml 模板
  7. 计数排序的应用----排序字符串
  8. Oreo授权系统V1.0.6公益开源版本
  9. 一个WIFI热点的脚本思路,顺记shell知识
  10. IPython notebook安装指导
  11. 图像块的访问(填充 padding,步长 stride,窗 Window/kernel/filter)
  12. Microsoft Visio Pro 2016产品密钥破解完整免费下载
  13. linux怎么重新扫描磁盘,vmware linux添加新磁盘后刷新,重新扫描 SCSI 总线,在不重启虚拟机的情况下添加 SCSI 设备教程,vmwarescsi...
  14. apollo学习之:如何测试canbus模块
  15. 计算机woyd打不开,word打不开怎么办?word文档打不开的几种解决方法
  16. GPG使用方法总结(密钥管理,加解密文件)
  17. mysql拼图游戏代码_HTML+Javascript制作拼图小游戏详解(二)
  18. STM32F103/107 移植Freemodbus RTU
  19. MDK的编译过程及文件类型全解——(二)
  20. 【Redis】Key的层级结构

热门文章

  1. Excel导入导出(导出有两种方式:直接返回流、返回下载地址)
  2. 哥德巴赫猜想说是说,任何一个超过 2 的偶数都可以写成两个素数之和,例如,4=2+2,8=5+3 等
  3. VIP邮箱怎么使用超大附件?个人邮箱怎么上传和发送超大附件?
  4. 干货分享,招银提前批:一面+二面+HR面面经,顺便聊聊程序员应该去银行上班吗?
  5. 计算机硬盘用u盘维修,硬盘坏了怎么修复 教你如何修复硬盘坏道
  6. 移动 pC 浏览器兼容 postcss-plugin-px2rem
  7. 最令人厌恶10种网络广告 横幅广告居首
  8. 0欧姆电阻、磁珠的应用
  9. java工程师座右铭_做一个座右铭工具每天激励自己
  10. 华为云网站安全如何保障企业安稳运行?