js实现常用函数

实现一个new操作符

new操作符做了这些事:

  • 它创建了一个全新的对象。
  • 将新对象的原型设置为构造函数的原型
  • 使用apply 或者 call 将 this 指向为创建的对象,并继承构造函数的属性。
  • 如果构造函数本身没有返回对象类型Object,那么new 函数返回创建的对象。
function myNew(Con,...args){
	let res={}
	res.__proto__ = Con.prototype
	let result = Con.apply(res,args)
	return result instanceof Object ? result : res
}

var obj = new funcA(a,b)
var obj = myNew(funcA,a,b) //效果一样
实现一个 instanceOf
  • 判断右边对象(函数)的原型 prototype 是否会在左边对象的原型链上
const myInstanceOf = (left, right) => {
    let leftValue = left.__proto__
    let rightValue = right.prototype
    while(true) {
        if(leftValue === null) return false
        if(leftValue === rightValue) return true
        leftValue = leftValue.__proto__
    }
}
实现一个call或 apply
  • call语法:
 fun.call(thisArg, arg1, arg2, ...),调用一个函数, 其具有一个指定的this值和分别地提供的参数(参数的列表)
  • apply语法:
  func.apply(thisArg, [argsArray]),调用一个函数,以及作为一个数组(或类似数组对象)提供的参数。
Function.call的模拟实现
Function.prototype.myCall = function(context){
	context = context || window
	context.fn = this
	const args = [...arguments].slice(1)
	const result = context.fn(...args)
	delete conetext.fn
	return result
}
Function.apply的模拟实现
  • apply()的实现和call()类似,第二个参数可以不传,但类型必须为数组或者类数组
Function.prototype.myApply = function (context) {
	context = context || window
	context.fn = this
	const args = arguments[1]
	let result
	if(args){
		result = context.fn(..args)
	} else {
		result = context.fn()
	}
	delete context.fn
	return result
}
实现一个Function.bind()
  • 使用 call / apply 指定 this
  • 返回一个绑定函数
  • 当返回的绑定函数作为构造函数被new调用,绑定的上下文指向实例对象
  • 设置绑定函数的prototype 为原函数的prototype
Function.prototype.myBind = function(context,...args){
	const fn = this
	const bindFn = function(...newFnArgs){
		fn.call(
            this instanceof bindFn ? this : context,
            ...args, ...newFnArgs
        )
	}
	bindFn.prototype = Object.create(fn.prototype)
	return bindFn
}
6. 实现一个继承
  • 寄生组合式继承
    寄生组合式继承的高效体现在它只调用了一次父级的构造函数,并且因此避免了在Super.prototype上面创建不必要的、多余属性
function Super(a){
	this.a = a
}
Super.prototype.printA = function () {
	console.log(this.a)
}

function Sub(b){
	this.b = b
	Super.call(this)	
}
Sub.prototype = Object.create(Super.prototype)
// 子类的原型是父类的一个实例,所以此时Sub.prototype.constructor === Super
Sub.prototype.constructor = Sub
// 因为此时的构造函数是继承自原型链上的,所以需要将其归位为 Sub

也可以使用以下过程替代 Object.create()
function create(obj){
	function F(){}
	F.prototype = obj
	return new F()
}
Sub.prototype = create(Super.prototype)

  • ES6版继承
class Super {
    constructor(foo) {
      this.foo = foo
    }
    printFoo() {
      console.log(this.foo)
    }
  }
  class Sub extends Super {
    constructor(foo, bar) {
      super(foo)
      this.bar = bar
    }
  }
7. 实现一个JS函数柯里化
  • 什么是柯里化?
    在计算机科学中,柯里化(Currying)是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果的新函数的技术。
  • 函数柯里化的主要作用和特点就是参数复用、提前返回和延迟执行。
7.1 通用版
function curry(fn, args) {
    var length = fn.length;
    var args = args || [];
    return function(){
        newArgs = args.concat(Array.prototype.slice.call(arguments));
        if (newArgs.length < length) {
            return curry.call(this,fn,newArgs);
        }else{
            return fn.apply(this,newArgs);
        }
    }
}
 
function multiFn(a, b, c) {
    return a * b * c;
}
 
var multi = curry(multiFn);
 
multi(2)(3)(4);
multi(2,3,4);
multi(2)(3,4);
multi(2,3)(4);
7.2 ES6骚写法
const curry = (fn, arr = []) => (...args) => (
  arg => arg.length === fn.length
    ? fn(...arg)
    : curry(fn, arg)
)([...arr, ...args])
 
let curryTest=curry((a,b,c,d)=>a+b+c+d)
curryTest(1,2,3)(4) //返回10
curryTest(1,2)(4)(3) //返回10
curryTest(1,2)(3,4) //返回10
8. 手写一个Promise(中高级必考)
  • 我们来过一遍Promise/A+规范:
  • 三种状态pending| fulfilled(resolved) | rejected
    当处于pending状态的时候,可以转移到fulfilled(resolved)或者rejected状态
    当处于fulfilled(resolved)状态或者rejected状态的时候,就不可变。
  • 必须有一个then异步执行方法,then接受两个参数且必须返回一个promise:
    // onFulfilled 用来接收promise成功的值
    // onRejected 用来接收promise失败的原因
promise1=promise.then(onFulfilled, onRejected);
8.1 Promise的流程图分析
  • 回顾下Promise用法:
 var promise = new Promise((resolve,reject) => {
    if (操作成功) {
        resolve(value)
    } else {
        reject(error)
    }
})
promise.then(function (value) {
    // success
},function (value) {
    // failure
})
8.2 面试够用版
  • 完美符合Promise/A+规范的Promise
function myPromise(constructor){
    let self=this;
    self.status="pending" //定义状态改变前的初始状态
    self.value=undefined;//定义状态为resolved的时候的状态
    self.reason=undefined;//定义状态为rejected的时候的状态
    function resolve(value){
        //两个==="pending",保证了状态的改变是不可逆的
       if(self.status==="pending"){
          self.value=value;
          self.status="resolved";
       }
    }
    function reject(reason){
        //两个==="pending",保证了状态的改变是不可逆的
       if(self.status==="pending"){
          self.reason=reason;
          self.status="rejected";
       }
    }
    //捕获构造异常
    try{
       constructor(resolve,reject);
    }catch(e){
       reject(e);
    }
}

同时,需要在myPromise的原型上定义链式调用的then方法:

 myPromise.prototype.then=function(onFullfilled,onRejected){
   let self=this;
   switch(self.status){
      case "resolved":
        onFullfilled(self.value);
        break;
      case "rejected":
        onRejected(self.reason);
        break;
      default:       
   }
}
9. 手写防抖(Debouncing)和节流(Throttling)
  • scroll 事件本身会触发页面的重新渲染,同时 scroll 事件的 handler 又会被高频度的触发, 因此事件的 handler 内部不应该有复杂操作,例如 DOM 操作就不应该放在事件处理中。 针对此类高频度触发事件问题(例如页面 scroll ,屏幕 resize,监听用户输入等),有两种常用的解决方法,防抖和节流。
9.1 防抖(Debouncing)实现

典型例子:限制 鼠标连击 触发。
一个比较好的解释是:当一次事件发生后,事件处理器要等一定阈值的时间,如果这段时间过去后 再也没有 事件发生,就处理最后一次发生的事件。假设还差 0.01 秒就到达指定时间,这时又来了一个事件,那么之前的等待作废,需要重新再等待指定时间。

  • 函数节流(定时器)
const throttle = (fn, wait = 300) => {
    let timerId
    return function(...args) {
        if(!timerId) {
            timerId = setTimeout(() => {
                timerId = null
                return result = fn.apply(this, ...args)
            }, wait)
        }
    }
}
  • 函数节流(时间戳)
const throttle = (fn, wait = 300) => {
    let prev = 0
    let result
    return function(...args) {
        let now = +new Date()
        if(now - prev > wait) {
            prev = now
            return result = fn.apply(this, ...args)
        }
    }
}
  • 结合实例:滚动防抖
// 简单的防抖动函数
// 实际想绑定在 scroll 事件上的 handler
function realFunc(){
    console.log("Success");
}
 
// 采用了防抖动
window.addEventListener('scroll',debounce(realFunc,500));
// 没采用防抖动
window.addEventListener('scroll',realFunc);

在这里插入图片描述

9.2 节流(Throttling)实现
  • 可以理解为事件在一个管道中传输,加上这个节流阀以后,事件的流速就会减慢。实际上这个函数的作用就是如此,它可以将一个函数的调用频率限制在一定阈值内,例如 1s,那么 1s 内这个函数一定不会被调用两次
  • 简单的节流函数:
function throttle(fn, wait) {
   	let prev = new Date();
   	return function() { 
   	    const args = arguments;
   		const now = new Date();
   		if (now - prev > wait) {
   			fn.apply(this, args);
   			prev = new Date();
   		}
   	}
}
9.3 结合实践
  • 通过第三个参数来切换模式。
const throttle = function(fn, delay, isDebounce) {
  let timer
  let lastCall = 0
  return function (...args) {
    if (isDebounce) {
      if (timer) clearTimeout(timer)
      timer = setTimeout(() => {
        fn(...args)
      }, delay)
    } else {
      const now = new Date().getTime()
      if (now - lastCall < delay) return
      lastCall = now
      fn(...args)
    }
  }
}
10. 手写一个JS深拷贝
10.1 乞丐版
var newObj = JSON.parse( JSON.stringify( someObj ) );
10.2 面试够用版
function deepCopy(obj){
   //判断是否是简单数据类型,
    if(typeof obj == "object"){
        //复杂数据类型
        var result = obj.constructor == Array ? [] : {};
        for(let i in obj){
            result[i] = typeof obj[i] == "object" ? deepCopy(obj[i]) : obj[i];
        }
    }else {
        //简单数据类型 直接 == 赋值
        var result = obj;
    }
    return result;
}
    
12. 数组扁平化
技巧版
const flatten = (arr) => arr.toString().split(',').map(item => +item)
reduce实现
const flatten = (arr, deep = 1) => {
  return arr.reduce((cur, next) => {
    return Array.isArray(next) && deep > 1 ?
      [...cur, ...flatten(next, deep - 1)] :
      [...cur, next]
  },[])
}
13. 简单实现node 的 event 模板/发布-订阅模式/观察者模式

首先先了解一下EventEmitter模块的基本用法,EventEmitter本质上是一个观察者模式的实现,所谓观察者模式:

它定义了对象间的一种一对多的关系,让多个观察者对象同时监听某一个主题对象,当一个对象发生改变时,所有依赖于它的对象都将得到通知。

因此最基本的EventEmitter功能,包含了一个观察者和一个被监听的对象,对应的实现就是EventEmitter中的on和emit:

var events=require('events');
var eventEmitter=new events.EventEmitter();
eventEmitter.on('say',function(name){
    console.log('Hello',name);
})
eventEmitter.emit('say','Jony yu');

根据上述的例子,我们知道了EventEmitter模块的基础功能emit和on。下面我们实现一个包含emit和on方法的EventEmitter类。

  • on(eventName,callback)方法传入两个参数,一个是事件名(eventName),另一个是相应的回调函数,我们选择在on的时候针对事件名添加监听函数,用对象来包含所有事件。在这个对象中对象名表示事件名(eventName),而对象的值是一个数组,表示该事件名所对应的执行函数。
  • emit(eventName,…arg)方法传入的参数,第一个为事件名,其他参数事件对应的执行函数中的实参,emit方法的功能就是从事件对象中,寻找对应key为eventName的属性,执行该属性所对应的数组里面每一个执行函数。
function EventEmitter{
   this.on = function(eventName,callback){
      if(!this.handles){
        this.handles={};
      }
      if(!this.handles[eventName]){
        this.handles[eventName]=[];
      }
      this.handles[eventName].push(callback);
   }
   this.emit = function(eventName,...arg){
       if(this.handles[eventName]){
       for(var i=0;i<this.handles[eventName].length;i++){
           this.handles[eventName][i](...arg);
       }
   }
   return this
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值