手写代码题
手写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
- 创建一个ajax实例对象(XMLHttpRequest对象) -- 联系一个快递员
- 设置相关的属性, 如url和method -- 填写快递信息
- 通过实例对象的open方法将设置好的属性给传进去 -- 给钱
- 通过实例对象的send方法将请求发送出去 -- 发货
- 给实例对象设置好相应的事件监听 -- 等待收货
- 通过实例对象的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);
- 深拷贝
-
思路
- 遍历对象属性,判断属性值是不是简单数据类型
- 复杂([] 或 {})
- 如果是简单数据类型,直接复制。如果是复杂数据类型,就递归
- 定制出口(出口的定义:不是复杂数据类型)
- 注意:!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);