JS手撕代码

171 阅读7分钟

一些基础问题

call apply bind

fun.call(this指向,参数1,参数2) 函数执行 更改this指向

Function.prototype.mycall=functoion(context){
	context=context||window;
	context.fn=this;
        let args = [...arguments].slice(1);
	let result=context.fn(...args);
	delete context.fn;
	return result;
}

fun.apply(this指向,参数列表) 执行更改完this指向的函数

Function.prototype.myapply=function(context){
	context=context||window;
	context.fn=this;
        let args=arguments[1]||[];
	let result=context.fn(...args);
	delete context.fn;
	return result;
}

fun.bind() 只更改this的指向,函数并不执行,返回的是一个函数

Function.prototype.myBind = function (context, ...outerArgs) {
  let that = this;
  let res = function (...innerArgs) {
    if (this instanceof res) {// new操作符执行时
      that.call(this, ...outerArgs, ...innerArgs);
    } else {// 普通bind
      that.call(context, ...outerArgs, ...innerArgs);
    }
  };
  res.prototype = this.prototype; //!!!
  return res;
};

深拷贝 浅拷贝

浅拷贝:直接复制引用

function shallowCopy(obj){
	if(typeOf obj !== 'Object') return;
	let newObject = obj instanceof Array ? []:{};
	for(let key in obj){
		if(obj.hasOwnPrototype(key)){
			newObject[key]=obj[key]
		}
	}
	return newObject;
}

深拷贝:指完全的拷贝一个对象,即使嵌套了对象,两者也相互分离,修改一个对象的属性,也不会影响另一个。

function deepCopy(obj){
	if(typeof obj!='Object') return;
	let newObject== obj instanceof Array?[]:{};
	for(let key in obj){
		if(obj.haeOwnPrototype(key)){
			newObject[key]=typeOf(key)=='Object'? deepCopy(obj[key]):obj[key];
		}
	}
	return newObject;
}

instanceof

function myInstanceof(left, right) {
  let proto = Object.getPrototypeOf(left), // 获取对象的原型
  prototype = right.prototype; // 获取构造函数的 prototype 对象
  // 判断构造函数的 prototype 对象是否在对象的原型链上
  while (proto) {
    if (proto === prototype) return true;
    proto = Object.getPrototypeOf(proto);
  }
  return false; 
}

防抖节流

防抖 在事件被触发 n 秒后再执行回调,如果在这 n 秒内事件又被触发,则重新计时。

function debounce(fn, wait) {
  var timer = null;
  return function () {
    var context = this,args = arguments
    if (timer)   clearTimeout(timer);
    timer=setTimeout(function() {
      fn.apply(that, args);
      timer=null;
    }, wait);
  };
}

节流 规定一个单位时间,在这个单位时间内,只能有一次触发事件的回调函数执行,如果在同一个单位时间内某事件被触发多次,只有一次能生效。

function throttle(fn, delay) {
  let timer;
  return function () {
    var context = this,args = arguments
    if (timer)  return;
    timer = setTimeout(function () {
      fn.apply(that, args);
      timer = null; 
    }, delay)
  }
}

继承

//1原型
function Father(){}
Father.prototype.name="father"
functuon Son(){}
Son.prototype=Object.create(Father.prototype);//!!!!!
son.prototype.constructor=son;
let s1=new son()

//2构造函数
function Father(){}
functuon Son(){
    Father.call(this);
}
let s1=new son()
//3原型链
function Father(){}
Father.prototype.name="father"
functuon Son(){}
Son.prototype=new father()
let s1=new son()
//4原型链构造函数组合
function Father(){}
Father.prototype.name="father"
functuon Son(){
    Father.call(this);//继承属性
}
Son.prototype = new Father();//继承方法
let s1=new son()

//5寄生
function createP(obj){
    var clone=Object(obj);
    clone.getName=function(){
        console.log("create");
    }
    return clone;
}
//6组合寄生
function inheritPrototype(subType, superType){
    var prototype = object(superType.prototype); // 创建原型对象是超类原型对象的一个实例对象  
    prototype.constructor = subType; // 弥补因为重写原型而失去的默认的 constructor 属性。 
    subType.prototype = prototype; // 实现原型继承
}

new

function mynew(Func, ...args) {
    // 1.创建一个新对象
    const obj = {}
    // 2.新对象原型指向构造函数原型对象
    obj.__proto__ = Func.prototype
    // 3.将构建函数的this指向新对象
    let result = Func.apply(obj, args)
    // 4.根据返回值判断
    return result instanceof Object ? result : obj
}

数组去重

function removeSame(data){
	return [...new Set(data)]
}

function removeSame(data){
	return data.filter((value,index)=>{
		data.indexO[value]==index;
	})
}
function removeSame(data){
	let unique=[]
	data.forEach((element)=>{
		if(!unique.includes(element)){
			unique.push(element);
		}
	})
	return unique;
}

数组扁平化

function flat(arr){
	return arr.toString().split(",").map((item)=>{
		return Number(item);
	})
}

[].concat(...arr);

function flat(arr){
	return arr.reduce((result,item)=>{
		return result.concat(Array.isArray(item)?flat(item):item);
	},[])
}

pipe

const fn = pipe(addOne, addTwo, addThree, addFour); // 传入pipe的四个函数都是已实现的 fn(1); // 1 + 1 + 2 + 3 + 4 = 11,输出11

//使用reduce实现
const pipeFun=(...fns)=> 
	fns.reduce((f,g)=>{
		return (...args)=>{
			return g(f(..args))
		};
	})
// 同上
const pipe = ...args => x => 
  args.reduce(
    (outputValue, currentFunction) => currentFunction(outputValue), x )	
//ES5
const pipe = function(){
  const args = [].slice.apply(arguments);
  return function(x) {
    return args.reduce((res, cb) => cb(res), x);
  }
}	

const add5 = (x) => x + 5;
const multiply = (x, y) => x * y;
const add1 = (x) => x + 1;
const multiplyAndAdd5 = pipeFunctions(multiply, add5, add1); // 15
console.log(multiplyAndAdd5(5, 2));

compose

借助Array.prototype.reduce,这个方法会从左往右迭代,但是我们需要的是从右往左迭代,这个方法是Array.prototype.reduceRight:

const compose = function(){
  // 将接收的参数存到一个数组, args == [multiply, add]
  const args = [].slice.apply(arguments);
  return function(x) {
    return args.reduceRight((res, cb) => cb(res), x);
  }
}
const compose1 = (...fns) =>
  fns.reduceRight((f, g) => {
    return (...args) => {
      return g(f(...args));
    };
  });

compose函数和pipe函数 - it610.com

柯里化

//这是定长的函数柯里化的通用写法
function curry(fn) {
  // 获取原函数的参数长度
  const len = fn.length;
  let that = this;
  // 保存预置参数
  const outArgs = Array.prototype.slice.call(arguments, 1);
  // 返回一个新函数
  return function () {
    // 新函数调用时会继续传参
    const inArgs = Array.prototype.slice.call(arguments);
    const allArgs = [...outArgs, ...inArgs];
    if (allArgs.length >= len) {
      // 如果参数够了,就执行原函数
      return fn.apply(that, allArgs);
    } else {
      // 否则继续柯里化
      return curry.call(that, fn, ...allArgs);
    }
  };
}


//add 函数是不定长的 所以不可以用以上的通用写法
function add() {
  // 第一次执行时,定义一个数组专门用来存储所有的参数
  var _args = [].slice.call(arguments);
  console.log(_args);
  // 在内部声明一个函数,利用闭包的特性保存_args并收集所有的参数值,执行时已经收集所有参数为数组
  var adder = function () {
    var _adder = function () {
      // 执行收集动作,每次传入的参数都累加到原参数
      [].push.apply(_args, [].slice.call(arguments));
      return _adder;
    };
    // 利用隐式转换的特性,当最后执行时隐式转换,并计算最终的值返回
    _adder.toString = function () {
      return _args.reduce(function (a, b) {
        return a + b;
      });
    };
    return _adder;
  };
  return adder(_args);
}

彻底搞懂闭包,柯里化,手写代码,金九银十不再丢分! - 掘金 (juejin.cn)

发布订阅

对象间一对多的依赖关系,当一个对象的状态发生改变的时候,所有依赖于它的对象都将得到状态的改变

  • 创建一个对象
  • 在该对象上创建一个缓存列表-调度中心
  • on方法用来把函数fn加到缓存列表中-订阅者注册事件到调度中心
  • emit方法取到arguments里第一个当作event,根据event值去执行对应缓存列表的函数(发布者发布事件到调度中心,调度中心处理代码)
  • off方法根据event值取消订阅
//发布订阅模式
let eventEmitter = {
  list: {}, //监听队列
  //订阅
  on(event, fn) {
    if (!this.list[event]) {
      //没有event的值说明没有订阅过 就创建一个
      this.list[event] = [];
    }
    this.list[event].push(fn);
  },
  //发布
  emit() {
    let key = [].shift.call(arguments); //第一个参数是event的值
    fns = this.list[key]; //event的对应的队列
    if (!fns || fns.length == 0) {
      return false;
    }
    // 遍历 event 值对应的缓存列表,依次执行 fn
    fns.forEach((fn) => {
      fn.apply(this, arguments);
    });
  },
  //取消订阅
  remove(key, fn) {
    let fns = this.list[key];
    if (!fns) return false;
    if (!fn) {
      fns && (fns.length = 0);
    } else {
      fns.forEach((item, i) => {
        if (item == fn) {
          fns.splice(i, 1);
        }
      });
    }
  },
};

观察者

class Subject{
    constructor(name){
        this.name = name;
        this.state = '正常的';
        this.observer = [];
    }
    attach(key,o){
        this.observer.push(o);
    }
    
    setstate(newstate){
        this.state = newstate;
        this.observer.forEach(o => o.update(this));
    }
}
class Observer{
    constructor(name){
        this.name = name;
    }
    update(baby){
        console.log(this.name+'被通知了,被观察者状态是'+ baby.state);
    }
}
let baby = new Subject("小宝宝");
let father = new Observer("爸爸");
let mother = new Observer("妈妈");

baby.attach(father);
baby.attach(mother);
baby.setState("我饿了");

//忘记从那个博客截的图了 侵权删 image.png

一些面试中遇到的

setTimeout实现setInterval

 function myInterval(fn, delay) {
   let timer = null;
   function interval() {
     fn();
     timer = setTimeout(interval, delay);
   }
   timer = setTimeout(interval, delay);
 }
 ​
 let cancel = myInterval(() => {
   console.log(222);
 }, 1000);
 cancel();

DOM转JSON

 function convertToJson() {
     const root = document.getElementsByClassName('root')[0];
     const output = new Object();
     output.tagName = root.tagName;
     output.className = root.className;
     output.childs = getChilds(root);
 ​
     console.log(JSON.stringify(output));
 }
 ​
 function getChilds(node) {
     const childs = node.children;
     const result = new Array();
     if(!childs || childs.length === 0) return result;
     for (const child of childs) {
         const childOutput = new Object();
         childOutput.tagName = child.tagName;
         childOutput.className = child.className;
         childOutput.childs = getChilds(child);
         result.push(childOutput);
     }
     return result;
 }
 convertToJson();

数组转对象

  • Object.keys():会返回一个给定对象的自身可枚举属性组成的数组,数组中属性名的排列顺序和使用for…in循环遍历该对象时返回的顺序一致 。
  • Object.values():返回一个给定对象自身的所有可枚举属性值的数组,值的顺序与使用for…in循环的顺序相同 ( 区别在于 for-in 循环枚举原型链中的属性 )。
  • Object.entries():返回一个给定对象自身可枚举属性的键值对数组,其排列与使用for…in循环遍历该对象时返回的顺序一致(区别在于 for-in 循环还会枚举原型链中的属性)。
  • Object.fromEntries():把键值对列表转换为一个对象,是Object.entries()的逆操作。
 //[{},{}]=>{{},{}}
 function change(data){
     let obj={};
     data.map(a=>{
         obj[a.id]=a.value;//对象中的变量需要[]访问
     })
     return obj;
 }
 //使用forEach
 const arr = [1,2,3,4,5];let obj = {};
 arr.forEach((item,index) => { obj[index] = item;})
 console.log(obj); //{ 0: 1, 1: 2, 2: 3, 3: 4, 4: 5 }对象转数组
 function changeIdToValue(data) {
   data.map((item) => {
     const { id, name, children } = item; //解构赋值
     let _children = children ? changeIdToValue(children) : null;
     return { value: id, name: name, children: _children };
   });
   return data;
 }

对象转数组

 //得到对象的item
 const obj = { a:1 , b: 2, c: 3 };
 const arr = Object.entries(obj);
 console.log(arr); // [ ['a', 1], ['b', 2], ['c', 3] ]
 //Object.keys(obj)
 ​
 //得到对象的值
 const obj = { a:1 , b: 2, c: 3 };
 const arr = Object.values(obj);
 console.log(arr); // [1, 2, 3];
 //
 function objToArr() {
   const obj = { a: 1, b: 2, c: 3 };
   //const arr = Object.entries(obj);
   let arr = [];
   for (let o of Object.entries(obj)) {
     arr.push(o);
   }
   console.log(arr);
 }
 objToArr();

数组转树

 function transTree(data) {
     let result = []
     let map = {}
     if (!Array.isArray(data)) {//验证data是不是数组类型
         return []
     }
     data.forEach(item => {//建立每个数组元素id和该对象的关系
         map[item.id] = item //这里可以理解为浅拷贝,共享引用
     })
     data.forEach(item => {
         let parent = map[item.parentId] //找到data中每一项item的爸爸
         if (parent) {//说明元素有爸爸,把元素放在爸爸的children下面
             (parent.children || (parent.children = [])).push(item)
         } else {//说明元素没有爸爸,是根节点,把节点push到最终结果中
             result.push(item) //item是对象的引用
         }
     })
     return result //数组里的对象和data是共享的
 }
 console.log(JSON.stringify(transTree(data)))

二叉树转数组

 function transArr(node) {
     let queue= [node]
     let data = [] //返回的数组结构
     while (queue.length !== 0) { //当队列为空时就跳出循环
         let item = queue.shift() //取出队列中第一个元素
         data.push({
             id: item.id,
             parentId: item.parentId,
             name: item.name            
         })
         let children = item.children // 取出该节点的孩子
         if (children) { 
             for (let i = 0; i < children.length; i++) {
                 queue.push(children[i]) //将子节点加入到队列尾部
             }
         }
     }
     return data
 }
 console.log(transArr(node))