函数、对象相关手写代码题

1,071 阅读6分钟

call,apply,bind方法实现

call方法实现

使用一个指定的 this 值和一个或多个参数来调用一个函数。

实现要点:

  • this 可能传入 null;
  • 传入不固定个数的参数;
  • 函数可能有返回值;
Function.prototype.mycall=function(obj){
    obj.fn=this;
    obj.fn();
    delete obj.fn;
}
console.log('mycall-------------')
fn.mycall(obj)

Function.prototype.mycall2=function(obj){
    //判断是否是函数,只有函数有call方法
    if(typeof this!=='function'){
        throw new TypeError('not function')
    }
    //当call第一个参数为undefined或者null时,this默认指向window
    obj=obj||window;
    obj.fn=this;
    let args=[...arguments].slice(1)
    let res=obj.fn(...args)
    delete obj.fn;
    return res
}
console.log('mycall2-------------')
fn.mycall2(obj,222,111)
apply方法实现

apply 和 call 一样,唯一的区别就是 call 是传入不固定个数的参数,而 apply 是传入一个数组。

实现要点:

  • this 可能传入 null;
  • 传入一个数组;
  • 函数可能有返回值;
Function.prototype.myapply=function(context){
    if(typeof this !=='function'){
        throw new TypeError('err')
    }
    context=context||window
    context.fn=this
    let args=[...arguments].slice(1)
    let res;
    if(args.length>0){
        res=context.fn(...args[0])
    }else{
        res=context.fn()
    }
    delete context.fn
    return res
}
bind方法实现

bind 方法会创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。

实现要点:

  • bind() 除了 this 外,还可传入多个参数;
  • bing 创建的新函数可能传入多个参数;
  • 新函数可能被当做构造函数调用;
  • 函数可能有返回值;
//不同于call和apply方法,bind方法返回的是一个函数
Function.prototype.mybind=function(context){
    if(typeof this!=='function'){
        throw new TypeError('err')
    }
    let _this=this
    let args=[...arguments].slice(1)
    // instanceof用来检测某个实例对象的原型链上是否存在这个构造函数的prototype属性,
    // this instanceof func === true时,说明返回的fBound被当做new的构造函数调用,此时this=fBound(){},
    //否则this=window, 如果是的话使用新创建的this代替硬绑定的this
    return function F(){
        if(this instanceof F){
            return new _this(...args,...arguments)
        }else{
            return _this.apply(context,args.concat(...arguments))
        }
    }
}

实现new方法

new操作符的原理: 1.首先创建一个新对象 2.将这个对象链接到其原型 3.将this绑定到新创建的对象 4.返回新对象

function createNew(){
    let obj={}//新建对象
    //所以需要用一个数组对象[],调用shift方法,然后将arguments作为第一个参数传入
    //即将对象arguments中的this指向Array类型,然后调用Array上的方法
    //相当于arguments.shift()
    //[].shift 相当于 Array.prototype.shift
    //取到参数中传入的对象(第一个参数为函数对象)
    let constructor=[].shift.call(arguments);
    //obj.__proto__ 与 Object.prototype 是一样的,都是指的Object类型的原型
    obj.__proto__=constructor.prototype; //将传入对象链接到对象类型(obj)的原型
    let result=constructor.apply(obj,arguments); //将obj的this绑定到传入对象上并返回新对象
    return typeof result==='object'?result:obj //如果该函数没有返回值,则返回新对象,否则返回该函数的返回值
}
function People(name,age) {
    this.name = name
    this.age = age
}
let peo = createNew2(People,'Bob',22)
console.log(peo.name)
console.log(peo.age)

实现instanceOf

核心思想:判断左边实例的原型链上是否存在右边构造函数的prototype属性

function instanceOf(left,right) {
    let lefttype=left.__proto__;
    let righttype=right.prototype;
    while(true){
        if(righttype===null){
            return false;
        }
        if(lefttype===righttype){
            return true
        }
        lefttype=lefttype.__proto_
    }
}
let arr=[]
console.log(instanceOf(arr,Array))

实现 Object.create

Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__。

Object.create2 = function(proto, propertyObject = undefined) {
    if (typeof proto !== 'object' && typeof proto !== 'function') {
        throw new TypeError('Object prototype may only be an Object or null.')
    if (propertyObject == null) {
        new TypeError('Cannot convert undefined or null to object')
    }
    function F() {}
    F.prototype = proto
    const obj = new F()
    if (propertyObject != undefined) {
        Object.defineProperties(obj, propertyObject)
    }
    if (proto === null) {
        // 创建一个没有原型对象的对象,Object.create(null)
        obj.__proto__ = null
    }
    return obj
}

实现 Object.assign

Object.assign2 = function(target, ...source) {
    if (target == null) {
        throw new TypeError('Cannot convert undefined or null to object')
    }
    let ret = Object(target) 
    source.forEach(function(obj) {
        if (obj != null) {
            for (let key in obj) {
                if (obj.hasOwnProperty(key)) {
                    ret[key] = obj[key]
                }
            }
        }
    })
    return ret
}

实现对象属性的get、set方法

function Person(){ } 
Person.prototype.name="admin";
Person.prototype.getName=function(){
  return this.name;
};
Person.prototype.setName=function(val){
  this.name=val;
};
var per=new Person();
console.log(per.name)

或者(可链式调用)

function Person(){
    this.value=1;
    this.set=function(value){
        this.value = value;
        return this;
    }
    this.get=function(){
        return this.value;
    }
};
var person = new Person();
console.log(person.set(100).get()) // 100

继承

原型链继承
function Animal() {
    this.colors = ['black', 'white']
}
Animal.prototype.getColor = function() {
    return this.colors
}
function Dog() {}
Dog.prototype =  new Animal()

let dog1 = new Dog()
dog1.colors.push('brown')
let dog2 = new Dog()
console.log(dog2.colors)  // ['black', 'white', 'brown']

原型链继承存在的问题:

  • 问题1:原型中包含的引用类型属性将被所有实例共享;
  • 问题2:子类在实例化的时候不能给父类构造函数传参;
构造函数实现继承
function Animal(name) {
    this.name = name
    this.getName = function() {
        return this.name
    }
}
function Dog(name) {
    Animal.call(this, name)
}
Dog.prototype =  new Animal()

借用构造函数实现继承解决了原型链继承的 2 个问题:引用类型共享问题以及传参问题。但是由于方法必须定义在构造函数中,所以会导致每次创建子类实例都会创建一遍方法。

组合继承

组合继承结合了原型链和盗用构造函数,将两者的优点集中了起来。基本的思路是使用原型链继承原型上的属性和方法,而通过盗用构造函数继承实例属性。这样既可以把方法定义在原型上以实现重用,又可以让每个实例都有自己的属性。

function Animal(name) {
    this.name = name
    this.colors = ['black', 'white']
}
Animal.prototype.getName = function() {
    return this.name
}
function Dog(name, age) {
    Animal.call(this, name)
    this.age = age
}
Dog.prototype =  new Animal()
Dog.prototype.constructor = Dog

let dog1 = new Dog('奶昔', 2)
dog1.colors.push('brown')
let dog2 = new Dog('哈赤', 1)
console.log(dog2) 
// { name: "哈赤", colors: ["black", "white"], age: 1 }
寄生式组合继承

组合继承已经相对完善了,但还是存在问题,它的问题就是调用了 2 次父类构造函数,第一次是在 new Animal(),第二次是在 Animal.call() 这里。

所以解决方案就是不直接调用父类构造函数给子类原型赋值,而是通过创建空函数 F 获取父类原型的副本。

function object(o) {
    function F() {}
    F.prototype = o
    return new F()
}
function inheritPrototype(child, parent) {
    let prototype = object(parent.prototype)
    prototype.constructor = child
    child.prototype = prototype
}
inheritPrototype(Dog, Animal)
class 实现继承
class Animal {
    constructor(name) {
        this.name = name
    } 
    getName() {
        return this.name
    }
}
class Dog extends Animal {
    constructor(name, age) {
        super(name)
        this.age = age
    }
}

递归实现深拷贝

完整版:考虑各种数据类型

// 定义一个深拷贝函数  接收目标target参数
function deepClone(target) {
  // 定义一个变量
  let result;
  // 如果当前需要深拷贝的是一个对象的话
  if (typeof target === "object") {
    // 如果是一个数组的话
    if (Array.isArray(target)) {
      result = []; // 将result赋值为一个数组,并且执行遍历
      for (let i in target) {
        // 递归克隆数组中的每一项
        result.push(deepClone(target[i]));
      }
      // 判断如果当前的值是null的话;直接赋值为null
    } else if (target === null) {
      result = null;
      // 判断如果当前的值是一个RegExp对象的话,直接赋值
    } else if (target.constructor === RegExp) {
      result = target;
    } else if (Object.prototype.toString.call(target) === "[object Date]") {
      result = new Date(target);
    } else if (target instanceof Function) {
      result = function () {
        return target.apply(this, arguments);
      };
    } else {
      // 否则是普通对象,直接for in循环,递归赋值对象的所有值
      result = {};
      for (let i in target) {
        result[i] = deepClone(target[i]);
      }
    }
    // 如果不是对象的话,就是基本数据类型,那么直接赋值
  } else {
    result = target;
  }
  // 返回最终结果
  return result;
}

简单版:拷贝数组和对象

function deepClone(obj) {
  let res = obj instanceof Array ? [] : {};
  for (let i in obj) {
    if (obj.hasOwnProperty(i)) {
      res[i] = typeof obj[i] === "object" ? deepClone(obj[i]) : obj[i];
    }
  }
  return res;
}