手写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对象。
这个方法可以简单粗暴的实现深拷贝。但是如果拷贝对象中有函数
,undefined
,symbol
,当使用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);