【js面试系列】手写常见js方法

防抖

function debounce(fn, delay) {
    var timer = null;
    return function(...arg) {
        clearTimeout(timer);
        timer = setTimeout(() => {
            fn.apply(this, arg);
        }, delay || 500);
    };
}
// 不断触发一个函数,在规定时间内只让最后一次生效,前面都不生效
// 注意点:
// 如何在vue中使用  https://zhuanlan.zhihu.com/p/72363385
// 理解工作原理 this指向 参数 

节流

function throttle(fn, delay) {
    var timer = null;
    return function(...args) {
        if (!timer) {
            fn.apply(this, args);
            timer = setTimeout(function() {
                timer=null
            }, delay || 500);
        }
    };
}
// 不断触发一个函数后,执行第一次,只有大于设定的执行周期后才会执行第二次
// 防抖和节流的区别 应用场景

// 函数防抖和函数节流都是防止某一时间频繁触发
// 函数防抖是单位事件内 只执行最后一次触发的事件
// 函数节流是单位时间间隔内 只执行一次触发事件

// 比如:
// LOL技能cd就是用的 节流,
// 购物车提交订单就是用的 防抖

深拷贝

function deepClone(obj) {
    // 1.先判断是否为对象类型
    let isObject = (arg) => typeof arg === "object" && arg !== null
    // 2.如果不是,则返回本身
    if (!isObject(obj)) return obj;
    // 3.定义一个变量,判断是数组还是对象
    let cloneObj = Array.isArray(obj) ? [] : {};
    // 4.循环每一个元素,如果满足是自身属性,则每一个值再判断是否为对象类型,如果是则继续递归调用,否则返回本身
    for (let key in obj) {
        cloneObj[key] = isObject(obj[key]) ? deepClone(obj[key]) : obj[key];
    }
    // 5.返回创建的变量值
    return cloneObj;
}

浅拷贝

function shallowClone(obj) {
    if (typeof obj !== 'object' || obj === null) return obj
    // 3.定义一个变量,判断是数组还是对象
    let cloneObj = Array.isArray(obj) ? [] : {};
    // 4.循环每一个元素,
    for (let key in obj) {
        cloneObj[key] = obj[key]
    }
    // 5.返回创建的变量值
    return cloneObj;
}

new

function _new(Fn, ...arg) {
    //基于fn的prototype构建对象的原型
    const thisObj = Object.create(Fn.prototype);
    //将thisObj作为fn的this,继承其属性,并获取返回结果为result
    const result = Fn.apply(thisObj, arg);
    //根据result对象的类型决定返回结果
    return result instanceof Object ? result : thisObj
}

call

Function.prototype.myCall = function(obj, ...args) {
    // console.log(obj);
    // console.log(this);
    // if(obj==null||obj==undefined) obj=window //为空时,指向window
    const ctx=obj || window
    ctx.fn = this // this指向调用call的对象,即我们要改变this指向的函数
    return ctx.fn(...args) // 执行函数并return其执行结果
    delete ctx.fn  //删除该方法,不然会对传入的对象造成污染,添加了该方法
}	

apply

Function.prototype.myApply = function(obj, args) {
    // console.log(obj);
    // console.log(this);
    // if(obj==null||obj==undefined) obj=window //为空时,指向window
    const ctx=obj || window
    ctx.fn = this // this指向调用call的对象,即我们要改变this指向的函数
    return ctx.fn(...args) // 执行函数并return其执行结果
    delete ctx.fn  //删除该方法,不然会对传入的对象造成污染,添加了该方法
}	

bind

Function.prototype.myBind = function(thisArg, ...args) {
    var self = this //保存当前this,指的是当前fn调用者
    const fn = function(...fnArgs) {//提取调用时候的参数
        return self.apply(this instanceof self ? this : thisArg, [...args, ...fnArgs])
    }//且有返回值
    fn.prototype = this.prototype; //继承原型上的属性和方法
    return fn
}
// 输入:接受一个或者多个参数,第一个是要绑定的上下文,额外参数当作绑定函数的前置参数。
// 输出:返回原函数的拷贝,即返回一个函数,这个函数呢具备原函数的功能

instanceof

// instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。
function myInstanceof(left, right) {
    // 左侧是实例对象
    // 右侧是某个构造函数

    if (typeof left !== 'object' || left === null) return false // 基本数据类型直接返回false

    let RP = right.prototype; // 构造函数的原型对象
    let LP = Object.getPrototypeOf(left) // 优先用这个方法 去查找实例对象的隐式原型对象

    while (true) {
        if (LP === null) return false;
        if (LP === RP) return true;
        LP = Object.getPrototypeOf(LP) // 沿着原型链重新赋值
    }
}

数组去重


数组乱序


继承

原型链继承

function Animal(name) { // 定义一个动物类
    this.name = name || 'Animal'; // 属性
    this.sleep = function () { // 实例方法
        console.log(this.name + '正在睡觉!');
    }
}
Animal.prototype.eat = function (food) { // 原型方法
    console.log(this.name + '正在吃:' + food);
};

// 核心: 将父类的实例作为子类的原型
function Cat() {}
Cat.prototype = new Animal();
// Cat.prototype.name = 'cat';

// Test Code
var cat = new Cat();
console.log(cat.name); // cat
cat.eat('fish') // cat正在吃:fish
cat.sleep() // cat正在睡觉!
console.log(cat instanceof Animal); //true
console.log(cat instanceof Cat); //true

// 特点:

// 非常纯粹的继承关系, 实例是子类的实例, 也是父类的实例
// 父类新增原型方法 / 原型属性, 子类都能访问到
// 简单, 易于实现

// 缺点:

// 要想为子类新增属性和方法, 必须要在new Animal() 这样的语句之后执行, 不能放到构造器中
// 无法实现多继承
// 来自原型对象的所有属性被所有实例共享( 详细请看附录代码: 示例1)
// 创建子类实例时, 无法向父类构造函数传参

构造函数继承

// 核心:使用父类的构造函数来增强子类实例,等于是复制父类的实例属性给子类(没用到原型)
function Animal(name) { // 定义一个动物类
    this.name = name || 'Animal'; // 属性
    this.sleep = function () { // 实例方法
        console.log(this.name + '正在睡觉!');
    }
}
Animal.prototype.eat = function (food) { // 原型方法
    console.log(this.name + '正在吃:' + food);
};

function Cat(name) {
    Animal.call(this); // this指向cat实例对象,Animal方法调用 给cat实例对象添加方法
    this.name = name || 'Tom';
}

// Test Code
var cat = new Cat();
console.log(cat.name);
cat.sleep()
console.log(cat instanceof Animal); // false
console.log(cat instanceof Cat); // true

// 特点:

// 解决了1中, 子类实例共享父类引用属性的问题
// 创建子类实例时, 可以向父类传递参数
// 可以实现多继承( call多个父类对象)

// 缺点:

// 实例并不是父类的实例, 只是子类的实例
// 只能继承父类的实例属性和方法, 不能继承原型属性 / 方法
// 无法实现函数复用, 每个子类都有父类实例函数的副本, 影响性能

组合继承

// 核心:通过调用父类构造,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现函数复用
function Animal(name) { // 定义一个动物类
    this.name = name || 'Animal'; // 属性
    this.sleep = function () { // 实例方法
        console.log(this.name + '正在睡觉!');
    }
}
Animal.prototype.eat = function (food) { // 原型方法
    console.log(this.name + '正在吃:' + food);
};

function Cat(name) {
    Animal.call(this);
    this.name = name || 'Tom';
}
Cat.prototype = new Animal();


// 组合继承也是需要修复构造函数指向的。
Cat.prototype.constructor = Cat;

console.dir(Cat);


// Test Code
var cat = new Cat();
console.log(cat.name);
cat.sleep()
cat.eat('零食')
console.log(cat instanceof Animal); // true
console.log(cat instanceof Cat); // true

// 特点:
// 弥补了方式2的缺陷, 可以继承实例属性 / 方法, 也可以继承原型属性 / 方法
// 既是子类的实例, 也是父类的实例
// 不存在引用属性共享问题
// 可传参
// 函数可复用
// 缺点:

// 调用了两次父类构造函数, 生成了两份实例( 子类实例将子类原型上的那份屏蔽了)

原型式继承(实例继承)

function Animal(name) { // 定义一个动物类
    this.name = name || 'Animal'; // 属性
    this.sleep = function () { // 实例方法
        console.log(this.name + '正在睡觉!');
    }
}
function Cat(name){
    var instance = new Animal();
    instance.name = name || 'Tom';
    return instance;
}

// Test Code
var cat = new Cat();
console.log(cat.name);
console.log(cat.sleep());
console.log(cat instanceof Animal); // true
console.log(cat instanceof Cat); // false

// 特点:

// 不限制调用方式, 不管是new 子类() 还是子类(), 返回的对象具有相同的效果
// 缺点:

// 实例是父类的实例, 不是子类的实例
// 不支持多继承

拷贝继承


睡眠函数(sleep)

// 1
function sleep (time) {
    return new Promise((resolve) => setTimeout(resolve, time));
}

// 2
function sleep(delay) {
  var start = (new Date()).getTime();
  while ((new Date()).getTime() - start < delay) {
    continue;
  }
}

// (async function() {
//   console.log('Do some thing, ' + new Date());
//   await sleep(3000);
//   console.log('Do other things, ' + new Date());
// })();

函数柯里化(Currying)

/**
 * @description: 将函数柯里化的工具函数
 * @param {Function} fn 待柯里化的函数
 * @param {array} args 已经接收的参数列表
 * @return {Function}
 */
const currying = function(fn, ...args) {
    // fn需要的参数个数
    const len = fn.length
    // 返回一个函数接收剩余参数
    return function (...params) {
        // 拼接已经接收和新接收的参数列表
        let _args = [...args, ...params]
        // 如果已经接收的参数个数还不够,继续返回一个新函数接收剩余参数
        if (_args.length < len) {
            return currying.call(this, fn, ..._args)
        }
      	// 参数全部接收完调用原函数
        return fn.apply(this, _args)
    }
}

数组扁平化(flat)

let result = [];
let fn = function(ary) {
    for(let i = 0; i < ary.length; i++) }{
        let item = ary[i];
        if (Array.isArray(ary[i])){
            fn(item);
        } else {
            result.push(item);
        }
    }
}

lazyMan


filter


jsonp

function jsonp (url) {
    /*声明一个唯一的回调函数并挂载到全局上
   *创建一个script标签地址 指向 请求服务器 将回调函数名作参数带到服务器
   *服务器拿到回调名称 并返回前端 该回调的调用 把返回结果当作参数传入
   */
    let script = document.createElement('script')
    let uniqueName = `jsonpCallback${new Date().getTime()}`
    script.src = `url${url.indexOf('?') > -1 ? '&': '?'}callback=${uniqueName}`
    document.body.appendChild(script)

    window[uniqueName] = (res) => {
        cb && cb(res)
        document.body.removeChild(script)
        delete window[uniqueName]
    }
}

// 调用
jsonp('getList', (res) => {
    console.log(res)
})


// 服务器端
1. 获取参数, 拿到回调函数名称
2. 返回参数名的前端回调的调用 并 把要返回的参数作为实参调用

/*弊端 - 只支持get请求,并且不安全,需要服务器支持*/



function jsonp ({url, query}) {
    let script = document.createElement("script");
    let cb = `jsonpCallBack${new Date().getTime()}${Math.floor(Math.random(5)*100000)}`
    let params = {...query, cb}

    let arr = []
    for (let key in params) {
        arr.push(`${key}=${params[key]}`)
    }

    script.src = `${url}?${arr.join("&")}`
    document.body.appendChild(script)

    return new Promise((resolve, rej) => {
        window[cb] = function (res) {
            resolve(res)
            document.body.removeChild(script)
            delete window[cb]
        }
    })
}

jsonp({
    url:'/getList',
    query: {name: 'ys',age: 19}
}).then((res) => {
    console.log(res)
})

promise


promise.all


发布订阅(EventEmitter)

class EventEmitter {
    // 事件容器 用来装事件数组
    events = {}
add(eventName, cb) {
    // 首先判断events 内有没有eventName事件数组容器,没有则创建一个新数组容器 并将事件存入
    !this.events[eventName] && (this.events[eventName] = [])
    this.events[eventName].push(cb)
}
emit(eventName, ...params) {
    // 事件触发 更改this指向
    this.events[eventName] &&
        this.events[eventName].forEach(f => f.apply(this, params))
}
remove(eventName, cb) {
    // 移除事件 
    this.events[eventName] &&
        (this.events[eventName] = this.events[eventName].filter(f => f != cb))
}
}

// 运用
const event = new EventEmitter()

let fn1 = (...e) => {
    console.log(e);
    event.remove('on', fn1)
}

let fn2 = e => {
    console.log(e);
}

let fn3 = e => {
    console.log(3);
}


event.add('on', fn1)
event.add('on', fn2)
event.add('on', fn3)
event.add('on2', fn3)


document.onclick = function () {
    event.emit('on', 100, 200)
    event.emit('on2')
}
posted @ 2021-05-28 21:32  有风吹过的地方丨  阅读(58)  评论(0编辑  收藏  举报