手撕代码

2019-03-30  本文已影响0人  悄敲

前端笔试和面试都难免要手撕代码,有些面试官还就喜欢看你现场写。诚然,一个程序员在写代码时很容易暴露问题,但也能展现实力,就像厨师做菜。
Dancing as no one is watching, coding as everyone is watching.
(一)手撕 new操作符
实现 New(Func[,args])类似于 new Func([args])。

   //1.创建一个新对象
    //2.将构造函数的作用域赋给新对象(this 指向新创建的对象)
    //3.执行构造函数中的代码
    //4.返回新创建的对象.
    function New(func) {
        let res={};
        if(func.prototype!==null){
            res.__proto__=func.prototype;
        }
        let ret=func.apply(res,Array.prototype.slice.call(arguments,1));
        if(ret!==null &&(typeof ret==='object'|| typeof ret==='function')){
            return ret;
        }
        return res;
    }

**(二)手撕 JSON.stringify **

   //Boolean | Number| String 类型会自动转换成对应的原始值。
    //undefined、任意函数以及symbol,会被忽略(出现在非数组对象的属性值中时),或者被转换成 null(出现在数组中时)。
    //不可枚举的属性会被忽略
   // 如果一个对象的属性值通过某种间接的方式指回该对象本身,即循环引用,属性也会被忽略。
    function jsonStringify(obj) {
        let type=typeof obj;
        //特定基本类型
        if(type!=="object"){
            if(/undefined|function|symbol/.test(type)){
                return;
            }
            return String(obj);
        }
        //数组或非数组对象
        else {
            const isArr=Array.isArray(obj);
            let json=[];
            for(let k in obj){
                let v=obj[k];
                let type=typeof v;
                if(v===obj){
                    continue;
                }
                if(/undefined|function|symbol/.test(type)){
                    if(isArr){v="null";
                    }
                    else{
                        continue;
                    }
                }
                else if(type==="object" && v!==null){
                    v=jsonStringify(v);
                }
                else if(type==="string"){
                    v='"'+v+'"';
                }
                isArr? json.push(String(v)) : json.push('"'+k+'"'+':'+ String(v));
            }
            return isArr? "[" +String(json)+ "]" : "{" +String(json)+ "}";
        }
    }

**(三)手撕 JSON.parse **
以下两个方法仅应对面试,实际中还是用原生的JSON.parse.

//方法1 利用eval,但是eval并不安全,存在XSS漏洞,需要对json做校验。
function jsonParse(json) {
    const rx_one = /^[\],:{}\s]*$/;
    const rx_two = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g;
    const rx_three = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g;
    const rx_four = /(?:^|:|,)(?:\s*\[)+/g;
    if (rx_one.test(
            json
                .replace(rx_two, "@")
                .replace(rx_three, "]")
                .replace(rx_four, "")
        )
    ) {
        return eval("(" +json + ")");
    }
}

//方法二,利用new Function(),和eval都会动态编译js代码,实际中并不推荐
 function jsonParse2(json) {
     return new Function('return'+json);
 }

**(四)手撕 call apply bind **
对于call 或者 apply,实现的核心都是以下3步:
(以 foo.call(bar) 为例)
(1)将函数 foo 设为指定对象 bar 的属性;
(2)执行该函数;
(3)删除对象上的这个临时属性,并返回上一步的执行结果。

 // 1.call的模拟实现
   Function.prototype.call2=function (context = window) {
       context.fn=this;
       let args=[...arguments].slice(1);
       let result=context.fn(...args);
       delete context.fn;
       return result;
   }

   //2.apply 的模拟实现
    Function.prototype.apply2=function (context = window) {
        context.fn=this;
        let result;
        if(arguments[1]){
            result=context.fn(...arguments[1]);
        }
        else{
            result=context.fn();
        }
        delete context.fn;
        return result;
    }

bind的实现要复杂些,因为要考虑到将绑定后的函数作为构造函数来new 对象实例。
关于bind的详细资料,参考MDN
如果将绑定后的函数作为非构造函数调用,就比较简单。而作为构造函数调用时,如下所示

const foo=function(){ };// 待绑定的函数
const bar={ };// 指定的对象,传到 bind中作为第一个参数
const boundFoo=foo.bind(bar);
let bf1=new boundFoo();

需要注意:
(1)this指向:绑定后的函数boundFoo被当作构造函数调用,其内部的 this指向新创建的对象实例 bf1,而不是绑定 foo 时指定的对象 bar。
(2)原型链:需要维护原型链,即构造函数 boundFoo 继承自最初待绑定的函数 foo 。
完整的 bind 模拟函数如下:

 //3.bind的模拟实现
    Function.prototype.bind2=function(context){
        // closet thing possible to the ES5
        // internal IsCallable function
        if(typeof this!=='function'){
            throw new TypeError('Function.prototype.bind - ' +
                'what to be bound is not callable.');
        }

        let aArgs=Array.prototype.slice.call(arguments,1);
        let fnToBind=this;
        let fnNOP=function () {};
        let fnBound=function () {
            // this instanceof fnBound===true,说明返回的fnBound
            //被当作构造函数(via new)调用
            return fnToBind.apply(
                this instanceof fnBound? this: context,
                //获取调用(fnBound)时传入的参数
                aArgs.concat(...arguments)
            );
        };

        //维护原型关系
        if(this.prototype){
            // Function.prototype doesn't have a prototype property.
            fnNOP.prototype=this.prototype;
        }
        //若fnBound作为构造函数,则通过new生成的对象的__proto__指向fNOP的实例
        //(fnBound继承了fNOP)
        fnBound.prototype=new fnNOP();
        return fnBound;
    }

关于 bind ,还有一个地方要注意,就是在调用 bind绑定时,传给 bind 的参数除了第一个被指定为 绑定后的 this,其余参数(args1)会被插入到目标函数的参数列表的开始位置,而调用绑定好的函数时,传递给被绑定函数的参数(args2)会跟在它们(args1)后面。这个在上段代码中体现为 aArgs.concat(...arguments)
这个被称为偏函数,会造成调用绑定好的函数时,传入的参数由于在绑定时已经传入了相应位置上的参数而被忽略,如下所示。

// test
    let foo={
       value:1
    }
    function bar(name, age) {
        console.log(name);
        console.log(age);
        console.log(this.value);
    }
    // bar.call2(foo);
    // bar.apply2(foo);
    let boundBar=bar.bind2(foo,'Bob',25);
    boundBar('Ann',23); // 'Bob',25,1

**(五)手撕 promise **
(1)瓜皮版,可作为改进的草稿,面试实在写不出来就凑合用这个吧。

    // 实现1,瓜皮版,并不完全具备promise的功能
    // 缺点:(1)then返回的并不是promise对象;
    //  (2)实例化promise的时候无法处理异步的resolve
    // (3)then 指定的回调并不是异步的
    function MyPromise(constructor) {
        let self=this;
        self.status='pending';//定义状态改变前的初始状态
        self.value=undefined;//定义状态为resolved的时候的状态
        self.reason=undefined;//定义状态为rejected的时候的状态
        function resolve(value) {
            //两个==="pending",保证了状态的改变是不可逆的
            if(self.status==='pending'){
                self.value=value;
                self.status='resolved';
            }
        }
        function reject(reason) {
            //两个==="pending",保证了状态的改变是不可逆的
            if(self.status==='pending'){
                self.reason=reason;
                self.status='rejected';
            }
        }
        //捕获构造异常
        try{
            //实例化promise的时候指定何时调用resolve,何时调用reject
            constructor(resolve,reject);
        }catch (e) {
            reject(e);
        }
    }

    MyPromise.prototype.then=function (onFulfilled, onRejected) {
        let self=this;
        switch(self.status){
            case "resolved":
                onFulfilled(self.value);
                break;
            case "rejected":
                onRejected(self.reason);
                break;
            default:
        }
    }

(2)牛逼版,不过以下代码虽然符合 promise A+规范,可以在面试官面前装装逼,但只是模拟实现,其 then 回调并不是微任务,而且使用了 setTimeout 来模拟异步。应用中还是用原生的 Promise。
来源:1
2
3

 // 终极版
    /**
     * 1. new Promise时,需要传递一个 executor 执行器,执行器立刻执行
     * 2. executor 接受两个参数,分别是 resolve 和 reject
     * 3. promise 只能从 pending 到 rejected, 或者从 pending 到 fulfilled
     * 4. promise 的状态一旦确认,就不会再改变
     * 5. promise 都有 then 方法,then 接收两个参数,分别是 promise 成功的回调 onFulfilled,
     *      和 promise 失败的回调 onRejected
     * 6. 如果调用 then 时,promise已经成功,则执行 onFulfilled,并将promise的值作为参数传递进去。
     *      如果promise已经失败,那么执行 onRejected, 并将 promise 失败的原因作为参数传递进去。
     *      如果promise的状态是pending,需要将onFulfilled和onRejected函数存放起来,等待状态确定后,再依次将对应的函数执行(发布订阅)
     * 7. then 的参数 onFulfilled 和 onRejected 可以缺省
     * 8. promise 可以then多次,promise 的then 方法返回一个 promise
     * 9. 如果 then 返回的是一个结果,那么就会把这个结果作为参数,传递给下一个then的成功的回调(onFulfilled)
     * 10. 如果 then 中抛出了异常,那么就会把这个异常作为参数,传递给下一个then的失败的回调(onRejected)
     * 11.如果 then 返回的是一个promise,那么需要等这个promise,那么会等这个promise执行完,promise如果成功,
     *   就走下一个then的成功,如果失败,就走下一个then的失败
     */
    //https://juejin.im/post/5c88e427f265da2d8d6a1c84#heading-16
    const PENDING = 'pending';
    const FULFILLED = 'fulfilled';
    const REJECTED = 'rejected';
    function myPromise(executor) {
        let self = this;
        self.status = PENDING;
        self.onFulfilled = [];//成功的回调
        self.onRejected = []; //失败的回调
        //PromiseA+ 2.1
        function  resolve(value) {
            // 如果 value 本身就是一个promise
            if(value instanceof myPromise){
                return value.then(resolve,reject);
            }

            if (self.status === PENDING) {
                self.status = FULFILLED;
                self.value = value;
                self.onFulfilled.forEach(fn => fn());//PromiseA+ 2.2.6.1
            }
        }

        function reject(reason) {
            if (self.status === PENDING) {
                self.status = REJECTED;
                self.reason = reason;
                self.onRejected.forEach(fn => fn());//PromiseA+ 2.2.6.2
            }
        }

        try {
            executor(resolve, reject);
        } catch (e) {
            reject(e);
        }
    }

    //onFulfilled 和 onFulfilled的调用需要放在setTimeout,
    // 因为规范中表示: onFulfilled or onRejected must not be called
    // until the execution context stack contains only platform code。
    // 使用setTimeout只是模拟异步,原生Promise并非是这样实现的.
    myPromise.prototype.then = function (onFulfilled, onRejected) {
        //PromiseA+ 2.2.1 / PromiseA+ 2.2.5 / PromiseA+ 2.2.7.3 / PromiseA+ 2.2.7.4
        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
        onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason };
        let self = this;
        //PromiseA+ 2.2.7
        let promise2 = new myPromise((resolve, reject) => {
            if (self.status === FULFILLED) {
                //PromiseA+ 2.2.2
                //PromiseA+ 2.2.4 --- setTimeout
                setTimeout(() => {
                    try {
                        //PromiseA+ 2.2.7.1
                        let x = onFulfilled(self.value);
                        resolvePromise(promise2, x, resolve, reject);
                    } catch (e) {
                        //PromiseA+ 2.2.7.2
                        reject(e);
                    }
                });
            } else if (self.status === REJECTED) {
                //PromiseA+ 2.2.3
                setTimeout(() => {
                    try {
                        let x = onRejected(self.reason);
                        resolvePromise(promise2, x, resolve, reject);
                    } catch (e) {
                        reject(e);
                    }
                });
            }
            //  如果promise的状态是pending,需要将onFulfilled和onRejected函数存放起来,
            //  等待状态确定后,再依次将对应的函数执行(发布订阅)
            else if (self.status === PENDING) {
                self.onFulfilled.push(() => {
                    setTimeout(() => {
                        try {
                            let x = onFulfilled(self.value);
                            resolvePromise(promise2, x, resolve, reject);
                        } catch (e) {
                            reject(e);
                        }
                    });
                });
                self.onRejected.push(() => {
                    setTimeout(() => {
                        try {
                            let x = onRejected(self.reason);
                            resolvePromise(promise2, x, resolve, reject);
                        } catch (e) {
                            reject(e);
                        }
                    });
                });
            }
        });
        return promise2;
    }

    function resolvePromise(promise2, x, resolve, reject) {
        let self = this;
        //PromiseA+ 2.3.1
        if (promise2 === x) {
            reject(new TypeError('Chaining cycle'));
        }
        if (x && typeof x === 'object' || typeof x === 'function') {
            let used; //PromiseA+2.3.3.3.3 只能调用一次
            try {
                let then = x.then;
                //如果 x 是个 thenable 对象
                if (typeof then === 'function') {
                    //PromiseA+2.3.3
                    //then.call(x, resolvePromiseFn, rejectPromiseFn)
                    then.call(x, (y) => {
                        //PromiseA+2.3.3.1
                        if (used) return;
                        used = true;
                        // 递归调用
                        resolvePromise(promise2, y, resolve, reject);
                    }, (r) => {
                        //PromiseA+2.3.3.2
                        if (used) return;
                        used = true;
                        reject(r);
                    });

                }else{
                    //PromiseA+2.3.3.4
                    if (used) return;
                    used = true;
                    resolve(x);
                }
            } catch (e) {
                //PromiseA+ 2.3.3.2
                if (used) return;
                used = true;
                reject(e);
            }
        } else {
            //PromiseA+ 2.3.3.4
            resolve(x);
        }
    }
上一篇下一篇

猜你喜欢

热点阅读