js手撕笔记

Xayah,面试

手写 instanceof

检查 B 是否为 A 的父类或者祖先类型的函数,原理是利用 js 对象原型链和原型对象的知识一层一层寻找,最底层是 null:

let myInstanceof = (target,origin) => {
	while(target) {
	    if(target._proto_===origin.prototype) {
	        return true;
	    }
	    target=target._proto_;
	}
	return false;
}
 
// 测试
let a = [1,2,3];
console.log(myInstanceof(a,Array)); // true
console.log(myInstanceof(a,String)); // false

Local Image 每个函数对象都有自己的 prototype,如果直接 a.prototype 返回的是 undefined,但是 Object.prototypeArray.prototype 等则有具体的属性值,因为这些函数对象有完整的原型链。对象通过 __proto__ 来访问上一层的 prototype。 例如 a.__proto__ === Array.prototype

原生实现数组 map 方法

Map 参数:

Array.prototype.myMap = function(callback) {
	let res=[];
	for(let i=0;i<this.length;i++) {
	    res.push(callback(this[i],i,this));
	}
	return res;
}
 
// use
let a=[1,2,3];
let b=a.myMap((item)=>item+1);
console.log(b); // [2,3,4]

原生实现数组 reduce 方法

Reduce 参数:

Array.prototype.myReduce = function(callback, initialValue) {
	let num=initialValue==undefined?this[0]:initialValue;
	for(int i=initialValue==undefined?0:1;i<this.length;i++) {
	    num=callback(num,this[i],i,this);
	}
	return num;
}
 
// use
let a=[1,2,3];
let res=a.myReduce((acc,item)=>acc+item,0); // 累加

使用 reduce 实现 map 方法

Array.prototype.myMap = function(callback) {
	return this.reduce((acc,item)=>{ // reduce函数最后返回累加器即acc
	    acc.push(callback(item))
	    return acc;
	},[])
}
 
// use
let a=[1,2,3];
let b=a.myMap((item)=>item+1);
console.log(b); // [2,3,4]

使用 reduce 实现数组扁平化

数组扁平化意为将多维数组压缩成一维数组,concat 方法主要用于合并~

let flatten = function(arr) {
	return arr.reduce((acc,item)=>{
	    return acc.concat(Array.isArray(item) ? flatten(item) : item);
	},[])
}
 
// use
let a=[1,2,[3,4]];
flatten(a);
console.log(a); // [1,2,3,4]

实现柯里化函数

柯里化是指将一个多参数的函数转换成一系列单参数函数,实参个数>=形参个数时会执行该函数,多于参数忽略,如果小于则需要再接收实参,如果中止会返回 NaN

let curry = function(fn, ...arg) {
	return fn.length<=arg.length?fn(...arg):curry.bind(null,fn,...arg);
	// fn.length返回形参即fn需要的参数,使用bind目的是返回一个函数,其他(call apply)会返回值,不适用
}
 
// use
function sum(a,b,c) {
	return a+b+c;
}
 
let sumCurry=curry(sum);
console.log(sumCurry(1,2,3)); // curry(sum)(1,2,3) = 6
console.log(sumCurry(1,2)(3)); // 6
console.log(sumCurry(1,2,3,4)); // 6
console.log(sumCurry(1,2)); // NaN

浅拷贝及深拷贝

修改原对象的嵌套对象属性值时,深拷贝得到的对象不会受到影响,而浅拷贝得到的会

const shallowcopy=function(obj) {
	const newObj={};
	for(const key in obj) {
	    newObj[key]=obj[key];
	}
	return newObj;
}
 
const deepcopy=function(obj) {
	const newObj={};
	for(const key in obj) {
	    if(typeof obj[key]==='object') {
	        newObj[key]=deepcopy(obj[key]);
	    }else {
	        newObj[key]=obj[key];
	    }
	}
	return newObj;
}
 
// use
const a={a:1,b:{c:2}};
const b=shallowcopy(a);
const c=deepcopy(a);
a.b.c=3;
console.log(b.b.c); // 3
console.log(c.b.c); // 2

手写 call apply bind

这三个主要起到一个调用时改变 this 指向的作用 call:

Function.prototype.myCall=function(context,...args) {
	context=context||window;
	const fn=Symbol();
	context[fn]=this; // 即foo
	const result=context[fn](...args); // 即foo(...args)
	delete context[fn];
	return result;
}

apply: 和 call 的不同是,它传入的是一个数组,而 call 传入的是一个个单独的参数。 代码除了接收的是 args 而不是 ...args 以外,和 call 一模一样,因为 ...args 也可以指展开数组 args

Function.prototype.myApply=function(context, args) {
	context=context||window;
	const fn=Symbol();
	context[fn]=this;
	const result=context[fn](...args);
	delete context[fn];
	return result;
}

bind: 和前两个不同的是他会返回一个新的函数等待被调用,而不是直接返回一个值,接收的参数是独立的,不同于 apply 的数组,所以要用 concat 进行合并此时是一个数组,所以用 myApply

Function.prototype.myBind=function(context,...args) {
	context=context||window;
	const fn=this; // 即foo
	return function(...newArgs) {
	    return fn.myApply(context,args.concat(newArgs));
	}
}
 
// use
let obj={name:"Amy"};
function foo(a,b,c) {
	console.log(this.name, a+b+c);
}
foo.myCall(obj,1,2,3); // Amy 6
foo.myApply(obj,[1,2,3]); // Amy 6
foo.myBind(obj,1,2)(3); // Amy 6

睡眠函数

// 请你编写一个异步函数,它接收一个正整数参数 millis ,并休眠 millis 毫秒。要求此函数可以解析任何值。
 
/**
 * @param {number} millis
 * @return {Promise}
 */
 
async function sleep(millis) {
    return new Promise(resolve=>setTimeout(resolve, millis))
}
 
let t = Date.now()
sleep(100).then(() => console.log(Date.now() - t)) // 100

手动实现 new

/**
 * function Person(name, age) {
 *   this.name=name
 *   this.age=age
 * }
 * let p=myNew(Person,'Tom',17)
 * console.log(p)
 */
 
function myNew() {
	let obj={}
	let Constructor=[].shift.call(arguments)
	obj.__proto__=Constructor.prototype
	let res=Constructor.apply(obj,arguments)
	return typeof res==='object'?res:obj
}
 

手写 Promise

Promise 最主要有三个状态:加载中 pending、兑现 fulfilled、拒绝 rejected,根据状态的不同做出不同的处理

const Status = {
	PENDING:'pending',
	FULFILLED:'fulfilled',
	REJECTED:'rejected'
}
class myPromise {
	Constructor(executor) {
	    this.status=Status.PENDING
	    this.value=undefined
	    this.reason=undefined
	    this.onFulfilledQueue=[]
	    this.onRejectedQueue=[]
	    const resolve = value => {
	        if(this.status===Status.PENDING) {
	          this.status=Status.FULFILLED
	          this.value=value
	          this.onFulfilledQueue.forEach(fn=>fn())
	        }
	    }
	    const reject = reason => {
	        if(this.status===Status.PENDING) {
	          this.status=Status.REJECTED
	          this.reason=reason
	          this.onRejectedQueue.forEach(fn=>fn())
	        }
	    }
	    executor(resolve,reject)
	}
}

手写 then 和 all 方法,then 属于每个实例都用到的、all 属于所有实例都可用的,对标的对象不同所以 then 要用 MyPromise.prototype.then=... 写,all 要用 MyPromise.all=... 写~

MyPromise.prototype.then=function(onFulfilled, onRejected) {
	if(this.status===Status.FULFLILLED) onFulfilled(this.value)
	if(this.status===Status.REJECTED) onRejected(this.reason)
	if(this.status===Status.PENDING) {
	    this.onFulfilledQueue.push(()=>onFulfilled(this.value))
	    this.onRejectedQueue.push(()=>onRejected(this.reason))
	}
}
 
MyPromise.all=function(promises) {
	return new MyPromise((resolve,reject)=>{
	    let res=[] // 存结果
	    let count=0
	    promises.forEach((promise,index)=>{ // 同时执行多个异步
	        promise.then(value=>{
	            res[index]=value
	            count++
	            if(count===promises.length) {
	                resolve(res)
	            }
	        },reject) // 有任意一个拒绝了就直接返回拒绝
	    })
	})
}
 
// 和all不同的是race方法中只要有一个成功或失败了就直接返回
MyPromise.race=function(promises) {
	return new MyPromise((resolve,reject)=>{
	    promises.forEach(promise=>{ // 同时执行多个异步
	        promise.then(resolve,reject)
	    })
	})
}

解析 url 字符串里的参数

基础 map 版

function parse(url) {
	let obj={}
	url
	.slice(url.indexOf('?')+1)
	.split('##')[0]
	.split('&')
	.map(item=>{
	    const[key,value]=item.split('=')
	    obj[key]=value
	})
	return obj
}

基础 reduce 版

function parse(url) {
	return url
	.split('?')[1].split('##')[0].split('&')
	.reduce((acc,item)=>{
	    const[key,value]=item.split('=')
	    acc[key]=value
	    return acc
	},{}) // 注意reduce函数必须要返回和设置初始值!!
}

如果为空就不显示该参数而不是录入 undefined

function parse(url) {
	return url
    .split('?')[1].split('##')[0].split('&')
    .reduce((acc,item)=>{
        const[key,value]=item.split('=')
        if(!value) return acc
        acc[key]=value
        return acc
    },{})
}

解决参数有嵌套对象或者数组的问题

function parse(url) {
	return url
	.split("?")[1].split("##")[0].split("&")
	.reduce((acc,item)=>{
	    const[key,value]=item.split("=")
	    if(!value) return acc
	    const path=key.replace("[","]").split("]").fliter(x=>x)
	    // 把a[name]搓成[a,name]变成路径一个个找
	    deep_parse(acc,path,value) // 递归
	    return acc
	},{})
}
 
function deep_parse(obj,path,value) {
	let i=0
	while(i<path.length-1) { // 最后一个值是value
	    if(!obj[path[i]]) {
	        if(path[i+1].watch(/^\d+$/)) // 匹配数字
	            obj[path[i]]=[]
	        else obj[path[i]]={}
	    }
	    obj=obj[path[i]]
	    i++;
	}
	obj[path[i]]=value;
}

防抖和节流

// 我是节流
function throttle(fn,t) {
	let last=0
	return function(...args) {
	    let nowTime=Date.now()
	    if(nowTime-last>=t) {
	        fn(...args)
	        last=nowTime
	    }
	}
}
 
// 我是防抖
function debounce(fn,t) {
	let timer=null
	return function(...args) {
	    clearTimeout(timer)
	    timer=setTimeout(()=>{
	        fn(...args)
	    },t)
	}
}

数组去重

var arr=[2,0,2,4,1,0,0,8]
 
// ES6 set方法
var a_arr=[...new Set(arr)]
 
// indexOf方法
var a_arr=[]
for(let i=0;i<arr.length;i++) {
	if(a_arr.indexOf(arr[i])==-1) {
	    a_arr.push(arr[i])
	}
}
 
// filter+indexOf
function unique(array) {
	var res=array.filter(function(item,index,array)) {
	    return array.indexOf(item)===index  
	})
	return res
}
var a_arr=unique(arr)
 
// for循环(懒得写了)
© Xayah.RSS