在 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 指向,即使写一百层复杂嵌套函数,也能轻松判断出来。
(本文完)