美文网首页
手撕代码

手撕代码

作者: 悄敲 | 来源:发表于2019-03-30 21:22 被阅读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);
            }
        }
    

    相关文章

      网友评论

          本文标题:手撕代码

          本文链接:https://www.haomeiwen.com/subject/ovtebqtx.html