强撸这几十个原生JS,提升很大

147 阅读6分钟

实现数组api

forEach

原生的forEach方法中接收2个参数 callback 和 thisArg ,并且 callback 函数传入三个参数,数组当前项的值,索引,数组本身

注意:由于 forEach 方法没有返回值,因此 forEach 不支持链式操作

Array.prototype.forEach2 = function(callback,thisArgv){
    // 判断调用该API的元素是否为null
    if(this == null){
        throw new TypeError('this is null or not defined')
    }
    if(typeof callback!=="function"){
        throw new TypeError(callback + ' is not a function')
    }
    let self = this;
    for(let i = 0;i<this.length;i++){
        callback.call(thisArgv,self[i],i,self)
    }
}

let arr1 = [{
    name: 'ljc',
    age: 19
}, {
    name: 'xy',
    age: 18
}]

arr1.forEach((item,index,arr)=>{
    console.log(item,index,arr);
    item.age += 1
})
console.log(arr1);

map

与 forEach 方法相比,map 方法有返回值而 forEach 方法没有返回值。与forEach不同的是,map会返回一个新数组

map中的每一个元素都要执行回调函数,所以必须要有 return,因此不能采用map对数组进行过滤

map也叫映射,也就是将原数组映射成一个新数组

  1. 数组中的每一个元素都会调用一个提供的函数后返回结果。
  2. 会新建一个数组,需要有承载对象,也就是会返回一个新的对象
  3. 除非用原有数组去承载,否则原有数组不会改变

使用方法

let arr = [1, 2, 3, 4, 5] 
let newArr = arr.map(item => item * 2) 
console.log(newArr); 
//  [2, 4, 6, 8, 10]

手写代码

Array.prototype.map2 = function(callback,thisArg){
      // 和forEach相同需要进行两个排除
      // 与forEach不同的是,map会返回一个新数组
    if (this == undefined) {
        throw new TypeError('this is null or not defined');
    }
    if (typeof callback !== 'function') {
        throw new TypeError(callback + ' is not a function');
    }
    let self = this
    let result = []
    for(let i = 0;i < self.length; i++){
        result[i] = callback.call(thisArg,self[i],i,self)
    }
    return result
}

let arr = [1, 2, 3, 4, 5] 
let newArr = arr.map2(item => item * 2) 
console.log(newArr); 


filter

filter从名字上看可以知道是它是用来做筛选过滤的。和map一样,会返回一个新的对象数组,并不会改变原数组

用法:

const words = ['spray', 'limit', 'elite', 'exuberant', 'destruction', 'present'];

const result = words.filter(word => word.length > 6);

console.log(result);
Array.prototype.filter2 = function (callback, thisArgv) {
    if (this == null) {
        throw new TypeError('this is null or not defined')
    }
    if (typeof callback !== "function") {
        throw new TypeError(callback + ' is not a function')
    }
    let self = this,
    result = []
    for(let i = 0;i< self.length;i++){
        if(callback.call(thisArgv,self[i],i,self)){
            result.push(self[i])
        }
        
    }
return result
}


some

some方法用于检查数组中是否有符合条件的值,返回值是个布尔值

const array = [1, 2, 3, 4, 5]
const even = (element) => element % 2 === 0;
console.log(array.some(even));
Array.prototype.some2 = function (callback, thisArgv) {
    if (this == null) {
        throw new TypeError('this is null or not defined')
    }
    if (typeof callback !== "function") {
        throw new TypeError(callback + ' is not a function')
    }
    let self = this,
    result = []
    for(let i = 0;i< self.length;i++){
        if(callback.call(thisArgv,self[i],i,self)){
           return true
        }
       
    } 
    return false

}

every

some相比,每个成员都满足条件才返回true,有一个不满足都返回false

const isBelowThreshold = (currentValue) => currentValue < 40;
const array1 = [1, 30, 39, 29, 10, 13];
console.log(array1.every(isBelowThreshold));

Array.prototype.every2 = function (callback, thisArgv) {
    if (this == null) {
        throw new TypeError('this is null or not defined')
    }
    if (typeof callback !== "function") {
        throw new TypeError(callback + ' is not a function')
    }
    let self = this,
    result = []
    for(let i = 0;i< self.length;i++){
        if(!callback.call(thisArgv,self[i],i,self)){
           return false
        }
       
    } 
    return true

}

reduce

用法:

 let arr = [10, 20, 30, 40, 50];
 let result = arr.reduce((x, item, index) => {
   // x初始值是数组的第一项,从数组第二项开始迭代
   // 第一次 x=10 item=20 index=1  返回30
   // 第二次 x=30 item=30 index=2  返回60
   // 第三次 x=60 item=40 index=3  返回100
   // 第四次 x=100 item=50 index=4 返回150
   // 迭代结束,最后一次返回的150赋值给外面的result
   return x + item;
}); 


let result = arr.reduce((x, item, index) => {
   // x初始值是传递的第二个参数,从数组第一项开始迭代
   // 第一次 x=0 item=10 index=0  返回10
   // ...
   return x + item;
}, 0);

手写代码:

Array.prototype.reduce = function reduce(callbackFn, initialValue) {
    var self = this,
        result = initialValue,
        index = 0;
    if (typeof callbackFn !== "function") throw new TypeError('callbackFn is not a function');
    if (typeof initialValue === "undefined") {
        result = self[0];
        index = 1;
    }
    // 迭代数组
    for (; index < self.length; index++) {
        result = callbackFn(result, self[index], index);
    }
    return result;
};
let arr = [10, 20, 30, 40, 50];
let result = arr.reduce((x, item) => x + item),0);
console.log(result)

join

用法:join() 方法用于把数组中的所有元素转换一个字符串, 元素是通过指定的分隔符进行分隔的。

Array.prototype._join = function (s = ',') { 
let str = '' 
for(let i = 0; i < this.length; i++) {
str = i === 0 ? `${str}${this[i]}` : `${str}${s}${this[i]}` } 
return str 
}

var fruits = ["Banana", "Orange", "Apple", "Mango"];\
var energy = fruits._join();

push

Array.prototype.push = function push(value) {
    // this->arr
    this[this.length] = value;
    this.length++;
    return this.length;
};

flat

flat()  方法会按照一个可指定的深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回。

const arr1 = [0, 1, 2, [3, 4]];
console.log(arr1.flat())
const arr2 = [0, 1, 2, [[[3, 4]]]];
console.log(arr2.flat(2));
Array.prototype.flat2 = function () {
    let arr = this
    
    while (arr.some(item => Array.isArray(item))) {
       console.log(...arr)
     
        arr = [].concat(...arr)
        console.log(arr)
    }
    return arr
}

实现Function原型方法

call

const fn = function fn(x, y) {
    console.log(this, x, y);
    return x + y;
};
let obj = {
    name: 'obj'
};
let res = fn._call(obj, 10, 20);

Function.prototype._call = function call(context, ...params) {
    let self = this,
        key = Symbol('KEY'),
        result;
    context[key] = self;
    result = context[key](...params);
    // delete context[key]; //新增的属性用完后记得移除
    Reflect.deleteProperty(context, key); //ES6中,可以基于Reflect.deleteProperty移除对象的属性
    return result;
}

apply

Function.prototype.sx_apply = function (obj, args) { 
obj = obj || window // Symbol是唯一的,防止重名key 
const fn = Symbol() 
obj[fn] = this // 执行,返回执行值 
return obj[fn](...args) }

const testobj = { 
   name: '林三心', 
   testFn(age) { console.log(`${this.name}${age}岁了`) } } 
   
const testobj2 = { name: 'sunshine_lin' } 
testobj.testFn.sx_apply(testobj2, [22]) // sunshine_lin22岁了


bind

//-   需求:点击按钮,fn方法执行,我们想让其中的this变为o  
const submit = document.querySelector('#submit');
submit.onclick = fn
const obj = { name: 'obj' };
const fn = function fn(x, y, ev) {
    console.log(this, x, y, ev);
};
Function.prototype.bind = function bind(context, ...params) {
    // this->fn  context->obj  params->[10,20]
    let self = this;
    return function proxy(...args) {
        // args->[ev] this->submit
        params = params.concat(args);
         self.call(context, ...params);
    };
};

实现Object原型方法

new

function _new(Ctor, ...params) {
    let obj = Object.create(Ctor.prototype),
        result;
    result = Ctor.call(obj, ...params);
    if (result !== null && /^(object|function)$/.test(typeof result)) return result;
    return obj;
}

instanceof

var instance_of = function instance_of(obj, Ctor) {
    // 右侧必须是一个函数
    if (typeof Ctor !== "function") throw new TypeError("Right-hand side of 'instanceof' is not callable");
    // 原始值检测都是false
    if (obj == null || !/^(object|function)$/.test(typeof obj)) return false;
    // 构造函数必须具备prototype
    if (!Ctor.prototype) throw new TypeError("Function has non-object prototype 'undefined' in instanceof check");
    // 支持Symbol.hasInstance的使用这个方法处理
    if (typeof Symbol !== "undefined") return Ctor[Symbol.hasInstance](obj);
    // 不支持:自己按照原型链查找
    let proto = Object.getPrototypeOf(obj);
    while (proto) {
        if (proto === Ctor.prototype) return true;
        proto = Object.getPrototypeOf(proto);
    }
    return false;
};
let arr = [10, 20];
console.log(instance_of(arr, Array)); //true
console.log(instance_of(arr, RegExp)); //false
console.log(instance_of(arr, Object)); //true 

String

单词首字母大写

let str = "you can you up a" let reg = /\b([a-zA-Z])[a-zA-Z]*\b/g; //=>函数被执行了五次,let str = "you can you up a"
let reg = /\b([a-zA-Z])[a-zA-Z]*\b/g;
//=>函数被执行了五次,每一次都把正则匹配信息传递给函数
//=>每一次ARG:["you","y"] ["can","c"] ["you","y"]...
 str = str.replace(reg,(...arg)=>{
    let [content,$1]=arg;
    $1=$1.toUpperCase();
    content=content.substring(1);
    return $1+content;
});
console.log(str)

queryURLParams :获取URL地址问号和面的参数信息(可能也包含HASH值)

let url = 'http://www.javascriptpekkk.cn/course/1935/task/114966/show?a=b&c=d#test'
function queryURLParams() {
    let obj = {};
    this.replace(/([^?=&#]+)=([^?=&#]+)/g, (...[, $1, $2]) => obj[$1] = $2);            
    this.replace(/#([^?=&#]+)/g, (...[, $1]) => obj['HASH'] = $1);
    return obj;
}


防抖

const clearTimer = function clearTimer(timer) {
        if (timer !== null) clearTimeout(timer);
        return null;
    };
 const debounce = function debounce(func, wait) {
        if (typeof func !== "function") throw new TypeError("func is not a function!");
        if (typeof wait !== "number") wait = 300;
        let timer = null;
        return function operate(...params) {
        // 清楚之前设置的定时器
            timer = clearTimer(timer);
            timer = setTimeout(() => {
            // 清楚最后一次设置的定时器
                timer = clearTimer(timer);
                func.call(this,...params)
            }, wait);
           
        };
    };

let sub = document.getElementById('btn')
sub.onclick = debounce(()=>{
    console.log('click')
},300)

func this指向问题
func 里的this应该是要指向sub 直接绑定方法,方法里的this时绑定元素本身,现在绑定是operate函数,operate函数this是元素本身,要执行的func函数this指向window ,要把func的this也改为绑定事件的元素sub,箭头函数没有this,是所属上下文的this,就是operate里的this

数组扁平化

juejin.cn/post/711205…

对象扁平化

juejin.cn/post/711205…

一维数组转树结构

juejin.cn/post/711205…

树形结构转列表

const tree2list = (tree) => {
  let list = []
  let queue = [...tree]

  while (queue.length) {
    // 从前面开始取出节点
    const node = queue.shift()
    const children = node.children
    // 取出当前节点的子节点,放到队列中,等待下一次循环
    if (children.length) {
      queue.push(...children)
    }
    // 删除多余的children树形
    delete node.children
    // 放入列表
    list.push(node)
  }

  return list
}

// 测试
const data = [
  {
    "id": 1,
    "name": "部门1",
    "pid": 0,
    "children": [
      {
        "id": 2,
        "name": "部门2",
        "pid": 1,
        "children": []
      },
      {
        "id": 3,
        "name": "部门3",
        "pid": 1,
        "children": [
          {
            "id": 4,
            "name": "部门4",
            "pid": 3,
            "children": [
              {
                "id": 5,
                "name": "部门5",
                "pid": 4,
                "children": []
              }
            ]
          }
        ]
      }
    ]
  }
]

console.log(tree2list(data))
/*
[ 
  { id: 1, name: '部门1', pid: 0 },
  { id: 2, name: '部门2', pid: 1 },
  { id: 3, name: '部门3', pid: 1 },
  { id: 4, name: '部门4', pid: 3 },
  { id: 5, name: '部门5', pid: 4 } 
]
*/

高阶函数

柯理化函数

实现求和

let res = fn(1, 2)(3); 
console.log(res)
const fn = function fn(...params) {
    // params:[1,2]  预储存
    return function proxy(...args) {
        // args:[3] 
        return params.concat(args).reduce((x, item) => x + item);
    };
};
let res = fn(1, 2)(3); 
console.log(res)

compose函数

  const add1 = (x) => x + 1;
  const mul3 = (x) => x * 3;
  const div2 = (x) => x / 2;
  div2(mul3(add1(add1(0)))); //=>3

样的写法可读性明显太差了,我们可以构建一个compose函数,它接受任意多个函数作为参数(这些函数都只接受一个参数),然后compose返回的也是一个函数,达到以下的效果:

 const operate = compose(div2, mul3, add1, add1)
 operate(0) //=>相当于div2(mul3(add1(add1(0)))) 
 operate(2) //=>相当于div2(mul3(add1(add1(2))))
const add1 = x => x + 1;
const mul3 = x => x * 3;
const div2 = x => x / 2;

const compose = function compose(...funcs) {
    // funcs:数组,依次存储要执行的函数,而且按照从右到左的顺序执行
    return function operate(x) {
        // x:函数执行的初始值
        let len = funcs.length;
        if (len === 0) return x;
        if (len === 1) return funcs[0](x);
        return funcs.reduceRight((x, item) => {
            return item(x);
        }, x);
    };
};
const operate = compose(div2, mul3, add1, add1);
console.log(operate(0)); //3
console.log(operate(2)); //6

惰性函数

惰性函数:懒,执行一次可以搞定的,绝对不会执行第二次

var getCss = function (elem, attr) {
    if (window.getComputedStyle) {
        return window.getComputedStyle(elem)[attr];
    }
    return elem.currentStyle[attr];
};
console.log(getCss(document.body, 'margin'));
console.log(getCss(document.body, 'padding'));

瑕疵:浏览器没换、页面没关,第一次执行需要判断兼容性是必须的,但是第二次及以后再执行,如果还要判断兼容性,是没有必要的

var getCss = function (elem, attr) {
    if (window.getComputedStyle) {
        getCss = function (elem, attr) {
            return window.getComputedStyle(elem)[attr];
        };
    } else {
        getCss = function (elem, attr) {
            return elem.currentStyle[attr];
        };
    }
    return getCss(elem, attr);
};
console.log(getCss(document.body, 'margin'));
console.log(getCss(document.body, 'padding'));

数组算法

两数之和

给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target  的那 两个 整数,并返回它们的数组下标。

你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。

你可以按任意顺序返回答案。

输入: nums = [3,2,4], target = 6
输出: [1,2]
const twoSum = (nums, target) => {
    const cacheMap = {}
  
    for (let i = 0, len = nums.length; i < len; i++) {
      const diff = target - nums[ i ]
     
      if (cacheMap[ diff ] !== undefined) {
        return [ cacheMap[ diff ], i ]
      } else {
        cacheMap[ nums[ i ] ] = i
      }
    }
  }
  
  console.log(twoSum([ 2, 7, 11, 15 ], 22))

合并两个有序数组