在 JavaScript 的世界里,一切都可以理解为对象。包括函数,函数在 JS 中也是一类对象,不过是一类特殊的对象,将函数作为构造类来使用,可以生成新的对象,从而实现JS 世界中的类的概念。
接下来我们从头开始回顾函数的发展过程。
我们知道,函数包含定义
和调用
两个阶段,函数在使用之前,必须先定义。
函数定义
有的同学可能更习惯用
声明
来表示定义,没关系,都可以。
函数定义共分为如下几种:
- 声明式定义
- 表达式定义
- 构造函数式
- 箭头函数
接下来我们一一介绍。
声明式定义函数
最早我们定义一个函数是这样子的,也是最常见的。
function test(){ //todo}复制代码
很简单,定义好之后,我们就可以使用了:
test();复制代码
表达式定义函数
定义一个函数,也可以理解为定义一个变量,将这个变量的值指向一个函数,于是就有了另外一种定义函数的方式(表达式定义):
var test = function(){ //todo}复制代码
调用也很简单
test();复制代码
那声明式定义函数与表达式定义函数之间的区别是什么? 大家看下面两段代码:
test();function test(){ //todo}复制代码
对象方法的调用:
var a = { say:function(){ //todo } }a.say();复制代码
函数调用时, 内部的this,一定是指向函数所属的对象的。
构造函数式定义
var say = new Function('a','b','return a + b');复制代码
箭头函数
ES6 规范中的函数定义方式。
let say = () => {};复制代码
函数调用
直接调用
let say = function(){ console.log(this)}say();复制代码
间接调用
间接调用分为两种,call
和 apply
,call
和 apply
的作用就是改变函数调用的上下文。下面举个例子来说明一下它们的作用。
我们先定义一个函数 test:
function test(){ console.log(this);}复制代码
这个函数很简单,仅仅是打印当前上下文 this。
我们这样调用
test();复制代码
输出结果是 window。
我现在又有一个对象 a :
var a = { name: '小A'}复制代码
我想让a 对象执行 test 方法提供的功能,该怎么办呢?
当然,大家可以说,我给 a 对象增加一个方法 testA,然后执行 a 对象的 testA 方法,不就可以了吗?
a.testA = function(){ console.log(this);}a.testA();复制代码
那我们如果不想为 a 对象额外增加这个方法,而是复用最开始定义的 test 方法,怎么办?? 这就是 call 和 apply 的作用。
test.call(a);//或者test.apply(a);复制代码
这就是改变函数执行上下文的意思。
call 和 apply 的唯一区别是传参不同。
call 的第一个参数是上下文对象,剩余的参数就是函数调用时的参数。
test.call(a, 1, 2, 3);复制代码
apply 的第一个参数是上下文对象,第二个参数是个数组,数组中存放的是函数调用时的参数。
test.apply(a, [1, 2, 3]);复制代码
函数执行上下文 this 指向。
var a = { name: 'A', say: function(){ console.log(this.name); }}a.say();复制代码
打印的是 'A'。
我们接着看:
var a = { name: 'A', say: function(){ function test(){ console.log(this.name); } test(); }}a.say();复制代码
执行结果是 undefined,而不是 'A'。 这是为什么?
一个简单的理解方法:
1、凡是通过function 定义的函数里面的 this 指向,一定是该函数调用时
所指向的对象。
test() 等价为 window.test()
所以 test() 执行时 this 指向 window。
2、箭头函数里面的 this 指向,一定是该箭头函数在定义时
所指向的执行上下文。
以上就是对函数 this 指向的一些归纳,利用这些方法,能让你精准地判断 this 指向,即使写一百层复杂嵌套函数,也能轻松判断出来。
(本文完)