【面试题】你是如何让js 代码变得简洁的?

简介: 【面试题】你是如何让js 代码变得简洁的?

前端面试题库 (面试必备)            推荐:★★★★★

地址:前端面试题库

前言

js中有很多API贼好用,省下了很多工夫,你知道它的原理吗?这篇文章对它们做一个总结。

正文

一、手撕instanceof

  • instanceof的原理:通过判断对象的原型是否等于构造函数的原型来进行类型判断
  • 代码实现:
const myInstanceOf=(Left,Right)=>{
  if(!Left){
    return false
  }
  while(Left){
    if(Left.__proto__===Right.prototype){
      return true
    }else{
      Left=Left.__proto__
    }
  }
  return false
}
//验证
console.log(myInstanceOf({},Array));  //false

二、手撕call,apply,bind

call,apply,bind是通过this的显示绑定修改函数的this指向

1. call

call的用法:a.call(b) -> 将a的this指向b

我们需要借助隐式绑定规则来实现call,具体实现步骤如下:

往要绑定的那个对象(b)上挂一个属性,值为需要被调用的那个函数名(a),在外层去调用函数

function foo(x,y){
  console.log(this.a,x+y);
}
const obj={
  a:1
}
Function.prototype.myCall=function(context,...args){
  if(typeof this !== 'function')  return new TypeError('is not a function')
  const fn=Symbol('fn') //使用Symbol尽可能降低myCall对其他的影响
  context[fn]=this  //this指向foo
  const res=context[fn](...args)  //解构,调用fn
  delete context[fn]  //不要忘了删除obj上的工具函数fn
  return res  //将结果返回
}
//验证
foo.myCall(obj,1,2)   //1,3

2. apply

apply和call的本质区别就是接受的参数形式不同,call接收零散的参数,而apply以数组的方式接收参数,实现思路完全一样,代码如下:

function foo(x,y){
  console.log(this.a,x+y);
}
const obj={
  a:1
}
Function.prototype.myApply=function(context,args){
  if(typeof this !== 'function')  return new TypeError('is not a function')
  const fn=Symbol('fn') //尽可能降低myCall对其他的影响
  context[fn]=this
  context[fn](...args)
  delete context[fn]
}
//验证
foo.myApply(obj,[1,2])  //1,3

3. bind

bind和call,apply的区别是会返回一个新的函数,接收零散的参数

需要注意的是,官方bind的操作是这样的:

  • 当new了bind返回的函数时,相当于new了foo,且new的参数需作为实参传给foo
  • foo的this.a访问不到obj中的a
function foo(x,y,z){
  this.name='zt'
  console.log(this.a,x+y+z);
}
const obj={
  a:1
}
Function.prototype.myBind=function(context,...args){
  if(typeof this !== 'function')  return new TypeError('is not a function')
  context=context||window
  let _this=this
  return function F(...arg){
    //判断返回出去的F有没有被new,有就要把foo给到new出来的对象
    if(this instanceof F){
      return new _this(...args,...arg) //new一个foo
    }
    _this.apply(context,args.concat(arg))  //this是F的,_this是foo的  把foo的this指向obj用apply
  }
}
//验证
const bar=foo.myBind(obj,1,2)
console.log(new bar(3));   //undefined 6  foo { name: 'zt' }

三、手撕深拷贝

四、手撕Promise

思路:

  • 我们知道,promise是有三种状态的,分别是pending(异步操作正在进行), fulfilled(异步操作成功完成), rejected(异步操作失败)。我们可以定义一个变量保存promise的状态。
  • resolve和reject的实现:把状态变更,并把resolve或reject中的值保存起来留给.then使用
  • 要保证实例对象能访问.then,必须将.then挂在构造函数的原型上
  • .then接收两个函数作为参数,我们必须对所传参数进行判断是否为函数,当状态为fulfilled时,onFulfilled函数触发,并将前面resolve中的值传给onFulfilled函数;状态为rejected时同理。
  • 当在promise里放一个异步函数(例:setTimeout)包裹resolve或reject函数时,它会被挂起,那么当执行到.then时,promise的状态仍然是pending,故不能触发.then中的回调函数。我们可以定义两个数组分别存放.then中的两个回调函数,将其分别在resolve和reject函数中调用,这样保证了在resolve和reject函数触发时,.then中的回调函数即能触发。

代码如下:

const PENDING = 'pending'
const FULFILLED = 'fullfilled'
const REJECTED = 'rejected'
function myPromise(fn) {
  this.state = PENDING
  this.value = null
  const that = this
  that.resolvedCallbacks = []
  that.rejectedCallbacks = []
  function resolve(val) {
    if (that.state == PENDING) {
      that.state = FULFILLED
      that.value = val
      that.resolvedCallbacks.map((cb)=>{
        cb(that.value)
      })
    }
  }
  function reject(val) {
    if (that.state == PENDING) {
      that.state = REJECTED
      that.value = val
      that.rejectedCallbacks.map((cb)=>{
        cb(that.value)
      })
    }
  }
  try {
    fn(resolve, reject)
  } catch (error) {
    reject(error)
  }
}
myPromise.prototype.then = function (onFullfilled, onRejected) {
  const that = this
  onFullfilled = typeof onFullfilled === 'function' ? onFullfilled : v => v
  onRejected= typeof onRejected === 'function' ? onRejected : r => { throw r }
  if(that.state===PENDING){
    that.resolvedCallbacks.push(onFullfilled)
    that.resolvedCallbacks.push(onRejected)
  }
  if (that.state === FULFILLED) {
    onFullfilled(that.value)
  }
  if (that.state === REJECTED) {
    onRejected(that.value)
  }
} 
//验证   ok ok
let p = new myPromise((resolve, reject) => {
  // reject('fail')
  resolve('ok')
})
p.then((res) => {
  console.log(res,'ok');
}, (err) => {
  console.log(err,'fail');
})

六、手撕数组API

1. forEach()

思路:

  • forEach()用于数组的遍历,参数接收一个回调函数,回调函数中接收三个参数,分别代表每一项的值、下标、数组本身。
  • 要保证数组能访问到我们自己手写的API,必须将其挂到数组的原型上

代码实现:

const arr = [
  { name: 'zt', age: 18 },
  { name: 'aa', age: 19 },
  { name: 'bb', age: 18 },
  { name: 'cc', age: 21 },
]
//代码实现
Array.prototype.my_forEach = function (callback) {
  for (let i = 0; i < this.length; i++) {
    callback(this[i], i, this)
  }
}
//验证
arr.my_forEach((item, index, arr) => {      //111  111
  if (item.age === 18) {
    item.age = 17
    return   
  }
  console.log('111');
})

2. map()

思路:

  • map()也用于数组的遍历,与forEach不同的是,它会返回一个新数组,这个新数组是map接收的回调函数返回值
    代码实现:
const arr = [
  { name: 'zt', age: 18 },
  { name: 'aa', age: 19 },
  { name: 'bb', age: 18 },
  { name: 'cc', age: 21 },
]
Array.prototype.my_map=function(callback){
  const res=[]
  for(let i=0;i<this.length;i++){
    res.push(callback(this[i],i,this))
  }
  return res
}
//验证
let newarr=arr.my_map((item,index,arr)=>{
  if(item.age>18){
    return item
  }
})
console.log(newarr);  
//[
 // undefined,
 // { name: 'aa', age: 19 },
 // undefined,
 // { name: 'cc', age: 21 }
//]

3. filter()

思路:

  • filter()用于筛选过滤满足条件的元素,并返回一个新数组

代码实现:

const arr = [
  { name: 'zt', age: 18 },
  { name: 'aa', age: 19 },
  { name: 'bb', age: 18 },
  { name: 'cc', age: 21 },
]
Array.prototype.my_filter = function (callback) {
  const res = []
  for (let i = 0; i < this.length; i++) {
    callback(this[i], i, this) && res.push(this[i])
  }
  return res
}
//验证
let newarr = arr.my_filter((item, index, arr) => {
  return item.age > 18
})
console.log(newarr);   [ { name: 'aa', age: 19 }, { name: 'cc', age: 21 } ]

4. reduce()

思路:

  • reduce()用于将数组中所有元素按指定的规则进行归并计算,返回一个最终值
  • reduce()接收两个参数:回调函数、初始值(可选)。
  • 回调函数中接收四个参数:初始值 或 存储上一次回调函数的返回值、每一项的值、下标、数组本身。
  • 若不提供初始值,则从第二项开始,并将第一个值作为第一次执行的返回值

代码实现:

const arr = [
  { name: 'zt', age: 18 },
  { name: 'aa', age: 19 },
  { name: 'bb', age: 18 },
  { name: 'cc', age: 21 },
]
Array.prototype.my_reduce = function (callback,...arg) {
  let pre,start=0
  if(arg.length){
    pre=arg[0]
  }
  else{
    pre=this[0]
    start=1
  }
  for (let i = start; i < this.length; i++) {
    pre=callback(pre,this[i], i, this)   
  }
  return pre
}
//验证
const sum = arr.my_reduce((pre, current, index, arr) => { 
  return pre+=current.age
},0)  
console.log(sum);  //76

5. fill()

思路:

  • fill()用于填充一个数组的所有元素,它会影响原数组 ,返回值为修改后原数组
  • fill()接收三个参数:填充的值、起始位置(默认为0)、结束位置(默认为this.length-1)。
  • 填充遵循左闭右开的原则
  • 不提供起始位置和结束位置时,默认填充整个数组

代码实现:

Array.prototype.my_fill = function (value,start,end) {
  if(!start&&start!==0){
    start=0
  }
  end=end||this.length
  for(let i=start;i<end;i++){
    this[i]=value
  }
  return this
}
//验证
const arr=new Array(7).my_fill('hh',null,3)  //往数组的某个位置开始填充到哪个位置,左闭右开
console.log(arr);   //[ 'hh', 'hh', 'hh', <4 empty items> ]

6. includes()

思路:

  • includes()用于判断数组中是否包含某个元素,返回值为 true 或 false
  • includes()提供第二个参数,支持从指定位置开始查找

代码实现:

const arr = ['a', 'b', 'c', 'd', 'e']
Array.prototype.my_includes = function (item,start) {
  if(start<0){start+=this.length}
  for (let i = start; i < this.length; i++) {
    if(this[i]===item){
      return true
    }
  }
  return false
}
//验证
const flag = arr.my_includes('c',3)  //查找的元素,从哪个下标开始查找
console.log(flag); //false

7. join()

思路:

  • join()用于将数组中的所有元素指定符号连接成一个字符串

代码实现:

const arr = ['a', 'b', 'c']
Array.prototype.my_join = function (s = ',') {
  let str = ''
  for (let i = 0; i < this.length; i++) {
    str += `${this[i]}${s}`
  }
  return str.slice(0, str.length - 1)
}
//验证
const str = arr.my_join(' ')
console.log(str);  //a b c

8. find()

思路:

  • find()用于返回数组中第一个满足条件元素,找不到返回undefined
  • find()的参数为一个回调函数

代码实现:

const arr = [
  { name: 'zt', age: 18 },
  { name: 'aa', age: 19 },
  { name: 'bb', age: 18 },
  { name: 'cc', age: 21 },
]
Array.prototype.my_find = function (callback) {
  for (let i = 0; i < this.length; i++) {
    if(callback(this[i], i, this)){
      return this[i]
    }
  }
  return undefined
}
//验证
let j = arr.my_find((item, index, arr) => {  
  return item.age > 19   
})
console.log(j);   //{ name: 'cc', age: 21 }

9. findIndex()

思路:

  • findIndex()用于返回数组中第一个满足条件索引,找不到返回-1
  • findIndex()的参数为一个回调函数

代码实现:

const arr = [
  { name: 'zt', age: 18 },
  { name: 'aa', age: 19 },
  { name: 'bb', age: 18 },
  { name: 'cc', age: 21 },
]
Array.prototype.my_findIndex = function (callback) {
  for (let i = 0; i < this.length; i++) {
    if(callback(this[i], i, this)){
      return i
    }
  }
  return -1
}
let j = arr.my_findIndex((item, index, arr) => {  
  return item.age > 19
})
console.log(j);  //3

10. some()

思路:

  • some()用来检测数组中的元素是否满足指定条件。
  • 有一个元素符合条件,则返回true,且后面的元素会再检测。

代码实现:

const arr = [
  { name: 'zt', age: 18 },
  { name: 'aa', age: 19 },
  { name: 'bb', age: 18 },
  { name: 'cc', age: 21 },
]
Array.prototype.my_some = function (callback) {
  for (let i = 0; i < this.length; i++) {
    if(callback(this[i], i, this)){
      return true
    }
  }
  return false
}
//验证
const flag = arr.some((item, index, arr) => {
  return item.age > 20
})
console.log(flag);  //true

11. every()

思路:

  • every() 用来检测所有元素是否都符合指定条件。
  • 有一个不满足条件,则返回false,后面的元素都会再执行。

代码实现:

const arr = [
  { name: 'zt', age: 18 },
  { name: 'aa', age: 19 },
  { name: 'bb', age: 18 },
  { name: 'cc', age: 21 },
]
Array.prototype.my_every = function (callback) {
  for (let i = 0; i < this.length; i++) {
    if(!callback(this[i], i, this)){
      return false
    }
  }
  return true
}
//验证
const flag = arr.my_every((item, index, arr) => {
  return item.age > 16
})
console.log(flag);  //true

七、数组去重

1. 双层for循环 + splice()

let arr = [1, 1, '1', '1', 2, 2, 2, 3, 2]
function unique(arr) {
    for (let i = 0; i < arr.length; i++) {
        for (let j = i + 1; j < arr.length; j++) {
            if (arr[i] === arr[j]) {
                arr.splice(j, 1)
                j--  //删除后j向前走了一位,下标需要减一,避免少遍历一位
            }
        }
    }
    return arr
 }
console.log(unique(arr)) //[ 1, '1', 2, 3 ]

2. 排序后做前后比较

let arr = [1, 1, '1', '1', 2, 2, 2, 3, 2]
function unique(arr) {
  let res = []
  let seen //记录上一次比较的值
  let newarr=[...arr]  //解构出来,开辟一个新数组
  newarr.sort((a,b)=>a-b)  //sort会影响原数组  n*logn
  for (let i = 0; i < newarr.length; i++) {
    if (newarr[i]!==seen) {
      res.push(newarr[i])
    }
    seen=newarr[i]
  }
  return res
}
console.log(unique(arr)) //[ 1, '1', 2, 3 ]

3. 借助include

let arr = [1, 1, '1', '1', 2, 2, 2, 3, 2]
function unique(arr) {
  let res = []
  for (let i = 0; i < arr.length; i++) {
      if(!res.includes(arr[i])){
        res.push(arr[i])
      }
  }
  return res
}
console.log(unique(arr)) //[ 1, '1', 2, 3 ]

4. 借助set

let arr = [1, 1, '1', '1', 2, 2, 2, 3, 2]
const res1 = Array.from(new Set(arr));
console.log(res1);   //[ 1, '1', 2, 3 ]

八、数组扁平化

1. 递归

let arr1 = [1, 2, [3, 4, [5],6]]
function flatter(arr) {
  let len = arr.length
  let result = []
  for (let i = 0; i < len; i++) {  //遍历数组每一项
    if (Array.isArray(arr[i])) {   //判断子项是否为数组并拼接起来
      result=result.concat(flatter(arr[i]))//是则使用递归继续扁平化
    }
    else {
      result.push(arr[i])  //不是则存入result
    }
  }
  return result
}
console.log(flatter(arr1))  //[ 1, 2, 3, 4, 5, 6 ]

2. 借助reduce (本质也是递归)

let arr1 = [1, 2, [3, 4, [5],6]]
const flatter = arr => {
  return arr.reduce((pre, cur) => {
    return pre.concat(Array.isArray(cur) ? flatten(cur) : cur);
  }, [])
}
console.log(flatter(arr1))  //[ 1, 2, 3, 4, 5, 6 ]

3. 借助正则

let arr1 = [1, 2, [3, 4, [5],6]]
const res = JSON.parse('[' + JSON.stringify(arr1).replace(/\[|\]/g, '') + ']');
console.log(res)  //[ 1, 2, 3, 4, 5, 6 ]

九、函数柯里化

思路:

  • 函数柯里化是只传递给函数一部分参数调用它,让它返回一个函数去处理剩下的参数
  • 传入的参数大于等于原始函数fn的参数个数,则直接执行该函数,小于则继续对当前函数进行柯里化,返回一个接受所有参数(当前参数和剩余参数) 的函数

代码实现:

const my_curry = (fn, ...args) => 
  args.length >= fn.length 
  ? fn(...args) 
  : (...args1) => curry(fn, ...args, ...args1);
function adder(x, y, z) {
    return x + y + z;
}
const add = my_curry(adder);
console.log(add(1, 2, 3));  //6
console.log(add(1)(2)(3));  //6
console.log(add(1, 2)(3));  //6
console.log(add(1)(2, 3));  //6

十、new方法

思路:

  • new方法主要分为四步:
    (1) 创建一个新对象
    (2) 将构造函数中的this指向该对象
    (3) 执行构造函数中的代码(为这个新对象添加属性
    (4) 返回新对象
function _new(obj, ...rest){
    // 基于obj的原型创建一个新的对象
    const newObj = Object.create(obj.prototype);
    // 添加属性到新创建的newObj上, 并获取obj函数执行的结果.
    const result = obj.apply(newObj, rest);
    // 如果执行结果有返回值并且是一个对象, 返回执行的结果, 否则, 返回新创建的对象
    return typeof result === 'object' ? result : newObj;
}

总结不易,动动手指给个赞吧!💗

前端面试题库 (面试必备)            推荐:★★★★★

地址:前端面试题库

相关文章
|
14天前
|
JavaScript
短小精悍的js代码
【10月更文挑战第17天】
116 58
|
24天前
|
JavaScript 前端开发 开发者
如何在 Visual Studio Code (VSCode) 中使用 ESLint 和 Prettier 来检查代码规范并自动格式化 Vue.js 代码。
【10月更文挑战第7天】随着前端开发技术的快速发展,代码规范和格式化工具变得尤为重要。本文介绍了如何在 Visual Studio Code (VSCode) 中使用 ESLint 和 Prettier 来检查代码规范并自动格式化 Vue.js 代码。通过安装和配置这两个工具,可以确保代码风格一致,提升团队协作效率和代码质量。
185 2
|
1天前
|
JavaScript
原生js炫酷随机抽奖中奖效果代码
原生js随机抽奖是一个炫酷的根据数据随机抽奖的代码,该网页可进行随机抽取一个数据,页面动画高科技、炫酷感觉的随机抽奖效果,简单好用,欢迎下载!
9 3
原生js炫酷随机抽奖中奖效果代码
|
1天前
|
JSON JavaScript 前端开发
[JS]面试官:你的简历上写着熟悉jsonp,那你说说它的底层逻辑是怎样的?
本文介绍了JSONP的工作原理及其在解决跨域请求中的应用。首先解释了同源策略的概念,然后通过多个示例详细阐述了JSONP如何通过动态解释服务端返回的JavaScript脚本来实现跨域数据交互。文章还探讨了使用jQuery的`$.ajax`方法封装JSONP请求的方式,并提供了具体的代码示例。最后,通过一个更复杂的示例展示了如何处理JSON格式的响应数据。
[JS]面试官:你的简历上写着熟悉jsonp,那你说说它的底层逻辑是怎样的?
|
6天前
|
JavaScript 前端开发 开发者
如何在 Visual Studio Code (VSCode) 中使用 ESLint 和 Prettier 检查代码规范并自动格式化 Vue.js 代码,包括安装插件、配置 ESLint 和 Prettier 以及 VSCode 设置的具体步骤
随着前端开发技术的快速发展,代码规范和格式化工具变得尤为重要。本文介绍了如何在 Visual Studio Code (VSCode) 中使用 ESLint 和 Prettier 检查代码规范并自动格式化 Vue.js 代码,包括安装插件、配置 ESLint 和 Prettier 以及 VSCode 设置的具体步骤。通过这些工具,可以显著提升编码效率和代码质量。
73 4
|
8天前
|
JSON 移动开发 数据格式
html5+css3+js移动端带歌词音乐播放器代码
音乐播放器特效是一款html5+css3+js制作的手机移动端音乐播放器代码,带歌词显示。包括支持单曲循环,歌词显示,歌曲搜索,音量控制,列表循环等功能。利用json获取音乐歌单和歌词,基于html5 audio属性手机音乐播放器代码。
46 6
|
4天前
|
JavaScript 前端开发 开发者
如何在 Visual Studio Code (VSCode) 中使用 ESLint 和 Prettier 检查代码规范并自动格式化 Vue.js 代码
随着前端开发技术的快速发展,代码规范和格式化工具变得尤为重要。本文介绍如何在 Visual Studio Code (VSCode) 中使用 ESLint 和 Prettier 检查代码规范并自动格式化 Vue.js 代码。通过安装和配置这些工具,可以确保代码风格一致,提高代码质量和可读性。
14 1
|
22天前
|
JavaScript 前端开发 开发者
如何在 VSCode 中使用 ESLint 和 Prettier 检查并自动格式化 Vue.js 代码,提升团队协作效率和代码质量。
【10月更文挑战第9天】随着前端开发技术的发展,代码规范和格式化工具变得至关重要。本文介绍如何在 VSCode 中使用 ESLint 和 Prettier 检查并自动格式化 Vue.js 代码,提升团队协作效率和代码质量。通过安装插件、配置 ESLint 和 Prettier,以及设置 VSCode,实现代码实时检查和格式化,确保代码风格一致。
18 2
|
23天前
|
JavaScript 前端开发 开发者
如何在 Visual Studio Code (VSCode) 中使用 ESLint 和 Prettier 检查并自动格式化 Vue.js 代码,提升代码质量和团队协作效率。
【10月更文挑战第8天】本文介绍了如何在 Visual Studio Code (VSCode) 中使用 ESLint 和 Prettier 检查并自动格式化 Vue.js 代码,提升代码质量和团队协作效率。通过安装 VSCode 插件、配置 ESLint 和 Prettier,实现代码规范检查和自动格式化,确保代码风格一致,提高可读性和维护性。
32 2
|
19天前
|
Web App开发 JavaScript 前端开发
前端Node.js面试题
前端Node.js面试题