一文让你了解各种原生JS的实现方式

本文详细介绍了JavaScript的call、apply、new关键字及Promise的实现原理和应用,包括防抖和节流技巧,还有EventEmitter的基本操作。适合开发者提升对原生API的理解和实践能力。
摘要由CSDN通过智能技术生成

习惯了平时的快速开发的模式,相信大家对于一些原生的方法也是熟记于心,比如改变this指向的call,apply方法,创建对象的new关键字,实现异步编程的promise,又或者是自己手动实现的累加函数等等,今天本人就在这里给大家分享部分原生js方法的实现方式。本文只是方便个人知识存储,但也希望对大家有用。

call

原理:call方法接收一个目标对象以及一系列的参数,把当前的this对象指向目标对象,并执行方法

        Function.prototype.call = function (context) {
            context = context || window // 这里要兼容一种情况就是context如果是null,那么我们要把context指向window
            context.fn = this // 在context上挂载一个fn方法,并指向this
            let arg = []// 把argumengts类数组转化为数组,跳过第一个对象参数,索引从1开始
            for(let i = 1;i<arguments.length;i++){
                arg.push(arguments[i])
            }
            let res = context.fn(...arg) // 把执行结果保存起来并返回
            delete context.fn // 删除原来挂载在context对象上的fn,删除对this对引用
            return res // 返回结果
        }
        let obj = {
            name: 'jack',
            age: '28',
            sayHi() {
                console.log(`my name is ${this.name}, i'm ${this.age} years old`);
            }
        }
        let obj2 = {
            name: 'ma',
            age: '27'
        };
        console.log(obj.sayHi.call(obj2))

        // console.log(Math.max.call(null, 1,2,3))

apply

原理apply与call的原理差不多,只是参数的处理方式不大一样

        Function.prototype.apply = function (context) {
            context = context || window // 这里要兼容一种情况就是context如果是null,那么我们要把context指向window
            context.fn = this // 在context上挂载一个fn方法,并指向this
            let arg = []// 把argumengts类数组转化为数组,跳过第一个对象参数,索引从1开始
            if(arguments[1]){
                // 如果第二个参数有传参数才处理
                arg = Array.prototype.slice.call(arguments[1])
            }
            let res
            if(arg.length>0){
                res = context.fn(...arg) // 把执行结果保存起来并返回
            }else{
                res = context.fn() // 把执行结果保存起来并返回
            }
            
            delete context.fn // 删除原来挂载在context对象上的fn,删除对this对引用
            return res // 返回结果
        }
        let obj = {
            name: 'jack',
            age: '28',
            sayHi() {
                console.log(`my name is ${this.name}, i'm ${this.age} years old`);
            }
        }
        let obj2 = {
            name: 'ma',
            age: '27'
        };
        console.log(obj.sayHi.apply(obj2))

        console.log(Math.max.apply(null, [1,2,3]))

call和apply对比

  • Function.prototype.apply 和 Function.prototype.call 的作用是一样的,区 别在于传入参数的不同;
  • 第一个参数都是,指定函数体内 this 的指向;
  • 第二个参数开始不同,apply 是传入带下标的集合,数组或者类数组, apply 把它传给函数作为参数,call 从第二个开始传入的参数是不固定的,都会 传给函数作为参数。
  • call 比 apply 的性能要好,平常可以多用 call, call 传入参数的格式正是内部所需要的格式

new

原理:对于new关键字大家一定不会陌生,使用new创建对象内部的实现原理也经常在面试题当中出现,我们来分析一下new背后的原理。

  • 首先创建了一个新的空对象,并将对象的原型设置为函数的 prototype 对象。
  • 让函数的 this 指向这个对象,执行构造函数的代码(为这个新对象添加属性)。
  • 判断函数的返回值类型,如果是值类型,返回创建的对象。如果是引用类型,就返回这个引用类型的对象。
        function myNew(fn) { 
            const newObj = Object.create(fn.prototype); 
            result = fn.apply(newObj, [...arguments].slice(1)); 
            return typeof result === "object" ? result : newObj; 
        }

promise

    function MyPromise(fn) { 
      // 保存初始化状态 
      var self = this;
      // 初始化状态 
      this.state = PENDING; 
      // 用于保存 resolve 或者 rejected 传入的值 
      this.value = null; 
      // 用于保存 resolve 的回调函数 
      this.resolvedCallbacks = []; 
      // 用于保存 reject 的回调函数 
      this.rejectedCallbacks = []; 
      // 状态转变为 resolved 方法 
      function resolve(value) { 
        // 判断传入元素是否为 Promise 值,如果是,则状态改变必须等待前一个状态改变后再 进行改变
        if (value instanceof MyPromise) { 
          return value.then(resolve, reject); 
        }
        // 保证代码的执行顺序为本轮事件循环的末尾 
        setTimeout(() => { 
          // 只有状态为 pending 时才能转变, 
          if (self.state === PENDING) { 
            // 修改状态 
            self.state = RESOLVED;
            // 设置传入的值 
            self.value = value; 
            // 执行回调函数 
            self.resolvedCallbacks.forEach(callback => { 
              callback(value); 
            }); 
          } 
        }, 0); 
      }
      // 状态转变为 rejected 方法 
      function reject(value) { 
        // 保证代码的执行顺序为本轮事件循环的末尾 
        setTimeout(() => { 
          // 只有状态为 pending 时才能转变 
          if (self.state === PENDING) { 
            // 修改状态 
            self.state = REJECTED; 
            // 设置传入的值 
            self.value = value; 
            // 执行回调函数
            self.rejectedCallbacks.forEach(callback => { 
              callback(value); 
            }); 
          } 
        }, 0); 
      }
      // 将两个方法传入函数执行 
      try { 
        fn(resolve, reject); 
      } catch (e) { 
        // 遇到错误时,捕获错误,执行 reject 函数 
        reject(e); 
      }
    } 
    MyPromise.prototype.then = function(onResolved, onRejected) { 
      // 首先判断两个参数是否为函数类型,因为这两个参数是可选参数 
      onResolved = typeof onResolved === "function" ? onResolved : function(value) { return value; }; 
      onRejected = typeof onRejected === "function" ? onRejected : function(error) { throw error; }; 
      // 如果是等待状态,则将函数加入对应列表中 
      if (this.state === PENDING) { 
        this.resolvedCallbacks.push(onResolved); 
        this.rejectedCallbacks.push(onRejected); 
      }
      // 如果状态已经凝固,则直接执行对应状态的函数 
      if (this.state === RESOLVED) { 
        onResolved(this.value); 
      }
      if (this.state === REJECTED) { 
        onRejected(this.value); 
      }
    };

promise.all

function myPromiseAll(promiseList = []){
	// 校验参数类型
	if(!Array.isArray(promiseList)){
		return reject(new TypeError('arguments muse be an array'))
	}
	// 获取参数长度,定义结果数组
	let len = promiseList.length, res = []
	for(let i=0;i<len;i++){
	    // 这里大家可以用Object.prototype.toString.call(promiseList[i])判断元素是否是promise实例,但可以直接使用Promise.reslove(...)因为不管它是不是promise的实例都会呗转化为promise,因此不需要判断
		Promise.reslove(promiseList[i]).then(result => {
			// 这里不能用push,不同的promise可能完成的先后顺序不一样,会导致顺序混乱
			res[i] = result
			if(res.length === len){
				reslove(res)
			}
		}).catch(e => reject(e))
	}
}

防抖

什么是防抖?当持续触发事件时,一定时间段内没有再触发事件,事件处理函数才会执行一次,如果设定的时间到来之前,又一次触发了事件,就重新开始延时。

function debounce(fn, wait){
	let timer = null
	return function(){
		if(timer !== null){
			clearTimeout(timer)
		}
		let that = this
		timer = setTimeOut(() => {
			fn.call(that) //不需要传参
			fn.call(that, ...arguments) // 需要传参
		},wait)
		
	}
}

节流

什么是节流?
当持续触发事件时,保证一定时间段内只调用一次事件处理函数

function throttle(fn, delay){
	let timer = null
	let startDate = Date.now()
	return function(){
		let that = this
		clearTimeOut(timer)
		let current = Date.now() 
		let remaining =  delay - (current - startDate)
		if(remaining <=0){
			fn.apply(that, arguments)
			startDate = Date.now()
		}else{
			timer = setTimeOut(()=>{
				fn.apply(that, arguments)
			},remaining)
		}
	}
}

EventEmitter

EventEmitter是node.js的一个事件队列,这里主要实现on,emit,once,off

class EventEmitter {
	constructor(){
		this.events = {}
	}

	on(event, cb){
		let cbs = this.events[event] || []
		cbs.push(cb)
		this.events = cbs

		return this
	}

	emit(event, ...args){
		let cbs = this.events[event]

		cbs.forEach(fn => {
			fn(...args)
		});

		return this
	}

	off(event, cb){
		let cbs = this.events[event]

		this.events[event] = cbs && cbs.filter(fn => fn !== cb) 

		return this
	}

	once(event, cb){
		let fn = function(...args){
			cb(...args)

			this.off(event, cb)
		}

		this.on(event, fn)

		return this
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值