apply与call的作用
apply与call的作用是在特定的作用域中调用函数
举个例子,这是函数没有参数的情况
num = 1;// 默认声明到全局作用域
function logNum() {
console.log(this.num);
}
logNum(); // 1
logNum.apply({ num: 99 });// 99
logNum.call({ num: 88 });// 88
如上代码可知:call和apply接收的第一个参数都是一个作用域,在作用域里面num都被赋了不一样的值,所以最后this.num输出不一样的值。
当函数有参数时,是这样的:
sum = 1;
function getSum(num1, num2) {
this.sum = this.sum + num1 + num2;
console.log(this.sum);
}
getSum(2, 3); // 6
getSum.apply({ sum: 100 }, [99, 33]); // 232
getSum.apply({ sum: 100 }, [99, 33, 999]); // 232
// getSum.apply({sum:100},99,33); // TypeError: CreateListFromArrayLike called on non-object
getSum.call({ sum: 100 }, 99, 133); // 232
getSum.call({ sum: 100 }, 99, 133, 99); // 232
getSum.call({ sum: 100 }, [99, 33]); // 10099,33undefined 自动转为string再拼接了
由以上例子可以看出,apply与call的不同点只是接收的参数不一样
- apply接收的参数是一个数组,这个数组的全部会按照顺序传递给执行的函数。
- call接收的参数是和执行函数一样的形式,一个个赋值给执行函数,就是说除了第一个作用域参数,其他参数都会传递给执行函数
注意
有一个比较特殊的是apply函数还可以接收arguments对象
sum = 1;
function getSum(num1, num2) {
this.sum = this.sum + num1 + num2;
console.log(this.sum);
}
function myGetSum() {
console.log(arguments); // [Arguments] { '0': 99, '1': 88 }
getSum.apply({ sum: 100 }, arguments);
}
getSum(1, 2);
myGetSum(99, 88);
所以apply的第二个参数只能是数组或者arguments对象,假设是普通的对象,无法被解析,最终只会传入undefined到执行函数中。
bind的作用
不接收参数时
num = 1;
function logNum() {
console.log(this.num);
}
const myLogNum = logNum.bind({ num: 100 });
logNum(); // 1
myLogNum(); // 100
接收参数时
sum = 1;
function getSum(num1, num2) {
this.sum = this.sum + num1 + num2;
console.log(this.sum);
}
const myGetSum = getSum.bind({ sum: 100 }, 100, 200);
getSum(100,200); // 301
myGetSum(); // 400
bind的作用是将一个作用域绑定到一个函数中,再返回这个已经绑定作用域的函数。 执行该函数时与apply和call是一样的效果。对!需要执行,不会直接调用。
apply,call与bind的相同点
- 都是函数非继承而来的方法
- 函数有两个属性:
- length
- prototype(来继承属性与方法)
- 函数有三个非继承而来的方法:
- call
- apply
- bind
- 函数有两个属性:
- 都可以改变函数对运行作用域,也可以说改变this对指向
apply,call与bind的不同点
-
返回值不一样
bind返回值类型固定:是一个绑定作用域后的函数实例
apply和call的返回值类型不固定:是执行函数的返回值(啥都行,如果没有返回值则返回:undeifined)
-
执行情况不一样
bind返回一个函数之后需要调用才执行
apply和call直接执行函数
apply与call的不同点
接收的参数不一样:
- apply:接收一个包含多个参数的数组(oThis,[args1,...])
- call:接收多个参数的列表(oThis,args1,...)
手写bind(简单版,了解原理)
bind其实是通过call或者apply实现的,按照接收参数的形式call更适合一些,当然apply也可以使用argument对象或者数组来实现,但使用call可以节省参数解析的时间。
实现一个简单的bind函数
Function.prototype.myBind = function (runArea,...args) {
const oThis = this; // 函数实例
return function () {
return oThis.call(runArea,...args); // 加上return 是因为函数可能会有返回值
};
};
num = 1;
function logNum() {
console.log(this.num);
}
logNum(); // 1
const logByBind = logNum.bind({ num: 100 });
logByBind(); // 100
const logByMyBind = logNum.myBind({ num: 100 });
logByMyBind(); // 100
步骤其实挺简单的:
- 声明一个bind函数(myBind)
- 在bind函数里面返回一个函数
- 在返回的函数中,执行call方法,加上return可以返回方法的返回值
手写call(简单版,了解原理)
Function.prototype.myCall = function (runArea, ...args) {
const oThis = runArea || window;
const fn = Symbol('fn'); // 防止替换掉函数实例本身的fn方法
oThis[fn] = this; // 绑定函数实例的作用域
oThis[fn](...args); //执行,看有人用eval来执行,不太推荐
delete oThis[fn]; //执行后删除这个方法
};
手写apply(简单版,了解原理)
Function.prototype.myApply = function (runArea, args) {
const oThis = runArea || window;
const fn = Symbol("fn"); // 防止替换掉函数实例本身的fn方法
oThis[fn] = this; // 绑定函数实例的作用域
oThis[fn](...args); //执行,看有人用eval来执行,不太推荐
delete oThis[fn]; //执行后删除这个方法
};
简单的实现了下,发现apply和call是可以一样的,因为参数这里用了解构处理,不管是数组还是对象都可以处理成列表形式的参数。
总结一下
一遍写下来发现这三个改变作用域的函数真没那么复杂,关键是多练习多比较,今天就先到这里,未能再深究,要开始写需求了,今天还用到了Symbol,一直以为它没啥用处呢。
有错误之处希望大家帮忙指出!