【JS高频】手写题汇总

286 阅读9分钟

一、【JS基础 jsonp的实现】

 function jsonp(url, data = {}, callback = 'callback') {
      // 处理json对象,拼接url
      //data.callback = callback
      let params = []
      for (let key in data) {
        params.push(key + '=' + data[key])
      }
      console.log(params.join('&'))
      // 创建script元素
      let script = document.createElement('script')
      script.src = url + '?' + params.join('&')
      document.body.appendChild(script)
      // 返回promise
      return new Promise((resolve, reject) => {
        window[callback] = (data) => {
          try {
            resolve(data)
          } catch (e) {
            reject(e)
          } finally {
            // 移除script元素
            //script.parentNode.removeChild(script)
            console.log(script)
          }
        }
      })

    }
    jsonp('http://photo.sina.cn/aj/index', {
      page: 1,
      cate: 'recommend'
    }, 'jsoncallback').then(data => {
      console.log(data)
    })

二、 【JS基础 ajax 的实现】

let xhr = new XMLHttpRequest() 
// 初始化 
xhr.open(method, url, async) 
// 发送请求 
xhr.send(data) 
// 设置状态变化回调处理请求结果 
xhr.onreadystatechange = () => { 
	if (xhr.readyStatus === 4 && xhr.status === 200) { 
		console.log(xhr.responseText) 
	} 
}
0>【JS基础】JS的执行顺序(事件循环EventLoop)

一、任务分为:同步任务 与 异步任务

二、异步任务分为:

1. 宏任务(setTimeout, setInterval、点击等事件、请求、IO读写)

2. 微任务(promise.thenawait下面的脚本)

三、 【JS基础 防抖】

function debounce(func, delay) {
  let timeout
  return function() {
    let args = [...arguments];
    clearTimeout(timeout) // 如果持续触发,那么就清除定时器,定时器的回调就不会执行。
    timeout = setTimeout(() => {
      func.apply(this, args)
    }, delay)
  }
}

2> 【JS基础 节流】

function throttle(func, delay) {
    let run = true
    return function () {
      let args = [...arguments];
      if (!run) { 
        return  // 如果开关关闭了,那就直接不执行下边的代码
      }
      run = false // 持续触发的话,run一直是false,就会停在上边的判断那里
      setTimeout(() => {
        func.apply(this, args)
        run = true // 定时器到时间之后,会把开关打开,我们的函数就会被执行
      }, delay)
    }
  }
// 调用的时候:
box.onmousemove = throttle(function (e) {
  box.innerHTML = `${e.clientX}, ${e.clientY}`
}, 1000)
// 这样,就实现了节流,节流还可以用时间间隔去控制,就是记录上一次函数的执行时间,
// 与当前时间作比较,如果当前时间与上次执行时间的时间差大于一个值,就执行。

四、 【JS基础 Router的实现 Vue的hash模式是监听事件:hashChange】

class Routers{
    /**构造器方便测试,默认输入两个对象
     * @输入 路由列表对象,包括 hash 与其 对应的回调, 如 { "a": () => { console.log('a') }, "b": () => { console.log('b') }... }
     * @输出 路由列表数组,默认存储的 hash 历史  ["a","b"]
     * */ 
    constructor(routersList,hashHistory){
        this.routersList = routersList || {};// 路由列表对象,用来存放所有路由与对应的回调
        this.hashHistory = hashHistory || []; // 存储hash历史的 数组
        // 当前的hash索引,默认指向hash历史数组的末尾,前进时+1,后退时-1
        this.currentHashIndex = this.hashHistory.length-1;
        // 当页的hash值
        //this.currentHash = "";
        // 页面初始化时,监听load事件,并执行当前hash对应的回调
        window.addEventListener('load',this.refresh,false);
        // 页面hash被修改时,监听 hashchange 事件,并执行当前hash对应的回调
        window.addEventListener('hashchange',this.refresh,false);
    }
    /**
     * 1. 通过当前hash索引+1,得到下一个索引。并修改hash为下一个索引
     * 2. 通过监听 hashchange 来调用refresh执行回调
     * */ 
    go(){
        this.currentHashIndex++;
        console.log('下一个hash索引 ', this.currentHashIndex)
        //边界处理,如果路由当前索引小于history数组时,才能前时,否则 当前索引减1,并报错。
        if(this.currentHashIndex < this.hashHistory.length){
            location.hash = this.hashHistory[this.currentHashIndex]
        }else{
            //前进时,当前索引增加了1,但是没有对应的路由,所以需要减去1,否则,走到这一步边界时,需要调用back两次才会生效。
            this.currentHashIndex = this.currentHashIndex -1;
            throw new Error("路由数组中,没有前进路由了");
        }
    }
    /**
     * 1. 添加新的hash,和callback
     * 2. 并跳转到新的hash,通过监听 hashchange 来调用refresh执行回调
     * */JS基础 Router的实现 Vue的hash模式是监听事件:hashChange】 
    push(hash,cb){
        //新增hash到hashHistory数组
        this.hashHistory.push(hash);
        //新增hash和对应callback到routerList对象
        this.routersList[hash] = cb || (() =>{ });
        // 修改当前页面hash为新增的hash
        window.location.hash=hash;
        // 更新当前的索引为改变history长度后的索引。指向其中最后一个hash
        this.currentHashIndex = this.hashHistory.length-1;
    }
    /**
     * 1. 通过当前hash索引-1,得到上一个索引。并修改hash为上一个索引
     * */ 
    back(){
        // 如果当前的hash索引小于0 就让其等于0;
        if(this.currentHashIndex <= 0){
            this.currentHashIndex = 0;
            throw new Error("已到hashHistory最顶端")
        }else{
            this.currentHashIndex = this.currentHashIndex - 1;
        }
        console.log('当前hash索引 ',this.currentHashIndex)
        location.hash = this.hashHistory[this.currentHashIndex]
    }
    /**
     * 1. 获取Url中的hash,并执行当前hash的回调
     * */ 
    refresh(){
        this.currentUrl = location.hash.slice(1) || '/';   //   '/a' 方便测试用
        // 执行当前路由的回调   
        this.routersList[this.currentUrl] &&&emsp;this.routersList[this.currentUrl]();
    }

}

var routersList = { "a": () => { console.log('a') }, "b": () => { console.log('b') } };
var hashHistory = ["a","b"]
var routers = new Routers(routersList,hashHistory);  
routers.push('c',()=>{console.log('c')});
console.log(routers);  // 此时,我们的当前hash是c , 路由实例为:{routersList: {…}, hashHistory: Array(3), currentHashIndex: 2}
// 通过实例的go,和 back 方法来前进 、 后退, 无需传参
routers.go()
routers.back()
// 记得边界处理

五、 【JS基础 数组铺平】

function flat(arr){
	if(Object.prototype.toString.call(arr) != "[object Array]"){return false};
	let res = [];
	for(var i=0;i<arr.length;i++){
		if(arr[i] instanceof Array){
			res = res.concat(flat(arr[i]))
		}else{
			res.push(arr[i])
		}
	};  return res;
};
var arr = [1,2,[3,4,5,[6,7,8],9],10,[11,12]];
flat(arr); //[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
// 方法2 
function flat(arr){
	if(Object.prototype.toString.call(arr) != "[object Array]"){return false};
	let res = arr.reduce((prev,cur)=>{
	    return prev.concat(Array.isArray(cur) ? flat(cur) : cur)
	},[])
	return res;
};
var arr = [1,2,[3,4,5,[6,7,8],9],10,[11,12]]; flat(arr); // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]

// demo2
const arr = [[1, 2], [3, [3.1,3.2]], [5, 6]];
// prevRes的初始值是传入的[],以后会是每次迭代计算后的值
const flatArr = (arr)=>{
   return arr.reduce((prevRes, item) => prevRes.concat(Array.isArray(item) ? flatArr(item): item), []);
}
console.log(flatArr(arr)); // [1, 2, 3, 3.1, 3.2, 5, 6]

六、 【JS基础 深拷贝-解决循环引用问题】

function deepCopy(obj, map = new Map()) {
  if (typeof obj === 'object') {
     let res = Array.isArray(obj) ? [] : {};
	 if(map.get(obj)){
		return map.get(obj);
	 }
	 map.set(obj,res);
     for(var i in obj){
		res[i] = deepCopy(obj[i],map);
	 }
	 return map.get(obj);
  }else{
	return obj;
  }
};
// 虽然解决了循环引用问题,但是拷贝出来的数据是无限的
var A={a:1};
A.A=A;
var B = deepCopy(A);
console.log(B);//{a: 1, A: {a: 1, A: {…}}

// test 2
var A = {a:1,b:[1,2,3]};
var B = deepCopy(A);
console.log(B); //{a:1,b:[1,2,3]}

七、 【JS基础- push的实现】

Array.prototype._push=function(){
	var args = [...arguments];
	//var arr = this.slice(0);
	args.map(item =>{
		 this[this.length] = item
	});
    return this.length;
};
var arr=[1];  arr._push(2,3);//返回值数组的长度 3
console.log(arr);//[1,2,3]

八、 【JS基础-new的实现】

function Person(name,age){
 this.name = name;
 this.age= age;
 this.fn=()=>{ console.log('方法')}
}
Person.prototype={ 
    fn1:function(){console.log("fn1")}
}
var p=new Person('andy');//可以不加小括号,不传参的话
p.name//"andy"
p.fn()//"方法"{name: 'andy', age: undefined, fn: ƒ}
console.log(p);// {name: 'andy', age: undefined, fn: ƒ}

function _new(/*constr,params*/){//...arg更方便
   let args=[...arguments];
   let constructor=args.shift();
   var newObj={};
   newObj.__proto__=constructor.prototype;
  //let newObj = Object.create(constructor);//issue
    let result =  constructor.apply(newObj ,args);
    console.log("result",result,"newObj ",newObj )
    return typeof result === "object" ? result: newObj 
}
var p = _new(Person,'andy',22); // result undefined newObj  {name: 'andy', age: 22, fn: ƒ}

九、【JS基础-call的实现】

Function.prototype.myCall = function(obj){
	//console.log(obj);//改变的this指向,this指向A 然后obj.fn=A
	//console.log(A);// 原调用函数
	obj.fn = this;// 改变this指向为obj。此时obj.fn就是函数A
	obj.fn([...arguments].slice(1));//改变this后,调用原函数A。
	delete obj.fn;// 不能改变obj。把增加的fn属性删除
};
function A(arr){
    console.log(this.b+arr[0]+arr[1])
	
};
var B={b:1};
A.myCall(B,2,3); //6

十、 【订阅发布模式 实现】

    class EventBus{
        constructor(){
            // 在event对象中 存放 所有的事件与回调数组,如:
            // {a: [ ()=>{console.log(1)}, (a)=>{console.log(a)}], b: [()=>{console.log(1)}]}
            this.event=Object.create(null);
        };
        on(name,fn){
            if(!this.event[name]){
                //一个事件可能有多个监听者
                this.event[name]=[];
            };
            this.event[name].push(fn);
        };
        emit(name,...args){
            //遍历要触发的事件对应的数组回调函数。依次调用数组当中的函数,并把参数传入每一个cb。
            this.event[name] && this.event[name].forEach(fn => {
                fn(...args)
            });
        };
        // 只触发一次事件@功能 借助变量cb,同时完成了对该事件的注册、对该事件的触发,并在最后取消该事
        once(name,fn){
            var cb=(...args)=>{
                fn(...args);  //触发
                this.off(name,fn); //取消
            };
            this.on(name,cb); //监听
        };
        off(name,offcb){
            if(this.event[name]){
                // 找到要取消事件在回调数组中的索引
                let index=this.event[name].findIndex((fn)=>{
                    return offcb===fn;
                });
                //通过索引删除掉对应回调数组中的回调函数。
                this.event[name].splice(index,1);
                // 回调数组长度为0时(没有回调数组时)
                if(!this.event[name].length){
                    // 删除事件名
                    delete this.event[name];
                }
            }
        }
    }
var a = new EventBus()
// on emit 
a.on('a',()=>{console.log(1)}); a.on('a',(a)=>{console.log(a)}); a.on('b',()=>{console.log(1)});
a.emit('a','2'); // 1  2
a.emit('b'); // 1
console.log(a.event);
// {a: [ ()=>{console.log(1)}, (a)=>{console.log(a)}], b: [()=>{console.log(1)}] }

十一、 【观察者模式】

与订阅发布模式都是定义了一个一对多的依赖关系,当有关状态发生变更时则执行相应的更新。 不同的是,在观察者模式中依赖于 Subject 对象的一系列 Observer 对象在被通知之后只能执行同一个特定的更新方法,

而在发布订阅模式中则可以基于不同的主题去执行不同的自定义事件。相对而言,发布订阅模式比观察者模式要更加灵活多变。

2> 实例化后A类添加B类到A类的观察者列表数组中: Aclass.add(Bclass) -> Aclass.prototype.observerList = [{cb: ()=>{'最终要执行的观察者回调'}, update:fn}, {cb: ()=>{'最终要执行的观察者回调'}, update:fn}, ...] -> Acalss通知所有观察者列表中的观察者 -> Bclass.cb() 调用观察者上的callback

1) 【观察者模式】

class Observer {
	// 回调函数,收到目标对象通知时执行
    constructor(cb){
        if (typeof cb === 'function') {
            this.cb = cb
        } else {
            throw new Error('Observer构造器必须传入函数类型!')
        }
    }
	// 被目标对象通知时执行
    update() {
        this.cb()
    }
}
// 被观察者(目标对象)
class Subject {
    constructor() {
        // 维护观察者列表 `Aclass中存的  [Bclass, Bclass, Bclass ...]`
        this.observerList = [];   
    }
	// 添加一个观察者 `Aclass.add(Bclass)` 
    addObserver(observer) {
        this.observerList.push(observer);
    }
	// 通知所有的观察者  `Aclass.notify` -> `Bcalss.update` -> `Bcalss.cb`
    notify() {
        this.observerList.forEach(observer => {
            observer.update()
        })
    }
}
const observerCallback = function() {
    console.log('observerCallback 我被通知了')
}
const observer = new Observer(observerCallback)
const subject = new Subject();
subject.addObserver(observer);
subject.notify(); // observerCallback 我被通知了

十二、 Promise 完整版本的实现

function myPromise(constructor){
    let self=this;
    self.status="pending" //定义状态改变前的初始状态
    self.value=undefined;//定义状态为resolved的时候的状态
    self.reason=undefined;//定义状态为rejected的时候的状态
    self.onFullfilledArray=[]; // 存储 成功的回调函数
    self.onRejectedArray=[];// 存储 失败的回调函数
    function resolve(value){
       if(self.status==="pending"){ // 保证了状态的改变是不可逆的
          self.value=value;
          self.status="resolved";
          // 在pending时把状态改变为成功并把数据传过来。
          // 并遍历调用成功的回调数组
          console.log('构造函数中 resolved中的 成功回调数组:', self.onFullfilledArray);
          self.onFullfilledArray.forEach(function(f){
                f(self.value);
                //如果状态从pending变为resolved,
                //那么就遍历执行里面的异步方法
          });
        
       }
    }
    function reject(reason){
       if(self.status==="pending"){
          self.reason=reason;
          self.status="rejected";
          self.onRejectedArray.forEach(function(f){
              f(self.reason);
             //如果状态从pending变为rejected,
             //那么就遍历执行里面的异步方法
          })
       }
    }
    //捕获构造异常
    try{
       constructor(resolve,reject);
    }catch(e){
       reject(e);
    }
}

myPromise.prototype.then=function(onFullfilled,onRejected){
    let self=this;
    let promise2;
    switch(self.status){
      case "pending":
        promise2=new myPromise(function(resolve,reject){
             self.onFullfilledArray.push(function(){
                try{
                   let temple=onFullfilled(self.value);
                   resolve(temple)
                }catch(e){ reject(e);}
             });
             self.onRejectedArray.push(function(){
                 try{
                   let temple=onRejected(self.reason);
                   reject(temple)
                 }catch(e){ reject(e);}
             });
        })
      case "resolved":
        promise2=new myPromise(function(resolve,reject){
            try{
              let temple=onFullfilled(self.value);
              //将上次一then里面的方法传递进下一个Promise的状态
              resolve(temple);
            }catch(e){
              reject(e); }
        });break;
      case "rejected":
        promise2=new myPromise(function(resolve,reject){
            try{
               let temple=onRejected(self.reason);
               //将then里面的方法传递到下一个Promise的状态里
               resolve(temple);   
            }catch(e){
               reject(e);
            }
        }); break;
      default:       
   }
   return promise2;
}
var p=new myPromise(function(resolve,reject){setTimeout(function(){resolve(111)},1000)});
p.then(function(x){console.log(x)}).then(function(){console.log("链式调用1")}).
then(function(){console.log("链式调用2")})
//输出   链式调用1  链式调用2  111

十三、Promise.allSettled的实现

 Promise.allSettled1 = function (promises) {
    return new Promise((resolve, reject) => {
      promises = Array.isArray(promises) ? promises : []
      let len = promises.length
      const argslen = len
      // 如果传入的是一个空数组,那么就直接返回一个resolved的空数组promise对象
      if (len === 0) return resolve([])
      let args = Array.prototype.slice.call(promises)// 将传入的参数转化为数组,赋给args变量
      const compute = () => {// 计算当前是否所有的 promise 执行完成,执行完毕则resolve
        if(--len === 0) { 
          resolve(args)
        }
      }
      function resolvePromise(index, value) {
        if(value instanceof Promise) { // 判断传入的是否是 promise 类型
          const then = value.then
          then.call(value, function(val) {
            args[index] = { status: 'fulfilled', value: val}
            compute()
          }, function(e) {
            args[index] = { status: 'rejected', reason: e }
            compute()
          })
        } else {
          args[index] = { status: 'fulfilled', value: value}
          compute()
        }
      }
   
      for(let i = 0; i < argslen; i++){
        resolvePromise(i, args[i])
      }
    })
  }

const p1 = Promise.resolve(1)
const p2 = Promise.resolve(2)
const p3 = Promise.reject(0)
Promise.allSettled1([p1,p2,p3]).then(data=>{
    console.log('resolve:',data)
},err=>{
    console.log('then 中 reject:',err)
}).catch(err=>{
    console.log('catch 中 reject:',err)
});
// 输出 [{status: 'fulfilled', value: 1},{status: 'fulfilled', value: 2},{status: 'rejected', reason: 0}]

 

十四、【Promise race的实现】

  Promise.race1(promiseArr) {
    return new Promise((resolve, reject) => {
      for (let i = 0; i < promiseArr.length; i++) {
        Promise.resolve(promiseArr[i]).then(
          (res) => {
            //promise数组只要有任何一个promise 状态变更  就可以返回
            resolve(res);
          },
          (err) => {
            reject(err);
          }
        );
      }
    });
  }
};
const p1 = new Promise((resolve, reject) => {
  setTimeout(reject, 3000, '1');
});
const p2 = new Promise((resolve, reject) => {
  setTimeout(reject, 2000, '2');
});
const p3 = new Promise((resolve, reject) => {
  setTimeout(reject, 1000, '3');
});
Promise.race([p1,p2,p3]).then(data=>{
    console.log('resolve:',data)
},err=>{
    console.log('then 中 reject:',err)
}).catch(err=>{
    console.log('catch 中 reject:',err)
});// then 中 reject: 3 ; 注意:then中捕获了错误,catch就不会在执行
// 如果then中没有捕获则catch中会执行。

十五、【Promise all的实现】

  Promise.all1 = promiseArr => {
    let result = [];
    //声明一个计数器 每一个promise返回就加一
    let count = 0;
    return new Promise((resolve, reject) => {
        for (let i = 0; i < promiseArr.length; i++) {
            //这里用 Promise.resolve包装一下 防止不是Promise类型传进来
            Promise.resolve(promiseArr[i]).then(
                res => {
                    //这里不能直接push数组  因为要控制顺序一一对应
                    result[i] = res;
                    count++;
                    //只有全部的promise执行成功之后才resolve出去
                    if (count === promiseArr.length) {
                        resolve(result);
                    }
                },
                err => {
                    reject(err);
                }
            );
        }
    });
};
const p1 = Promise.resolve(1)
const p2 = Promise.resolve(2)
const p3 = Promise.resolve(100)

Promise.all1([p1,p2,p3]).then(data=>{
    console.log('resolve:',data)
},err=>{
    console.log('then 中 reject:',err)
}).catch(err=>{
    console.log('catch 中 reject:',err)
}).then(a=>{console.log(1,a)})
// resolve: [1,2,100] // 注意:如果有一个失败则返回失败的

十六、 ajax 实现

let xhr = new XMLHttpRequest() 
// 初始化 
xhr.open(method, url, async) 
// 发送请求 
xhr.send(data) 
// 设置状态变化回调处理请求结果 
xhr.onreadystatechange = () => { 
	if (xhr.readyStatus === 4 && xhr.status === 200) { 
		console.log(xhr.responseText) 
	} 
}

//2> pipe实现
const pipe = (...args) => x => args.reduce((res, cb) => cb(res), x);

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

const add = x => x + 1; const square = x => x ** 2; const sub = x => x - 1;
let calc1 = compose(sub, square, add);
let res = calc1(1);
console.log(res);  // 3