原版
先来看一个call实例,看看call到底做了什么:
1 | let foo = { |
从代码的执行结果,我们可以看到,call首先改变了this的指向,使函数的this指向了foo,然后使bar函数执行了。
总结一下:
- call改变函数this指向
- 调用函数
自己动手
- 首先我们对参数
context
做了兼容处理,不传值,context
默认值为window
; - 然后我们将函数挂载到
context
上面,context.fn = this
; - 处理参数,将传入
myCall
的参数截取,去除第一位,然后转为数组; - 调用
context.fn
,此时fn
的this
指向context
; - 删除对象上的属性
delete context.fn
; - 将结果返回。
1 | Function.prototype.myCall = function(context) { |
apply
跟 call
的区别在于参数, 其他没有差别,实现如下
1 | // myApply的参数形式为(obj,[arg1,arg2,arg3]); |
bind
和 call
、 apply
作用都是改变 this
的指向,区别在于 bind
改变后不会立即执行,而 call
和 apply
会立即执行,我们看一下 bind
的用法
1 | function Person(){ |
仔细观察上面的代码,再看输出结果。
我们对 Person
类使用了 bind
将其 this
指向 obj
,得到了 changePerson
函数,此处如果我们直接调用 changeperson
会改变 obj
,若用 new
调用 changeperson
会得到实例 p
,并且其 __proto__
指向 Person
,我们发现 bind
失效了。
我们得到结论:用 bind
改变了 this
指向的函数,如果用 new
操作符来调用, bind
将会失效。
这个对象就是这个构造函数的实例,那么只要在函数内部执行 * this instanceof 构造函数 * 来判断其结果是否为 true
,就能判断函数是否是通过 new
操作符来调用了,若结果为 true
则是用 new
操作符调用的,总结如下:
- 保存当前
this
指向 - 保存环境上下文
- 保存参数,去掉第一个对象参数
- 返回待执行函数
- 数组化剩余参数
- 判断是否为构造函数
- 若是执行构造函数,若不是改变
this
指向执行1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17// bind实现
Function.prototype.myBind = function(context){
let _this = this; // 1、保存函数
context = context || window; // 2、保存目标对象
let rest = [...arguments].slice(1); // 3、保存目标对象之外的参数,将其转化为数组;
// 此处开始与 call 和 apply 不同,不是返回结果,而是返回一个函数
return function F(){ // 4、返回一个待执行的函数
let rest2 = Array.prototype.slice.call(arguments) // 5、这里的arguments是F函数的参数,转换为数组;
if(this instanceof F){
return new _this(...rest2) // 6、若是用new操作符调用,则直接用new 调用原函数,并用扩展运算符传递参数
}else{
_this.apply(context,rest.concat(rest2)); // 7、用apply调用第一步保存的函数,并绑定this,传递合并的参数数组,
// 即context._this(rest.concat(rest2))
}
}
};