前言
大家一定要摆脱舒适圈,只有用时间才能换来你想要的,还是那句话,光羡慕有什么用,咱们得行动起来。
call和apply
如果我们想要改变函数内部的this指向,有三种方法,其中call和apply类似
const res = myFn.call(context, ...args);
const res = myFn.apply(context, args);
两者都是传入函数上下文context和其他参数后直接调用,获取返回值,只是其他参数的传递形式不同罢了。
因此实现这两种方法比较简单:
- 声明一个myCall/myApply函数
- 在内部通过
arguments获取到传入的上下文context,和其他剩余的参数args - 将调用该方法的函数
fn(fn = this)挂载到context上 - 通过
context.fn的方法来调用即可 - 将挂载的临时属性删除,并返回结果
myCall(context, ...args) {
// 判断调用者是否为函数,this就是调用的函数
if (typeof this !== "function") {
throw new Error("need function");
}
// 借鉴别人的思路,生成唯一值用于挂载,后续删除
const fnKey = Symbol();
// **异常判断**如果传入的context为null undefined等
if (!context) {
context = window;
}
// 挂载,调用
context[fnKey] = this;
const res = context[fnKey](...args);
delete context[fnKey];
return res;
}
bind
bind在此比较特殊,因为它不仅改变了上下文this,还返回了一个全新的函数(并不是直接调用),我们有以下几个需要注意的点:
bind时传递的额外参数会排列在后续调用时传递的实际参数之前- 返回的函数可能被
new操作符调用
new

没有。new一个吧
new的过程到底发生了什么呢?
-
新建一个对象obj
-
将新对象obj的原型指向构造函数的原型对象,即
obj.__proto__ = Cons.prototype或者Object.setPrototypeOf(obj, Cons.prototype) -
将构造函数的
this指向obj,并调用,可以这样实现Cons.call(obj, ...args) -
根据构造函数的返回值来判断
new该返回什么- 如果构造函数返回基础类型(没有return语句相当于返回undefined),则
new操作会返回新建的obj对象 - 如果构造函数返回的时引用类型,则会忽略new操作符,直接返回构造函数的return值
- 如果构造函数返回基础类型(没有return语句相当于返回undefined),则
手写new
function New(fn, ...args) {
const obj = {};
Object.setPrototypeOf(obj, fn);
// 等价于obj.__proto__ = fn.prototype或者
// const obj = Object.create(fn.prototype)
const res = fn.call(obj, ...args);
// 返回值需要做特殊判断处理
return res instanceof Object ? res : obj;
}
实现bind
myBind(context, ...preArgs) {
const fn = this;
if (typeof fn !== "function") {
throw new Error("need function");
}
function newFn(...args) {
// 判断外部是否是通过new操作符调用
// 是的话,先前传入的context直接作废,this指向新创建的obj
// new的过程会对构造函数进行call(obj)的操作,所以当前上下文this就是obj
const isByNew = this instanceof fn ? this : context;
// !!重点!!
const newContext = isByNew ? this : context;
return fn.call(newContext, ...preArgs, ...args);
}
// 拷贝函数就得完整,同时也要判断箭头函数没有prototype的情况
// 如果此处不对原型进行拷贝,那上面就无法准确判断出是否通过new调用
if (fn.prototype) {
// 不可以直接引用赋值
newFn.prototype = Object.create(fn.prototype);
}
return newFn;
}
// 补充Object.create原理
// Object.create = function (prototype) {
// const F = function () {};
// F.prototype = prototype;
// return new F();
// }
这里的逻辑稍微有点绕,但只要明白new的过程会发生Cons.call(obj)就比较好理解判断isByNew的过程了。
扩展instanceof操作符
不要睡,咱们还能扩展,最后谈谈instanceof吧
MDN解释:instanceof操作符用于判断该对象的原型链上(__proto__属性) 是否存在对构造函数的原型对象(prototype)的引用
所以执行完setPrototype(obj, Cons.prototype)后得到obj instanceof Cons === true
灵魂拷问
如果对箭头函数进行一个bind操作会怎么样?
我们不妨试试
const arrow = () => {
console.log(this.a)
};
arrow(); // undefined
a = 1;
arrow(); // 1
const bindArrow = arrow.bind({a: 2});
bindArrow(); // 1
可以看到,我们对箭头函数进行绑定时,传入的context,其实直接被忽略了。