你真的理解call和apply吗

重新认识了一下函数

函数的三种角色

一些函数作为对象的属性

  • length:形参的个数;
  • name:函数名;
  • prototype:类的原型,在原型上定义的方法都是当前这个类的实例的公有方法;
  • __proto__:把函数当做一个普通对象,指向Function这个类的原型

函数的角色

  • 普通函数。它本身是一个普通的函数,执行的时候会形成私有的作用域,然后进行形参赋值、预解析、代码执行、执行完成后内存销毁;
  • 。有自己的实例,也有一个叫做prototype的属性是自己的原型,它的实例都可以指向自己的原型;
  • 普通对象。它作为对象可以有一些自己的私有属性,也可以通过__proto__找到Function.prototype

call方法原理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function sum(){
console.log(this);
}
function fn(){
console.log(this);
}
Function.prototype.myCall = function (context) {
// 允许传null
let context = context || window;
context.fn = this;
// 重新执行这个函数
let args = [];
for (let i = 0; i < arguments.length; i++) {
args.push("arguments[" + i + "]");
}
// 函数是可以有返回值的
let result = new Function("", `${ "context.fn(" + args + ")" }`)();

delete context.fn;
return result;
};
fn.myCall(obj); // fn作为普通对象, 所以myCall中的this为fn函数本身
sum.myCall(obj);

一个经典栗子(需要深入理解call原理和原型链)

1
2
3
4
5
6
7
8
9
function fn1() {
console.log(1);
}
function fn2() {
console.log(2);
}

fn1.call(fn2); // 1
fn1.call.call(fn2); // 2

第二个输出有了前面的基础就很好理解了。前面了解到函数可以作为普通对象,然后有原型链搜索这一过程。

1
2
3
fn2 {
fn: fn1.call
}

第二次的callfn1.call这个函数的this绑定到fn2上(将fn2作为普通对象),再将fn1.call重新执行,此时,fn1.call函数里面的this就指向了fn2这个对象了,所以不是重新执行fn1(),而是重新执行fn2()

思考

1
Array.prototype.slice.call(arguments) || [].slice.call(arguments)

这个方法用于将类数组对象转化为数组。

call、apply、bind的区别

callapply基本一样,就是传递参数形式不一样

1
2
3
4
5
6
function fn(num1, num2) {
console.log(num1 + num2);
console.log(this);
}
fn.call(obj , 100 , 200);
fn.apply(obj , [100, 200]);

bind有点不一样,它会返回函数,而不是立即执行

1
2
var tempFn = fn.bind(obj, 1, 2);
tempFn();

补充一下ES5的严格模式

1
2
3
4
5
6
7
8
"use strict" -- 严格模式

function fn() {
console.log(this);
}
fn.call(); // 普通模式下this是window,在严格模式下this是undefined
fn.call(null); // 普通模式下this是window,在严格模式下this是null
fn.call(undefined); // 普通模式下this是window,在严格模式下this是undefined

applycall的实现基本一样,就是处理arguments不一样。

1
2
3
4
5
if(arguments[1]) {
result = context.fn(...arguments[1])
} else{
result = context.fn()
}

自己实现一个bind

1
2
3
4
5
6
7
8
9
10
function myBind() {

let self=this;//保存原函数
// 有了前面的基础,就可以理解为什么要[].shift.call(arguments)了
let context=[].shift.call(arguments);//保存需要绑定的this上下文
let args=[].slice.call(arguments);//将剩余参数转化为数组
return function(){
self.apply(context,[].concat.call(args, [].slice.call(arguments)));
}
}

Function.apply.bind

看一个js的高级技巧

1
2
3
Promise.resolve([10,20]).then(Function.apply.bind(function(x, y){
console.log(x, y);
}, null)); // 10,20

先不管Promise

1
2
3
4
5
var sum = function(x, y) {
console.log(x, y);
}
var foo = Function.apply.bind(sum, null);
foo([10, 20]); // 10, 20

sum.apply(null, [10, 20])很好理解,bind(sum)使Function.apply这个函数里面的this指向了sum这个函数对象,所以Function.apply.bind(sum, null)等价于sum.apply.bind(sum, null)bind的使命已经完成了。所以就等价于sum.apply

Function.bind.apply

1
Function.bind.apply(sum, null)

原理一样的。等价于sum.bind(sum, null),就等价于sum

看起来很简单,其实还是有很多东西的,哈哈,是不是感觉收获颇丰啊,不用谢了啦。

-------------要说再见啦感谢大佬的光临~-------------