文章目录

  • 前言
  • 一、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. TPF111视频信号放大器研究
  2. 如何利用计算机网络辅助自己,如何借助计算机网络辅助大学英语写作教学.doc...
  3. Python二叉树遍历
  4. 基于udp的协议netty课设题目_Netty UDP示例
  5. 3、Go语言核心编程(高级篇)
  6. java的编译原理_Javac编译原理 - Martiny的个人空间 - OSCHINA - 中文开源技术交流社区...
  7. mysql中dint_MySQL基础篇(1)SQL基础
  8. java葫芦娃喜羊羊格斗_课内资源 - 基于JAVA的葫芦娃大战妖怪
  9. CRISPR技术有效的防止脱靶现象方法
  10. 把你问到哑口无言,HR是专业的!
  11. 机器学习 api_开发人员会喜欢的10种机器学习API
  12. js库笔记(一):swr ahooks
  13. EASYOPS 运维平台绿色社区版 V1.2.10
  14. Javascript中click与blur事件的顺序详析
  15. 【python设计模式】6、装饰器模式
  16. Camera2(api2) 打开过程(一)
  17. Spring源码学习:BeanPostProcessor注册和执行时机
  18. 【精品软件】6502电气集中仿真软件
  19. canvas绘制圆形马赛克方法二
  20. 【原力计划】打工人,你真的应该该停下来仔细考虑这几件事了。

热门文章

  1. .NET面试题总结---献给即将找工作的同行们
  2. 使用js代码实现类似php的ksort方法
  3. C4D模型工具—坍塌
  4. TT语音母公司IPO:算法社交的暗流和壁垒
  5. 优动漫PAINT-紫藤花画法
  6. 「黑科技分享」好消息,手机号能“一键解绑”各种APP,真香
  7. 科学计算机上怎么查看度数,科学计算器角度换算(学生计算器怎么算角度)
  8. 信托互联网化改造 核心是大数据
  9. A-Level是“最简单的”国际课程吗?
  10. Android性能优化—apk瘦身