JavaScript-常用手写题总结

94 阅读7分钟

1. 数组去重

let arr=[1,2,3,4,4,3,4,6,1,5]

//方法一:new Set()
function unique(arr){
    return [...new Set(arr)]  
   //return Array.from(new Set(arr)) new Set()方法返回的是类数组,使用Array.from转换为数组
}

//方法二:filter+indexOf
function unique(arr){
    let result = arr.filter(function(item,index,arr){
        return arr.indexOf(item) !== index
    })
    return result
}

//方法三:for+includes
function unique(arr){
    let newArr=[]
    for(let i=0;i<arr.length;i++){
        if(!newArr.includes(arr[i])){
            newArr.push(arr[i])
        }
    }
    return newArr
}

//方法四:sort
function unique(arr){
    let newArr=[]
    arr.sort()
    for(let i=0;i<arr.length;i++){
        if(arr[i]!==arr[i+1]){
            newArr.push(arr[i])
        }
    }
    return newArr
}

2. 数组扁平化

  • 方法一:Array.flat()
    优点:代码简单,使用内置方法,性能较好
    缺点:不支持ES2019的环境
let arr=[1,2,3,[4,5,[6,[7]]]]
let _arr=arr.flat(Infinity) //Infinity展开任意深度的嵌套数组
  • 方法二:for循环递归
    优点:代码简单,方便易懂
    缺点:存在性能问题,对于非常大的数组和嵌套很深的数组,递归可能导致堆栈溢出
let arr=[1,2,3,[4,5,[6,[7]]]]
function flatten(arr){
    let flatArr=[]
    arr.forEach((item)=>{
        if(Array.isArray(item)){
            flatArr=flatArr.concat(flatten(item))
        }else{
            flatArr.push(item)
        }
    })
    return flatArr
}
  • 方法三:toString和split
    优点:适用于简单的扁平化需求
    缺点:对于包含字符串和对象元素的数组,可能会出现不符合预期的结果
function flatten(arr){
    return arr.toString().split(',').map(item=>+item)
}
  • 方法四:使用正则表达式+JSON
    优点:适用于简单的扁平化需求
    缺点:对于包含字符串和对象元素的数组,可能会出现不符合预期的结果
function flatten(arr){
    return JSON.parse('['+JSON.stringify(arr).replace(/\[|\]/g,'')+']')
}
  • 方法五:扩展运算符和递归
    优点:结合了扩展运算符和递归的优点
    缺点:存在性能问题,对于非常大的数组和嵌套很深的数组,递归可能导致堆栈溢出
function flatten(arr){
    return [].concat(...arr.map(item=>Array.isArray(item)?flatten(item):item))
}

3. 发布订阅

class eventBus{
    constructor(){
        this.events={}
    }
    on(eventName,callback){
        if(!this.events[eventName]){
            this.events[eventName]=[]
        }
        this.events[eventName].push(callback)
    }
}

4. 深浅拷贝

  • 浅拷贝:拷贝基础数据类型时,不受任何影响,拷贝引用数据类型时,拷贝的是堆内存中的地址指针,所以修改拷贝对象,原对象也会被修改
  • 深拷贝:深拷贝就是在堆内存中开辟新的内存区域,赋值一份新的值,修改拷贝对象时原对象不会被影响
  • 浅拷贝的常用方法
  1. 赋值
  2. Object.assign(target,origin)
  3. ES6扩展运算符
let person = {
  name: ' tom ',
  appearance: {
    tall: 180,
    skin: 'yellow',
    weight: 76
  }
}

//赋值
let p1 = person
p1.tall = 192
console.log(p1.tall) //192

//Object.assign() 当属性值是一个引用类型时,是浅拷贝
let p1 = Object.assign({}, person)
p1.appearance.tall = 192
console.log(person.appearance.tall) //192

//扩展运算符
let p1={...person}
p1.appearance.tall = 192
console.log(person.appearance.tall) //192
  • 深拷贝的常用方法
  1. JSON.parse(JSON.stringify(obj))
  • JOSN.stringify方法无法处理一些特殊的对象,如RegExp,Error等,序列化的结果为空对象
  • 属性值为日期对象,序列化的值为字符串
  • 属性值为函数,Symbol或undefined,序列化时会将函数,Symbol和undefined丢失
  • 属性值为NaN,infinity或-infinity,序列化结果为null
  • JOSN.stringify()只能序列化对象的可枚举属性,如果属性值为构造函数的实例,序列化后将丢弃对象的constructor属性
function Person(name, age) {
  this.name = name
  this.age = age
}
let p1 = new Person('lili', 28)
let data = {
  data1: new Date('2023-10-24'),
  data2: new RegExp('\\w+'),
  data3: undefined,
  data4: function () {
    console.log('Hello world')
  },
  data5: NaN,
  data6: Infinity,
  data7: Symbol('123'),
  data8: p1
}
let jsonData = JSON.parse(JSON.stringify(data))
jsonData.data8.constructor == Person //false
jsonData.data8.constructor == Object //true
console.log(jsonData)
  1. 手写深拷贝
function deepClone(obj){
    if(typeof obj !== 'object' || obj === null){
        return obj
    }
    let newObj=Array.isArray(obj)?[]:{}
    for(let key in obj){
        if(obj.hasOwnProperty(key)){
            newObj[key]=typeof obj[key] === 'object'?deepClone(obj[key]):obj[key]
        }
    }
    return newObj
}

5. 字符串模板

function template(strings, ...values) {  
    let result = strings[0];  
  
    for (let i = 0; i < values.length; i++) {  
        result += values[i] + strings[i + 1];  
    }  
  
    return result;  
}  
  
// 使用示例  
const name = 'Alice';  
const age = 30;  
const greeting = template`Hello, my name is ${name} and I am ${age} years old.`;  
  
console.log(greeting);  // 输出: Hello, my name is Alice and I am 30 years old.

6. 函数防抖

触发高频事件,N秒后只会执行一次,N秒内再次触发,将会重新计算(将多次执行变成最后一次执行)

function debounce(fn, delay) {
  let timer = null
  return function () {
    let _args = arguments
    if (timer) {
      clearTimeout(timer)
      timer = null
    }
    timer = setTimeout(() => {
      fn.apply(this, _args)
    }, delay)
  }
}

function test(text) {
  alert(`Hello World ${text}`)
}
let testScrollFn = debounce(test, 500)
window.onscroll = function () {
  testScrollFn('lili')
}

7. 函数节流

触发高频事件,N秒内只会执行一次

//方法一:时间戳来实现
function throttle(fn,wait){
    let previous=0
    return function(){
        let now=+new Date()
        let args=arguments
        if(now-previous>wait){
            fn.apply(this,args)
            previous=now
        }
    }
}

//方法二:定时器来实现
function throttle(fn,wait){
    let timer=null
    return function(){
        if(timer){
            return
        }
        let context=this
        let args=arguments
        timer=setTimeout(()=>{
            fn.apply(context,args)
            timer=null
        },wait)
    }
}

let testThrottle = throttle(test, 500)
function test(text) {
  alert(`Hello World ${text}`)
}
window.onscroll = function () {
  testThrottle('lili')
}

8. AJAX

  • 优缺点
    优点:可以无需刷新页面与服务器端进行通信,根据用户事件来更新部分页面内容
    缺点:没有浏览历史,无法回退,存在跨域问题
  • 发送Ajax请求的五个步骤
  1. 创建异步对象,即XMLHttpRequest对象
  2. 使用open方法设置请求参数open(method,url,async)(method:方法;url:请求url;async:是否异步)
  3. 发送请求:send()
  4. 注册onreadystatechange事件,状态改变时就会调用
  5. 服务器端响应,获取返回的数据
function ajaxFn({method,url,data}){
    return new Promise((resolve,reject)=>{
        const xhr=XMLHttpRequest?new XMLHttpRequest():new ActiveXObject('Microsoft.XMLHTTP');
        xhr.send(method,url,)
    })
}

9. new关键字

1. 创建一个新对象
2. 继承原型,将新对象的__proto__属性指向构造函数的prototype属性
3. 将构造函数的this,指向新的对象
4. 执行构造函数中的代码,为这个新的对象添加属性和方法
5. 如果构造函数返回非空对象,则返回该对象,否则返回新对象

function mynew(func,...args){
    //创建一个新对象
    let obj={}
    //将新对象的__proto__属性指向构造函数的prototype属性
    Object.setPrototypeOf(obj,func.prototype)
    //将构造函数的this指向新对象,并执行构造函数中的代码
    let result=func.apply(obj,args)
    //如果构造函数返回的为非空对象,则返回该对象,否则返回新对象
    return result instanceof Object ? result:obj
}

//测试一下
function Person(name,age){
    this.name=name
    this.age=age
}
Person.prototype.getInfo=function(){
    console.log(`姓名:${this.name};年龄:${this.age}`)
}
let p1 = mynew(Person,'lili',28) //{name: 'lili', age: 28}

10. instanceof

function Person(){}
let p1=new Person()
p1.__proto__==Person.prototype //true

//方法一:
function instanceOfFn(left,right){
    if(typeof left!==null&&(typeof left==='object'||typeof left==='function')){
        if(left.__proto__==right.prototype){
            return true
        }else{
            if(left.__proto__===null){
                return false
            }
            return instanceOfFn(left.__proto__.__proto__,right)
        }
    }else{
        return false
    }
}

//方法二:
function instanceofFn2(left, right) {
  if (left !=== null && (typeof left === 'object' || typeof left === 'function')) {
    let proto = Object.getPrototypeOf(left)
    while (true) {
      if (proto == null) return false
      if (proto == right.prototype) return true
      proto = Object.getPrototypeOf(proto)
    }
  } else {
    return false
  }
}

11. Object.create

Object.create()方法用于创建一个新对象,使用现有的对象作为新对象的原型

Object.createFn=function(obj){
    if(typeof obj !== 'object' && typeof obj !== 'function'){
        throw new TypeError('Object prototype may only be an Object or null')
    }
    function Func(){}
    Func.prototype=obj
    Func.prototype.constructor=F
    return new Func()
}

const person={getInfo:function(){console.log(`My name is ${this.name},My age is ${this.age}`)}}
let 

12. 继承

  • 原型链继承

问题1:原型中包含的引用类型属性将被所有实例共享
问题2:子类在实例化的时候,无法给父类的构造函数传参

function Person(name,age){
    this.name=name
    this.age=age
    this.des={nationality:'China',skinColor:'Yellow'}
}
Person.prototype.getBaseInfo=function(){
    return `姓名:${this.name},年龄:${this.age},身高:${this.tall},语言:${this.des.language}`
}
function Child(tall){
    this.tall=tall
}

Child.prototype=new Person()

let child1=new Child('lili',28) 
child1.des.language='Chinese'
child1.getBaseInfo() //'姓名:undefined,年龄:undefined,身高:lili,语言:Chinese'
  • 借用构造函数实现继承

优点:解决了引用类型共享和构造函数传参
问题1:由于方法定义在子类构造函数中,所以每次创建子类实例时,都会调用一次父类方法 问题2:只能继承父类的实例属性和方法,不能继承原型属性和方法

function Person(name,age){
    this.name=name
    this.age=age
    this.des={nationality:'China',skinColor:'Yellow'}
}
Person.prototype.getBaseInfo=function(){
    return `姓名:${this.name},年龄:${this.age},身高:${this.tall},语言:${this.des.language}`
}
function Child(name,age,tall){
    this.tall=tall
    Person.call(this,name,age)
}

let child1=new Child('lili',28,'168cm') 
child1.des.language='Chinese'
let child2=new Child('tom',30,'180cm')
child2.getBaseInfo() //Uncaught TypeError: child2.getBaseInfo is not a function
  • 组合继承

优点:集合了原型链和借用构造函数的优点
问题:调用了两次父类构造函数,生成了两份实例(第一次:new Person();第二次:Person.call(this,name,age))
实现思路:使用原型链继承原型链上的方法,使用借用构造函数继承实例属性,把方法定义在原型上得以重用,又可以让每个实例都有自己的属性

function Person(name,age){
    this.name=name
    this.age=age
    this.des={nationality:'China',skinColor:'Yellow'}
}
Person.prototype.getBaseInfo=function(){
    return `姓名:${this.name},年龄:${this.age},身高:${this.tall},语言:${this.des.language}`
}
function Child(name,age,tall){
    this.tall=tall
    Person.call(this,name,age)
}

Child.prototype=new Person() 
Child.prototype.constructor=Child

let child1=new Child('lili',28,'168cm') 
child1.des.language='Chinese'
let child2=new Child('tom',30,'180cm')
child2.getBaseInfo() //'姓名:tom,年龄:30,身高:180cm,语言:undefined'
  • 寄生组合继承

优点:解决了调用了两次父类构造函数,生成了两份实例的问题

function Person(name,age){
    this.name=name
    this.age=age
    this.des={nationality:'China',skinColor:'Yellow'}
}
Person.prototype.getBaseInfo=function(){
    return `姓名:${this.name},年龄:${this.age},身高:${this.tall},语言:${this.des.language}`
}
function Child(name,age,tall){
    this.tall=tall
    Person.call(this,name,age)
}

Child.prototype=Object.create(Person.prototype) //核心语法
Child.prototype.constructor=Child

let child1=new Child('lili',28,'168cm') 
child1.des.language='Chinese'
let child2=new Child('tom',30,'180cm')
child2.getBaseInfo() //'姓名:tom,年龄:30,身高:180cm,语言:undefined'
  • Class继承
class Person{
    constructor(name,age){
        this.name=name
        this.age=age
        this.des={nationality:'China',skinColor:'Yellow'}
    }
    getBaseInfo(){
        return `姓名:${this.name},年龄:${this.age},身高:${this.tall},语言:${this.des.language}`
    }
}

class Child extends Person{
    constructor(name,age,tall){
        super(name,age)
        this.tall=tall
    }
}

let child1=new Child('lili',28,'168cm') 
child1.des.language='Chinese'
let child2=new Child('tom',30,'180cm')
child2.getBaseInfo() //'姓名:tom,年龄:30,身高:180cm,语言:undefined'