js面试手写题

数据类型判断:

function myTypeof(tar) {
    return Object.prototype.toString.call(tar).slice(8, -1).toLowerCase();
}

instanceof实现

function myInstanceof(tar, type) {
    if (!Object.getPrototypeOf(tar)) {
        return false;
    }
    return Object.getPrototypeOf(tar) === type.prototype ? true : myInstanceof(tar.__proto__, type);
}
// 测试
let date = new Date
myInstanceof(date, Object)
  • 原型链:
// __proto__ 是每个对象都有的属性;prototype是构造函数的属性;他们指向的是同一个对象(原型)。
// 例如:
function F(){};

var f = new F();

f.__proto__ === F.prototype  //true
原型链

call,apply和bind实现

  • call 和 apply 实现:
Function.prototype.myCall = function(context, ...args) {
    context = context || window;
    context.fn = this;
    const res = context.fn(...args);
    delete context.fn;
    return res;
}

// apply  只是参数变成数组
Function.prototype.myApply = function(obj, args = []) {
    if (!Array.isArray(args)) {
        throw Error('not array');
    }  
    context = obj || window;
    context.fn = this;
    const res = context.fn(...args);
    delete context.fn;
    return res;
}

// 测试
function test(num) {
    this.age = num;
    console.log(this, this.name)
}
let obj = {
    name: 'sss'
}
test.myCall(obj, 12)
test.myCall(null, 13)
  • bind实现
// bind 返回是函数以外,它 的参数和 call 一样,bind会收集参数,
// 子函数被调用时,this指向bind的第一个参数; 当子函数使用new 调用时,该参数无效
Function.prototype.myBind = function(obj, ...fArgs) {
    const context = obj || window;
    const fn = this;
    return function(...childArgs) {
        const args = [...fArgs, ...childArgs];
        // new 执行函数时,使用call修改了函数的指向为实例对象
        return this !== window ? fn.call(this, args) : fn.call(context, args);
    }
}

// 测试
function Person(name, age) {
    this.name = name;
    this.age = age;
}
let o = {};
var _Person = Person.bind(o);
let p = new _Person('sx', 12); // o为{},p为Person {name: 1, age: 2}
let res = _Person('sx', 12); // res为undefined,o为{name: 1, age: 2}

数组扁平化

let arr = [1, 2, 3, [2, [23]], 0];
// 无脑拍平
Array.prototype.mySupFlat = function() {
    return this.toString().split(',');
}
// flat 实现,参数 n 控制展开层数
Array.prototype.myFlat = function(n) {
    let arr = this.slice();
    let tmp = [];
    while (n > 0) {
        arr.forEach(item => {
            Array.isArray(item) ? tmp.push(...item) : tmp.push(item);
        })
        arr = tmp;
        tmp = [];
        n--;
    }
    return arr;
}

数组去重

// es5 需要双层遍历(数组中可能有引用类型)
// es6
Array.prototype.unique = function() {
    let set = new Set(this);
    return [...set];
}

object.create实现

object.create实际是将参数obj作为空对象的原型,然后返回空对象。如果是参数obj是null,则返回的是原型为null的空对象

Object.prototype.myCreate  = function (proto, propertyObject = undefined) {
  if (propertyObject === null) {
    // 这里没有判断propertyObject是否是原始包装对象
    throw 'TypeError'
  } else {
    function Fn () {}
    Fn.prototype = proto
    const obj = new Fn()
    // 第二个参数
    if (propertyObject !== undefined) {
      // obj会新增propertyObject的key作为属性,该方法默认新增属性不可枚举
      Object.defineProperties(obj, propertyObject)
    }
    if (proto === null) {
      // 创建一个没有原型对象的对象,Object.create(null)
      obj.__proto__ = null
    }
    return obj
  }
}
第二个参数

promise系列实现

const STATUS = {
    pend: 'pendding',
    res: 'fulfilled',
    rej: 'rejected'
}
class myPromise {
    constructor(exeuctor) {
        this.status = STATUS.pend;
        this.val = null;
        this.reason = null;
        // 1个promise 实例,可以调用多次 then,用于多次触发 then 注册的回调。
        this.onResolve = [];
        this.onReject = [];
        // es6 class内默认开启严格模式,如果实例函数单独调用,this为undefined
        // 严格模式下,funcion 声明有问题,没有this,
        // => 函数刚好, this 指向实例
        const resolve = val => {
            if (this.status !== STATUS.pend) return;
            this.val = val;
            // 只要有 resolve,就修改状态,防止下一次的resolve
            this.status = STATUS.res;
            //用setTimeout模拟「微任务」执行,延迟绑定 then 收集的回调函数,
            // 不延迟绑定时,如果promise 的resolve 是同步执行的话,此时还未执行then,找不到注册的回调(onResolve是空数组),影响执行时机
            // promise.then 的「微任务」执行时机就在这,不然就是主函数任务。
            setTimeout(() => {
                // 循环执行 then 收集的回调,
                this.onResolve.forEach(fn => fn(this.val));
            }, 0)
        };
        const reject = reason => {
            if (this.status !== STATUS.pend) return;
            this.reason = reason;
            console.log('reject this', JSON.stringify(this))
            this.status = STATUS.rej;
            setTimeout(() => {
                this.onReject.forEach(fn => {
                    fn(this.reason)
                });
            }, 0)
        }
        // exeuctor 可能不是函数
        try {
            exeuctor(resolve, reject);
        } catch (err) {
            reject(err);
        }
    }

    then(onResolve, onReject) {
        onResolve = onResolve ? onResolve : res => res;
        onReject = onReject ? onReject : err => err;
        // 返回新promise,用于链式调用
        return new myPromise((retRes, retRej) => {
            // then 的执行时机跟 resolve 执行的时机相比 『可前可后』
            // 当实例的 then 调用,resolve 和 reject 未触发时,则继续收集 then 注册的回调
            // 如果在还没有收集到处理函数 onResolve时,this.onResolve是空数组,不会报错
            if (this.status === STATUS.pend) {
                this.onResolve.push(() => {
                    // promise1 的 onResolve(this.val) 可能有 return 「值」,把该值作为下个promise2 中 resolve(「值」),链式透传
                    // retRes(onResolve(this.val)); 此方法链式调用有问题
                    resolvePromise(retRes, retRej, onResolve, this.val);
                });
                this.onReject.push(() => {
                    // retRej(onReject(this.reason));
                    resolvePromise(retRes, retRej, onReject, this.reason);

                });
            }

            // 当实例的 then 调用,resolve 也已触发时,则直接在「微任务」中触发当前的注册回调,并使promise2 的 resolve 包裹
            if (this.status === STATUS.res) {
                setTimeout(() => {
                    // retRes(onResolve(this.val));
                    resolvePromise(retRes, retRej, onResolve, this.val);
                }, 0)
            }
            if (this.status === STATUS.rej) {
                setTimeout(() => {
                    // retRej(onReject(this.reason));
                    resolvePromise(retRes, retRej, onReject, this.reason);
                }, 0)
            }
        });
    }

    catch(errHandle) {
        return this.then(() => { }, errHandle);
    }

    finally(cb) {
        return this.then(cb, cb);
    }
}

function resolvePromise(retRes, retRej, fn, args) {
    // promise1的then注册的onRes或onRej执行时,不同的返回值和类型有不同的触发效果
    // 1. 只要then注册的回调函数触发没有返回值,则then 返回的promise2 状态就是fulfilled,value是 undefined
    // 2. 只要触发的函数返回的是值 X, 那么promise2 状态就是fulfilled,value是 X,[注意:非promise的普通值返回时,被Promise.resolve(x)包装]
    // 3. 只要触发的函数返回的是promise 实例,则promise2 状态和值跟该返回实例保持一致
    // 4. 只要触发的函数有「错误」,则promise2的状态是rejected,reason就是抛出的异常对象 e
    try {
        const res = fn(args);
        if (res instanceof myPromise) { // 如果返回的是promise 实例时,则promise2 的状态跟此实例状态和值保持一致
            res.then(val => {
                retRes(val)
            }, reason => {
                retRej(reason)
            });
        }
        else {
            // promise1的then注册的onRes或onRej执行时,如果不返回值,则promise2的状态变成fulfilled,value为undefined
            // 如果onFulfilled或onRejected返回一个值x,那么promise2的状态是fulfilled, value是 X
            retRes(res);
        }
    } catch (error) {  // promise1的then注册的onRes或onRej执行时,如果有throw err 则在promise2 中reject
        retRej(error);
    }
}
  • promise.all 实现
myPromise.all = function (promiseArr) {
    if (promiseArr.length === 0) return myPromise.resolve([])
    promiseArr = promiseArr.map(item => item instanceof myPromise ? item : myPromise.resolve(item));
    const length = promiseArr.length;
    let sum = 0, result = [];
    return new myPromise((res, rej) => {
        promiseArr.forEach((promise, index) => {
            promise.then(value => {
                result[index] = value;
                sum++;
                if (sum === length) {
                    res(result);
                }
            }, err => {
                rej(err);
            })
        })
    })
}
  • promise.allSettled实现
myPromise.allSettled = function (promises) {
  if (promises.length === 0) return myPromise.resolve([])
  
  const _promises = promises.map(
    item => item instanceof myPromise ? item : myPromise.resolve(item)
    )
  
  return new Promise((resolve, reject) => {
    const result = []
    let unSettledPromiseCount = _promises.length
    
    _promises.forEach((promise, index) => {
      promise.then((value) => {
        result[index] = {
          status: 'fulfilled',
          value
        }
        
        unSettledPromiseCount -= 1
        // resolve after all are settled
        if (unSettledPromiseCount === 0) {
          resolve(result)
        }
      }, (reason) => {
        result[index] = {
          status: 'rejected',
          reason
        }
        
        unSettledPromiseCount -= 1
        // resolve after all are settled
        if (unSettledPromiseCount === 0) {
          resolve(result)
        }
      })
    })
  })
}

new实现

function myNew(fn, ...args) {
    let obj = {};
    // 修改原型链,继承方法
    obj.__proto__ = fn.prototype;
    
    // 构造函数执行初始化,对obj增加属性
    let ret = fn.call(obj, ...args); // apply:第二个参数是数组

    // 当构造函数返回的是一个对象时,则new 返回该对象(res),如果无返回或者返回的是基本类型则返回最初的对象(obj)
    return typeof ret === 'object' && ret !== null ? ret : obj;
}

函数柯里化

function cury(fn, ...fArgs) {
    // 参数个数
    const length = fn.length;
    return function(...childArgs) {
        const args = [...fArgs, ...childArgs];
        return args.length >= length ? fn.apply(null, args) : cury(fn, ...args);
    }
}

// 测试
function add(a, b ,c) {
    console.log(a, b ,c, a+ b+c);
}
let fn = cury(add);
fn(1)(2)(3);

深拷贝

包含特殊对象的深拷贝

function deepCopy(obj, map = new WeakMap()) {
    if (typeof obj === 'object' && obj !== null) {
        // 防止循环引用
        if (map.has(obj)) {
            return map.get(obj);
        }
        map.set(obj, obj);
        let res = Array.isArray(obj) ? [] : {};
        for (let key in obj) {
            if (obj.hasOwnProperty(key)) {
                value = obj[key];
                res[key] = deepCopy(value, map);
            }

        }
        // 特殊对象如date,map等的拷贝
        if (!Array.isArray(res) && !Object.keys(res).length) {
            res = new obj.constructor(obj);
        }
        return res;
    }
    else {
        return obj
    }
}
// 测试数据
var test = { name: "test", d: {age: 12} };
test.x = test;
var data = {
    a: "123",
    b: 123,
    c: true,
    d: [43, 2],
    e: undefined,
    f: null,
    g: function () { console.log("g"); },
    h: new Set([3, 2, null]),
    i: Symbol("fsd"),
    j: test,
    k: new Map([["name", "张三"], ["title", "Author"]])
};
deepCopy(data);
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 222,252评论 6 516
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,886评论 3 399
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 168,814评论 0 361
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,869评论 1 299
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,888评论 6 398
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,475评论 1 312
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 41,010评论 3 422
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,924评论 0 277
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,469评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,552评论 3 342
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,680评论 1 353
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,362评论 5 351
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 42,037评论 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,519评论 0 25
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,621评论 1 274
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 49,099评论 3 378
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,691评论 2 361

推荐阅读更多精彩内容