前言
努力! 奋斗! 爱折腾的程序员, 不会没关系, 最重要的是我们一起学习! ~
常见的 js手写大家经常会在工作面试中遇到, 这次也总结了一些,发现有很多js的文章, 我也总结了下自己的经常的用到的,方便自己学习,今天也给大家分享出来, 欢迎大家一起学习交流, 有更好的方法欢迎评论区指出, 后序我也将持续整理总结,也欢迎补充其他的js手写~
数据劫持
vue2数据劫持
function defineReactive (data ) {
if (!data || Object .prototype .toString .call (data) !== '[object Object]' )
return ;
for (let key in data) {
let val = data[key];
Object .defineProperty (data, key, {
enumerable : true ,
configurable : true ,
get : function ( ) {
track (data, key);
return val;
},
set : function ( ) {
trigger (val, key);
},
});
if (typeof val === "object" ) {
defineReactive (val);
}
}
}
function trigger (val, key ) {
console .log ("sue set" , val, key);
}
function track (val, key ) {
console .log ("sue set" , val, key);
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
const data = { name :‘better’ , firends :[‘1’ ,‘2’ ] } defineReactive (data) console .log (data.name ) console .log (data.firends [1 ]) console .log (data.firends [0 ]) console .log (Object .prototype .toString .call (data))
复制代码
vue3 proxy
function reactive (obj ) {
const handler = {
get (target, prop, receiver ) {
track (target, prop);
const value = Reflect .get (...arguments );
if (typeof value === 'Object' ) {
reactive (value)
}else {
return value
}
},
set (target,key, value, receiver ) {
trigger (target,key, value);
return Reflect .set (...arguments );
},
};
return new Proxy (obj,handler)
}
function track (data, key ) {
console .log ("sue set" , data, key);
}
function trigger (data, key,value ) {
console .log ("sue set" , key,':' ,value);
}
const dinner = {
name :'haochi1'
}
const proxy =reactive (dinner)
proxy.name
proxy.list = []
proxy.list .push (1 )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
复制代码
防抖节流
依稀记得当年孤身面字节的时候
防抖
使用场景: 输入框输入搜索,拖拽( mousemove )
效果: 不是每次操作后执行函数.在频繁操作的最后一次操作结束后在设置的时间内没有触发操作时才执行回调
两种思路
立即执行: 在第一次触发事件的时候立即执行当前操作的回调,后面的操作在最后一次操作结束后在设置的时间内没有触发操作时才执行回调 无立即执行: 按最后一次操作结束后的规定时间执行
function debounce (fn, delay, immediate ) {
let timer;
return function ( ) {
let self = this ;
let arg = arguments ;
clearTimeout (timer);
if (immediate) {
const callNow = !timer;
timer = setTimeOut (() => {
timer = null ;
}, delay);
if (callNow) fn.apply (self.arg );
} else {
timer = setTimeout (() => {
fn.apply (self, arg);
}, delay);
}
};
}
复制代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
节流
使用场景:滚动条滚动,频繁点击请求接口
效果:预定一个函数只有在大于等于执行周期时才执行
两种思路:
时间戳,先会立即执行,达到时间周期再执行
function throttle (fn, delay ) {
let t;
return function ( ) {
let self = this ;
let arg = arguments ;
if (!t || Date .now () - t >= delay) {
fn.apply (self, arg);
t = new Date ();
}
};
}
复制代码
定时器,定时一定时间周期之后去执行,但是在这时间内中不停的调用,不让他的定时器清零重新计时,不会影响当前的结果,还是那时间继续等,等到达时间周期后触发(会出现停止操作还是会触发)
function throttle (fn,delay ) {
let timer
retrun function ( ) {
let self = this
let arg = arguments
if (timer) return
timer = setTimeOut (()=> {
fn.apply (fn,arg)
timer = null
},delay)
} } 复制代码
call apply bind
三者都是用来改变 this 指向的
call 使用:
function .call (thisArg,arg1,grg2,...)
复制代码
thisArg 可选参数,function 执行时内部的 this 指向thisArgarg1,arg2,... 可选参数,传递给 function 的参数列表 返回值:在指定的 this 值和所传递的参数下调用此函数的返回结果 注意:
function 函数将会立即执行 在执行时,会将函数内部的 this 指向 thisArg 出 thisArg 外的所有剩余参数将全部传递给 function 返回 function 函数执行后的结果
Function .prototype .myCall = function (context, ...arr ) {
console .log ('调用mycall中的this' , this );
if (context === null || context === undefined ) {
context = window ;
} else {
context = Object (context);
}
const specialPrototype = Symbol ('特殊属性symbol' );
context[specialPrototype] = this ;
let result = context[specialPrototype](...arr);
delete context[specialPrototype];
return result;
};
复制代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
apply
注意:
使用 apply 只支持两个参数,第一个为 thisArg,第二个是包括多个参数的数组
Function .prototype .myApply = function (context, arr ) {
console .log (this );
if (context === null || context === undefined ) {
context = window ;
} else {
context = Object (context);
}
const specialPrototype = Symbol ('特殊属性symbol' );
context[specialPrototype] = this ;
let result = context[specialPrototype](...arr);
delete context[specialPrototype];
return result;
};
复制代码
bind 使用:
function .bind (thisArg,arg1,grg2,...)
复制代码
thisArg 可选参数,function 执行时会生成一个包裹着function(...)的邦迪函数,并且将function(...)的 this 指向 thisArg,如果使用 new 运算符调用这个生成的绑定函数,则忽略thisArgarg1,arg2,... 可选参数,传递给 function 的参数列表 返回值:在指定的 this 值和所传递的参数下调用此函数的返回结果 注意:
bind 方法将创建并返回一个新的函数,新函数称为绑定函数,并且此绑定函数包裹着原始函数 执行时,会显示将原始函数内部的 this 指向了thisArg 除 thisArg 外所有剩余参数将全部传递给 function 执行邦定函数时,如果传递了参数,这些参数将全部传递给原始函数 function 如果使用 new 运算符调用生成的绑定函数,则忽略 thisArg
Function .prototype .mybind = function ( ) {
if (typeof this !== 'function' ) {
throw new Error (
'function.prototype.bind - what is trying to be bound is not callable' ,
);
}
const argsArr = […arguments ]; const args = argsArr.shift (); const self = this ; const fToBind = function ( ) { console .log (‘返回函数的参数’ , arguments ); const isNew = this instanceof fToBind; const context = isNew ? this : Object (args); return self.apply (context, argsArr.concat ([…arguments ])); }; if (self.prototype ) { fToBind.prototype = Object .create (self.prototype ); } return fToBind; }; 复制代码
手写 new 关键字
作用 创建一个用户定义的对象类型的实例或者具有构造函数的内置对象的实例
特点 可以通过 new 一个构造函数的方式来创建一个对象实例,但是构造函数的差异导致创建的实例有一定的不同
构造函数的返回值不同
无返回值:生成一个实例化对象,构造函数中的 this 指向该对象 返回一个对象:return 之前的都被覆盖了,new 生成是 return 的对象 返回一个非对象:跟没有 return 是一样的结果
function myNew (fn, ...args ) {
const obj = new Object ();
obj._proto_ = fn.prototype ;
let res = fn.apply (obj, args); return typeof ret === ‘object’ ? ret || obj : obj; } 复制代码
instanceof
function myInstanceof (A, B ) {
let p = A;
while (p) {
p = p.__proto__ ;
if (p === B.prototype ) {
return true ;
}
}
return false ;
}
function Foo ( ) {}
var f = new Foo ();
console .log (myInstanceof (f, Foo ));
console .log (myInstanceof (f, Object ));
console .log (myInstanceof ([1 , 2 ], Array ));
console .log (myInstanceof ({ a : 1 }, Array ));
console .log (myInstanceof (Array , Object ));
console .log (Array instanceof Object );
复制代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
深浅克隆
function shallowClone (source ) {
const target = {};
for (let key in source) {
if (source.hasOwnProperty (key)) {
target[key] = source[key];
}
}
return target;
}
function deeClone1 (source ) {
if (typeof source === 'object' ) {
let target = Array .isArray (source) ? [] : {};
for (let key in source) {
if (source.hasOwnProperty (key)) {
target[key] = deeClone1 (source[key]);
}
}
return target;
} else {
return source;
}
}
const textObject = {
field1 : 1 ,
field2 : undefined ,
field3 : {
child : 'child' ,
},
field4 : [2 , 4 , 8 ],
};
const deepResult = deeClone1 (textObject);
const shallowResult = shallowClone (textObject);
console .log ('深克隆' , deepResult);
console .log ('浅克隆' , shallowResult);
deepResult.field4 .push (1 );
console .log ('深克隆' , deepResult, textObject);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
const textObject2 = { field1 : 1 , field2 : undefined , field3 : { child : ‘child’ , }, field4 : [2 , 4 , 8 ], }; textObject2.textObject2 = textObject2;
function deeCloneMap (source, map = new Map () ) { if (typeof source === ‘object’ ) { let target = Array .isArray (source) ? [] : {}; if (map.get (source)) { return map.get (source); } map.set (source, target); for (let key in source) { if (source.hasOwnProperty (key)) { target[key] = deeCloneMap (source[key], map); } } return target; } else { return source; } } const deepResult2 = deeCloneMap (textObject2); console .log (‘mapClone’ , deepResult2);
function deeCloneWeakMap (source, map = new WeakMap () ) { if (typeof source === ‘object’ ) { let target = Array .isArray (source) ? [] : {}; if (map.get (source)) { return map.get (source); } map.set (source, target); for (let key in source) { if (source.hasOwnProperty (key)) { target[key] = deeCloneMap (source[key], map); } } return target; } else { return source; } } function forEach (array, iteratee ) { let index = -1 ; const length = array.length ; while (++index < length) { iteratee (array[index], index); } return array; }
function deeCloneWhile (source, map = new WeakMap () ) { if (typeof source == null ) return source; if (source instanceof Date ) return new Date (osourcebj); if (source instanceof RegExp ) return new RegExp (source);
if (typeof source === ‘object’ ) { const isArray = Array .isArray (source); let target = isArray ? [] : {}; if (map.get (source)) { return map.get (source); } const keys = isArray ? undefined : Object .keys (source); map.set (source, target); forEach (keys || source, (value, key ) => { if (keys) { key = value; } target[key] = deeCloneWhile (source[key], map); }); return target; } else { return source; } } const textObject3 = { field1 : 1 , field2 : undefined , field3 : { child : ‘child’ , }, field4 : [2 , 4 , 8 ], f : { f : { f : { f : { f : { f : { f : { f : { f : { f : { f : { f : {} } } } } } } } } } }, }, }; textObject3.textObject3 = textObject3; const deepResult3 = deeCloneWhile (textObject3); console .log (‘deeCloneWhile’ , deepResult3); 复制代码
promise
Promise.allSettled
特点 接收一个数组作为参数,数组的每一项都是一个Promise对象,返回一个新的Promise对象,只有等到参数数组的所有的Promise对象都发生状态改变,返回的Promise对象才会发生变更
Promise .allSettled = function (arr ) {
let result = [];
return new Promise ((resolve, reject ) => {
arr.forEach ((item, index ) => {
Promise .resolve (item)
.then ((res ) => {
result.push ({
status : 'fulfilled' ,
value : res,
});
result.length === arr.length && resolve (result);
})
.catch ((error ) => {
result.push ({
status : 'rejected' ,
value : error,
});
result.length === arr.length && resolve (result);
});
});
});
};
复制代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
Promise.all()
Promise.all()方法用于将多个 promise 实例包装成一个新的 Promise 实例
Promise .all = function (arr ) {
let index = 0 ,
result = [];
return new Promise ((resolve, reject ) => {
arr.forEach ((item, i ) => {
Promise .resolve (item)
.then ((res ) => {
index++;
result[i] = res;
if (index === arr.length ) resolve (res);
})
.catch ((error ) => reject (error));
});
});
};
复制代码
手写 fill
最近发现有很多地方让手写 fill 函数
Array.prototype.(fill())[developer.mozilla.org/zh-CN/docs/… ] 方法用一个固定值填充一个数组中从起始索引到终止索引内的全部元素, 不包括终止索引
(MDN)
语法
arr.fill(value[, start[, end]])
fill接收三个参数 vlaue start 和end ,start和end是可选的,其默认值分别为 0 和this对象的length属性值
如果 start 是个负数, 则开始索引会被自动计算成length+start, 其中length是this对象的length属性值,如果 end 是个负数, 则结束索引会被自动计算成 length + end
返回值 修改后的数组
示例
[1 , 2 , 3 ].fill (4 );
[1 , 2 , 3 ].fill (4 , 1 );
[1 , 2 , 3 ].fill (4 , 1 , 2 );
[1 , 2 , 3 ].fill (4 , 1 , 1 );
[1 , 2 , 3 ].fill (4 , 3 , 3 );
[1 , 2 , 3 ].fill (4 , -3 , -2 );
[1 , 2 , 3 ].fill (4 , NaN , NaN );
[1 , 2 , 3 ].fill (4 , 3 , 5 );
Array (3 ).fill (4 );
[].fill .call ({ length : 3 }, 4 );
复制代码
参考 MDN 中的手写代码
if (!Array .prototype .fill ) {
Object .defineProperty (Array .prototype , 'fill' , {
value : function (value ) {
if (this == null ) {
throw new TypeError ('this is null or not defined' );
}
var O = Object (this );
var len = O.length >>> 0 ;
var start = arguments [1 ];
var relativeStart = start >> 0 ;
var k = relativeStart < 0 ?
Math .max (len + relativeStart, 0 ) :
Math .min (relativeStart, len);
var end = arguments [2 ];
var relativeEnd = end === undefined ?
len : end >> 0 ;
var final = relativeEnd < 0 ?
Math .max (len + relativeEnd, 0 ) :
Math .min (relativeEnd, len);
while (k < final) {
O[k] = value;
k++;
}
return O;
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
}); }
复制代码
数组扁平化
对于数组的扁平化,我已经出了一个详细的文章分析
这里也给出一个我比较喜欢的实现方式,其他的实现方式欢迎大家查看原文学习更多的实现方式
思路
通过 some 来判断数组中是否用数组,通过 while 不断循环执行判断, 如果是数组的话可以使用 拓展运算符... ... 每次只能展开最外层的数组,加上 contact 来减少嵌套层数,
function flatten (arr ) {
while (arr.some (item => Array .isArray (item))) {
console .log (...arr)
arr = [].concat (...arr)
console .log (arr)
}
return arr
}
console .log (flatten (arr));
复制代码
获取 URL 的参数
前段时间自己还在某次尝试中遇到了这个题
获取浏览器参数都很熟悉,第一反应是使用正则表达式去对浏览器的参数进行切割获取,然而浏览器已经提供了一个URLSearchParams 这个接口给我们去操作 URL 的查询字符串
URLSearchParams 是一个构造函数,可以通过get()方法来获取
1. 使用URLSearchParams构造函数
const urlSearchParams = new URLSearchParams (window .location .search );
const params = Object .fromEntries (urlSearchParams.entries ());
console .log (params); console .log (params.id ); 复制代码
使用正则表达式获取 url
function getParams (url, params ) {
var res = new RegExp ('(?:&|/?)' + params + '=([^&$]+)' ).exec (url);
return res ? res[1 ] : '' ;
}
const id = getParams (window .location .search , ‘id’ );
console .log (id); 复制代码
\
图片懒加载
概念: 图片懒加载就是开始加载页面的时候把图片的 src 给替换,在图片出现在页面的可视区域内的时候再加载图片的 src
思路
获取所有的 img 标签,获取并替换所有 src 数据,将 src 替换成 data-src 判断当前图片是否进入可视区域内,进入后进行展示
getBoundingClientRect 方法返回元素的大小及其相对于视口的位置
let imgList = [...document .querySelectorAll ('img' )]
let length = imgList.length
const imgLazyLoad = (function ( ) {
let count = 0
return function ( ) { let deleteIndexList = [] imgList.forEach ((img, index ) => { let rect = img.getBoundingClientRect () if (rect.top < window .innerHeight ) { img.src = img.dataset .src deleteIndexList.push (index) count++ if (count === length) { document .removeEventListener (‘scroll’ , imgLazyLoad) } } }) imgList = imgList.filter ((img, index ) => !deleteIndexList.includes (index)) } })()
document .addEventListener (‘scroll’ , imgLazyLoad) 复制代码
函数柯里化
概念 将使用多个参数的函数转换成一系列使用一个参数
比如:
function add (a,b,c) {
retrun a+b+c
}
add (1 ,2 ,3 )
let curryAdd =curry (add)
curryAdd (1 )(2 )(3 )
复制代码
思路
function curry (fn ) {
let judge = (...args ) => {
if (args.length == fn.length ) return fn (...args)
return (...arg ) => judge (...args, ...arg)
}
return judge
}
复制代码
实现链表
class Node {
constructor (data ){
this .data = data
this .next = null
}
}
class SinglyLinkedList {
constructor ( ){
this .head = new Node ()
}
add (data ){
let node = new Node (data)
let current = this .head
while (current.next ){
current = current.next
}
current.next = node
}
addAt (index, data ){
let node = new Node (data)
let current = this .head
let counter = 1
while (current){
if (counter === index){
node.next = current.next
current.next = node
}
current = current.next
counter++
}
}
removeAt (index ){
let current = this .head
let counter = 1
while (current.next ){
if (counter === index){
current.next = current.next .next
}
current = current.next
counter++
}
}
reverse ( ){
let current = this .head .next
let prev = this .head
while (current){
let next = current.next
current.next = prev===this .head ? null : prev
prev = current
current = next
}
this .head .next = prev
}
}
复制代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
二叉树遍历
前序
var Traversal = function (root ) {
const stack = [];
while (root || stack.length ){
while (root){
stack.push (root);
root = root.left ;
}
root = stack.pop ();
root = root.right ;
}
return res;
};
复制代码
中序
const inorder2 = (root ) => {
if (!root) return ;
const res = []
const track = []
while (track.length ||root) {
while (root) {
track.push (root)
root = root.left
}
root = track.pop ()
res.push (root.val )
root = root.right
console .log ('root' ,root)
}
return res
};
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
复制代码
后序
var postorderTraversal = function (root ) {
const res =[];
const stack = [];
while (root || stack.length ){
while (root){
stack.push (root);
res.unshift (root.val );
root = root.right ;
}
root = stack.pop ();
root = root.left ;
}
return res;
};
复制代码
翻转二叉树
var invertTree = function (root ) { let now = [root]
while (now.length ) {
const next = []
now.forEach (item => {
if (item === null ) return
let n = item.left
item.left = item.right
item.right = n
next.push (item.left )
next.push (item.right )
})
now = next
}
return root
};
复制代码
二分查找
题目: 给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。
思路:
定义头尾两个指针 left = 0 ,right = length 中间索引 mid = left + (right - left) / 2 当left <= right 如果mid上的值等于target, 返回mid, 如果小于targt, left = mid + 1(砍掉左半边) 如果大于target , right = mid - 1(砍掉右半边) 如果while 循环结束后都没有找到target , 返回-1
const search = (nums, target ) => { let i = 0 let j = nums.length - 1 let midIndex = 0
while (i <= j) { midIndex = Math .floor ((i + j) / 2 ) const midValue = nums[ midIndex ] if (midValue === target) { return midIndex } else if (midValue < target) { i = midIndex + 1 } else { j = midIndex - 1 } }
return -1 } console .log (search ([-1 ,0 ,3 ,5 ,9 ,12 ], 9 ))
复制代码
快排
var sortArray = function (nums ) {
let left = 0 ,
right = nums.length - 1 ;
main (nums, left, right);
return nums;
function main (nums, left, right ) {
if (nums.length === 1 ) {
return ;
}
let index = partition (nums, left, right);
if (left < index - 1 ) {
main (nums, left, index - 1 );
}
if (index < right) {
main (nums, index, right);
}
}
function partition (nums, left, right ) {
let pivot = nums[Math .floor ((left + right) / 2 )];
while (left <= right) {
while (nums[left] < pivot) {
left++;
}
while (nums[right] > pivot) {
right--;
}
if (left <= right) {
[nums[left], nums[right]] = [nums[right], nums[left]];
left++;
right--;
}
}
return left;
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
};