防抖
原理
事件高频触发后,n秒内函数只会执行一次,若n秒内事件再次触发,则重新计时,总之就是要等触发完事件 n 秒内不再触发事件,函数才执行
实现
// 基本逻辑
function debounce(){
let timer = null; // 消除之前可能存在的定时任务
if(timer) timer = null
timer = setTimeout(()=>{
console.log('function called')
timer = null // 执行完毕后,清楚定时任务
},500)
}
// 封装防抖函数
function(callback, waitingTime = 500){
let timer = null;
return function (){
if(timer) timer = null // 消除之前可能存在的定时任务
timer = setTimeout(()=>{
callback.apply(this,...arguments)
timer = null // 执行完毕后,清楚定时任务
},waitingTime)
}
}
节流
原理
实现
function throttle(callback,waitingTime,callImmediately){
let timer = null,
isFirstTime = true;
return function(){
if(isFisrtTime){
callback.aplly(this,arguments)
}else{
if(timer) return
timer = setTimeout(()=>{
callback.aplly(this,arguments)
timer = null
},waitingTime)
}
}
}
模拟new运算符
原理
- 新建一个空对象
- 链接到原型
- 绑定this
- 返回该对象
实现
function myNew() {
// 1.新建一个空对象
let obj = {}
// 2.获得构造函数
let con = [].shift.call(arguments)
// 3.链接原型,实例的 __proto__ 属性指向构造函数的 prototype
obj.__proto__ = con.prototype
// 4.绑定this,执行构造函数
let res = con.apply(obj, arguments)
// 5.返回新对象
return typeof res === 'object' ? res : obj
}
function Person(name) {
this.name = name
}
let person = myNew(Person,'nanjiu')
console.log(person) //{name: "nanjiu"}
console.log(typeof person === 'object') //true
console.log(person instanceof Person) // true
模拟instanceof
instanceof 用于检测构造函数的prototype是否在实例的原型链上,需要注意的是instanceof只能用来检测引用数据类型,对于基本数据检测都会返回false
原理
通过循环检测实例的proto属性是否与构造函数的prototype属性相等
实现
/**
* instanceof 用于检测构造函数的prototype是否在实例的原型链上
*/
function myInstanceof(left, right) {
// 先排除基本数据类型
if(typeof left !== 'object' || left === null) return false
let proto = left.__proto__
while(proto) {
if(proto === right.prototype) return true
proto = proto.__proto__
}
return false
}
function Person() {}
let person = new Person()
console.log(myInstanceof(person,Person)) // true
模拟Function.prototype.apply()
实现
Function.prototype.myApply = function(context) {
var context = context || window // 获取需要绑定的this
context.fn = this // 获取需要改变this的函数
const arg = arguments[1] // 获取传递给函数的参数
if(!(arg instanceof Array)) {
throw Error('参数需要是一个数组')
}
const res = context.fn(...arg) // 执行函数
delete context.fn // 删除该方法
return res // 返回函数返回值
}
function say(a,b,c) {
console.log(this.name,a,b,c)
}
say.myApply({name:'nanjiu'},[1,2,3]) //nanjiu 1 2 3
say.apply({name:'nanjiu'},[1,2,3]) //nanjiu 1 2 3
模拟Function.prototype.call()
实现
Function.prototype.myCall = function(context) {
var context = context || window // 获取需要改变的this
context.fn = this // 获取需要改变this的函数
const args = [...arguments].slice(1) // 获取参数列表
const res = context.fn(...args) // 将参数传给函数并执行
delete context.fn // 删除该方法
return res // 返回函数返回值
}
function say(a,b,c) {
console.log(this.name,a,b,c)
}
say.myCall({name:'nanjiu'},1,2,3) //nanjiu 1 2 3
say.call({name:'nanjiu'},1,2,3) //nanjiu 1 2 3
模拟Function.prototype.bind()
实现
Function.prototype.myBind = function(context) {
var context = context || window //获取需要改变的this
context.fn = this // 获取需要改变this的函数
//获取函数参数
const args = [...arguments].slice(1)
// 与apply,call不同的是这里需要返回一个函数
return () => {
return context.fn.apply(context,[...args])
}
}
function say(a,b,c) {
console.log(this.name,a,b,c)
}
say.bind({name: 'nanjiu'},1,2,3)() //nanjiu 1 2 3
say.myBind({name: 'nanjiu'},1,2,3)() //nanjiu 1 2 3
模拟Array.prototype.forEach()
实现
Array.prototype.myForEach = function(callback, context) {
const arr = this // 获取调用的数组
const len = arr.length || 0
let index = 0 // 数组下标
while(index < len) {
callback.call(context ,arr[index], index)
index++
}
}
let arr = [1,2,3]
arr.forEach((item,index) => {
console.log(`key: ${index} - item: ${item}`)
})
console.log('----------')
arr.myForEach((item,index) => {
console.log(`key: ${index} - item: ${item}`)
})
/**
* key: 0 - item: 1
key: 1 - item: 2
key: 2 - item: 3
----------
key: 0 - item: 1
key: 1 - item: 2
key: 2 - item: 3
*/
模拟Array.prototype.map()
实现
/**
* map() 方法创建一个新数组,其结果是该数组中的每个元素是调用一次提供的函数后的返回值。
*/
Array.prototype.myMap = function(callback, context) {
const arr = this,res = []
const len = arr.length || 0
let index = 0
while(index < len) {
res.push(callback.call(context, arr[index], index))
index ++
}
return res // 与forEach不同的是map有返回值
}
const arr = [1,2,3]
let res1 = arr.map((item,index) => {
return `k:${index}-v:${item}`
})
let res2 = arr.myMap((item,index) => {
return `k:${index}-v:${item}`
})
console.log(res1) // [ 'k:0-v:1', 'k:1-v:2', 'k:2-v:3' ]
console.log(res2) // [ 'k:0-v:1', 'k:1-v:2', 'k:2-v:3' ]
模拟Array.prototype.filter()
实现
/**
* `filter()` 方法创建一个新数组, 其包含通过所提供函数实现的测试的所有元素。
*/
Array.prototype.myFilter = function(callback, context) {
const arr = this,res = []
const len = arr.length
let index = 0
while(index < len) {
if(callback.call(context,arr[index],index)) {
res.push(arr[index])
}
index ++
}
return res
}
const arr = [1,2,3]
let res1 = arr.filter((item,index) => {
return item<3
})
let res2 = arr.myFilter((item,index) => {
return item<3
})
console.log(res1) // [ 1, 2 ]
console.log(res2) // [ 1, 2 ]
函数柯里化
实现
function curry(fn, curArgs) {
const len = fn.length // 需要柯里化函数的参数个数
curArgs = curArgs || []
return function() {
let args = [].slice.call(arguments) // 获取参数
args = curArgs.concat(args) //拼接参数
// 基本思想就是当拼接完的参数个数与原函数参数个数相等才执行这个函数,否则就递归拼接参数
if(args.length < len) {
return curry(fn, args)
}else{
return fn.apply(this, args)
}
}
}
let fn = curry(function(a,b,c){
console.log([a,b,c])
})
fn(1,2,3) // [ 1, 2, 3 ]
fn(1,2)(3) // [ 1, 2, 3 ]
fn(1)(2,3) // [ 1, 2, 3 ]
fn(1)(2)(3) // [ 1, 2, 3 ]
类数组转数组
类数组是具有length属性,但不具有数组原型上的方法。常见的类数组有arguments、DOM操作方法返回的结果。
实现
function translateArray() {
//方法一:Array.from
const res1 = Array.from(arguments)
console.log(res1 instanceof Array, res1) // true [ 1, 2, 3 ]
// 方法二:Array.prototype.slice.call
const res2 = Array.prototype.slice.call(arguments)
console.log(res2 instanceof Array, res2) // true [ 1, 2, 3 ]
// 方法三:concate
const res3 = [].concat.apply([],arguments)
console.log(res3 instanceof Array, res3) // true [ 1, 2, 3 ]
// 方法四:扩展运算符
const res4 = [...arguments]
console.log(res4 instanceof Array, res4) // true [ 1, 2, 3 ]
}
translateArray(1,2,3)
实现深拷贝
const isComplexDataType = (obj) => (typeof obj === 'object' && obj !== null);
function deepClone(origin , hash = new WeakMap()){
if(isComplectDataType(obj)){
//判断date、RegExp类型对象
if(instanceof Date){
return new Date(obj)
}
if(instanceof Regxp){
return new Regxp(obj)
}
// 判断hash表中是否已有缓存,避免自身引用导致的循环递归
if(hash.get(obj)) return hash.get(obj)
//获取属性描述符,然后创建新对象
let descriptors = Object.getOwnPropertyDescriptors(obj);
let newObj = Object.create(Object.getPrototypeOf(obj) ,descriptors);//可以创建对象和数组
hash.set(obj, newObj);
for(let key of Reflect.ownKeys(obj)){
newObj[key] = isComplexDataType(origin[key]) ? deepClone(origin[key]) : origin[key]
}
return newObj
}else{
return obj
}
}
实现AJAX
过程
- 创建XMLHttpRequest对象
- 打开链接 (指定请求类型,需要请求数据在服务器的地址,是否异步i请求)
- 向服务器发送请求(get类型直接发送请求,post类型需要设置请求头)
接收服务器的响应数据(需根据XMLHttpRequest的readyState属性判定调用哪个回调函数)
实现
function ajax(url, method, data=null) {
const xhr = XMLHttpRequest() // 咱们这里就不管IE低版本了
// open()方法,它接受3个参数:要发送的请求的类型,请求的url和是否异步发送请求的布尔值。
xhr.open(method, url ,false) // 开启一个请求,当前还未发送
xhr.onreadyStatechange = function() {
if(xhr.readyState == 4) {
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){
alert(xhr.responseText);
} else {
console.log("Request was unsuccessful: " + xhr.status);
}
}
}
if(method === 'post') {
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
}
xhr.send(data) // get请求,data应为null,参数拼接在URL上
}
多维数组扁平化
function flat(arr) {
const res = []
// 递归实现
const stack = [...arr] // 复制一份
while(stack.length) {
//取出复制栈内第一个元素
const val = stack.shift()
if(Array.isArray(val)) {
// 如果是数组,就展开推入栈的最后
stack.push(...val)
}else{
// 否则就推入res返回值
res.push(val)
}
}
return res
}
const arr = [1,[2],[3,4,[5,6,[7,[8]]]]]
console.log(flat(arr)) //[1, 2, 3, 4, 5, 6, 7, 8]
// 调用flat
const arr = [1,[2],[3,4,[5,6,[7,[8]]]]]
console.log(arr.flat(Infinity)) //[1, 2, 3, 4, 5, 6, 7, 8]
setTimeout 模拟 setInterval
递归调用setTimeout
function mySetInterval(callback, delay) {
let timer = null
let interval = () => {
timer = setTimeout(()=>{
callback()
interval() // 递归
}, delay)
}
interval() // 先执行一次
return {
id: timer,
clear: () => {
clearTimeout(timer)
}
}
}
let time = mySetInterval(()=>{
console.log(1)
},1000)
setTimeout(()=>{
time.clear()
},2000)
setInterval 模拟 setTimeout
setInterval执行一次后将setInterval清除即可
function mySetTimeout(callback, delay) {
let timer = null
timer = setInterval(()=>{
callback()
clearInterval(timer)
},delay)
}
mySetTimeout(()=>{
console.log(1)
},1000)
sleep
实现一个函数,n秒后执行指定函数
function sleep(func, delay) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(func())
}, delay)
})
}
function say(name) {
console.log(name)
}
async function go() {
await sleep(()=>say('nanjiu'),1000) //过一秒打印nanjiu
await sleep(()=>say('前端南玖'),2000) // 再过两秒打印前端南玖
}
go()
数组去重的多种实现方式
set
let arr = [1,2,3,2,4,5,3,6,2]
function arrayToHeavy1(arr) {
return [...new Set(arr)]
}
console.log(arrayToHeavy1(arr)) //[ 1, 2, 3, 4, 5, 6 ]
indexOf
function arrayToHeavy2(arr) {
let newArr = []
for(let i=0; i<arr.length; i++) {
if(newArr.indexOf(arr[i]) == -1){
newArr.push(arr[i])
}
}
return newArr
}
console.log(arrayToHeavy2(arr)) //[ 1, 2, 3, 4, 5, 6 ]
filter
function arrayToHeavy3(arr) {
return arr.filter((item, index) => {
return arr.indexOf(item) == index
})
}
console.log(arrayToHeavy3(arr)) //[ 1, 2, 3, 4, 5, 6 ]
include
function arrayToHeavy5(arr) {
let res = []
for(let i=0; i<arr.length; i++) {
if(!res.includes(arr[i])) {
res.push(arr[i])
}
}
return res
}
console.log(arrayToHeavy5(arr)) //[ 1, 2, 3, 4, 5, 6 ]
解析url参数
function queryData(key) {
let url = window.location.href,obj = {}
let str = url.split('?')[1] // 先拿到问号后面的所有参数
let arr = str.split('&') // 分割参数
for(let i=0; i< arr.length; i++) {
let kv = arr[i].split('=')
obj[kv[0]] = decodeURIComponent(kv[1])
}
console.log(url,obj)
// {a: '1', b: '2', c: '3', name: '南玖'}
return obj[key]
}
//http://127.0.0.1:5500/src/js/2022/%E6%89%8B%E5%86%99/index.html?a=1&b=2&c=3
console.log(queryData('name')) // 南玖
function getURL(){
let queryStr = window.location.href.split('?')[1],
queryObj = {};
let arr = queryStr.split('&');
for(let i = 0;i < arr.length; i++){
let singleQueryArr = arr[i].split('=');
queryObj[singleQueryArr[0]] = singleQueryArr[1]
}
return queryObj
}
斐波那契数列
基础版
function fib(n) {
if(n == 0) return 0
if(n == 1 || n == 2) return 1
return fib(n-1) + fib(n-2)
}
// console.log(fib(4)) //F(4)=F(3)+F(2)=F(2)+F(1)+F(2)=1+1+1=3
let t = +new Date()
console.log(fib(40)) //102334155
console.log(+new Date()-t) //783ms
优化版
function fib2(n) {
if(fib2[n] !== undefined) return fib2[n]
if(n == 0) return 0
if(n == 1 || n == 2) return 1
const res = fib2(n-1) + fib2(n-2)
fib2[n] = res
return res
}
let t1 = +new Date()
console.log(fib2(40)) //102334155
console.log(+new Date()-t1) //5ms