call和apply都是修改this指向的方法。
我们先来一个简单使用
var foo = {
value: 1
};
function bar(name, age) {
console.log(name,age,this.value)
}
bar.apply(foo,['kevin', 18]); //kevin 18 1
var foo = {
value: 1
};
function bar(name, age) {
console.log(name,age,this.value)
}
bar.call(foo,'kevin', 18); //kevin 18 1
用法上只有传参的区别。call需要吧参数一个一个传入,apply参数是以数组的方式传入
下面我们来实现call这个方法
试想一下,怎么可以让bar属于foo。因为只有bar输入foo的时候,this才是指向foo的
说道这朋友们肯定能想到,把方法添加到·foo上然后再执行,执行以后在删除就可以了。
Function.prototype.call2 = function(context){
context.fn = this;
context.fn();
delete context.fn;
}
//我们在测试一下
var foo = {
value: 1
};
function bar() {
console.log(this.value);
}
bar.call2(foo); // 1
这里打印1了,实现了。 (▽)
别太着急开心,还有其他的需求要实现。
call在没有传入目标对象的时候默认是window。还有就是会有携带参数的情况。
那让我们来优化一下
Function.prototype.call2 = function(context){
var context = context || window;
context.fn = this;
let arr = [];
for(let i = 1,len = arguments.length; i < len; i++ ){
arr.push(arguments[i]);
}
context.fn(...arr);
console.log(context.fn)
delete context.fn;
}
//下面是简化版
Function.prototype.call3 = function(context){
var context = context || window;
context.fn = this;
var arr = Array.prototype.slice.call(arguments,1);
context.fn(...arr);
delete context.fn;
}
//下面是测试
var foo = {
value: 1
};
function bar(name, age) {
console.log(name,age,this.value)
}
bar.call2(foo, 'kevin', 18); //kevin 18 1
上面我们先做了一个判断,判断目标参数是不是存在,不存在指向window。
把传过来的参数,取到数组中。这里有一个其他的知识点,就是arguments。(接受传入的所有参数);我们把去除第一个参数的全部参数放到数组中,然后调用时传入。我这边是使用es6的方法。
有别的大佬使用的是eval(‘context.fn(’ + args +’)’);这个写法。执行完成删除方法。
这样我们就实现了一个call的方法。
下面是实现apply的代码,原理都是一样的我就不多说了。
Function.prototype.apply2 = function(context,arr){
var context = context || window;
context.fn = this;
context.fn(...arr);
delete context.fn;
}
var foo = {
value: 1
};
function bar(name, age) {
console.log(name)
console.log(age)
console.log(this.value);
}
bar.apply2(foo,['kevin', 18]);
bind的实现
我们先看一下bind简单的用法吧
let obj = {
value:1
}
function d(name){
console.log(this.value)
console.log(name);
}
var y = d.bind(obj,'lly')
y()
//1
//lly
上面可以看到bind返回一个函数,而且可以接受一个参数
看一下怎么实现这个
Function.prototype.bind2 = function(context){
var self = this;
var arge = Array.prototype.slice.call(arguments,1); //取出接受除去第一个的参数数组
var funone = function(){
return self.apply(context,arge) //通过apply来修改this
}
return funone
}
从上面的内容我们看到bind2返回了一个函数,并且把接受的参数传了过去。
bind有一个其他情况看代码
let obj = {
value:1
}
function d(name,age){
console.log(this.value);
console.log(name)
console.log(age)
}
var y = d.bind2(obj,'lly')
y(20) //1 'lly' 20
上面的代码我们看到,这个bind不仅可以在绑定的时候传参还可以在调用的时候接着传参。
这个也好实现让我们看代码
Function.prototype.bind2 = function(context){
var self = this;
var arge = Array.prototype.slice.call(arguments,1); //取出接受除去第一个的参数数组
var funone = function(){
let arr = Array.prototype.slice.call(arguments);
return self.apply(context,arge.concat(arr)) //通过apply来修改this
}
return funone
}
concat (把俩个数组进行合并在一起)
我们可以看到,只要在创建函数的时候再次接受一次参数。传入的时候合在一起就好
下面我们再看一段代码,bind另一个用法的使用放回函数被当做构造函数的时候
var value = 2;
let obj = {
value:1
}
function d(name,age){
console.log(this.value)
console.log(name)
console.log(age)
}
var y = d.bind(obj,'lly');
let as = new y(20);
//undefind lly 20
这时候我们可以看到,里面的value找不到了,这时候this已经不再属于obj了。这是因为啥那,想一下new的这个过程干了什么呢。(不知道的同学看一去看一下点击这边去看一下)也可以去看看别人的写的更好一点的。这个过程this已经指向了新new的这个对象上。所以我们全局的value也找不到。bind绑定的this也找不到。但传入的参数依然生效下面来实现在做对象的构造函数的时候的情况
Function.prototype.bind2 = function(context){
var self = this;
var arge = Array.prototype.slice.call(arguments,1); //取出接受除去第一个的参数数组
let funa = function(){
};
var funone = function(){
let arr = Array.prototype.slice.call(arguments);
return self.apply(this instanceof self?this:context,arge.concat(arr)) //通过apply来修改this
}
funa.prototype = this.prototype;
funone.prototype = new funa();
return funone
}
参考
https://juejin.cn/post/6844903476477034510#heading-2
https://juejin.cn/post/6844903476623835149