小白也能搞懂的五个常见手写函数
Hello,这里是mouche,当然你也可以叫我某车,反正大家都爱这么叫😁
以下函数的实现都有详细讲解,带你一步一步搞懂它
一、new的实现
👉过程
- 创建一个新的对象
- 将对象与构造函数的原型通过原型链连接起来
- 将构建函数中的
this
指向新建的对象obj
- 这里可以通过
apply
,call
改变this
的指向,如果使用bind
还需要对他进行调用
- 这里可以通过
- 根据构造函数的返回类型作判断,如果是原始值则忽略,如果是返回对象,则需要处理
- 原因:传入的这个函数是可以返回值的

👉代码实现
function myNew(func, ...args) {
let res = {}; //创建一个新对象
if(func.prototype !== null) {
res.__proto__ = func.prototype; //如图一
}
let result = func.apply(res, args); //绑定this
return result instanceof Object? result: res; //如图二
}
👉思考
- 如果使用
call
:let result = func.call(res, ...args)
- 如果使用
bind
:let result = func.bind(res, ...args)()
- 如果最后一步使用
typeof
:return (typeof result === "object" && result !== null) || (typeof result === "function") ? result : res
我们上面使用到了call,bind,apply,接下去我们就挨个实现
二、实现call
👉了解
- 语法:
func.call(thisArg, param1, param2, ...)
- 返回值:
func
的执行结果,若没有返回值则返回undefined
- 指定为
null
或undefined
时会自动替换为指向全局对象,原始值会被包装
👉过程
- 对传入的
thisArg
进行判断封装- 如果为
null
或undefined
则绑定到window
- 否则可以使用
Object()
- 传入为原始类型会将其转为对应的包装对象的实例
- 传入对象则返回对象
- 如果为
- 通过
this
隐式绑定的思想,将函数本身绑定到对象,然后调用,此时this就指向对象 - 返回函数调用的返回值
👉代码实现
Function.prototype.myCall = function(thisArg, ...args) {
//如果传入的为null或者undefined则指向全局对象
if(thisArg === null || thisArg === undefined) {
thisArg = window
} else {
//用Object对其进行包装
thisArg = Object(thisArg)
}
//通过Symbol()生成一个唯一的属性
const symBolPrototype = Symbol();
//将函数绑定到对象该唯一的属性上
thisArg[symBolPrototype] = this;
//在对象上调用函数,则this指向对象,并将返回值赋值给result
let result = thisArg[symBolPrototype](...args);
//删除我们刚刚绑定的那个属性,防止污染对象
delete thisArg[symBolPrototype];
//返回我们调用函数的返回值
return result
}
👉测试
- 这里传入
thisArg
分别为对象和空 - 对象
obj
里面的a
为20,window
上的a通过var a = 10
设置为10
const obj = {
a: 20
}
var a = 10;
function test(num1, num2) {
console.log(this.a + num1 + num2);
}
test.myCall(obj,3,5) //28
test.myCall(null,3,5) //18
👉思考一
- 看到有一些实现博客直接使用了
thisArg = thisArg ? Object(thisArg) : window;
- 这样是不可以的,像如果thisArg为
0
,为false
的情况下会把它指向window
,但是实际上call是应该把它指向这些原始值的实例对象上
👉思考二
- 也看到像这样使用的
thisArg = Object(thisArg) || window
- 也是不太行的,这个只需要用上面测试的代码测试一下就知道了,
Object()
遇到null
和undefined
会返回一个空对象{}
而不是空引用,所以此时并不会指向window
三、实现apply
👉了解
- 实现了call再来实现apply就很简单了
- 只需要记住他们的差异,call传入的为参数列表,apply传入的为参数数组或者类数组对象(快速记忆:apply以数组Array的a开头)
👉代码实现
//修改了传参
Function.prototype.myApply = function(thisArg, args) {
if(thisArg === null || thisArg === undefined) {
thisArg = window
} else {
thisArg = Object(thisArg)
}
const symBolPrototype = Symbol();
thisArg[symBolPrototype] = this;
let result = thisArg[symBolPrototype](...args);
delete thisArg[symBolPrototype];
return result
}
四、实现bind
👉了解
- 我抽取了一些实现的点出来:具体可以看:Function.prototype.bind() MDN
- 对于thisArg:如果使用
new
运算符构造绑定函数,则忽略该值
涉及知识点:new绑定this的优先级大于显示绑定this
- 返回值:返回一个原函数的拷贝,并拥有指定的
this
值和初始参数 - 内部使用的是
call
方法 - 可以二次传参
👉思路
- 判断是否使用new构造绑定函数:可以使用new.target, 如果是普通函数则返回
undefined
👉代码实现
Function.prototype.myBind = function (thisArg, ...params) {
const thisFn = this; //先存储该函数的this
//这里是为了可以二次传参
let cb = function (...secondParams) {
const isNew = typeof new.target !== 'undefined' //普通函数就返回undefined
// new构造函数就绑定到this上,否则就绑定到传入的thisArg上
const context = isNew ? this : thisArg
return thisFn.call(context, ...params, ...secondParams); // 内部使用call
};
//一些情况下函数没有prototype,比如箭头函数
if (thisFn.prototype) {
// 把源函数的prototype给cb
cb.prototype = Object.create(thisFn.prototype);
}
return cb; // 返回拷贝的函数
}
👉测试
const obj = {
a: 20,
name:'某车'
}
var a = 10;
function test(num1, num2) {
this.name = 'mouche';
console.log(this.a + num1 + num2);
}
const testBind = test.myBind(obj,3)
testBind(5) //20+3+5 = 28
const testBind1 = test.myBind(null,3);
testBind1(5) //10+3+5 = 18
const testObj = new testBind();
console.log(testObj.name); //是mouche而不是某车
五、instanceof
👉了解
- 语法:
object instanceof constructor
- 谈到类型判断就会提到instanceof
- 提到instanceof就会提到它是:顺着原型链寻找,直到找到相同的原型对象
- 那我们只需要实现这个即可
👉思路
- 主要就是检测
constructor.prototype
是否存在于参数object
的原型链上,那么只需要每次都进行判断,如果存在的话就返回true
,如果不存在的话则继续找它的__proto__
,直到为null
的时候都找不到的话则返回false
👉代码实现
function myInstanceof(leftValue, rightValue) {
//如果左边的为空或者不为对象则返回false
if(typeof leftValue !== 'object' || leftValue === null) return false;
//constructor.prototype
let rightProto = rightValue.prototype;
//object的原型
let leftProto = leftValue.__proto__;
while (true) {
//如果搜到最上面null了,则说明constructor.prototype没有出现在object的原型链,那么返回false
if (leftProto === null) {
return false;
}
//如果找到了就返回true
if (leftProto === rightProto) {
return true;
}
leftProto = leftProto.__proto__ //顺着原型链判断
}
}
👉测试
- 分别用myInstanceof函数去测试他们是否有相同的原型
function Car(make, model, year) {
this.make = make;
this.model = model;
this.year = year;
}
const auto = new Car('Honda', 'Accord', 1998);
console.log(myInstanceof(auto, Car)); //true
console.log(myInstanceof(auto, Object)); //true
console.log(myInstanceof(auto, Function)); //false
- 原型链图
转载自:https://juejin.cn/post/7151609034699718692