【前端面试之手写代码】手写new,bind,深拷贝

手写new操作符

在调用new的过程中发生了四件事:
1、首先创建一个新的空对象
2、设置原型,将对象的原型设置为函数的protortype对象
3、将函数的this指向这个对象,执行构造函数,并将参数进行赋值。
4、判断函数的返回值类型,如果是值类型,返回创建的对象。如果是引用类型,就返回这个引用类型的对象。

function One(name,age){
	this.name=name;
	this.age=age;
}
function myNew(fn,...args){
	let obj={};
	obj.__proto__=fn.prototype;
	let res=fn.apply(obj,args);
	if(res instanceof Object){
		return res;
	}else{
		return obj;
	}
}
let person=myNew(One,"张三",18);
注意:这里需要注意的是,apply方法是立即执行的,执行之后,fn内部的this指向已经指向了obj。
最后加的一个判断是用来判断,放fn内部是否存在返回值。
如果没有返回值,res就是undefined,返回的就是obj。
如果说有返回值,且返回返回的是引用类型的,那么就是return res这个。

手写bind函数

bind函数的实现步骤:

  • 判断调用对象是否为函数,即使我们是定义在函数的原型是哪个的,但是可能出现使用call等方式调用的情况。
  • 保存当前函数的引用,获取其余传入参数值。
  • 创建一个函数返回
  • 函数内部使用apply来绑定函数调用,需要判断函数作为构造函数的情况,这个时候需要传入当前函数的this给apply调用,其余情况都传入指定的上下文对象。这一步需要格外的注意,对应的代码是方法最后的的那个嵌套的return语句
function fn(...args) {
    console.log(this, args);
}
let obj = function fgb(name) {
    this.name = name
    this.age = age
}
// bind 函数实现
Function.prototype.myBind = function(context) {
  // 判断调用对象是否为函数
  if (typeof this !== "function") {
    throw new TypeError("Error");
  }
  // 获取参数
  var args = [...arguments].slice(1),
      fn = this;
  return function Fn() {
    // 根据调用方式,传入不同绑定值
    return fn.apply(
      this instanceof Fn ? this : context,
      args.concat(...arguments)
    );
  };
};

//调用方式:
//方式一:不传值
let newObj=fn.myBind(obj,1,2);
console.log(newObj);
newObj();
// 方式二:只在bind中传递函数参数
fn.myBind (obj,1,2)()
// 方式三:在bind中传递函数参数,也在返回函数中传递参数
fn.myBind (obj,1)(2)
//方式四:作为构造函数,new一个对象
let newObj=fn.myBind(obj);
let new=new newObj();

问题1:第一个return的作用?
返回的是一个函数。
函数体是:(这里拿第一个例子举例,只需要打印输出newObj就可以了。
在这里插入图片描述

因为Bind跟apply以及call不一样,不能立即执行,因此他返回的是一个函数。

问题2:第二个return的作用?
当调用newObj()的时候,return里面的返回的方法就会自动执行。
这里又要问了,为啥不写成函数形式呢,例如下面这种样子?如果后面不需要当做构造函数,然后new出来一个对象的话,其实这样是可以的。

function Fn() {
    // 根据调用方式,传入不同绑定值
   fn.apply(
      this instanceof Fn ? this : context,
      args.concat(...arguments)
);

相当于是:

let newObj=function Fn() {
    // 根据调用方式,传入不同绑定值
   fn.apply(
      this instanceof Fn ? this : context,
      args.concat(...arguments)
);
newObj()

其实一样也可以调用的。
但是我们需要考虑当它作为构造函数的时候,必须要进行返回一个新的对象。
因此当接下来来看一下当作为构造函数发生了什么吧,这里进入到第三个问题,第三个问题也是为了兼容构造函数的。

问题3:第二个return中的fn.apply中的this是谁的this,为什么要进行这一步判断?

这里呢,我们要知道在调用new的过程到底发生了什么?可以跳到这篇博客的第一个手写代码环节:手写new的部分

let newObj=fn.myBind(obj);
let new=new newObj();

newObj依然是如下的代码:

newObj=Fn() {
  // 根据调用方式,传入不同绑定值
  return fn.apply(
    this instanceof Fn ? this : context,
    args.concat(...arguments)
  );
};

new的过程中一共发生了四件事情:
这里前三件可以先不管,先看第四件中:
这里newObj是有返回值的,因为调用apply立即执行了嘛,因此一旦执行完毕之后,他的返回值就是undefined。因此此时new返回的其实就是新建的一个空对象,并且顺便把Fn里面的this指向了新建的空对象,然后又把fn的this指向了Fn中的this,相当于就是把fn里面的this指向了新构建的这个空对象。
这里有点绕哈,一开始我也绕晕了,因为涉及到了new,涉及到了闭包,涉及到了apply涉及到了构造函数中有返回值的情况。反正理清楚之后,也相当于是对手写new的再一遍回顾。
在这里插入图片描述

实现深拷贝

浅拷贝:浅拷贝指的是将一个对象的属性复制给另一个对象,如果有的属性为引用类型的话,那么会将这个引用的话,可以将这个引用的地址复制给对象,因此两个对象会有同一个引用类型的引用。浅拷贝可以采用Object.assign展开运算符来实现的。
深拷贝:如果遇到属性值为引用类型的时候,会新建一个引用类型并将对应的值赋值给他,因此对象获得的是一个新的引用类型而不是一个原有类型的引用。
(1)JSON.stringfy()
JSON.parse(JSON.stringify(obj))是目前比较常用的深拷贝方法之一,他的原理就是利用JSON.stringify将js对象序列化,再使用JSON.parse来反序列化js对象。
这个方法可以简单粗暴的实现深拷贝。但是如果拷贝对象中有函数undefinedsymbol,当使用JSON.stringify()进行处理之后,都会消失。

let obj1 = {  a: 0,
              b: {
                 c: 0
                 }
            };
let obj2 = JSON.parse(JSON.stringify(obj1));
obj1.a = 1;
obj1.b.c = 1;
console.log(obj1); // {a: 1, b: {c: 1}}
console.log(obj2); // {a: 0, b: {c: 0}}

在这里插入图片描述

obj1改为如下的样子,obj1中的函数,obj2就无法得到了。
let obj1 = {  a: 0,
              b: {
                 c: 0
                 },
              cc: function () {
                this.c = 2;
            }
            };

在这里插入图片描述
属性值存在undefined的,结果如下图所示。
在这里插入图片描述
如果属性值有Symbol类型的,结果如下图所示。
在这里插入图片描述
(2)函数库lodash的_.cloneDeep方法
该函数库也有提供_.cloneDeep用来做Deep Copy

var _ = require('lodash');
var obj1 = {
    a: 1,
    b: { f: { g: 1 } },
    c: [1, 2, 3]
};
var obj2 = _.cloneDeep(obj1);
console.log(obj1.b.f === obj2.b.f);// false

(3)手写实现深拷贝函数
用到了递归

  • 判断当前的object是不是对象,如果不是就直接返回,因为原始数据类型可以直接赋值,并不是地址。
  • 然后再判断当前的对象是数组还是对象,初始化newObject (用于存放object里面的数据)
  • 遍历object里面的属性,当不是对象,则直接赋值,如果是对象,则再次调用deepCopy。
  • 这里注意一点的是object[属性],如果当前属性不存在,则直接创建,如果存在就是在原有的基础上进行修改
//采用的递归
function deepCopy(object) {
   if (!object || typeof object !== "object") return;

    let newObject = Array.isArray(object) ? [] : {};

    for (let key in object) {
        if (object.hasOwnProperty(key)) {
            newObject[key] =
                typeof object[key] === "object" ? deepCopy(object[key]) : object[key];
        }
    }

    return newObject;
}
var obj1 = {
    a: 1,
    b: { f: { g: 1 } },
    c: [1, 2, 3]
};  
var obj2 = deepCopy(obj1);  
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值