JS之arguments,callee,caller,call,apply,bind

1、arguments

首先f12跑一下函数:

function myfun(){
  console.log(arguments);
}
myfun(1,2,3,4,5);

结果如下截图:
在这里插入图片描述
可以看出argumetns好像是数组,又不是数组,因为[]中又包含了其他的东西;
展开可得:
在这里插入图片描述
可以看出,arguments其实是一个对象,它与数组一样有索引以及length的属性。但是却不能使用数组的方法。但是在实际开发中,我们可以使用arguments获取到所有的实参,同时也需要对其使用写数组的方法。

arguments对象转化为数组的方法:
(1)借用数组的slice方法

Array.prototype.slice.apply(arguments);  
Array.prototype.slice.call(arguments);   
//以上调用Array对象原型中的方法,所以需要prototype
//而以下写法中[]是一个Array的实例,所以不需要prototype
[].slice().apply(arguments);

PS:为什么要加上apply或call()?
apply和call方法接受的第一个参数便是指定的作用域,接受arguments作为第一个参数说明Array.prototype.slice将在特定作用域argumnts中去调用slice方法。

(2)借用数组的concat方法
thisArg是新的空数组,apply方法将函数this指向thisArg,arguments做为类数组传参给apply。根据apply的方法的作用,即将Array.prototype.concat方法在指定的this为thisArg内调用,并将参数传给它。

Array.prototype.concat.apply(thisArg,arguments);

(3)使用es6的扩展运算符

...arguments;
2、arguments.callee()

callee 是 arguments 对象的一个属性。它可以用于引用该函数的函数体内当前正在执行的函数。这在函数的名称是未知时很有用,例如在没有名称的函数表达式 (也称为“匿名函数”)内。

严格模式下,ES5禁止使用 arguments.callee() 。当一个函数必须调用自身的时候, 避免使用 arguments.callee(), 则要么需要给函数表达式一个名字,要么使用一个函数声明。

function factorial (n) {
    return !(n > 1) ? 1 : factorial(n - 1) * n;
}

[1,2,3,4,5].map(factorial);

//在匿名函数中
[1,2,3,4,5].map(function (n) {
    return !(n > 1) ? 1 : arguments.callee(n - 1) * n;
});
3、caller()

在一个函数调用另一个函数时,被调用函数会自动生成一个caller属性,指向调用它的函数对象。如果该函数当前未被调用,或并非被其他函数调用,则caller为null。

function testCaller(){  
  var caller = testCaller.caller; //被调用函数才有的caller属性
  console.log(caller);
}

function aCaller() {  
  testCaller();  //被调用
}

aCaller();

结果如下:
在这里插入图片描述
call,apply,bind都是用来重新定义this指向的。
普通函数调用,this都是指向window的。
setTimeout是一个回调函数,它的this指的是window。

4、call()

call 方法第一个参数是要绑定给this的值,后面传入的是一个参数列表。当第一个参数为null、undefined的时候,默认指向window。

var arr = [1, 2, 3, 89, 46]
var max = Math.max.call(null, arr[0], arr[1], arr[2], arr[3], arr[4]);
//89


var obj = {
  message: 'My name is: '
}
function getName(firstName, lastName) {
  console.log(this.message + firstName + ' ' + lastName)
}
//getName中的this指向obj,后面传入参数列表
getName.call(obj, 'pengpeng', 'maizi');
//My name is: pengpeng maizi

5、apply()

apply接受两个参数,第一个参数是要绑定给this的值,第二个参数是一个参数数组。当第一个参数为null、undefined的时候,默认指向window。

var arr = [1,2,3,89,46]
var max = Math.max.apply(null,arr)//89

var obj = {
    message: 'My name is: '
}
function getName(firstName, lastName) {
    console.log(this.message + firstName + ' ' + lastName)
}
//getName中的this指的是obj,第二个参数传入的是数组
getName.apply(obj, ['pengpeng', 'maizi'])
// My name is: pengpegn maizi

call和apply可用来借用别的对象的方法,这里以call()为例,apply同理:

var Person1  = function () {
  this.name = 'pengpeng qimai';
}
var Person2 = function () {
  this.getname = function () {
      console.log(this.name);
  }
  Person1.call(this);  //即Person2里边的this指的是Person1
}
var person = new Person2();
person.getname();   
//pengpeng qimai
6、bind()

和call很相似,第一个参数是this的指向,从第二个参数开始是接收的参数列表。区别在于bind方法返回值是函数以及bind接收的参数列表的使用。

var obj = {
  name: 'pengpeng qimai'
}

function printName() {
  console.log(this.name)
}

var dot = printName.bind(obj) ; 
//会返回一个新的函数,而原函数不会被改变
console.log(dot) ; 
dot()  // pengpeng qimai

bind 方法不会立即执行,而是返回一个改变了上下文 this 后的函数。而原函数 printName 中的 this 并没有被改变,依旧指向全局对象 window。

function fn(a, b, c) {
  console.log(a, b, c);
}
var fn1 = fn.bind(null, 'pengpengqimai');

fn('A', 'B', 'C');            // A B C
fn1('A', 'B', 'C');           // pengpengqimai A B
fn1('B', 'C');                // pengpengqimai B C
fn.call(null, 'pengpengqimai');     
// pengpengqimai undefined undefined

call 是把第二个及以后的参数作为 fn 方法的实参传进去,而 fn1 方法的实参实则是在 bind 中参数的基础上再往后排。

7、应用场景

(1)求数组中的最大和最小值

var arr = [1,2,3,89,46]
var max = Math.max.apply(null,arr)//89
var min = Math.min.apply(null,arr)//1

(2)将类数组转化为数组

var trueArr = Array.prototype.slice.call(arrayLike)

(3)数组追加

var arr1 = [1,2,3];
var arr2 = [4,5,6];
var total = [].push.apply(arr1, arr2);//6
// arr1 [1, 2, 3, 4, 5, 6]
// arr2 [4,5,6]

(4)判断变量类型

function isArray(obj){
    return Object.prototype.toString.call(obj) == '[object Array]';
}
isArray([]) // true
isArray('pengpengqimai') // false

(5)利用call和apply做继承

function Person(name,age){
    // 这里的this都指向实例
    this.name = name
    this.age = age
    this.sayAge = function(){
        console.log(this.age)
    }
}
function Female(){
    Person.apply(this,arguments)//将父元素所有方法在这里执行一遍就继承了
}
var dot = new Female('pengpengqimai',2);

(6)使用 log 代理 console.log

function log(){
  console.log.apply(console, arguments);
}
// 当然也有更方便的 var log = console.log()
8、总结

call、apply和bind函数存在的区别:

  • bind返回对应函数, 便于稍后调用; apply, call则是立即调用。 除此外,
  • 在 ES6 的箭头函数下, call 和 apply 将失效, 对于箭头函数来说:

(1)箭头函数体内的 this 对象, 就是定义时所在的对象, 而不是使用时所在的对象;所以不需要类似于var _this = this这种丑陋的写法
(2)箭头函数不可以当作构造函数,也就是说不可以使用 new 命令, 否则会抛出一个错误
(3)箭头函数不可以使用 arguments 对象,,该对象在函数体内不存在. 如果要用, 可以用 Rest 参数代替
(4)不可以使用 yield 命令, 因此箭头函数不能用作 Generator 函数,什么是Generator函数可自行查阅资料,推荐阅读阮一峰Generator 函数的含义与用法,Generator 函数的异步应用

参考文章:https://www.jianshu.com/p/bc541afad6ee(原文写的更加详细,想深入了解的小伙伴可以戳进去研究一下~)

发布了28 篇原创文章 · 获赞 4 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/weixin_40736319/article/details/89309811