2020年,初中级前端必会 JavaScript 面试题
人不狠话不多,尽量直接上代码。
实现节流函数 (throttle)
防抖函数原理:规定在一单位时间内,能触发一次函数,如果多次触发函数,只生效一次。
//节流函数
<!DOCTYPE html>
<html lang="en">
<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>throttle-函数截流</title>
</head>
<body>
<script>
// 函数截流:规定时间内只触发一次
window.onresize = throttle(function() {
console.log('onresize')
})
function throttle(fn) {
let timer = null
return function() {
if (!timer) {
timer = setTimeout(() => {
clearTimeout(timer)
fn.apply(this, arguments)
timer = null
}, 500)
}
}
}
window.location.hash = 'hash字符串' // 用于设置 hash 值
let hash = window.location.hash // 获取当前 hash 值
// 监听hash变化,点击浏览器的前进后退会触发
window.addEventListener(
'hashchange',
function(event) {
let newURL = event.newURL // hash 改变后的新 url
let oldURL = event.oldURL // hash 改变前的旧 url
},
false
)
</script>
</body>
</html>
适用场景 :
- 按钮提交场景: 防止多次提交按钮,只执行最后提交的一次
- 服务端验证场景 : 表单验证需要服务端配合,只执行一段连续的输入事件的最后一次,还有搜索联想词功能类似
实现防抖函数 (debounce)
防抖函数原理 : 在事件被触发n秒后在执行回调,如果在这n秒内又被触发,则重新计时。
<!DOCTYPE html>
<html lang="en">
<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>debounce - 函数去抖</title>
</head>
<body></body>
</html>
<script>
// 函数去抖:规定时间内只触发一次,多次触发则重新计算触发时间
function debounce(fn,delay = 500) {
let timer = null
return function(...args) {
if (timer) {
clearTimeout(timer)
timer = setTimeout(() => {
fn.apply(this, args)
timer = null
}, 500)
} else {
timer = setTimeout(() => {
fn.apply(this, args)
}, delay)
}
}
}
</script>
适用场景:
- 拖拽场景: 固定时间内只执行一次,防止超高频次触发位置变动
- 缩放场景: 监控浏览器resize
- 动画场景: 避免短时间内多次触发动画引起性能问题
深克隆 (deepclone)
<!DOCTYPE html>
<html lang="en">
<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>克隆等问题</title>
</head>
<body></body>
</html>
<script>
let sym = Symbol('a symbole')
function person(pname) {
this.name = pname;
}
const Messi = new person('Messi');
let obj = {
a: say,
b:Messi,
name: '盖伦',
age: 18,
a: undefined,
b: null,
c: NaN,
getName() {
console.log(this.name)
},
skills: [
{
name: '大宝剑',
hurt: 100
},
{
name: '大旋转',
hurt: 90
}
],
[sym]:'a symbole',
fn:function(){
console.log('21312')
},
dat:new Date(),
reg:new RegExp()
}
//克隆简单json类型对象
//局限性 :
//1、他无法实现函数、RegExp等特殊对象的克隆
//2、会抛弃对象的constructor,所有的构造函数会指向Object
//3、对象有循环引用,会报错
console.log(JSON.parse(JSON.stringify(obj)));
console.log(deepClone(obj));
function deepClone(obj) {
const isType = target => type =>
`[object ${type}]` === Object.prototype.toString.call(target)
let result
if (isType(obj)('Object')) {
result = {}
} else if (isType(obj)('Array')) {
result = []
} else {
result = obj
}
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
const copy = obj[key]
if (isType(copy)('Object') || isType(copy)('Array')) {
result[key] = deepClone(copy)
} else {
result[key] = copy
}
}
}
return result
}
</script>
new 关键字实现
<!DOCTYPE html>
<html lang="en">
<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>new关键字实现</title>
</head>
<body>
new操作符做了这些事:
他创建了一个全新的对象
他会被执行[[Prototype]] (也就是__proto__) 链接
它使this指向新创建的对象
通过new创建的每个对象将最终被[[Prototype]]链接到这个函数的prototype对象上
如果函数没有返回对象类型Object(包含Function,Array,Date,RegExg,Error),那么new表达式中的函数调用将返回对象引用
</body>
</html>
<script>
function New() {
let Foo = Array.prototype.shift.call(arguments);//获取函数,arguments长度减一
let res = Object.create(Foo.prototype);//创建新对象,并且链接Foo.prototype
let result = Foo.apply(res,arguments);//执行构造函数,改变this指向
// 确保 new 出来的是个对象 返回的值是什么就return什么
return typeof result === 'object' ? result : res;
}
function Person(name,age) {
this.name = name;
this.age = age;
this.getName = function () {
return this.name;
}
}
let obj1 = New(Person,'jack',18)
let obj2 = new Person('jack',18)
console.log(obj1);
console.log(obj2);
</script>
instanceOf 实现
实现原理: 只要右边变量的 prototype 在左边变量的原型链上即可。因此,instanceof 在查找的过程中会遍历左边变量的原型链,直到找到右边变量的 prototype,如果查找失败,则会返回 false,告诉我们左边变量并非是右边变量的实例。
function new_instance_of(leftVaule, rightVaule) {
let rightProto = rightVaule.prototype; // 取右表达式的 prototype 值
leftVaule = leftVaule.__proto__; // 取左表达式的__proto__值
while (true) {
if (leftVaule === null) {
return false;
}
if (leftVaule === rightProto) {
return true;
}
leftVaule = leftVaule.__proto__
}
}
几种继承以及实现方式
<!DOCTYPE html>
<html lang="en">
<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>继承以及实现方式</title>
</head>
<body></body>
</html>
<script>
/* -------------------------- 原型链继承 ------------------ */
function Parent(name) {
this.name = name
this.skills = ['大宝剑', '大旋转']
}
Parent.prototype.getName = function() {
return this.name
}
function Child() {
this.age = 18
}
Child.prototype = new Parent('陈先生')
let child = new Child()
let child2 = new Child()
/*
原型链继承问题:
如果通过child2,修改了Parent的skills, 则child 都会受影响
*/
console.log('child---', child)
/* -------------------------- 构造函数继承 ------------------ */
{
function SuperClass(name) {
this.name = name
this.skills = ['大宝剑', '大旋转']
}
SuperClass.prototype.showSkills = function() {
console.log(this.skills)
}
function SubClass(name) {
SuperClass.call(this, name)
}
SubClass.prototype.getName = function() {
console.log(this.name)
}
let sub1 = new SubClass('张三')
let sub2 = new SubClass('李四')
sub1.skills.push('天崩地裂')
console.log('sub1---', sub1.getName())
console.log('sub2---', sub2.getName())
/*
构造函数继承问题:
未能继承父类的原型(prototype),在子类构造函数中实现继承后,都会单独拥有一份,违背代码复用原则,
*/
}
/* -------------------------- 组合继承继承 ------------------ */
{
function SuperClass(name) {
this.name = name
this.skills = ['大宝剑', '大旋转']
}
SuperClass.prototype.showSkills = function() {
console.log(this.skills)
}
function SubClass(name) {
SuperClass.call(this, name)
this.time = time
}
SubClass.prototype = new SuperClass()
SubClass.prototype.getTime = function() {
console.log(this.time)
}
/*
组合继承继承缺点:
父类构构造函数执行了两次,并不完美
*/
}
{
/* -------------------------- 原型式继承,寄生式继承 ------------------ */
function inheritObject(o) {
//原型式继承
function F() {}
F.prototype = o
return new F()
}
function createBook(obj) {
var o = new inheritObject(obj)
o.getName = function() {
console.log(name)
}
return o
}
var book = {
name: 'js book',
alikeBook: ['css', 'html', 'js']
}
var oBook = createBook(book)
console.log('oBook---', oBook)
}
{
/* -------------------------- 寄生组合式继承 ------------------ */
function inheritPrototype(SubClass, SuperClass) {
//复制父类
var p = Object.create(SuperClass.prototype)
//修正子类原型被重写导致的constructor属性变化
p.constructor = SubClass
//设置子类原型
SubClass.prototype = p
}
function SuperClass(name) {
this.name = name
this.skills = ['大宝剑', '大旋转']
}
SuperClass.prototype.showSkills = function() {
console.log(this.skills)
}
function SubClass(name) {
SuperClass.call(this, name)
this.time = '11111'
}
// SubClass.prototype = new SuperClass();
SubClass.prototype.getTime = function() {
console.log(this.name)
}
inheritPrototype(SubClass, SuperClass)
var instance1 = new SubClass('张三')
var instance2 = new SubClass('李四')
console.log('instance1---', instance1)
console.log('instance2---', instance2)
}
</script>
实现数组 reduce-filter-map-find方法
<!DOCTYPE html>
<html lang="en">
<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>实现数组 reduce-filter-map-find方法</title>
</head>
<body></body>
</html>
<script>
// reduce() 方法接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终计算为一个值。
Array.prototype._reduce = function(fn, initVal, context) {
let arr = Array.prototype.slice.call(this)
for (let i = 0; i < arr.length; i++) {
initVal = fn.call(context, initVal, arr[i], i, this)
}
return initVal
}
// map()方法返回一个新数组,数组中的元素为原始数组元素调用函数处理的后值。
Array.prototype._map = function(fn, context) {
let arr = Array.prototype.slice.call(this)
let ret = []
for (let i = 0; i < arr.length; i++) {
ret.push(fn.call(context, arr[i], i, this))
}
return ret
}
Array.prototype._map2 = function(fn, context) {
//reduce实现
let arr = Array.prototype.slice.call(this)
return arr._reduce((init, cur, i) => {
return [...init, fn.call(context, cur, i, this)]
}, [])
}
Array.prototype._filter = function(fn, context) {
let arr = Array.prototype.slice.call(this)
let ret = []
for (let i = 0; i < arr.length; i++) {
fn.call(context, arr[i], i, this) && newArr.push(arr[i])
}
return ret
}
Array.prototype._filter2 = function(fn, context) {
//reduce实现
let arr = Array.prototype.slice.call(this) //获取数组,可以和简单原数组解耦
return arr.reduce((init, cur, i) => {
return fn.call(context, cur, index, this) ? [...init, cur] : [...init]
}, [])
}
// find() 方法返回通过测试(函数内判断)的数组的第一个元素的值。
Array.prototype._find = function(fn, context) {
let arr = Array.prototype.slice.call(this)
for (let i = 0; i < arr.length; i++) {
if (fn.call(context, arr[i], i, this)) {
return arr[i]
}
}
}
// some() 方法会依次执行数组的每个元素:
// 如果有一个元素满足条件,则表达式返回true , 剩余的元素不会再执行检测。
// 如果没有满足条件的元素,则返回false。
Array.prototype._some = function(fn, context) {
let arr = Array.prototype.slice.call(this)
for (let i = 0; i < arr.length; i++) {
if (fn.call(context, arr[i], i, this)) {
return true
}
}
return false
}
// every方法用于检测数组所有元素是否都符合指定条件(通过函数提供)。
Array.prototype._every = function(fn, context) {
let arr = Array.prototype.slice.call(this)
for (let i = 0; i < arr.length; i++) {
if (!fn.call(context, arr[i], i, this)) {
return false
}
}
return true
}
var arr = [2, 4, 6, 8]
let result1 = arr._reduce(function(val, item, index, origin) {
return val + item
}, 0)
console.log(result1) //20
let result2 = arr._map2(function(item, index, origin) {
return item * 5
})
console.log(result2)
var myArr = [1, 2, 10, 100]
// console.log(
// 'myReduce:',
// myArr.myReduce((init=0,cur,i,arr)=>{
// return init+cur
// })
// );
// console.log(
// 'myFilter',
// myArr.myFilter((item,i)=>{
// return item>5
// })
// );
// console.log(
// 'myMap',
// myArr.myMap((item,i)=>{
// return item*5
// })
// );
// console.log(
// 'myFind',
// myArr.myFind((item,i)=>{
// return item>10
// })
// );
// console.log(
// '_myMap',
// myArr._myMap((item,i)=>{
// return item*2
// })
// );
// console.log(
// '_myFilter',
// myArr._myFilter((item,i)=>{
// return item>5
// })
// );
</script>
call,apply,bind函数的实现
<!DOCTYPE html>
<html lang="en">
<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>call,apply,bind函数的实现</title>
</head>
<body></body>
</html>
<script>
// 根据call的规则设置上下文对象,也就是this的指向。
// 通过设置context的属性,将函数的this指向隐式绑定到context上
// 通过隐式绑定执行函数并传递参数。
// 删除临时属性,返回函数执行结果
Function.prototype.myCall = function(context, ...arr) {
if (context === null || context === undefined) {
//这里如果context为0,false,’‘则上下文还是为window
// 指定为 null 和 undefined 的 this 值会自动指向全局对象(浏览器中为window)
context = window
} else {
context = Object(context) // 值为原始值(数字,字符串,布尔值)的 this 会指向该原始值的实例对象
}
//this指向需要借用的函数
const specialPrototype = new Symbol('特殊属性Symbol') // 用于临时储存函数
context[specialPrototype] = this // 函数的this指向隐式绑定到context上
let result = context[specialPrototype](...arr) // 通过隐式绑定执行函数并传递参数
delete context[specialPrototype] // 删除上下文对象的属性
return result // 返回函数执行结果
}
// 传递给函数的参数处理,不太一样,其他部分跟call一样。
// apply接受第二个参数为类数组对象, 这里用了JavaScript权威指南中判断是否为类数组对象的方法。
Function.prototype.myApply = function(context) {
if (context === null || context === undefined) {
context = window // 指定为 null 和 undefined 的 this 值会自动指向全局对象(浏览器中为window)
} else {
context = new Object(context) // 值为原始值(数字,字符串,布尔值)的 this 会指向该原始值的实例对象
}
// JavaScript权威指南判断是否为类数组对象
function isArrayLike(o) {
if (
o && // o不是null、undefined等
typeof o === 'object' && // o是对象
isFinite(o.length) && // o.length是有限数值
o.length >= 0 && // o.length为非负值
o.length === Math.floor(o.length) && // o.length是整数
o.length < 4294967296
)
// o.length < 2^32
return true
else return false
}
const specialPrototype = Symbol('特殊属性Symbol')
context[specialPrototype] = this // 隐式绑定this指向到context上
let args = arguments[1] // 获取参数数组
let result
// 处理传进来的第二个参数
if (args) {
// 是否传递第二个参数
if (!Array.isArray(args) && !isArrayLike(args)) {
throw new TypeError(
'myApply 第二个参数不为数组并且不为类数组对象抛出错误'
)
} else {
args = Array.from(args) // 转为数组
result = context[specialPrototype](...args) // 执行函数并展开数组,传递函数参数
}
} else {
result = context[specialPrototype]() // 执行函数
}
delete context[specialPrototype] // 删除上下文对象的属性
return result // 返回函数执行结果
}
// 1.拷贝源函数:
// 通过变量储存源函数
// 使用Object.create复制源函数的prototype给fToBind
// 2.返回拷贝的函数
// 3.调用拷贝的函数:
// new调用判断:通过instanceof判断函数是否通过new调用,来决定绑定的context
// 绑定this+传递参数
// 返回源函数的执行结果
Function.prototype.myBind = function(objThis, ...params) {
const thisFn = this // 存储源函数以及上方的params(函数参数),thisFn=>被绑定的函数
// 对返回的函数 secondParams 二次传参
let fToBind = function(...secondParams) {
const isNew = this instanceof fToBind // this是否是fToBind的实例 也就是返回的fToBind是否通过new调用
const context = isNew ? this : Object(objThis) // new调用就绑定到this上,否则就绑定到传入的objThis上
return thisFn.call(context, ...params, ...secondParams) // 用apply调用源函数绑定this的指向并传递参数,返回执行结果
}
fToBind.prototype = Object.create(thisFn.prototype) // 复制源函数的prototype给fToBind
return fToBind // 返回拷贝的函数
}
var obj = {
name: 'aaaaaaaa',
getName() {
console.log(this.name)
}
}
function a(args) {
console.log(this.name)
}
a.apply(obj, [1, 2, 3, 4])
// a._myApply(obj,[1,2,3,4]);
</script>
简单够用版Promise实现
<!DOCTYPE html>
<html lang="en">
<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>简单够用版Promise实现</title>
</head>
<body>
</body>
</html>
<script>
class MyPromise {
constructor(fn) {
if (typeof this !== 'object') {
throw new TypeError('Promises must be constructed via new');
}
if (typeof fn !== 'function') {
throw new TypeError('Promise constructor\'s argument is not a function');
}
this.doneList = [];
this.failList = [];
fn(this.resolve.bind(this), this.reject.bind(this));//把当前resolve,reject的this指向MyPromise
}
resolve() {
let args = Array.prototype.slice.call(arguments);
setTimeout(() => {
this.doneList.forEach((item, key, arr) => {
result = item.apply(null, args);
arr.shift();
});
}, 0);
}
reject() {
let args = Array.prototype.slice.call(arguments);
this.failList.forEach((item, key, arr) => {
setTimeout(() => {
item.apply(null, args);
arr.shift();
}, 0);
});
}
then(cb) {
if (cb.constructor.name === 'Function') {
this.doneList.push(cb);
return this;//在同步执行时候返回this,转为链式
} else if (cb.constructor.name === 'MyPromise') {
this.doneList.push(cb)
return cb;//在同步执行时候返回this,转为链式
} else {
throw new Error('缺少回调函数');
return cb;//在同步执行时候返回this,转为链式
}
}
fail(cb) {
if (typeof cb === 'function') {
this.failList.push(cb)
} else {
throw new Error('缺少回调函数');
}
}
}
</script>