文章目录

  • 前言
  • 一、arguments对象的callee属性
  • 二、函数的caller属性
  • 三、new.target
  • 四、call、apply和bind方法
    • 1.call方法
    • 2.apply方法
    • 3.bind方法
  • 总结

前言

写本《JavaScript简餐》系列文章的目的是记录在阅读学习《JavaScript高级程序设计(第4版)》一书时出现的各个知识点。虽是对读书的笔记和总结,但是希望它轻量、简洁、犀利,不会引起阅读疲劳,可以在碎片化时间和闲暇之余轻巧地沐浴一下知识点。每篇文章只针对一个小部分进行讲解式的梳理,来达到个人复习总结和分享知识的目的。


一、arguments对象的callee属性

关于arguments对象,前面已经详细介绍过了,它是一个类数组对象,包含调用函数时传入的所有参数。其中有一个比较有意思的属性叫callee,这是指向arguments对象所在函数的指针。直接来看一下具体例子来理解一下:

function person() {console.log(arguments.callee);
}person();
//输出结果就是这个函数:
// ƒ person() {//   console.log(arguments.callee);
// }

这个属性一个实用的地方是在递归函数中将函数逻辑与函数名解耦。来看一个用递归计算阶乘的例子:

function factorial(num) {if (num <= 1) {return 1;} else {return num * factorial(num - 1);}
}

这个例子中,这个函数要正确执行就必须保证函数名是factorial,从而导致了紧密耦合。那么,我们就可以使用arguments.callee来让函数逻辑与函数名解耦:

function factorial(num) {if (num <= 1) {return 1;} else {return num * arguments.callee(num - 1); // 这里我们将factorial换成arguments.callee来代替函数名}
}

这样就意味着无论函数叫什么名称,都可以引用正确的函数。再来考虑下面这种函数被中途修改的情况:

let anotherFactorial = factorial;factorial = function () {return 0;
};console.log(anotherFactorial(5)); // 120
console.log(factorial(5)); // 0

在这里,anotherFactorial被赋值为刚刚定义的factorial函数,而后面factorial函数被重写为一个返回0的函数。如果像我们第一次的那个版本,在递归函数内使用factorial函数进行递归而不是使用arguments.callee,那么这两个函数的运行结果就都是0。不过将函数逻辑与函数名称解耦后,anotherFactorial函数就依旧可以返回正确的阶乘运算结果。


二、函数的caller属性

caller这个属性引用的是调用当前函数的函数,或者如果是在全局作用域中调用的则为null:

function world() {person();
}function person() {console.log(person.caller);
}world();
//输出结果就是调用person函数的earth函数:
// ƒ world() {//   person();
// }

当然,这里如果想像前面那样降低耦合性的话我们还是可以用arguments.callee来代替person:

function world() {person();
}function person() {console.log(arguments.callee.caller);
}world();
//输出结果就是调用person函数的earth函数:
// ƒ world() {//   person();
// }

结果是一样的。


三、new.target

ECMAScript中的函数既可以当作普通函数来调用,也可以当作构造函数来实例化一个对象。在ECMAScript6中新增了检测函数是否使用了new关键字调用的new.target属性。如果函数是正常调用的,那么在函数中调用new.target则会返回undefined,如果函数是使用new关键字来调用的,那么new.target将会引用被调用的构造函数。来看一个具体例子:

function Person(name, age) {if (new.target === Person) {console.log("当前函数被当作构造函数使用来初始化一个实例!");} else {console.log("当前函数被当作普通函数调用。");}
}new Person(); // 当前函数被当作构造函数使用来初始化一个实例!
Person(); // 当前函数被当作普通函数调用。

四、call、apply和bind方法

call、apply、bind方法都是用来改变函数体内this对象的指向的,同时还具备传参的功能,我们一个个看过来:

1.call方法

还是直接通过代码实例来理解一下吧:

//实例1
const person = {name: "Lucy",age: 20,
};function sayInformation() {console.log(this.name);console.log(this.age);
}sayInformation.call(person);
// Lucy
// 20//实例2
function doAdd(x, y) {return x + y;
}function add(x, y) {return doAdd.call(this, x, y);
}console.log(add(1, 2)); // 3

在实例1中person对象中没有定义任何方法,打印信息的函数我们在全局中定义了一个sayInformation函数,可是这个函数打印的是this.name和this.age而不是person.name和person.age。那如果我们想打印person对象中的name和age属性要怎么办呢?那当然是要在函数和对象之间搭个桥了。这个时候call方法就派上用场了,就用它来搭桥。原理就是让sayInformation函数的this对象指向person。所以在最后我们调用的是sayInformation.call(person)。这样就可以打印出person中的属性了。
在实例2中,我们想进行加法运算,于是定义了一个add方法,但是我们不想让这个add函数干活,想让别的函数替它进行运算,所以我们定义了doAdd方法,让它来执行运算。此时我们就可以利用call方法将参数传给doAdd。在doAdd.call(this, x, y)这句中会将doAdd函数的this对象指向add函数的this(在这里this是window,因为add是在全局window对象中被调用的),之后将add接收到的参数x和y传给doAdd,即1和2。最终,我们得到相加的结果3。(当然直接return doAdd(x, y)也是可以的哈,在这里主要是为了演示call方法的传参功能。)


2.apply方法

apply在功能上与call方法是等价的,只不过在传参的方法上不同。call方法需要一个一个传参,而apply方法需要传入参数数组。将上面的例子改成利用apply方法的版本如下:

//实例1
const person = {name: "Lucy",age: 20,
};function sayInformation() {console.log(this.name);console.log(this.age);
}sayInformation.apply(person);
// Lucy
// 20//实例2
function doAdd(x, y) {return x + y;
}function add(x, y) {return doAdd.apply(this, [x, y]); // 在这里不是逐个传参了,而是传一个参数数组
}console.log(add(1, 2)); // 3

到底是使用apply还是call,完全取决于怎么给要调用的函数传参更方便。如果想直接传arguments对象或者一个数组,那就用apply();否则就用call()。如果不用给被调用的函数传参,则用哪种方法都一样。


3.bind方法

ECMAScript5出于同样的目的定义了一个新方法:bind()。bind()方法会创建一个新的函数实例,其this值会被绑定到传给bind的对象。其实换汤不换药,来看以下例子:

const numbers = {x: 1,y: 2,
};function add(num1, num2) {return this.x * num1 + this.y * num2;
}let getResult = add.bind(numbers, 2, 3);
console.log(getResult()); // 8

在这个例子中我们定义了numbers对象,其中有属性x和y。之后我们又定义了add函数,函数的功能是将x和y分别乘以一个数字并返回。主要看下面的部分,我们先利用bind方法将add函数的this指向numbers对象,顺便传了两个参数2和3(注意,这里的参数是逐个传的,和call方法相同)。这会创建一个新的函数实例,之后我们将这个新产生的函数实例赋值给getResult变量,在最后我们通过getResult()来调用这个函数。当然最后一部分也可以将两句简写成一句let getResult = add.bind(numbers, 2, 3) ()来直接调用。
将前面的call和apply的两个例子写成bind的版本如下:

//实例1
const person = {name: "Lucy",age: 20,
};function sayInformation() {console.log(this.name);console.log(this.age);
}sayInformation.bind(person)();
// Lucy
// 20//实例2
function doAdd(x, y) {return x + y;
}function add(x, y) {return doAdd.bind(this, x, y)(); // 在这里利用bind,并且直接调用函数。参数逐个传
}console.log(add(1, 2)); // 3

总结

以上就是今天要讲的内容,今天比较详细地介绍了arguments对象的callee属性、函数中的caller属性、new.target属性以及如何利用call、apply和bind方法来改变函数的this指向。下一篇,我们来讲一下JavaScript中的闭包。撒花~

JavaScript简餐——那些函数属性与方法(call、apply和bind)相关推荐

  1. JavaScript简餐——初识函数

    文章目录 前言 初始函数 1.函数声明 2.函数表达式 3.箭头函数(ES6新特性) 4.使用Function构造函数 总结 前言 写本<JavaScript简餐>系列文章的目的是记录在阅 ...

  2. JavaScript简餐——细看函数的参数

    文章目录 前言 一.理解参数 二.箭头函数中的参数 三.总结 前言 写本<JavaScript简餐>系列文章的目的是记录在阅读学习<JavaScript高级程序设计(第4版)> ...

  3. JavaScript简餐——关于箭头函数

    文章目录 前言 一.箭头函数 1.箭头函数定义 2.区别参数个数的不同写法 3.有无花括号的区别 4.箭头函数的限制 总结 前言 写本<JavaScript简餐>系列文章的目的是记录在阅读 ...

  4. JavaScript简餐——关于盗用构造函数

    文章目录 前言 一.什么是盗用构造函数? 二.使用实例 三.参数传递 四.盗用构造函数的问题所在 五.总结 前言 写本<JavaScript简餐>系列文章的目的是记录在阅读学习<Ja ...

  5. JavaScript简餐——类构造函数

    文章目录 前言 一.类的构造函数及其实例化 二.把类当成特殊函数 三.总结 前言 写本<JavaScript简餐>系列文章的目的是记录在阅读学习<JavaScript高级程序设计(第 ...

  6. JavaScript简餐——寄生组合继承

    文章目录 前言 一.什么是寄生组合继承? 二.寄生组合继承的基本模式 三.总结 前言 写本<JavaScript简餐>系列文章的目的是记录在阅读学习<JavaScript高级程序设计 ...

  7. JavaScript简餐——原型是个啥?

    文章目录 前言 一.理解原型 二.对象实例与原型 三.图解 四.判断对象实例和原型 总结 前言 写本<JavaScript简餐>系列文章的目的是记录在阅读学习<JavaScript高 ...

  8. JavaScript简餐——对象是个啥?

    目录 前言 一.对象是什么? 二.创建对象 1.创建Object的一个实例 2.使用对象字面量 总结 前言 写本<JavaScript简餐>系列文章的目的是记录在阅读学习<JavaS ...

  9. JavaScript简餐——创建对象的三种模式

    文章目录 前言 一.工厂模式 二.构造函数模式 三.原型模式 总结 前言 写本<JavaScript简餐>系列文章的目的是记录在阅读学习<JavaScript高级程序设计(第4版)& ...

最新文章

  1. 一起读Bert文本分类代码 (pytorch篇 五)
  2. 【区块链Go语言实现】区块链基本原型
  3. 用户界面设计准则从何而来
  4. Rabbit MQ安装配置及常见问题
  5. 这次被问懵了!搞定了这些SQL优化技巧,下次横着走
  6. 高德机器人的名字是怎么呼叫的_“一键呼叫”上门办服务!龙岗这个“智慧社区”很贴心~...
  7. 我们可以写100%,但是这样不科学
  8. 机器学习:如何在安卓上集成TensorFlow
  9. Java 并发(生产者/消费者 模式)
  10. c++ 标准异常类层次结构_Java入门教程十一(异常处理)
  11. python sftp连接_python 进行ftp服务器和sftp服务器连接
  12. element级联选择框的使用~干货分享
  13. 计算机考试试题大一上学期,大一第一学期期末考试计算机试题
  14. python学习----简易版非诚勿扰
  15. 2020哈工程计算机考研复试——网络线上自述
  16. 微信浏览器字体调整 PHP,如何针对微信浏览器的调整字体大小功能进行适配?...
  17. 惠普打印机驱动服务器系统安装教程,Hp打印机驱动怎么安装,详细图文版教程...
  18. 刷新浏览器后不进行任何点击操作时,不播放声音 | 解决方案(VUE-Element)
  19. 集群中zeus平台的变更
  20. js将图片转base64两种方法

热门文章

  1. html网页源码加密
  2. 递归函数法解水手分椰子问题
  3. PV_Summary
  4. 2020年区块链和分布式账本技术的5大趋势
  5. android device设备删除不了,Android Studio 删除多余的虚拟设备(Virtual Device)
  6. 两台Linux主机之间文件传输的几种方式
  7. MC9S12XS128 PWM输出配置
  8. 实战:OpenKruise运维增强控制器-2022.3.12
  9. 基于Python、Keras和OpenCV的实时人脸活体检测
  10. 小心NSDictionary的initWithObjectsAndKeys