手写代码题

97 阅读11分钟

手写代码题

Vue面试题
JavaScript面试题

手写call

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    
</body>
<script>
    // call 函数的实现步骤:
    //     1. 判断调用对象是否为函数,即使我们是定义在函数的原型上的,但是可能出现使用 call 等方式调用的情况。
    //     2. 判断传入上下文对象是否存在,如果不存在,则设置为 window 。
    //     3. 处理传入的参数,截取第一个参数后的所有参数。
    //     4. 将函数作为上下文对象的一个属性。
    //     5. 使用上下文对象来调用这个方法,并保存返回结果。
    //     6. 删除刚才新增的属性。
    //     7. 返回结果。

    Function.prototype.myCall = function(context) {
        // 判断调用对象
        if(typeof this !== "function"){
            console.error("type error");
        }
        // 获取参数
        let args = [...arguments].slice(1)
        let result = null 
        // 判断context是否传入,如果未传入则设置为window
        context = context || window
        // 将调用函数设为对象的方法
        context.fn = this 
        // 调用函数
        result = context.fn(...args)
        // 将属性删除
        delete context.fn 
        return result
    }

    // 测试
    let test = {
        name: 'alex',
        hello() {
            console.log(this);
            console.log(this.name);
        }
    }

    let obj = {name:'demo'}

    test.hello()

    test.hello.myCall(obj)

</script>
</html>

手写apply

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    
</body>

<script>
    // apply 函数的实现步骤:
    //     1. 判断调用对象是否为函数,即使我们是定义在函数的原型上的,但是可能出现使用 call 等方式调用的情况。
    //     2. 判断传入上下文对象是否存在,如果不存在,则设置为 window 。
    //     3. 将函数作为上下文对象的一个属性。
    //     4. 判断参数值是否传入
    //     5. 使用上下文对象来调用这个方法,并保存返回结果。
    //     6. 删除刚才新增的属性
    //     7. 返回结果

    Function.prototype.myApply = function(context) {
        // 判断调用对象是否为函数
        if(typeof this !== "function") {
            console.error("type error");
        }
        let result = null 
        // 判断 context是否存在,如果未传则为window
        context = context || window
        // 将函数设为对象的方法
        context.fn = this
        // 调用方法
        if(arguments[1]) {
            result = context.fn(...arguments[1])
        } else {
            result = context.fn()
        }
        // 将属性删除
        delete context.fn 
        return result
    }

    // 测试
    let test = {
        name: 'alex',
        hello() {
            console.log(this);
            console.log(this.name);
        }
    }

    let obj = {name:'haha'}

    test.hello()

    test.hello.myApply(obj)
</script>
</html>

手写new

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    
</body>

<script>
    function myNew(constructor,...args) {
        // 1、创建一个新的空对象
        const obj = {}
        // 2、将新对象的原型链接到构造函数的prototype对象
        obj.__proto__ = constructor.prototype
        // 3、将这个新对象作为this上下文,并调用构造函数
        const result = constructor.apply(obj,args)
        // 4、如果构造函数返回的是一个对象,则返回这个对象,否则返回新创建的对象
        return result instanceof Object? result:obj 
    }

    // 测试
    function Person(name,age) {
        this.name = name 
        this.age = age 
    } 

    Person.prototype.sayHello = function() {
        console.log(`hello,我是${this.name},我已经${this.age}岁了`);
    }

    const person = myNew(Person,'haha',20)
    person.sayHello()
</script>
</html>

防抖

function debounce(fn,wait) { // fn是要执行的函数
  let timer // 创建一个定时器
  return function(...args) {
    // 相当于把前n-1次的操作都无效化了
    clearTimeout(timer)  // 每次调用时,先清除之前的定时器

    let _this = this // 一定要处理好this指向和传进fn的参数
    timer = setTimeout(function() {
      fn.apply(_this, args)
    }, wait) // wait是设置防抖的延迟时间
  }
}

// 测试防抖函数是否成功
// 原始函数(假设是输入框的搜索请求)
function search(query) {
console.log(`Searching for: ${query}`);
}

// 创建防抖版本(等待用户停止输入3000ms后执行)
const debouncedSearch = debounce(search, 3000);

// 模拟连续触发
debouncedSearch("a");  // 被取消
debouncedSearch("ab"); // 被取消
debouncedSearch("abc");// 3000ms后执行:Searching for: abc

节流

function throttle(fn, delay) { // fn是要执行的函数 delay是延迟时间
	let prevTime = Date.now()
  return function() {
    let curTime = Date.now()
    // 判断上次触发时间与这次触发时间的差值是否大于延迟时间
    if (curTime - prevTime > delay) {
      let _this = this
      fn.apply(_this, arguments) // 相当于fn.call(_this, ...arguments)
      prevTime = Date.now() // 刷新时间戳
    }
  }
}

// 测试节流
const myEfficientFn = throttle(function () {
    // 需要节流执行的函数
    console.log('节流');
}, 2000);

window.addEventListener('mousemove', myEfficientFn);

数组去重

  • set
let arr = [6,8,4,5,6,7]
let set = [...new Set(arr)]
console.log(set);
  • indexOf
let arr = [6,8,4,5,6,7]
let newArr = []
arr.forEach((item,index) => {
    if(newArr.indexOf(item) == -1) {
        newArr.push(item)
    }
})
console.log(newArr);
  • 双重for循环去重
// 双重for循环去重
let arr = [6,8,3,5,8,7,8,3,2,8]
for(let i = 0; i < arr.length; i++) {
    for(j = i + 1; j < arr.length; j++) {
        if(arr[i] == arr[j]) {
            arr.splice(j, 1)
        }
    }
}
console.log(arr);

数组排序

  • 冒泡排序
// 1.先遍历数组,让挨着的两个进行比较,如果前一个比后一个大,那就交换位置
// 2.遍历一遍后,最后一个数字就是最大的
// 3.然后进行二次遍历,还是按照之前的规则,第二大的数字会在倒数第二位
// 4.一直到整个数组排序完毕
const arr = [5,6,5,58,2,8,5,8]
for(let i = 0; i < arr.length; i++) {
    for(let j = 0; j < arr.length; j++) {
        if(arr[j] > arr[j+1]) {
            let temp;
            temp = arr[j]
            arr[j] = arr[j+1]
            arr[j+1] = temp
        }
        console.log(j,arr);
    }
}
  • sort()排序
// 参数:function(a, b){}   a前一个元素,b后一个元素
const arr = [5,6,5,58,2,8,5,8]
const y = arr.sort((a,b) => {
    return a - b
})

console.log(y);
  • 插入排序
// 假设前面n的元素是已经排好序,将第n+1个元素插入到前面已经排好的序列中
let arr = [5,4,3,2,1]
for(let i =0; i< arr.length; i++) {
    for(let n = i; n>=0; n--) {
        if(arr[n]>arr[n+1]){
            let temp;
            temp = arr[n]
            arr[n] = arr[n+1]
            arr[n+1] = temp
            console.log('i='+ i,arr);
        }
        // [4,5,3,2,1] [4,3,5,2,1] [3,4,5,2,1]....
    }
}

js找出一个字符串中出现次数最多的字符,并统计次数

const getMaxText = (str) => {
  // 将字符串转成数组
  let arr = [...str]
  // 新建map结构
  let myMap = new Map()
  // 按照规则存放数据到map结构中
  for(let i = 0; i < arr.length; i++) {
    if(myMap.has(arr[i])) {
      myMap.set(arr[i],myMap.get(arr[i]) + 1)
    } else {
      myMap.set(arr[i],1)
    }
  }
  let max = 0
  // 找出出现最多的次数为多少
  for(const value of myMap.values()){
    if(value > max) {
      max = value
    }
  }
  console.log(max);
  for(const [key,value] of myMap.entries()) {
    if(value === max) {
      console.log(key);
    }
  }
}
getMaxText("hjhjkkhuihkyguu164tyyf")

手写ajax

  1. 创建一个ajax实例对象(XMLHttpRequest对象) -- 联系一个快递员
  2. 设置相关的属性, 如url和method -- 填写快递信息
  3. 通过实例对象的open方法将设置好的属性给传进去 -- 给钱
  4. 通过实例对象的send方法将请求发送出去 -- 发货
  5. 给实例对象设置好相应的事件监听 -- 等待收货
  6. 通过实例对象的readyState和status属性综合判断数据是否成功响应 -- 验货
// 如何邮寄快递 => ajax的流程
// 1.联系快递员
    // 创建ajax对象 
    let xhr = new XMLHttpRequest() 
// 2.填写快递地址 和 方式
    // 地址
    // let url = 'xxx'
    // 这个例子参数有2个    _limit限制当前返回的数据多少条      _page
    // let url = 'http://jsonplaceholder.typicode.com/posts'
    let url = 'http://jsonplaceholder.typicode.com/posts?_limit=10&_page=1'

    // 方式
    let method = 'get'
// 3.给钱
    xhr.open(method,url)
// 4.发货
    xhr.send()
// 5.等待收货 => 事件readystatechange => 监听数据回来了
    xhr.addEventListener('readystatechange',function() {
        // 6.验货 xhr.status    xhr.readyState
        if(xhr.readyState == 4) {
            // xhr.readyState   ajax的状态机
            // 0  初始化    还没有调用open()
            // 1  启动      已经调用了open(),还没掉用send
            // 2  发送      已经调用send,但还没有数据的响应,事件监听没有触发
            // 3  接收      数据已经接收了一部分,还不完整
            // 4  完成      数据才是完全接收完毕
            if(xhr.status == 200) {
                // http的状态码 => 200 http成功返回
                // 进入这个分支,表示数据成功回来了
                })
            }
        }
    })

js将数字每千分位用逗号隔开

let num = 1234567.89
let newNum = num.toLocaleString()
console.log(newNum)

手写Promise

const PENDING = "pending"
const RESOLVED = "resolved"
const REJECTED = "rejected"

function MyPromise(fn) {
    // 保存初始化状态
    let self = this 
    // 初始化状态
    this.state = PENDING
    // 用于保存 resolve 或者 rejected 传入的值
    this.value = null 
    // 用于保存resolve的回调函数
    this.resolvedCallbacks = []
    // 用于保存reject的回调函数
    this.rejectedCallbacks = []
    // 状态转变为resolve方法
    function resolve(value) {
        // 判断传入元素是否为Promise值,
        // 如果是,则状态改变必须等待前一个状态改变后再进行改变
        if(value instanceof MyPromise) {
            return value.then(resolve,reject)
        }

        // 保证代码的执行顺序为本轮事件循环的末尾
        setTimeout(() => {
            // 只有状态为pending时才能转变
            if(self.state == PENDING) {
                // 修改状态
                self.state = RESOLVED
                // 设置传入的值
                self.value = value
                // 执行回调函数
                self.resolvedCallbacks.forEach((callback) => {
                    callback(value)
                })
            }
        },0)
    }

    // 状态转变为rejected方法
    function reject(value) {
        // 保证代码的执行顺序为本轮事件循环的末尾
        setTimeout(() => {
            // 只有状态为pending时才能转变
            if(self.state == PENDING) {
                // 修改状态
                self.state = REJECTED
                // 修改传入的值
                self.value = value
                // 执行回调函数
                self.rejectedCallbacks.forEach((callback) => {
                    callback(value)
                })
            }
        },0)
    }

    // 将两个方法传入函数执行
    try {
        fn(resolve,reject)
    } catch(e) {
        // 遇到错误时,捕获错误,执行reject函数
        reject(e)
    }
}

// .then方法实现
MyPromise.prototype.then = function(onResolved,onRejected) {
    const promise2 = new MyPromise((resolve,reject) => {
        // 封装回调执行逻辑,并在其中调用resolve/reject
        // 首先判断两个参数是否为函数类型,因为这两个参数是可选参数
        onResolved = typeof onResolved === "function" ? onResolved : function(value) {
            return value
        }

        onRejected = typeof onRejected === "function" ? onRejected : function(error) {
            throw error
        }

        // 如果是等待状态,则将函数加入对应列表中
        if(this.state === PENDING) {
            this.resolvedCallbacks.push(onResolved)
            this.rejectedCallbacks.push(onRejected)
        }

        // 如果状态已经凝固,则直接执行对应状态的函数
        if(this.state === RESOLVED) {
            setTimeout(() => {
                onResolved(this.value)
            },0)
        }

        if(this.state === REJECTED) {
            setTimeout(() => {
                onRejected(this.value)
            },0)
        }
    })
    // 返回新Promise
    return promise2
}

Promise红绿灯案例

function red() {
    console.log('red');
}

function green() {
    console.log('green');
}

function yellow() {
    console.log('yellow');
}

function sleep(fn,delay) {
    return new Promise((resolve,reject) => {
        setTimeout(() => {
            fn()
            // 让fn先做完自己的事情,才做后面事情
            resolve()
        },delay)
    })
}

function main() {
    Promise.resolve()
    .then(() => {
        return sleep(red,1000)
    })
    .then(() => {
        return sleep(yellow,2000)
    })
    .then(() => {
        return sleep(green,3000)
    })
    .then(() => {
        main()
    })
}
main()

Promise实现图片的异步加载

        let imgSrc = 'https://tse3-mm.cn.bing.net/th/id/OIP-C.6szqS1IlGtWsaiHQUtUOVwHaQC?rs=1&pid=ImgDetMain'
        function loadImg(src) {
            // 图片的地址不一定是渲染的时候才加载
            // 图片的加载需要时间 如果网络慢或图片太大 导致加载的时间可能很长
            // 当大量这种并行的操作一起发生 会影响浏览器性能

            // 通常用promise包装图片加载
            return new Promise((resolve, reject) => {
                let img = new Image() // new Image() 等同于创建了一个新的img节点
                img.src = src
                img.onload = function () {
                    // 图片加载成功时触发
                    resolve(img)
                }
            })
        }

        loadImg(imgSrc).then((res) => {
            // 拿到节点后就能进行dom操作
            console.log(res)
            document.body.appendChild(res)
        })  

使用Promise封装ajax

function ajaxRequest(url, method = 'GET', data = null) {  
    return new Promise((resolve, reject) => {  
        const xhr = new XMLHttpRequest();  
        xhr.open(method, url, true);  
  
        // 设置请求头  
        if (method === 'POST') {  
            xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');  
        }  
  
        xhr.onload = function() {  
            if (this.status === 200) {  
                resolve(this.responseText);  
            } else {  
                reject(new Error(this.statusText));  
            }  
        };  
  
        xhr.onerror = function() {  
            reject(new Error('Network Error'));  
        };  
  
        if (data) {  
            xhr.send(data);  
        } else {  
            xhr.send();  
        }  
    });  
}  
  
// 使用示例  
ajaxRequest('https://api.example.com/data', 'GET')  
    .then(response => {  
        console.log(response);  
    })  
    .catch(error => {  
        console.error('Error:', error);  
    });

手写深浅拷贝

  • 浅拷贝
let a = 1
let obj = {
    b:1
}

// 方式一:Object.create()
// 该方法的作用:在内存中开辟新的栈内存空间
// 参数:就会把对象中的属性和属性值拷贝
let newObj = Object.create(obj)
newObj.b = 2
console.log(obj.b);
console.log(newObj.b);

// 方式二:Object.assign({},)
// 该方法的作用:对象的合并
// 注意:后面的对象传递的只是数值,第一个参数是返回值的地址
// 参数:第一个参数是新对象,第二个参数是旧对象
let newObj2 = Object.assign({},obj)
newObj2.b = 2 
console.log(obj.b);
console.log(newObj2.b);
  • 深拷贝
    • 思路

      1. 遍历对象属性,判断属性值是不是简单数据类型
      2. 复杂([] 或 {})
      3. 如果是简单数据类型,直接复制。如果是复杂数据类型,就递归
      4. 定制出口(出口的定义:不是复杂数据类型)
      5. 注意:!null !false 注意null特殊值
const obj = {
    a:2,
    b:{
        c:3
    },
    arr:[1,2,4,5]
}

function deepClone(obj) {
    if(!obj || typeof obj !== 'object') {
        return {}
    }
    const newObj = Array.isArray ? []:{}
    for(let key in obj) {
        const value = obj[key]
        if(typeof value == 'object') {
            newObj[key] = deepClone(value)
        } else {
            // 是简单数据类型,直接将obj的key复制给newObj[key]
            newObj[key] = value
        }
    }
    return newObj
}   

// 测试深拷贝
const newObj = deepClone(obj)
console.log(newObj.b.c);

交换a,b的值

let a = 5;
let b = 22;

// 方法一:临时变量
let temp = a
a = b 
b = temp 

console.log(a);
console.log(b);

// 方法二:解构赋值
[a, b] = [b, a];  
console.log(a); 
console.log(b); 

实现数组元素就和

let arr = [12,5,5,9,7]
// 参数:function(累加数, 元素,下标){}累加数初始值
// 返回值: 总和
let newArr = arr.reduce((prev,cur,index) => {
    return prev + cur
},0)
console.log(newArr);

实现数组的扁平化

// 方法一:递归
function flattenArray(arr) {
    let result = []
    for(let i = 0; i < arr.length; i++) {
        if(Array.isArray(arr[i])) {
            result = result.concat(flattenArray(arr[i]))
        } else {
            result.push(arr[i])
        }
    }
    return result
}

let nestedArray = [1, [2, [3, [4]], 5]];  
console.log(flattenArray(nestedArray)); // 输出 [1, 2, 3, 4, 5]

// 方法二:使用 Array.prototype.flat(ES6+)
// 直接使用内置方法,可指定扁平化深度(默认深度为 1,Infinity 表示完全扁平化):
const arr = [1, [2, [3]]];
const flattened = arr.flat(Infinity);
console.log(flattened); // [1, 2, 3]

实现伪数组转为数组

// 方法一:Array.from()
// 示例:将 arguments 对象转为数组
function example() {
    // 1. 使用 Array.from() 将类数组 arguments 转换为数组
    const argsArray = Array.from(arguments);

    // 2. 输出转换后的数组
    console.log(argsArray);
}

example(1, 2, 3); // 输出 [1, 2, 3]

// 方法二:扩展运算符
// 示例:将 NodeList 转为数组
const nodeList = document.querySelectorAll('div');

// 1. 使用扩展运算符展开类数组
const nodeArray = [...nodeList];

// 2. 输出结果
console.log(nodeArray); // 如 [div, div, div]

将js对象转化为树形结构

const data = [
    { id: 1, name: 'Root' },
    { id: 2, parentId: 1, name: 'Child 1' },
    { id: 3, parentId: 1, name: 'Child 2' },
    { id: 4, parentId: 2, name: 'Grandchild 1' },
];

function buildTree(data) {
    // 1、创建一个映射对象,用于快速查找节点
    const map = {}
    // 2、存储所有根结点(无parentId的节点)
    const roots = []
    // 3、第一次遍历:初始化所有节点并存入映射
    data.forEach((node) => {
        map[node.id] = {...node,children:[]}
    })
    // 4、第二次遍历,建立父子关系
    data.forEach((node) => {
        if(node.parentId !== undefined && map[node.parentId]) {
            // 如果存在父节点,将当前节点添加到父节点的children中
            map[node.parentId].children.push(map[node.id])
        } else {
            // 否则视为根节点
            roots.push(map[node.id])
        }
    })
    // 5、返回根节点数组
    return roots 
}

// 调用函数并打印结果
const tree = buildTree(data)
console.log(tree);

使用setTimeout实现setInterval

function mySetInterval(callback, delay) {
    // 初始调用  
    callback();
    // 递归调用 setTimeout 来模拟 setInterval  
    const intervalId = setTimeout(() => {
        // 清除前一个 setTimeout,防止在回调函数执行时间较长时产生累积的延迟  
        clearTimeout(intervalId);
        // 递归调用 mySetInterval  
        mySetInterval(callback, delay);
        // 执行回调函数  
        callback();
    }, delay);
}

// 使用示例  
mySetInterval(() => console.log('Hello, world!'), 1000);