手撕JS面试题系列

352 阅读4分钟

1-函数防抖和节流

/*
 * debounce:函数防抖
 *  @params
 *    func:自己最终要执行的任务
 *    wait:多久操作一次算是频繁触发「默认值:300ms」
 *    immediate:控制触发的边界 「默认值:false结束边界  true开始边界」
 *  @return
 *    proxy处理函数,处理函数会在频繁触发的时候,频繁执行;
 *    函数内部,控制我们想要操作的func只执行一次;
 */
const debounce = function debounce(func, wait, immediate) {
    if (typeof func !== "function") throw new TypeError('func must be an function!');
    if (typeof wait === "boolean") immediate = wait;
    if (typeof wait !== "number") wait = 300;
    if (typeof immediate !== "boolean") immediate = false;
    let timer = null;
    return function proxy(...params) {
        let now = !timer && immediate,
            result;
        if (timer) clearTimeout(timer);
        timer = setTimeout(() => {
            // 清除最后一次定时器
            clearTimeout(timer);
            timer = null;
            // 符合执行的是最后一次「触发在结束边界」
            if (!immediate) result = func.call(this, ...params);
        }, wait);
        // 符合第一次立即执行「触发在开始的边界」
        if (now) result = func.call(this, ...params);
        return result;
    };
}

const handle = function handle(ev) {
    console.log(this, ev);
};
submit.onclick = debounce(handle, true);
/*
 * throttle:函数节流
 *  @params
 *    func:自己最终要执行的任务
 *    wait:触发的频率「默认值:300ms」
 *  @return
 *    proxy处理函数
 */
const throttle = function throttle(func, wait) {
    if (typeof func !== "function") throw new TypeError('func must be a function!');
    wait = +wait;
    if (isNaN(wait)) wait = 300;
    let timer = null,
        previous = 0;
    return function proxy(...params) {    
        let now = +new Date,
            remaining = wait - (now - previous),
            result;
        if (remaining <= 0) {
            // 两次间隔时间已经超过WAIT了,此时我们立即执行
            if (timer) {
                clearTimeout(timer);
                timer = null;
            }
            previous = now;
            result = func.call(this, ...params);
            return result;
        }
        // 没有定时器我们才设置,有定时器说明上次还没执行呢,只有上一次执行了,我们在开启下一次
        if (!timer) {
            timer = setTimeout(() => {
                clearTimeout(timer);
                timer = null;
                previous = +new Date;
                result = func.call(this, ...params);
            }, remaining);
        }
        return result;
    };
}

const handle = function handle(ev) {
    console.log(this, ev);
    // console.log('OK');
};
window.onscroll = throttle(handle);

2-hasPubProperty

需求:封装检测某个属性是否为对象的公有属性方法

思路1:既是其属性又不是私有的属性
检测属性=> in 「不论是私有还是公有」
检测私有属性 => hasOwnProperty

function hasPubProperty(obj, attr) {
    return (attr in obj) && !obj.hasOwnProperty(attr);
}
// 弊端:不能检测既是私有的,也是公有的属性

思路2:不管私有是否存在,只要公有中有,则当前属性就是对象的公有属性

Object.prototype.hasPubProperty = function hasPubProperty(attr) {
    // 找到当前对象的原型,而且一直向上找,直到找到Object.prototype为止;
    // 查找的时候,只要某个原型对象中有attr这个属性,这个属性就是对象的公有属性
    let proto = Object.getPrototypeOf(this);
    while (proto) {
        if (proto.hasOwnProperty(attr)) return true;
        proto = Object.getPrototypeOf(proto);
    }
    return false;
};
let obj = {
    name: 'obj',
    toString() {}
};
console.log(obj.hasPubProperty('name')); //=>false
console.log(obj.hasPubProperty('toString')); //=>true 

3-重写内置new

需求:

function Dog(name) {
    this.name = name;
}
Dog.prototype.bark = function () {
    console.log('wangwang');
}
Dog.prototype.sayName = function () {
    console.log('my name is ' + this.name);
}
/*
let sanmao = new Dog('小黑');
sanmao.sayName();
sanmao.bark();
*/
function _new() {
    //=>完成你的代码   
}
let sanmao = _new(Dog, '小黑');
sanmao.bark(); //=>"wangwang"
sanmao.sayName(); //=>"my name is 小黑"
console.log(sanmao instanceof Dog); //=>true

思路:了解new做了哪些事

  1. 让构造函数中的this指向这个实例对象
  2. this.xxx = xxx 都是给实例对象设置私有属性和方法
  3. 如果构造函数没有return或者返回的是原始值,则把创建的实例对象返回;若返回的是对象,则以自己返回的为主
const _new = function _new(Ctor, ...params) {
    // 1.格式校验:函数 & 有原型对象 & 不是Symbol/BigInt
    if (typeof Ctor !== "function") throw new TypeError(`${Ctor} is not a constructor!`);
    let name = Ctor.name,
        proto = Ctor.prototype,
        obj,
        result;
    if (/^(Symbol|BigInt)$/i.test(name) || !proto) {
        throw new TypeError(`${name} is not a constructor!`);
    }
    
    // 2.创建当前类的一个实例对象
    obj = Object.create(proto);
    
    // 3.把构造函数像普通函数一样执行,但是this需要指向创建的实例对象
    result = Ctor.call(obj, ...params);
    
    // 4.看函数的返回值,如果没有写返回值,或者返回的是原始值,我们默认返回实例对象;
    // 如果返回的是对象,则以自己返回的为主;
    if (result !== null && /^(object|function)$/i.test(typeof result)) return result;
    return obj;
};
let sanmao = _new(Dog, '小黑');
sanmao.bark(); //=>"wangwang"
sanmao.sayName(); //=>"my name is 小黑"
console.log(sanmao instanceof Dog); //=>true

4-queryURLParams

需求:获取URL地址中的问号参数信息(哈希信息)

思路1:创建a便签,把url字符串赋值给href,通过解构出hash和search,再对字符串处理

/*
 *   @params
 *     attr:需要获取这个属性的信息(不传递获取的是所有的信息)
 *   @return 
 *     具体某个属性信息值
 *     包含所有信息的对象
 */
String.prototype.queryURLParams = function queryURLParams(attr) {
    attr = attr + ''; 
    let obj = {},
        link = document.createElement('a');
    link.href = this;
    let {
        hash,
        search
    } = link;
    if (hash) obj['_HASH'] = hash.substring(1);
    if (search) {
        search.substring(1).split('&').forEach(item => {
            let [key, value] = item.split('=');
            obj[key] = value;
        });
    }
    if (attr !== "undefined") return obj[attr];
    return obj;
};

let url = "http://www.zhufengpeixun.cn/?name=leon&age=18#amazing";
console.log(url.queryURLParams("name")); //=> "leon"
console.log(url.queryURLParams("_HASH")); //=> "amazing"
console.log(url.queryURLParams()); //=> {_HASH: "amazing", name: "leon", age: "18"}

思路2:正则

/*
 *   @params
 *     attr:需要获取这个属性的信息(不传递获取的是所有的信息)
 *   @return 
 *     具体某个属性信息值
 *     包含所有信息的对象
 */
String.prototype.queryURLParams = function queryURLParams(attr) {
    attr = attr + '';
    let obj = {};
    this.replace(/#([^?=&#]+)/g, (_, $1) => obj['_HASH'] = $1);
    this.replace(/([^?=&#]+)=([^?=&#]+)/g, (_, $1, $2) => obj[$1] = $2);
    if (attr !== "undefined") return obj[attr];
    return obj;
};

let url = "http://www.zhufengpeixun.cn/?name=leon&age=18#amazing";
console.log(url.queryURLParams("name")); //=> "leon"
console.log(url.queryURLParams("_HASH")); //=> "amazing"
console.log(url.queryURLParams()); //=> {_HASH: "amazing", name: "leon", age: "18"}

5-手撕call

Function.prototype.call = function call(context, ...params) {
    // this->fn   context->obj   params->[10,20]
    if (context == null) context = window;
    if (!/^(object|function)$/i.test(typeof context)) context = Object(context);
    let key = Symbol(),
        result;
    context[key] = this;
    result = context[key](...params);
    delete context[key];
    return result;
};

const fn = function fn(x, y, ev) {
    console.log(this, x, y, ev);
    return x + y;
};
let result = fn.call('AA', 10, 20);
console.log(result); // {"AA", Symbol(): ƒ} 10 20 undefined

6-手撕apply

思路:闭包的保存作用 “柯理化函数” => 预处理思想

Function.prototype.bind = function bind(context, ...params) {
    return (...args) => {
        return this.call(context, ...params.concat(args));
    };
};