手撕JavaScript call apply bind 函数
Posted eyes++
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了手撕JavaScript call apply bind 函数相关的知识,希望对你有一定的参考价值。
call ,apply ,bind ,是 js中非常重要的三个函数原型上的方法,其作用都是改变this的指向:
call函数
call函数语法:
Fun.call(Obj[, para...]);
即第一个参数是this的指向,之后的参数都会作为实参传给新函数。
示例代码如下:
function Fn(para1, para2) {
console.log(this.clothes);
this.clothes = "nihao";
console.log(this.clothes);
console.log("para1:", para1, " para2:", para2);
}
const o = {
clothes: "shirt"
};
Fn();
Fn.call(o, 5, "hello");
console.log(o.clothes);
对于Fn.call(o),其处理实际上类似于下面这种方式,其底层的调用还是o.Fn()
const o = {
clothes: "shirt",
Fn() {
...
...
}
}
然后我们就可以使用代码写出一个效果和call一样的方法:
function Fn(a, b, c) {
console.log(this.clothes, a, b, c);
}
const o = {
clothes: "shirt"
};
Function.prototype.myCall = function (o, ...args) {
// 防止调用myCall不传第一个参数而报错
o = o || window;
// 把Fn函数设置为o的方法
o.Fn = this;
// 调用函数
let res = o.Fn(...args);
// 不修改函数的原型对象
delete o.Fn;
// 原函数有返回值则call函数返回res,没有则仍返回undefind
return res;
}
Fn.myCall(o, 1, 2, 3);
apply方法
apply接受两个参数,第一个参数是this的指向,第二个参数是函数接受的参数,以数组的形式传入,且当第一个参数为null、undefined的时候,默认指向window(在浏览器中),使用apply方法改变this指向后原函数会立即执行,且此方法只是临时改变thi指向一次。
apply方法的使用如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
function Fn(a, b, c) {
console.log(this.clothes, a, b, c);
}
const o = {
clothes: "shirt"
};
Fn.apply(o, [12, 23, 34]);
</script>
</body>
</html>
一个类似apply方法的实现如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
function Fn(a, b, c) {
console.log(this.clothes, a, b, c);
}
const o = {
clothes: "shirt"
};
Function.prototype.myApply = function (o, args) {
o = o || window; // 防止调用myApply不传第一个参数而报错
o.Fn = this; // 将原函数设置为o的方法
const newArgs = [];
let res = null;
if (!args) {
res = o.Fn();
} else {
for (let i = 0; i < args.length; i++) {
newArgs.push("args[" + i + "]");
}
// 调用函数,如果有返回值,则记录在res中
res = eval("o.Fn(" + newArgs + ")");
}
// 不修改函数的原型对象
delete o.Fn;
// 原函数有返回值则call函数返回res,没有则仍返回undefind
return res;
}
Fn.myApply(o, [1, 3, 5]);
</script>
</body>
</html>
bind方法
bind方法和call很相似,第一参数也是this的指向,后面传入的也是一个参数列表(但是这个参数列表可以分多次传入,call则必须一次性传入所有参数),但是它改变this指向后不会立即执行,而是返回一个永久改变this指向的函数。
bind的使用示例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
function Fn(a, b, c) {
console.log(this.clothes, a, b, c);
}
const o = {
clothes: "shirt"
};
// 分次传参
let temp = Fn.bind(o, 12); // 不执行
temp(23, 34); // 执行
// 立即执行
Fn.bind(o, 45)(56, 67);
</script>
</body>
</html>
bind类似功能的实现:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
function Fn(a, b, c) {
console.log(this.clothes, a, b, c);
}
const o = {
clothes: "shirt"
};
Function.prototype.mybind = function (o) {
if (typeof this !== 'function') {
throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
}
let aArgs = Array.prototype.slice.call(arguments, 1),
fToBind = this,
fNOP = function () {},
fBound = function () {
// this instanceof fBound === true时,说明返回的fBound被当做new的构造函数调用
return fToBind.apply(this instanceof fBound ?
this :
o,
// 获取调用时(fBound)的传参.bind 返回的函数入参往往是这么传递的
aArgs.concat(Array.prototype.slice.call(arguments)));
};
// 维护原型关系
if (this.prototype) {
// 当执行Function.prototype.bind()时, this为Function.prototype
// this.prototype(即Function.prototype.prototype)为undefined
fNOP.prototype = this.prototype;
}
// 下行的代码使fBound.prototype是fNOP的实例,因此
// 返回的fBound若作为new的构造函数,new生成的新对象作为this传入fBound,新对象的__proto__就是fNOP的实例
fBound.prototype = new fNOP();
return fBound;
};
let temp = Fn.mybind(o, 12, 23);
temp(34);
</script>
</body>
</html>
call、apply、bind三者的区别
- 三者都可以改变函数的this对象指向。
- 三者第一个参数都是this要指向的对象,如果如果没有这个参数或参数为undefined或null,则默认指向全局window。
- 三者都可以传参,但是apply是数组,而call是参数列表
- apply和call是一次性传入参数,而bind可以分为多次传入。
- bind 是返回绑定this之后的函数,便于稍后调用;apply 、call 则是立即执行 。
为什么要修改this指向
在如下代码可以观察到,正常情况下 say 方法中的 this 是指向调用它的 obj 对象的,而定时器 setTimeout 中的 say 方法中的 this 是指向window对象的(在浏览器中),这是因为 say 方法在定时器中是作为回调函数来执行的,因此回到主栈执行时是在全局执行上下文的环境中执行的,但我们需要的是 say 方法中 this 指向obj对象,因此我们需要修改 this 的指向。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
var name = "张三";
let obj = {
name: "李四",
say: function () {
console.log(this.name);
}
};
obj.say(); // 李四,this指向obj对象
setTimeout(obj.say); // 张三,this指向window对象
</script>
</body>
</html>
以上是关于手撕JavaScript call apply bind 函数的主要内容,如果未能解决你的问题,请参考以下文章