1、call、apply、bind的区别
bind 和 call/apply 一样,都是用来改变上下文 this 指向的,不同的是,call/apply 是直接使用在函数上,而 bind 绑定 this 后返回一个函数(闭包)。call参数依次传入,apply参数是数组或类数组。
call 的作用就是当执行一个方法的时候希望能够使用另一个对象来作为作用域对象而已,简单来说就是当我执行 A 方法的时候,希望通过传入参数的形式将一个对象 B 传进去,用以将 A 方法的作用域对象替换为对象 B
call,apply和bind来自哪里?
在js中所有的函数都是Function的实例,而且对于Function来说,它的原型即Function.prototype中含有很多东西,其中call,apply和bind方法就是Function原型中的方法,所以根据原型的规则,所有的函数都可以使用原型中属性和方法,所以来说,对于所有的函数都可以使用call,apply和bind方法。
Function.prototype.ca_ll = function() {
// 剩余运算符,得到的是一个数组,包含除了this值的其他参数
let [thisArgs, ...args] = arguments;
if(!thisArgs) {
thisArgs = typeof window === 'undefined' ? global : window;
}
thisArgs.func = this; //将调用call函数的函数对象添加到thisArg的属性中
let result = thisArgs.func(...args);//让新的对象可以执行该函数,并能接受参数
delete thisArgs.func; // thisArgs上并没有func属性,所以执行结果之后需要移除
return result;
}
// 测试用例
var foo = {
name: 'zl'
}
function func(job, age) {
console.log(this.name);
console.log(job, age);
}
func.ca_ll(foo, 'coder', 45); //zl coder 45
给对象B添加一个外部函数func ,调用再删除
注意点:
- call的参数是依次传入,不用扩展运算符会遗漏参数
- 如果第一个参数没有传入,在全局环境下,那么默认
this
指向window(浏览器) / global(node)
(非严格模式); - 给thisArgs创建一个fn属性,并将值设置为需要调用的函数
- call的实现其实和为对象添加了一个外部函数然后执行效果差不多,只不过call最后没有为对象添加额外的属性,可以认为是call执行完之后删除掉了新增的属性
2、apply
Function.prototype.app_ly = function() {
let [thisArgs, args] = arguments;
let result; // 函数返回结果
if(!thisArgs) {
thisArgs = typeof window === 'undefined' ? global : window;
}
thisArgs.func = this;
if(!args) {
// 第二个参数为null或者是undefined
//因为是个空数组:args is not iterable
result = thisArgs.func();
} else {
result = thisArgs.func(...args);
}
delete thisArgs.func;
return result;
}
// 测试用例
let foo = {
name: 'zl'
}
function func(job, age) {
console.log(this.name);
console.log(job, age);
}
func.app_ly(foo, ['coder', 45]); //zl coder 45
var args=[1,2,3]
console.log(...args)//输出:1,2,3
3、bind
bind是返回一个函数,他的实现是靠柯里化。
Function.prototype.my_bind = function() {
console.log(arguments);
var self = this, // 保存需要绑定的this上下文
context = Array.prototype.shift.call(arguments),
// arguments 是类数组对象,它没有 shift 等数组独有的方法,想要弹出传入的参数中的第一个参数,就只有用这种方式了。
//shift() 方法用于把数组的第一个元素从其中删除,并返回第一个元素的值。
args = Array.prototype.slice.call(arguments); // 剩余的参数转为数组
return function() {
self.apply(context,args.concat(Array.prototype.slice.apply(arguments)))
//Array.prototype.slice.apply(arguments)返回的是9这个参数,只有7,8和9连接起来才是完整的参数
}
};
//样例
function a(m, n, o) {
console.log(this.name + ' ' + m + ' ' + n + ' ' + o);
}
var b = {
name: 'kong'
};
a.my_bind(b, 7, 8)(9); // kong 7 8 9
Function.prototype.my_bind = function() {
console.log(arguments);
var self = this; // 保存需要绑定的this上下文
let [context, ...args] = arguments
return function() {
self.apply(context,args.concat(Array.prototype.slice.apply(arguments)))
}
};
//样例
function a(m, n, o) {
console.log(this.name + ' ' + m + ' ' + n + ' ' + o);
}
var b = {
name: 'kong'
};
a.my_bind(b, 7, 8)(9); // kong 7 8 9
只有两者合并了之后才是返回函数的完整参数
4、arguments
arguments是个类数组,除了有实参所组成的类似数组以外,还有自己的属性,如callee,arguments.callee就是当前正在执行的这个函数的引用,它只在函数执行时才存在。因为在函数开始执行时,才会自动创建第一个变量arguments对象。
arguments其实是一个对象,它与数组一样有索引以及length的属性。但是却不能使用数组的方法。但是在实际开发中,我们使用arguments可以很方便的获取到所有的实参,并且也需要对其使用是写数组的方法。
arguments转化成数组的方法:
//遍历
function fun(){
var arr =[];
console.log(arguments);
for(var i = 0 ; i<arguments.length;i++){
arr.push(arguments[i]);
}
return arr;
}
console.log(fun(1,2,3,4,5));
function fun(){
console.log(arguments);
return Array.prototype.slice.call(arguments);
//或者
//return [].slice.call(arguments);
}
console.log(fun(1,2,3,4,5));
call的作用:就是让对象arguments有了方法slice,并调用他。
当arr.slice() 里面没有实参的时候它返回的其实是数组本身。
call()在这里就是让arguments对象(本来是没有slice()这一方法的)但是借用了array对象的这个方法 的一种手段。
多次用到 Array.prototype.slice.call(arguments, 1),不就是等于 arguments.slice(1) 吗?
因为arguments并不是真正的数组对象,只是与数组类似而已,所以它并没有slice这个方法,而Array.prototype.slice.call(arguments, 1)可以理解成是让arguments转换成一个数组对象,让arguments具有slice()方法。要是直接写arguments.slice(1)会报错。
arguments是一个具有length属性的对象, 通过call 这个方法,把arguments 指向了Array.prototype.slice方法的作用域,也就是说通过call方法,让Array.prototype.slice对arguments对象进行操作
//es6语法中新增了Array.from(),可以Array.from(obj)就直接转化成数组!
function fun(){
console.log(arguments);
return Array.from(arguments);
}
console.log(fun(1,2,3,4,5));
//将arguments强制转化为数组的其他方式 :利用es6的展开表达式
function fn(){
var a = [...arguments]; //1,2,6,3,4,12
var b = [...arguments].slice(1); //2,6,3,4,12
console.log(a);
console.log(b);
}
fn(1,2,6,3,4,12);
Array.prototype.slice.apply(arguments)_易安sparkle的博客-CSDN博客_array.prototype.slice.apply(arguments)