面试手撕题js
# 事件委托
请补全JavaScript代码,要求如下:
- 给"ul"标签添加点击事件
- 当点击某"li"标签时,该标签内容拼接"."符号。如:某"li"标签被点击时,该标签内容为".." 注意:
- 必须使用DOM0级标准事件(onclick)
- target表示
当前触发事件
的元素- currentTarget是
绑定处理函数的元素
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<style>
/* 填写样式 */
</style>
</head>
<body>
<ul>
<li>.</li>
<li>.</li>
<li>.</li>
</ul>
<!-- 填写标签 -->
<script type="text/javascript">
// 填写JavaScript
` document.querySelector('ul').onclick = event => {
const li = event.target
li.innerHTML += '.'
}`
</script>
</body>
</html>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 数组去重
请补全JavaScript代码,要求去除数组参数中的重复数字项并返回该数组。 注意:
- 数组元素仅包含数字
输入:
_deleteRepeat([-1,1,2,2])
输出:
[-1,1,2]
利用Set
const _deleteRepeat = array => { const set = new Set(array) return [...set] } console.log(_deleteRepeat([-1,1,2,2]))
1
2
3
4
5set + Array.from方法(可浅拷贝一个可迭代对象)
const _deleteRepeat2 = array => { const set = new Set(array) return Array.from(set) } console.log(_deleteRepeat2([-1,1,2,2]))
1
2
3
4
5声明一个空数组,用includes寻找如果没有该元素则加入新数组
const _deleteRepeat3 = array => { const newArr = [] array.forEach(item => { if(!newArr.includes(item)) { newArr.push(item) } }) return newArr }
1
2
3
4
5
6
7
8
9利用reduce方法和includes
const _deleteRepeat4 = array => { return array.reduce((prev, curr) => { if(!prev.includes(curr)) { prev.push(curr) } return prev },[]) }
1
2
3
4
5
6
7
8hasOwnProperty方法可以判断类型两个{},{}
const _deleteRepeat5 = array => { const obj = {} return array.filter(item => { // // typeof {}+{}为object[object Object],判断有没有空对象,已经有的话return false,没有就作为对象的属性加进去,值为true return obj.hasOwnProperty(typeof item + item) ? false : obj[typeof item + item] = true }) }
1
2
3
4
5
6
7过滤器filter方法+indexof()方法,indexof会返回第一个找到的索引,如果当前数值之前出现过,
// 过滤器filter方法+indexof()方法,indexof会返回第一个找到的索引,如果当前数值之前出现过, // 则indexof返回的索引恒为之前的那个数与当前数值的索引不一致,故可以去重 const _deleteRepeat6 = array => { return array.filter((item,index) => { return array.indexOf(item) == index }) }
1
2
3
4
5
6
7
# 合法的URL
请补全JavaScript代码,要求以Boolean的形式返回字符串参数是否为合法的URL格式。 注意:
- 协议仅为HTTP(S)
const _isUrl = url => {
// 补全代码
// 开始符 ^
// 协议部分http(s):// 表示为((https|http|ftp|rtsp|mms)?:\/\/)
// 域名部分 表示为(([A-Za-z0-9]+-[A-Za-z0-9]+|[A-Za-z0-9]+)\.)+
// 顶级域名com cn等为2-6位 表示为([a-zA-Z]{2,6})
// 端口部分 表示为(:\d+)?, ?表示0次或1次
// 请求路径如/login 表示为 (\/.*)?
// 问号传参及哈希值如?age=1 表示为 (\?.*)?和(#.*)?
// 结束符 $
let reg = /^((https|http|ftp|rtsp|mms)?:\/\/)(([A-Za-z0-9]+-[A-Za-z0-9]+|[A-Za-z0-9]+)\.)+([A-Za-z]{2,6})(:\d+)?(\/.*)?(\?.*)?(#.*)?$/
return reg.test(url)
}
2
3
4
5
6
7
8
9
10
11
12
13
# 快速排序
请补全JavaScript代码,要求将数组参数中的数字从小到大进行排序并返回该数组。 注意:
- 数组元素仅包含数字
- 请优先使用快速排序方法
输入:
_quickSort([0,-1,1,-2,2])
输出:
[-2,-1,0,1,2]
const _quickSort = array => {
if(array == null || array.length <= 1) return array
var pivotIndex = Math.floor(array.length / 2)
var pivot = array.splice(pivotIndex, 1)[0]
var left = []
var right = []
for(var i = 0; i < array.length; i++) {
if(array[i] < pivot) {
left.push(array[i])
} else {
right.push(array[i])
}
}
return _quickSort(left).concat([pivot], _quickSort(right))
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 全排列
请补全JavaScript代码,要求以数组的形式返回字符串参数的所有排列组合。 注意:
- 字符串参数中的字符无重复且仅包含小写字母
- 返回的排列组合数组不区分顺序
输入:
_permute('abc')
输出:
['abc','acb','bac','bca','cab','cba']
// 回溯算法,解决全排列问题
const _permute = string => {
var path = []
var result = []
var strs = string.split('')
var used = new Array(strs.length).fill(0)
const backTrack = function backTracking(strs, used) {
if(path.length == strs.length) {
result.push(path.slice().join(''))
// result.push(path.slice())
return
}
for(let i = 0; i < strs.length; i++) {
if(used[i] == 1) continue; // 说明已经获取过了
path.push(strs[i])
used[i] = 1
backTracking(strs, used);
path.pop()
used[i] = 0
}
}
backTrack(strs, used)
return result
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
第二种写法
// 方法二,和上面一样,只是写法简单一些
const _permute2 = string => {
const res = []
const backTrack = path => {
if(path.length == string.length) {
res.push(path)
return
}
for(let item of string) {
if(path.includes(item)) continue
backTrack(path + item)
}
}
backTrack('')
return res
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
abc’的全排列等于 ('a'拼接上'bc'的全排列数组中的每一项) + ('b'拼接上'ac'的全排列数组的每一项) + ('c'拼接上'ab'的全排列数组的每一项)
// 方法三
const _permute3 = string => {
if(string.length == 1) {
return [string]
}
const result = []
for(let s of string) {
const arr = string.split('').filter(str => str != s)
_permute3(arr.join('')).forEach(item => {
result.push(s + item)
})
}
return result
}
2
3
4
5
6
7
8
9
10
11
12
13
14
# instanceof
请补全JavaScript代码,要求以Boolean的形式返回第一个实例参数是否在第二个函数参数的原型链上。
根据题目要求,实现一个仿instanceof功能的"_instanceof"函数,该函数可以判断首参是否在第二个Fn构造函数的原型链上,核心步骤有:
- 获取首个对象参数的原型对象
- 获取Fn函数的原型对象
- 进入死循环,当两个参数的原型对象相等时返回true
- 当两个参数的原型对象不相等时获取首个对象参数原型的原型并且循环该步骤直到null时返回false
const _instanceof = (target, Fn) => {
let proto = target.__proto__
while(true) {
if(proto === Fn.prototype) return true
if(proto == null) return false
proto = proto.__proto__
}
}
2
3
4
5
6
7
8
# Array.map
请补全JavaScript代码,要求实现Array.map函数的功能且该新函数命名为"_map"。
输入:
[1,2]._map(i => i * 2)
输出:
[2,4]
Array.prototype._map = function (Fn) {
if(typeof Fn != 'function') return
let newArr = []
for(let i = 0; i < this.length; i++) {
newArr[i] = Fn(this[i])
}
return newArr
}
2
3
4
5
6
7
8
- 判断参数是否为函数,如果不是则直接返回
- 创建一个空数组用于承载新的内容
- 循环遍历数组中的每个值,分别调用函数参数,将返回值添加进空数组中
- 返回新的数组
// arguments[1]
Array.prototype._map2 = function (Fn) {
if(typeof Fn != 'function') return
let newArr = []
for(let i = 0; i < this.length; i++) {
console.log(arguments[1])
let result = Fn.call(arguments[1], this[i], i, this)
newArr[i] = result
}
return newArr
}
2
3
4
5
6
7
8
9
10
11
# Array.filter
请补全JavaScript代码,要求实现Array.filter函数的功能且该新函数命名为"_filter"。
输入:
[1,2]._filter(i => i>1)
输出:
[2]
- 判断参数是否为函数,如果不是则直接返回
- 创建一个空数组用于承载新的内容
- 循环遍历数组中的每个值,分别调用函数参数,将满足判断条件的元素添加进空数组中
- 返回新的数组
Array.prototype._filter = function (Fn) {
if(typeof Fn !== 'function') return
const array = this
const newArray = []
for(let i = 0; i < array.length; i++) {
result = Fn.call(arguments[1], array[i], i , array)
result && newArray.push(array[i])
}
return newArray
}
const arr = [1,2]
console.log(arr._filter(i => i > 1))
2
3
4
5
6
7
8
9
10
11
12
# Array.reduce
请补全JavaScript代码,要求实现Array.reduce函数的功能且该新函数命名为"_reduce"。
输入:
[1,2,3]._reduce((left, right) => left + right)
输出:
6
Array.reduce的特点有:
- 接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终计算为一个值
- 可以接收一个初始值,当没有初始值时,默认初始值为数组中的第一项
实现该函数的核心步骤有:
- 在Array的原型对象上添加”_reduce“函数
- ”_reduce“函数第一个参数为回调函数,第二个参数为初始值
- 进入数组长度的循环体中
- 当初始值为空时,首个被加数为数组的第一项
- 当初始值不为空时,首个被加数为初始值
Array.prototype._reduce = function (fn, prev) {
for (let i = 0; i < this.length; i++) {
if (prev == undefined) {
prev = fn(this[i], this[i + 1], i + 1, this)
++i
} else {
prev = fn(prev, this[i], i, this)
}
}
return prev
}
const arr = [1, 2, 3]
console.log(arr._reduce((left, right) => left + right))
2
3
4
5
6
7
8
9
10
11
12
13
# _objectCreate
请补全JavaScript代码,要求实现Object.create函数的功能且该新函数命名为"_objectCreate"。
根据题目要求,实现一个仿Object.create功能的"_objectCreate"函数,该函数创建一个新对象,使用现有的对象来提供新创建的对象的proto,核心步骤有:
- 创建一个临时函数
- 将该临时函数的原型指向对象参数
- 返回该临时对象的实例
const _objectCreate = proto => {
if(typeof proto !== 'object' || proto === null) return
const fn = function() {
fn.prototype = proto
}
return fn()
}
const _objectCreate2 = function(proto,propertiesObject) {
if(typeof proto != 'object') return
let obj = {}
obj.__proto__ = proto
if(typeof propertiesObject == 'object') {
Object.defineProperties(obj,propertiesObject)
}
return obj
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# _call函数
请补全JavaScript代码,要求实现Function.call函数的功能且该新函数命名为"_call"。
Function.prototype._call = function(target = window) {
target['fn'] = this
const result = target['fn']([...arguments].shift())
delete target['fn']
return result
}
2
3
4
5
6
# Function.bind
请补全JavaScript代码,要求实现Function.bind函数的功能且该新函数命名为"_bind"。
# _new
根据题目要求,实现一个仿new功能的新"_new"函数,该函数会返回一个对象,该对象的构造函数为函数参数、原型对象为函数参数的原型,核心步骤有:
- 创建一个新对象
- 获取函数参数
- 将新对象的原型对象和函数参数的原型连接起来
- 将新对象和参数传给构造器执行
- 如果构造器返回的不是对象,那么就返回第一个新对象
const _new = function () {
const object1 = {}
const Fn = [...arguments].shift(); // 取出第一个参数
object1.__proto__ = Fn.prototype
const object2 = Fn.apply(object1, arguments)
return object2 instanceof Object ? object2 : object1
}
2
3
4
5
6
7
# Object.freeze
请补全JavaScript代码,要求实现Object.freeze函数的功能且该新函数命名为"_objectFreeze"
Object.freeze()。它的作用是冻结一个对象,被冻结的对象有以下几个特性:
- 不能添加新属性
- 不能删除已有属性
- 不能修改已有属性的值
- 不能修改原型
- 不能修改已有属性的可枚举性、可配置性、可写性
Object.preventExtensions()
方法用于令指定对象无法再添加新的属性。
const _objectFreeze = object => {
if(typeof object !== 'object' || object == null) {
throw new TypeError(object,'不是一个对象')
}
const keys = Object.getOwnPropertyNames(object);
const symbols = Object.getOwnPropertySymbols(object);
[...keys, ...symbols].forEach(key => {
Object.defineProperty(object, key, {
configurable: false,
writable: false
})
});
Object.preventExtensions(object);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
# _浅拷贝
请补全JavaScript代码,要求实现一个对象参数的浅拷贝并返回拷贝之后的新对象。 注意:
- 参数可能包含函数、正则、日期、ES6新对象
根据题目要求,实现一个对象参数的浅拷贝并返回拷贝之后的新对象,因为可能包含函数、正则、日期、ES6新对象,所以需要对这些对象类型进行特殊判断,核心步骤有:
- 如果对象参数的数据类型不为"object"或为"null",则直接返回该参数
- 如果是"object",就获取该参数的构造函数名,通过正则表达式判断该对象是否为函数、正则、日期、ES6新对象等,如果返回true,则直接返回该参数
- 当以上条件判断之后函数依然没有结束时,此时通过数组的原型方法判断该参数为普通对象或数组并创建相应数据类型的新变量
- 进入遍历体,将对象参数的每一项赋值给新变量
- 最终返回该新变量
const _shallowClone = target => {
// 补全代码
if(typeof target === 'object' && target !== null) {
const constructor = target.constructor
if(/^(Function|RegExp|Date|Map|Set)$/i.test(constructor.name))
return target
const cloneTarget = Array.isArray(target) ? [] : {}
for(prop in target) {
if(target.hasOwnProperty(prop)) {
cloneTarget[prop] = target[prop]
}
}
return cloneTarget
} else {
return target
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# For in遍历与hasOwnProperty()
原文链接:https://blog.csdn.net/weixin_42024074/article/details/122097347
for … in
是为遍历对象属性而构建的,不建议与数组一起使用,数组可以用
var obj = {a:1, b:2, c:3};
for (var prop in obj) {
// console.log("obj." + prop + " = " + obj.prop); //值都是undefined
console.log("obj." + prop + " = " + obj[prop]);
// "obj.a = 1"
// "obj.b = 2"
// "obj.c = 3"
}
2
3
4
5
6
7
8
9
hasOwnProperty()
方法详解
hasOwnProperty(propertyName)
是用来检测属性是否为对象的自有属性,如果是,返回true,否者false; 参数propertyName
指要检测的属性名;
let obj = {
name:'张',
age:18,
eat:{
eatname:'面',
water:{
watername:'水'
}
}
}
console.log(obj.hasOwnProperty('name')) //true
console.log(obj.hasOwnProperty('age')) //true
console.log(obj.hasOwnProperty('eat')) //true
console.log(obj.hasOwnProperty('eatname')) //false
console.log(obj.hasOwnProperty('water')) //false
console.log(obj.hasOwnProperty('watername')) //false
console.log(obj.eat.hasOwnProperty('eatname')) //true
console.log(obj.eat.hasOwnProperty('water')) //true
console.log(obj.eat.hasOwnProperty('watername')) //false
console.log(obj.eat.water.hasOwnProperty('watername')) //true
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
注意:
hasOwnProperty()
只会检查对象的自有属性,对象原形上的属性其不会检测;但是对于原型对象本身来说,这些原型上的属性又是原型对象的自有属性,所以原形对象也可以使用hasOwnProperty()
检测自己的自有属性;
一般for in与hasOwnProperty是配套存在的
var obj1 = {
a:1,
b:2,
c:3,
__proto__:{
lastName:"li"
}
};
for (let prop in obj1){
if (obj1.hasOwnProperty(prop)) {
console.log(obj1[prop]); //遍历出 1 ,2 ,3 ,但没有继承来的属性__proto__的值lastName:"li"
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
# 简易深拷贝
请补全JavaScript代码,要求实现对象参数的深拷贝并返回拷贝之后的新对象。 注意:
- 参数对象和参数对象的每个数据项的数据类型范围仅在数组、普通对象({})、基本数据类型中]
- 无需考虑循环引用问题
根据题目要求,实现对象参数的深拷贝并返回拷贝之后的新对象,因为参数对象和参数对象的每个数据项的数据类型范围仅在数组、普通对象({})、基本数据类型中且无需考虑循环引用问题,所以不需要做过多的数据类型判断,核心步骤有:
- 如果对象参数的数据类型不为“object”或为“null”,则直接返回该参数
- 根据该参数的数据类型是否为数组创建新对象
- 遍历该对象参数,将每一项递归调用该函数本身的返回值赋给新对象
const _sampleDeepClone = target => {
// 补全代码
if(typeof target === 'object' && typeof target != null) {
const cloneTarget = Array.isArray(target) ? [] : {}
for(prop in target) {
if(target.hasOwnProperty(prop)) {
cloneTarget[prop] = _sampleDeepClone(target[prop])
}
}
return cloneTarget
} else {
return target
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
# 深拷贝
请补全JavaScript代码,要求实现对象参数的深拷贝并返回拷贝之后的新对象。 注意:
- 需要考虑函数、正则、日期、ES6新对象
- 需要考虑循环引用问题
根据题目要求,实现对象参数的深拷贝并返回拷贝之后的新对象,因为需要考虑参数对象和参数对象的每个数据项的数据类型可能包括函数、正则、日期、ES6新对象且必须考虑循环引用问题,所以需要引入ES6新对象Map并且详细的判断数据类型,核心步骤有:
- 首先判断对象参数是否为“null”,是则返回“null”
- 判断对象参数数据类型是否为“object”,不是则返回该参数
- 获取到对象参数的构造函数名,判断是否为函数、正则、日期、ES6新对象其中之一,如果是则直接返回通过该参数对象对应的构造函数生成的新实例对象
- 当以上条件判断之后函数依然没有结束时继续进行以下操作
- 在Map对象中获取当前参数对象,如果能获取到,则说明这里为循环引用并返回Map对象中该参数对象的值
- 如果在Map对象中没有获取到对应的值,则保存该参数对象到Map中,作为标记
- 根据该参数的数据类型是否为数组创建新对象
- 遍历该对象参数,将每一项递归调用该函数本身的返回值赋给新对象
const _completeDeepClone = (target, map = new Map()) => {
// 补全代码
if(target === null) return target
if(typeof target !== 'object') return target
const constructor = target.constructor
if(/^(Function|RegExp|Date|Map|Set)$/i.test(constructor.name))
return new constructor(target)
if(map.get(target)) return map.get(target)
map.set(target, true)
const cloneTarget = Array.isArray(target) ? [] : {}
for(prop in target) {
if(target.hasOwnProperty(prop)) {
cloneTarget[prop] = _completeDeepClone(target[prop], map)
}
}
return cloneTarget
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 寄生组合式继承
请补全JavaScript代码,要求通过寄生组合式继承使"Chinese"构造函数继承于"Human"构造函数。要求如下:
- 给"Human"构造函数的原型上添加"getName"函数,该函数返回调用该函数对象的"name"属性
- 给"Chinese"构造函数的原型上添加"getAge"函数,该函数返回调用该函数对象的"age"属性
根据题目要求,通过寄生组合式继承使"Chinese"构造函数继承于"Human"构造函数。寄生组合式继承,即通过借用构造函数来继承属性,通过原型链的形式来继承方法,只调用了一次父类构造函数,效率高,也避免了在子类的原型对象上创建不必要的、多余的属性,原型链也不会被改变,核心步骤有:
- 在"Human"构造函数的原型上添加"getName"函数
- 在”Chinese“构造函数中通过call函数借助”Human“的构造器来获得通用属性
- Object.create函数返回一个对象,该对象的__proto__属性为对象参数的原型。此时将”Chinese“构造函数的原型和通过Object.create返回的实例对象联系起来
- 最后修复"Chinese"构造函数的原型链,即自身的"constructor"属性需要指向自身
- 在”Chinese“构造函数的原型上添加”getAge“函数
function Human(name) {
this.name = name
this.kingdom = 'animal'
this.color = ['yellow', 'white', 'brown', 'black']
}
Human.prototype.getName = function() {
return this.name
}
function Chinese(name, age) {
Human.call(this, name)
this.age = age
this.color = 'yellow'
}
Chinese.prototype = Object.create(Human.prototype)
Chinese.prototype.constructor = Chinese
Chinese.prototype.getAge = function() {
return this.age
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 发布订阅模式
请补全JavaScript代码,完成"EventEmitter"类实现发布订阅模式。 注意:
- 同一名称事件可能有多个不同的执行函数
- 通过"on"函数添加事件
- 通过"emit"函数触发事件
根据题目要求,完成"EventEmitter"类实现发布订阅模式,考虑到同一名称事件可能有多个不同的执行函数,所以在构造函数中需要以对象的结构存放事件,核心步骤有:
- 构造函数中创建”events“对象变量用于存放所有的事件
- 添加”on“函数,用于订阅事件。当总事件中不存在此事件时创建新的事件数组,当存在时将”fn“函数添加在该事件对应数组中
- 添加”emit“函数,用于发布事件,遍历该事件下的函数数组并全部执行
class EventEmitter {
constructor() {
this.events = {}
}
on(event, fn) {
if(!this.events[event]) {
this.events[event] = [fn]
} else {
this.events[event].push(fn)
}
}
emit(event) {
if(this.events[event]) {
this.events[event].forEach(callback => callback())
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 观察者模式
请补全JavaScript代码,完成"Observer"、"Observerd"类实现观察者模式。要求如下:
- 被观察者构造函数需要包含"name"属性和"state"属性且"state"初始值为"走路"
- 被观察者创建"setObserver"函数用于保存观察者们
- 被观察者创建"setState"函数用于设置该观察者"state"并且通知所有观察者
- 观察者创建"update"函数用于被观察者进行消息通知,该函数需要打印(console.log)数据,数据格式为:小明正在走路。其中"小明"为被观察者的"name"属性,"走路"为被观察者的"state"属性
注意:
- "Observer"为观察者,"Observerd"为被观察者
根据题目要求完成"Observer"、"Observerd"类实现观察者模式。核心步骤有:
- 被观察者构造函数声明三个属性分别为"name"用于保存被观察者姓名、"state"用于保存被观察者状态、"observers"用于保存观察者们
- 被观察者创建"setObserver"函数,该函数通过数组的push函数将观察者参数传入"observers"数组中
- 被观察者创建"setState"函数,该函数首先通过参数修改被观察者的"state"属性,然后通过遍历"observers"数组分别调用各个观察者的"update"函数并且将该被观察者作为参数传入
- 观察者创建"update"函数,用于打印信息
class Observerd {
constructor(name) {
this.name = name
this.state = '走路'
this.observers = []
}
setObserver(observer) {
this.observers.push(observer)
}
setState(state) {
this.state = state
this.observers.forEach(observer => observer.update(this))
}
}
class Observer {
constructor() {
}
update(observerd) {
console.log(observerd.name + '正在' + observerd.state)
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23