14道高频编写JS面试题和答案,巩固你的JS基础
目录
1. 手写深拷贝
-
function deepClone(startObj,endObj) {
-
let obj = endObj || {}
-
for (let i in startObj) {
-
if (typeof startObj[i] === 'object') {
-
startObj[i].constructor === Array ? obj[i] = [] : obj[i] = {}
-
deepClone(startObj[i],obj[i])
-
} else {
-
obj[i] = startObj[i]
-
}
-
}
-
return obj
-
}
值得注意的一点是,在递归调用的时候,需要把当前处理的 obj[i] 给传回去,否则的话 每次递归obj都会被赋值为空对象,就会对已经克隆好的数据产生影响。
我们验证一下深拷贝是否实现:
-
const person = {
-
name: 'zyj',
-
age: 20,
-
sister: {
-
name: 'duoduo',
-
age: 13,
-
mother: {
-
name: 'lili',
-
age:45
-
}
-
}
-
}
-
const newPerson = deepClone(person)
-
newPerson.sister.mother.age = 50
-
console.log(newPerson)
-
// {
-
// name: 'zyj',
-
// age: 20,
-
// sister: { name: 'duoduo', age: 13, mother: { name: 'lili', age: 50 } }
-
// }
-
console.log(person)
-
// {
-
// name: 'zyj',
-
// age: 20,
-
// sister: { name: 'duoduo', age: 13, mother: { name: 'lili', age: 45 } }
-
// }
2. 防抖函数
单位时间内,频繁触发一个事件,以最后一次触发为准。
-
function debounce(fn,delay) {
-
let timer = null
-
return function() {
-
clearTimeout(timer)
-
timer = setTimeout(() => {
-
fn.call(this)
-
}, delay);
-
}
-
}
我们看一下调用流程:
-
<body>
-
<input type="text">
-
<script>
-
const input = document.querySelector('input')
-
input.addEventListener('input',debounce(function() {
-
console.log(111);
-
},1000))
-
function debounce(fn,delay) {
-
let timer = null
-
return function() {
-
clearTimeout(timer)
-
timer = setTimeout(() => {
-
fn.call(this)
-
}, delay);
-
}
-
}
-
</script>
-
</body>
可能有些同学对 fn.call(this) 不太明白,在 debounce 中我们把匿名函数作为参数传进来,因为匿名函数的执行环境具有全局性,所以它的 this 一般指向 window ,所以要改变一下 this 指向,让它指向调用者 input 。
3. 节流函数
单位时间内,频繁触发一个事件,只会触发一次。
-
function throttle(fn,delay) {
-
return function () {
-
if (fn.t) return;//每次触发事件时,如果当前有等待执行的延时函数,则直接return
-
fn.t = setTimeout(() => {
-
fn.call(this);//确保执行函数中this指向事件源,而不是window
-
fn.t = null//执行完后设置 fn.t 为空,这样就能再次开启新的定时器
-
}, delay);
-
};
-
}
调用流程:
-
<script>
-
//节流throttle代码:
-
function throttle(fn,delay) {
-
return function () {
-
if (fn.t) return;//每次触发事件时,如果当前有等待执行的延时函数,则直接return
-
fn.t = setTimeout(() => {
-
fn.call(this);//确保执行函数中this指向事件源,而不是window
-
fn.t = null//执行完后设置 fn.t 为空,这样就能再次开启新的定时器
-
}, delay);
-
};
-
}
-
window.addEventListener('resize', throttle(function() {
-
console.log(11);
-
},1000));
-
</script>
只有当调整浏览器视口大小时才会输出,且每隔一秒输出一次
4. 模拟 instanceof
-
// 模拟 instanceof
-
function myInstance(L, R) {
-
//L 表示左表达式,R 表示右表达式
-
let RP = R.prototype; // 取 R 的显示原型
-
let LP = L.__proto__; // 取 L 的隐式原型
-
while (true) {
-
if (LP === null) return false;
-
if (RP === LP)
-
// 这里重点:当 O 严格等于 L 时,返回 true
-
return true;
-
LP = LP.__proto__;
-
}
-
}
-
function person(name) {
-
this.name = name
-
}
-
const zyj = new person('库里')
-
-
console.log(myInstance(zyj,person)); // true
5. 全局通用的数据类型判断方法
-
function getType(obj){
-
let type = typeof obj;
-
if (type !== "object") { // 先进行typeof判断,如果是基础数据类型,直接返回
-
return type;
-
}
-
// 对于typeof返回结果是object的,再进行如下的判断,正则返回结果
-
return Object.prototype.toString.call(obj).replace(/^\[object (\S )\]$/, '$1'); // 注意正则中间有个空格
-
}
6. 手写 call 函数
-
Function.prototype.myCall = function (context) {
-
// 先判断调用myCall是不是一个函数
-
// 这里的this就是调用myCall的
-
if (typeof this !== 'function') {
-
throw new TypeError("Not a Function")
-
}
-
-
// 不传参数默认为window
-
context = context || window
-
-
// 保存this
-
context.fn = this
-
-
// 保存参数
-
let args = Array.from(arguments).slice(1)
-
//Array.from 把伪数组对象转为数组,然后调用 slice 方法,去掉第一个参数
-
-
// 调用函数
-
let result = context.fn(...args)
-
-
delete context.fn
-
-
return result
-
-
}
7. 手写 apply 函数
-
Function.prototype.myApply = function (context) {
-
// 判断this是不是函数
-
if (typeof this !== "function") {
-
throw new TypeError("Not a Function")
-
}
-
-
let result
-
-
// 默认是window
-
context = context || window
-
-
// 保存this
-
context.fn = this
-
-
// 是否传参
-
if (arguments[1]) {
-
result = context.fn(...arguments[1])
-
} else {
-
result = context.fn()
-
}
-
delete context.fn
-
-
return result
-
}
-
8. bind方法
在实现手写bind方法的过程中,看了许多篇文章,答案给的都很统一,准确,但是不知其所以然,所以我们就好好剖析一下bind方法的实现过程。
我们先看一下bind函数做了什么:
bind() 方法会创建一个新函数。当这个新函数被调用时,bind() 的第一个参数将作为它运行时的 this,之后的一序列参数将会在传递的实参前传入作为它的参数。
读到这里我们就发现,他和 apply , call 是不是很像,所以这里指定 this 功能,就可以借助 apply 去实现:
-
Function.prototype.myBind = function (context) {
-
// 这里的 this/self 指的是需要进行绑定的函数本身,比如用例中的 man
-
const self = this;
-
// 获取 myBind 函数从第二个参数到最后一个参数(第一个参数是 context)
-
// 这里产生了闭包
-
const args = Array.from(arguments).slice(1)
-
return function () {
-
// 这个时候的 arguments 是指 myBind 返回的函数传入的参数
-
const bindArgs = Array.from(arguments)
-
// 合并
-
return self.apply(context, args.concat(bindArgs));
-
};
-
};
大家对这段代码应该都能看懂,实现原理和手写 call , apply 都很像,因为 bind 可以通过返回的函数传参,所以在 return 里面获取的 bindArgs 就是这个意思,然后最后通过 concat 把原来的参数和后来传进来的参数进行数组合并。
我们来看一下结果:
-
const person = {
-
name: 'zyj'
-
}
-
-
function man(age) {
-
console.log(this.name);
-
console.log(age)
-
}
-
-
const test = man.myBind(person)
-
test(18)//zyj 18
现在重点来了,bind 区别于 call 和 apply 的地方在于它可以返回一个函数,然后把这个函数当作构造函数通过 new 操作符来创建对象。
我们来试一下:
-
const person = {
-
name: 'zyj'
-
}
-
-
function man(age) {
-
console.log(this.name);
-
console.log(age)
-
}
-
-
const test = man.myBind(person)
-
const newTest = new test(18) // zyj 18
这是用的我们上面写的 myBind 函数是这个结果,那原生 bind 呢?
-
const person = {
-
name: 'zyj'
-
}
-
-
function man(age) {
-
console.log(this.name);
-
console.log(age)
-
}
-
-
const test = man.bind(person)
-
const newTest = new test(18) // undefined 18
由上述代码可见,使用原生
bind
生成绑定函数后,通过new
操作符调用该函数时,this.name 是一个 undefined,这其实很好理解,因为我们 new 了一个新的实例,那么构造函数里的 this 肯定指向的就是实例,而我们的代码逻辑中指向的始终都是 context ,也就是传进去的参数。
所以现在我们要加个判断逻辑:
-
Function.prototype.myBind = function (context) {
-
// 这里的 this/self 指的是需要进行绑定的函数本身,比如用例中的 man
-
const self = this;
-
// 获取 myBind 函数从第二个参数到最后一个参数(第一个参数是 context)
-
// 这里产生了闭包
-
const args = Array.from(arguments).slice(1)
-
const theBind = function () {
-
const bindArgs = Array.from(arguments);
-
-
// 当绑定函数作为构造函数时,其内部的 this 应该指向实例,此时需要更改绑定函数的 this 为实例
-
// 当作为普通函数时,将绑定函数的 this 指向 context 即可
-
// this instanceof fBound 的 this 就是绑定函数的调用者
-
return self.apply(
-
this instanceof theBind ? this : context,
-
args.concat(bindArgs)
-
);
-
};
-
return theBind;
-
};
现在这个效果我们也实现了,那我们的 myBind 函数就和其他的原生 bind 一样了吗?来看下面的代码:
-
const person = {
-
name: 'zyj'
-
}
-
function man(age) {
-
console.log(this.name);
-
console.log(age)
-
}
-
man.prototype.sayHi = function() {
-
console.log('hello')
-
}
-
const test = man.myBind(person)
-
const newTest = new test(18) // undefined 18
-
newTest.sayHi()
如果 newTest 是我们 new 出来的 man 实例,那根据原型链的知识,定义在man的原型对象上的方法肯定会被继承下来,所以我们通过 newTest.sayHi 调用能正常输出 hello 么?
该版代码的改进思路在于,将返回的绑定函数的原型对象的
__proto__
属性,修改为原函数的原型对象。便可满足原有的继承关系。
-
Function.prototype.myBind = function (context) {
-
// 这里的 this/self 指的是需要进行绑定的函数本身,比如用例中的 man
-
const self = this;
-
// 获取 myBind 函数从第二个参数到最后一个参数(第一个参数是 context)
-
// 这里产生了闭包
-
const args = Array.from(arguments).slice(1);
-
const theBind = function () {
-
const bindArgs = Array.from(arguments);
-
-
// 当绑定函数作为构造函数时,其内部的 this 应该指向实例,此时需要更改绑定函数的 this 为实例
-
// 当作为普通函数时,将绑定函数的 this 指向 context 即可
-
// this instanceof fBound 的 this 就是绑定函数的调用者
-
return self.apply(
-
this instanceof theBind ? this : context,
-
args.concat(bindArgs)
-
);
-
};
-
theBind.prototype = Object.create(self.prototype)
-
return theBind;
-
};
9. 模拟 new
-
// 手写一个new
-
function myNew(fn, ...args) {
-
// 创建一个空对象
-
let obj = {}
-
// 使空对象的隐式原型指向原函数的显式原型
-
obj.__proto__ = fn.prototype
-
// this指向obj
-
let result = fn.apply(obj, args)
-
// 返回
-
return result instanceof Object ? result : obj
-
}
有很多小伙伴不明白为什么要判断 result 是不是 Object 的实例,我们首先得了解,在JavaScript中构造函数可以有返回值也可以没有。
1. 没有返回值的情况返回实例化的对象
-
function Person(name, age){
-
this.name = name
-
this.age = age
-
}
-
console.log(Person()); //undefined
-
console.log(new Person('zyj',20));//Person { name: 'zyj', age: 20 }
2. 如果存在返回值则检查其返回值是否为引用类型,如果为非引用类型,如(string,number,boolean,null,undefined),上述几种类型的情况与没有返回值的情况相同,实际返回实例化的对象
-
function Person(name, age){
-
this.name = name
-
this.age = age
-
return 'lalala'
-
}
-
console.log(Person()); //lalala
-
console.log(new Person('zyj',20));//Person { name: 'zyj', age: 20 }
3. 如果存在返回值是引用类型,则实际返回该引用类型
-
function Person(name, age){
-
this.name = name
-
this.age = age
-
return {
-
name: 'curry',
-
ahe: 34
-
}
-
}
-
console.log(Person()); //{ name: 'curry', ahe: 34 }
-
console.log(new Person('zyj',20));//{ name: 'curry', ahe: 34 }
10. 类数组转化为数组的方法
-
const arrayLike=document.querySelectorAll('div')
-
-
// 1.扩展运算符
-
[...arrayLike]
-
// 2.Array.from
-
Array.from(arrayLike)
-
// 3.Array.prototype.slice
-
Array.prototype.slice.call(arrayLike)
-
// 4.Array.apply
-
Array.apply(null, arrayLike)
-
// 5.Array.prototype.concat
-
Array.prototype.concat.apply([], arrayLike)
11. 组合继承
-
function father (name) {
-
this.name = name
-
this.age = 18
-
}
-
-
father.prototype.getName = function(){} // 方法定义在父类原型上(公共区域)
-
-
function child () {
-
// 继承父类属性,可传入参数
-
father.call(this,'Tom')
-
// 将会生成如下属性:
-
// name:'tom'
-
// age: 18
-
}
-
child.prototype = new father() // 重写原型对象
-
child.prototype.constructor = child
这里的原型链关系应该是这样的:
该方式也叫做伪经典继承。其核心思路是:重写子类的原型对象为父类实例,并通过盗用构造函数继承父类实例的属性。
12. 原型式继承
基本思路是,对传入的对象做了一次浅复制,并赋值给一个空函数
F
(临时类型)的原型对象,并返回一个通过F
生成的实例。这个实例的__proto__
自然而然地指向了传入的对象,可以理解为一个挂钩🧷的过程。
-
function object(o) {
-
function F() {}
-
F.prototype = o;
-
return new F();
-
}
-
-
let father = function() {}
-
father.prototype.getName = function() {
-
console.log('zyj')
-
}
-
-
let son = object(father)
-
let daughter = object(father)
-
son.prototype.getName() // zyj
大概是这么个过程:
在 ECMAScript 5
中,通过增加 Object.create()
方法将原型式继承的概念规范化,即替代了上述自定义的 object()
函数。所以对于 Object.create()
的手写实现,核心思路与上述的自定义函数类似,只是添加了部分参数校验的环节。
let son = Object.create(father) // 等同于上述代码
13. 实现 Object.create()
-
Object.myCreate = function(proto, propertyObject) {
-
// 参数校验
-
if (typeof proto !== 'object' && typeof proto !== 'function') {
-
throw new TypeError('Object prototype may only be an Object or null.')
-
// 不能传一个 null 值给实例作为属性
-
if (propertyObject == null) {
-
new TypeError('Cannot convert undefined or null to object')
-
}
-
// 原型式继承的思想:用一个空函数(即忽略掉原有构造函数的初始化代码)创建一个干净的实例
-
function F() {}
-
F.prototype = proto // 确定后续的继承关系
-
const obj = new F()
-
-
// 如果有传入第二个参数,将其设为 obj 的属性
-
if (propertyObject != undefined) {
-
Object.defineProperties(obj, propertyObject)
-
}
-
-
// 即 Object.create(null) 创建一个没有原型对象的对象
-
if (proto === null) {
-
obj.__proto__ = null
-
}
-
return obj
-
}
14. 数组去重
ES5实现:
-
function unique(arr) {
-
var res = arr.filter(function(item, index, array) {
-
return array.indexOf(item) === index
-
})
-
return res
-
}
ES6实现:
var unique = arr => [...new Set(arr)]
这篇好文章是转载于:学新通技术网
- 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
- 本站站名: 学新通技术网
- 本文地址: /boutique/detail/tanhghfhkc
-
photoshop保存的图片太大微信发不了怎么办
PHP中文网 06-15 -
《学习通》视频自动暂停处理方法
HelloWorld317 07-05 -
word里面弄一个表格后上面的标题会跑到下面怎么办
PHP中文网 06-20 -
Android 11 保存文件到外部存储,并分享文件
Luke 10-12 -
photoshop扩展功能面板显示灰色怎么办
PHP中文网 06-14 -
微信公众号没有声音提示怎么办
PHP中文网 03-31 -
excel下划线不显示怎么办
PHP中文网 06-23 -
excel打印预览压线压字怎么办
PHP中文网 06-22 -
TikTok加速器哪个好免费的TK加速器推荐
TK小达人 10-01 -
怎样阻止微信小程序自动打开
PHP中文网 06-13