习惯了平时的快速开发的模式,相信大家对于一些原生的方法也是熟记于心,比如改变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
}
}