js中的各种手撕代码
1. new
分析问题:new关键字用于生成一个构造函数的实例
手撕步骤:
- 创建一个对象
- 为该对象完善原型链
- 以该对象执行构造函数
- 返回该对象
function _new(fn){
/*
let obj = {};
obj.__proto__ = fn.prototype
*/
let obj = Object.create(fn.prototype);
fn.apply(obj, arguments);
return obj;
// return obj = fn.apply(Object.create(fn.prototype)) 一句话形式
}
2. Array.isArray
判断一个对象是否为数组的方式
- Array.isArray(arr)
- Object.prototype.toString.call(arr) === ‘[object Array]’
- arr instanceof Array
- arr.proto === Array.prototype
function _isArray(ob){
return Object.prototype.toString.call(obj) === '[object Array]';
}
3. Array.prototype.reduce
Array中的方法都可以尝试使用迭代器模式进行手撕
reduce的用法
array.reduce(function(total, currentValue, currentIndex, arr), initialValue)
参数
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PJdEbrLv-1594387590099)(./img/reduce参数.png)]
Array.prototype._reduce = function(fn, initial){
let totalValue = initial ? initial : 0;
for (let i = 0, l = this.length; i < l; i++){
totalValue = fn.call(this[i], totalValue, this[i], i, this);
}
return totalValue;
}
const numbers = [65, 44, 12, 4];
function getSum(total, num) {
return total + num;
}
numbers._reduce(getSum); // 125
numbers._reduce(getSum, 1); //126
4. instanceof
instanceof关键字用于判断一个对象是否是指定构造函数的实例
function _instanceof(left, right){
let target = right.prototype;
// iterateProto需要不断在原型链上进行搜索
let iterateProto = left.__proto__;
while (true){
// 当proto为null时 代表失败
if (iterateProto === null){
return false;
}
if (iterateProto === target){
return true;
}
// 原型链上不断搜索
iterateProto = iterateProto.__proto__;
}
}
5. Array.prototype.flat
flat方法用于数组扁平化,这个方法较为特别,难以使用迭代器模式来完成
arr.flat(n);
其中,n为扁平化的级数
n可以取值为Infinity,表示全部展平
// 默认num = 1,num代表展开的级数
// Number数据类型中 NaN和0都会被转换成false
Array.prototype._flat = function(num = 1) {
if (!Array.isArray(this)){
console.error('this is not an Array');
return;
}
let result = [];
let n = num;
for (let item of this){
if (Array.isArray(item)){
n--;
if (n < 0){
result.push(item);
}else {
// 只要是数组就 递归 并 压入结果数组
result.push(...item._flat(n));
}
} else {
// 不是数组直接压入res中
result.push(item);
}
n = num;
}
return result;
}
// 其他的一些数组扁平化的方法
// 第一种
let arr = [1, 2, [1, 2], 7, [1, [1, 2]]];
arr = arr.toString()
.split(',')
.map(res=>Number(res));
// toString变成以逗号分割的字符串,并且对每个字符串进行类型转换
// 第二种
while(arr.some(item=>Array.isArray(item))){ //some方法用于判断某一项是否满足条件,比如在此验证是否有数组,只要有一个就返回true
arr = [].concat(...arr); //只要有数组就去掉一级
// eg. [].concat([1,2])===>[1,2]
}
6. 图片懒加载
<img src="loading.png" data-src="1.png" />
<img src="loading.png" data-src="2.png" />
<img src="loading.png" data-src="3.png" />
/*第一种方法*/
function lazyLoad(){
let clientHeight = document.documentElement.clientHeight; //获取屏幕可视高度
let imgArr = document.getElementsByTagName('img');
imgArr.forEach((item)=>{
let targetTop = item.getBoundingClientRect().top; //获取元素boundingclientrect对象
if (targetTop < clientHeight + 100){ //其中.top是指元素距离可视窗口左上角的高度距离
item.src = item.dataset.src; //item.getAttribute('data-src')
}
})
}
function lazyLoad(){
let clientHeight = document.documentElement.clientHeight; //获取屏幕可视高度
let scrollTop = document.documentElement.scrollTop || document.body.scrollTop; //获取页面滚动条偏移
let imgArr = document.getElementsByTagName('img');
imgArr.forEach((item)=>{
let offsetTop = item.offsetTop; //获取元素距离文档顶部的高度
if(offsetTop < scrollTop - clientHeight){ //只有当元素距离文档顶部的高度 小于 屏幕可视高度 + 滚动条滚动偏移 图片才加载
item.src = item.dataset.src;
}
})
}
lazyLoad(); //页面一进来的时候就判断加图片
window.onscroll = lazyLoad; //添加页面滚动事件
// 补充:可以进行函数节流操作
7. 防抖节流
防抖
防抖的作用是在短时间内多次触发同一事件,只执行一次(执行最后一次或执行最开始的一次)
function debounce(fn, dealy){
let timer = null;
return function(){
clearTimeout(timer); //清除计时器
timer = setTimeout(()=>{
fn.apply(this, arguments);
}, delay); //当超过delay之后执行函数
}
}
原理:用闭包封装一个变量timer,每次在执行函数时,首先清除计时器,如多多次触发事件,则计时器一直被清除和重新赋值。只有当一段时间(delay)之后,执行计时器内的回调函数
节流
节流的作用是在一段时间内,事件只触发一次
// 1.基于计时器的
function throttle(fn, delay){
let timer = null;
return function(){
if(!timer){ //如果没有计时器,则创建计时器;如果有计时器,则等待计时delay
timer = setTimeout(()=>{
fn.apply(this, arguments); //箭头函数没有arguments,也没有this,这里的arguments和this都是沿着作用域链向上寻找的
}, delay)
}
}
}
// 2.基于Date对象的
function throttle(fn, delay){
let preTime = Date().now();
return function(){
let nowDate = Date().now();
if(nowDate - preDate > delay){
fn.apply(this, arguments);
preTime = nowTime; // 记住需要重新赋值
}
}
}
节流原理:通过判断是否超过了一定时间,如果超过了一定时间就可以执行回调函数,如果还未超过指定时间则不可以执行。保证了在一段时间内只能操作一次
8. 数组去重
双重for循环
let uniqueArr = arr => {
let res = Array.prototype.slice.call(arr);
for(let i = 0; i < res.length; i++){
for(let j = i+1; j < res.length; j++){
if (res[i] === res[j]){
res.splice(j, 1);
j--; //删除重复元素并且将下标往左移
}
}
}
return res;
}
使用indexOf
let uniqueArr = arr => {
let res = arr.slice();
return res.filter((item, index)=>{ //过滤,对每一个项目进行回调函数中的过滤判断
return res.indexOf(item) === index;
})
}
使用includes
let uniqueArr = arr => {
let res = []; //结果空数组
for(let i = 0; i < arr.length; i++){
if(!res.includes(arr[i])){ //也可以替换成 res.indexOf(arr[i]) === -1
res.push(arr[i]);
}
}
return res;
}
使用Set (!!!)
let uniqueArr = arr => {
// new Set()创建一个set(可迭代类数组),使用扩展运算符扩展开
return [... new Set(arr)];
}