前言
解题方法有很多,这里只是参考
1 字符串
1.1 字符串逆序
// 法1
function reverseString1(str) {
return str.split('').reverse().join('')
}
// 法2
// 对于长字符串不推荐使用这种方法,会比较费内存(字符串不可变)
function reverseString2(str) {
let res = ''
for (let i = str.length - 1; i >= 0; i--) {
res += str.charAt(i)
}
return res
}
1.2 统计出现最多字符
function getMaxCount(str) {
let map = new Map()
str.split('').forEach((val) => {
if (map.has(val)) {
let count = map.get(val)
map.set(val, count + 1)
} else {
map.set(val, 1)
}
})
// 出现次数最多的字符及次数
let char = null
let maxCount = 0
for (let obj of map) {
// obj其实是个数组,第一个是键(字符),第二个是值(出现次数)
if (obj[1] > maxCount) {
char = obj[0]
maxCount = obj[1]
}
}
return { char, maxCount }
}
1.3 去除字符串中重复的字符
function removeStringChar(str) {
return [...new Set(str)].join('')
}
1.4 判断一个字符串是否为回文字符串
function isEequStr(str) {
let temp = str.toLowerCase().split('').reverse().join('')
return str === temp
}
2 判断为空
2.1 判断变量是否为空对象
function isEmpty(obj) {
for (let key in obj) {
// 遍历所有可枚举的属性
if (obj.hasOwnProperty(key)) {
return false;
}
}
return true;
}
}
扩展–判断某个属性是否存在于指定对象上
function hasPrototypeProperty(object, name) {
// 首先判断属性name是否可以在object上可以找到,如果可以找到,那可能是在object对象上,也可能在原型上,然后在判断是否是在object上
return name in object && object.hasOwnProperty(name)
}
2.2 判断变量是否为空数组
var arr=new Array()
arr instanceof Array && arr.length===0
3 Object类型相关考题
3.1 object.create()函数原理
首先该函数可以创建一个指定原型和指定属性的对象,然后来看下的它的实现原理,
Object.create=function(proto,propertiesObject){
//省略了其它判断操作
// 注意这里创建的是一个空的构造函数,而不是空对象(与模拟new操作符的实现要区分开来),创建构造函数这样才能为其添加属性
function F(){}
F.prototype=proto;
if(propertiesObject){ Object.defineProperties(F, propertiesObject)}
return new F()
}
在该函数中创建了一个空的构造函数,然后将他的prototype属性指向所指定的原型proto,为这个空的构造函数设定属性,最后返回这个空的构造函数创建的一个实例。该实例的__proto__指向它构造函数的原型,而他构造函数的原型又指向了proto,因此完成了create创建对象的功能,用代码表示如下,
function F(){}
F.prototype=proto
var f=new F()
f.__proto__===F.prototype
这里的一个重要应用场景就是继承,具体可以参见继承部分的内容
3.2 模拟new操作符的实现
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.sayHello = function () {
console.log('hello')
}
// 模拟new操作符,三件事
function New() {
var obj = {}
obj.__proto__ = Person.prototype
Person.apply(obj, arguments)
return obj
}
New('zs', 20).sayHello()
4 Array类型相关考题
4.1 判断变量是数组类型还是对象类型
不能使用typeof,因为typeof判断Array是object,没有办法区分变量是数组类型还是对象类型,下面有几种方法可以进行判断:
使用instanceof
function getType(o) {
if (o instanceof Array) {
return "Array";
} else if (o instanceof Object) {
return "Object";
} else {
return "参数类型不是Array也不是Object";
}
}
// 这里注意需要先判断Array,在判断Object,注意顺序,因为数组既是Array,又是object
var a = [1, 2, 3];
console.log(a instanceof Array); // true
console.log(a instanceof Object); // true
使用构造函数
function getType(o) {
//获取构造函数
var constructor = o.__proto__.constructor
if (constructor === Array) {
return 'Array'
} else if (constructor === Object) {
return 'Object'
} else {
return '参数类型不是Array也不是Object'
}
}
var a = [1, 2, 3]
console.log(getType(a))
使用object原型上的toString()方法
var arr = [1, 2, 3];
var obj = { userName: "zhangsan" };
console.log(Object.prototype.toString.call(arr)); //[object Array]
console.log(Object.prototype.toString.call(obj)); // [object Object]
console.log(arr.toString()); // 1,2,3,数组上toString()方法有改写
通过Array.isArray()函数来判断
var arr = [1, 2, 3];
var obj = { name: "zhangsan" };
console.log(Array.isArray(1)); //false
console.log(Array.isArray(arr)); //true
console.log(Array.isArray(obj)); //false
在这四种方法中,需要注意的地方:
- 在使用
instanceof
时,如果一个对象的原型发生了修改,会影响最后的判断
let obj = {}
obj.__proto__ = Array.prototype
console.log(obj instanceof Array) // true
console.log(Array.isArray(obj)) // false
- 如果对于一些老的浏览器不支持
Array.isArray()
,需要自己定义
if(!Array.isArray) {
Array.isArray = function(args) {
return Object.prototype.toString.call(args) === '[object Array]'
}
}
Object.prototype.toString
方法不能对自己定义的类进行一个精确的判断,可以使用instanceof
function Person(name) {
this.name = name
}
const p = new Person('zs')
console.log(Object.prototype.toString.call(p)) //[object object]
console.log(p instanceof Person) // true
4.2 手动实现find()函数
Array.prototype.findTest = function (fn) {
// 这里的this是隐式绑定,即谁调用就指向谁
for (var i = 0; i < this.length; i++) {
if (fn(this[i])) {
return this[i]
}
}
}
// 测试
var arr = [1, 2, 3]
var temp = arr.findTest((item) => {
return item > 2
})
console.log(temp)
4.3 手动实现filter()函数
Array.prototype.filterTest = function (fn) {
let newArr = []
for (let i = 0; i < this.length; i++) {
let num = this[i]
if (fn(num)) {
newArr.push(num)
}
}
return newArr
}
// 测试
var arr = [1, 2, 3]
var temp = arr.filterTest((item) => {
return item > 1
})
console.log(temp)
4.4 手动实现some()函数
Array.prototype.someTest = function (fn) {
for (let i = 0; i < this.length; i++) {
if (fn(this[i])) {
return true
}
}
return false
}
// 测试
var arr = [1, 2, 3]
var temp = arr.someTest((item) => {
return item > 1
})
console.log(temp)
4.5 手动实现every()函数
Array.prototype.everyTest = function (fn) {
for (let i = 0; i < this.length; i++) {
if (!fn(this[i])) {
return false
}
}
return true
}
// 测试
var arr = [1, 2, 3]
var temp = arr.everyTest((item) => {
return item < 5
})
console.log(temp)
4.6 手动实现map()函数
Array.prototype.mapTest = function (fn) {
var newArr = []
for (let i = 0; i < this.length; i++) {
let temp = fn(this[i], i, this)
newArr.push(temp)
}
return newArr
}
// 测试
var arr = [1, 2, 3]
var temp = arr.mapTest((item, index, array) => {
return item * item
})
console.log(temp)
4.7 手动实现reduce()函数
Array.prototype.reduceTest = function (fn, initValue) {
// 判断是否传入initValue,若没有传入,accumulator初始值为数组第一个;currentValue值为数组索引为1的数组元素
let hasInitValue = initValue !== undefined
var value = hasInitValue ? initValue : this[0]
for (let i = hasInitValue ? 0 : 1; i < this.length; i++) {
value = fn(value, this[i], i, this)
}
return value
}
let arr = [1, 2, 3]
let temp = arr.reduceTest((accumulator, currentValue) => {
return accumulator + currentValue
}, 0)
console.log(temp)
4.8 数组去重
// 数组去重
function fn1(arr) {
let newArr = []
for (let i = 0; i < arr.length; i++) {
if (newArr.indexOf(arr[i]) === -1) {
newArr.push(arr[i])
}
}
return newArr
}
// set数据结构去重
function fn2(arr) {
return [...new Set(arr)]
}
let arr = [1, 2, 3, 3, 2]
let temp = fn2(arr)
console.log(temp)
4.9 获取数组中最多的元素
function fn(arr) {
let map = new Map()
for (let i = 0; i < arr.length; i++) {
if (map.has(arr[i])) {
let count = map.get(arr[i])
map.set(arr[i], count + 1)
} else {
map.set(arr[i], 1)
}
}
let maxEle = null
let maxCount = 0
for (let item of map) {
if (item[1] > maxCount) {
maxEle = item[0]
maxCount = item[1]
}
}
return '在数组中出现最多的元素是' + maxEle + ',共出现了' + maxCount + '次'
}
let arr = [1, 2, 3, 2, 2]
let temp = fn(arr)
console.log(temp)
4.10 数组方法总结(是否会改变原数组)
- 会改变原数组:push, shift, unshift, pop, sort, reverse, splice
- 不会改变原数组:concat, join, reduce, map, forEach, filter, slice, findIndex
5 函数
5.1 手写call()
/**
* 手写call()函数
* @param {*} context 让this指向context
*/
Function.prototype.myCall = function (context) {
// 得到除了context的其他参数
let args = [...arguments].slice(1)
// 如果没有传入context,就指向window
context = context || window
// 哪个函数调用this就指向那个函数
context.fn = this
// 实现函数调用功能,随便改变this指向
let res = context.fn(...args)
return res
}
5.2 手写apply()
/**
* 手写apply()函数
* @param {*} context context 让this指向context
*/
Function.prototype.myApply = function (context) {
// 需要考虑如果除了context没有传入其他参数的情况
let tempArr = arguments[1] || []
context = context || window
context.fn = this
let res = context.fn(...tempArr)
return res
}
5.3 手写bind()
/**
* 手写bind()函数
* @param {*} context context 让this指向context
*/
Function.prototype.myBind = function (context) {
let args = [...arguments].slice(1)
let fn = this
return function () {
// 返回的函数有可能也有参数,所以要进行处理
let bindArgs = [...arguments]
// 拼接的时候也可以用args.concat(bindArgs)
return fn.apply(context, [...args, ...bindArgs])
}
}
// 测试
function add(a, b) {
console.log(this)
console.log(a + b)
}
function sub(a, b) {
console.log(a - b)
}
let newFun = add.myBind(sub, 1)
newFun(2)
6 对象
6.1 浅拷贝
法一:创建一个空对象,遍历原始对象,用等于直接赋值即可
var obj = { a: 1, arr: [2, 3], o: { name: 'zs' } }
function shallowCopy(src) {
let obj = {}
for (let prop in src) {
if (src.hasOwnProperty(prop)) {
obj[prop] = src[prop]
}
}
return obj
}
// 测试
let shallowObj = shallowCopy(obj)
obj.o.name = 'lisi'
console.log(shallowObj.o.name) //lisi,值受到了影响
obj.arr[0] = 20
console.log(shallowObj.arr[0]) //20,值受到了影响
obj.a = 10
console.log(shallowObj.a) // 1,值没有收到影响
法二:通过ES6
中的Object.assign()
函数来实现
6.2 深拷贝
法一:使用JSON.stringify()
和JSON.parse()
实现
这种方式会有一些问题:第一:无法实现对函数的拷贝;第二:如果对象中存在循环引用,会抛出异常;第三:对象中的构造函数会指向Object
,原型链关系被破坏。
法二:递归实现
let map = new Map()
function clone(target) {
if (typeof target === 'object') {
// 判断是否为引用数据类型,如果是
// 考虑是否为数组
let temp = Array.isArray(target) ? [] : {}
// 考虑循环引用情况
if (map.get(target)) {
return target
}
map.set(target, temp)
for (let prop in target) {
// 递归赋值
temp[prop] = clone(target[prop])
}
return temp
} else {
// 如果不是
return target
}
}
// 测试
function Person(name) {
userName: name
}
let person = new Person('zs')
var obj = {
userName: 'zhangsan',
o: person,
arr: [2, 3],
sayHi: function () {
console.log('hi')
}
}
var res = clone(obj)
// console.log(res)
console.log(res.o.constructor)
console.log(obj.o.constructor)
7 异步编程
7.1 手写promise构造函数
/**
*
* @param {*} task是一个函数,它有两个参数,分别是resolve和reject函数
*/
function MyPromise(task) {
let that = this
// promise的三个状态: pending(默认) fulfilled(成功) rejected(失败)
that.status = 'Pending'
that.value = undefined
// 存放成功和失败的回调函数的数组
that.onResolvedCallbacks = []
that.onRejectedCallbacks = []
function resolve(value) {
if (that.status === 'Pending') {
that.status = 'Resolved'
that.value = value
that.onResolvedCallbacks.forEach((item) => item(that.value))
}
}
function reject(reason) {
if (that.status === 'Pending') {
that.status = 'Rejected'
that.value = reason
//状态修改完成后,调用的是then 方法中处理失败的回调函数
that.onRejectedCallbacks.forEach((item) => item(that.value))
}
}
try {
task(resolve, reject)
} catch (err) {
reject(err)
}
}
MyPromise.prototype.then = function (onFulfilled, onRejected) {
let that = this
// resolve为同步任务,而promise.then为微任务,resolve函数先执行,这时onRejectedCallbacks和onRejectedCallbacks数组中还没有存放回调函数,因此需要在MyPromise.prototype.then中添加判断操作
// 以下是判断操作
if (that.status === 'Resolved') {
onFulfilled(that.value)
}
if (that.status === 'Rejected') {
onRejected(that.value)
}
that.onRejectedCallbacks.push(onRejected)
that.onRejectedCallbacks.push(onFulfilled)
}
// 测试
let myPromise = new MyPromise(function (resolve, reject) {
// setTimeout是一个异步任务,而promise.then微任务,微任务会先执行,所以当setTimeout执行的时候,onRejectedCallbacks和onRejectedCallbacks数组中已经存放了回调函数
// setTimeout(function () {
// let num = Math.random()
// if (num > 0.3) {
// resolve('成功了')
// } else {
// reject('失败了')
// }
// }, 1000)
// resolve为同步任务,而promise.then为微任务,resolve函数先执行,这时onRejectedCallbacks和onRejectedCallbacks数组中还没有存放回调函数,因此需要在MyPromise.prototype.then中添加判断操作
resolve('成功了')
})
myPromise.then(
function (value) {
console.log(value)
},
function (reason) {
console.log(reason)
}
)
7.2 模拟实现Promise.all()
function myAll(arr) {
let res = []
let index = 0
return new Promise((resolve, reject) => {
arr.forEach((cur, i) => {
// 数组可能有些元素不是promise对象,所以先用Promise.resolve()
Promise.resolve(cur).then(
(val) => {
res[i] = val
index++
if (index === arr.length) {
resolve(res)
}
},
(err) => reject(err)
)
})
})
}
7.3 模拟实现Promise.race()
function myRace(arr) {
return new Promise((resolve, reject) => {
for (let item of arr) {
Promise.resolve(item).then(
(val) => resolve(val),
(err) => reject(err)
)
}
})
}