常见手写题1

42 阅读4分钟

1.快速排序

function sort(start, end, arr){
    const pivot = arr[start]
    let left = start
    let right = end
    while(left<right){
        while(arr[right] >= pivot && right > left){
            right--
        }
        arr[left] = arr[right]
        while(arr[left] < pivot && right > left) {
            left++
        }
        arr[right] = arr[left]
    }
    arr[left] = pivot
    return left
}
function quickSort(start, end , arr) {
    if(start < end){
        const mid = sort(start, end, arr)
        quickSort(start, mid - 1, arr)
        quickSort(mid + 1, end, arr)
    }
}

2. instanceof

function myInstanceof(target, origin) {
    if(typeof target !== "object" || target == null) return false
    if(typeof origin !== "function") {
        throw new TypeError("origin not func")
    }
    let proto = Object.getPrototypeOf(target)
    while(proto) {
        if(proto === origin.prototype) return true
        proto = Object.getPrototype(proto)
    }
    return false
}

3. 数组扁平化

function flat(arr, depth = 1) {
    if(depth > 0) {
        arr.reduce((pre,cur) => {
            return pre.concat(Array.isArray(cur) ? flat(cur, depth - 1) : cur)
        },[])
    }
    return arr.slice()
}

4.手写reduce

//先不考虑第二个参数初始值
Array.prototype.reduce1 = function( fn ) {
    const arr = this
    let total = arr[0]
    for(let i = 1 ; i < arr.length ; i++) {
        total = fn(total, arr[i],i,arr)
    }
    return total
}

Array.prototype.reduce2 = function(fn, init){
    const arr = this
    let total = init || arr[0]
    
    for(let i = init?0 : 1 ; i < arr.length ; i++){
        total = fn(total , arr[i] , i , arr)
    }
    return total
}

5.数组去重(多种方法)

function unique(arr) {
    return [...new Set(arr)]
}

function unique(arr) {
    return arr.filter((item,index,array) => {
        return array.indexOf(item) === index
    }) 
}

6. new的实现

1.首先创建一个新的空对象

2.根据原型链,设置空对象的 __proto__ 为构造函数的 prototype

3.构造函数的this指向这个对象,致性构造函数的代码(为这个新对象添加属性)。

4.判断函数的返回值类型,如果是引用类型,就返回这个引用类型的对象

function myNew(context) {
    const obj = new Object()
    obj._proto_ = context.prototype
    const res = context.apply(obj,[...arguments].slice(1))
    return typeof res === "object" ? res : obj
    
}

7.call,apply,bind的实现

  • call : call() 方法在使用一个指定的 this 值和若干个指定的参数值的前提下调用某个函数或方法。

  • Function.prototype.myCall = function(context) {
        // 判断调用对象
        if(typeof this!= "function") throw new Error("Type error")
        
        let args = [..arguments].slice(1)
        let res= null
        //判断是否传入了this
        context = context || window
        //this就是我们使用的函数,obj.fn()时,fn中的this指向obj,有关this指向请看我的另一篇文章
        context.fn = this
        res = context.fn(...args)
        
        //删除属性
        delete context.fn
        return res
    }
    
  • apply: apply和call的区别在于 app把参数 按照数组的形式传进去了

    Function.prototype.myApply = function(context) {
         // 判断调用对象
        if(typeof this!= "function") throw new Error("Type error")
       
        let res= null
        //判断是否传入了this
        context = context || window
        //this就是我们使用的函数,obj.fn()时,fn中的this指向obj,有关this指向请看我的另一篇文章
        context.fn = this
        //可以使用Symbol防止同名属性
        //const fnSymbol = Symbol();
        //context[fnSymbol] = this;
        if(arguments[1]) {
            res = context.fn(...arguments[1])
            //result = context[fnSymbol](...arguments[1]);
        }else{
            res = context.fn()
        }
        
        //删除属性
        delete context.fn
        return res
    }
    
  • bind : 返回一个函数 可以继续传递参数

    Function.prototype.bind = function (context) {
        // 调用 bind 的不是函数,需要抛出异常
        if (typeof this !== "function") {
          throw new Error("Function.prototype.bind - what is trying to be bound is not callable");
        }
        
        // this 指向调用者
        var self = this;
        // 实现第2点,因为第1个参数是指定的this,所以只截取第1个之后的参数
        var args = Array.prototype.slice.call(arguments, 1); 
        
        // 实现第3点,返回一个函数
        return function () {
            // 实现第4点,这时的arguments是指bind返回的函数传入的参数
            // 即 return function 的参数
            var bindArgs = Array.prototype.slice.call(arguments);
            // 实现第1点
            return self.apply( context, args.concat(bindArgs) );
        }
    }
    

    但还有一个问题,bind 有以下一个特性:

    一个绑定函数也能使用 new 操作符创建对象:这种行为就像把原函数当成构造器,提供的 this 值被忽略,同时调用时的参数被提供给模拟函数。

    来个例子说明下:

    let value = 2;
    let foo = {
        value: 1
    };
    function bar(name, age) {
        this.habit = 'shopping';
        console.log(this.value);
        console.log(name);
        console.log(age);
    }
    bar.prototype.friend = 'kevin';
    
    let bindFoo = bar.bind(foo, 'Jack');
    let obj = new bindFoo(20);
    // undefined
    // Jack
    // 20
    
    obj.habit;
    // shopping
    
    obj.friend;
    // kevin
    

    上面例子中,运行结果 this.value 输出为 undefined ,这不是全局 value 也不是 foo 对象中的 value ,这说明 bindthis 对象失效了,new 的实现中生成一个新的对象,这个时候的 this 指向的是 obj

    这个可以通过修改返回函数的原型来实现,代码如下:

    Function.prototype.bind = function (context) {
        // 调用 bind 的不是函数,需要抛出异常
        if (typeof this !== "function") {
          throw new Error("Function.prototype.bind - what is trying to be bound is not callable");
        }
        
        // this 指向调用者
        var self = this;
        // 实现第2点,因为第1个参数是指定的this,所以只截取第1个之后的参数
        var args = Array.prototype.slice.call(arguments, 1);
        
        // 创建一个空对象
        var fNOP = function () {};
        
        // 实现第3点,返回一个函数
        var fBound = function () {
            // 实现第4点,获取 bind 返回函数的参数
            var bindArgs = Array.prototype.slice.call(arguments);
            // 然后同传入参数合并成一个参数数组,并作为 self.apply() 的第二个参数
            return self.apply(this instanceof fNOP ? this : context, args.concat(bindArgs));
            // 注释1
        }
        
        // 注释2
        // 空对象的原型指向绑定函数的原型
        fNOP.prototype = this.prototype;
        // 空对象的实例赋值给 fBound.prototype
        fBound.prototype = new fNOP();
        return fBound;
    }
     //简短版
    Function.prototype.myBind = function (context) {
      // 判断调用对象是否为函数
      if (typeof this !== "function") {
        throw new Error("Type error");
      }
      // 获取参数
      const args = [...arguments].slice(1),
      const fn = this;
      return function Fn() {
        return fn.apply(
          this instanceof Fn ? this : context,
          // 当前的这个 arguments 是指 Fn 的参数
          args.concat(...arguments)
        );
      };
    };
    

    注释1

    • 当作为构造函数时,this 指向实例,此时 this instanceof fBound 结果为 true ,可以让实例获得来自绑定函数的值,即上例中实例会具有 habit 属性。
    • 当作为普通函数时,this 指向 window ,此时结果为 false ,将绑定函数的 this 指向 context

    注释2

    • 修改返回函数的 prototype 为绑定函数的 prototype,实例就可以继承绑定函数的原型中的值,即上例中 obj 可以获取到 bar 原型上的 friend
    • 至于为什么使用一个空对象 fNOP 作为中介,把 fBound.prototype 赋值为空对象的实例(原型式继承),这是因为直接 fBound.prototype = this.prototype 有一个缺点,修改 fBound.prototype 的时候,也会直接修改 this.prototype