开篇说明
随着科技的发展和历史的变迁,我对 this 的理解也越来越深入了,还记得之前写的一篇关于 this 的文章,自己看起来都感觉“味同嚼蜡”😂,所以更新一篇,将功补过,哈哈~~,我们拿五种情况来说明 this 的指向问题,优先级从低到高,但是在这之前我们先说一下 js 的作用域链的问题。
作用域链
案例一
function outer() {
var name = 'outer';
function inner() {
console.log( 'name: ', name );
}
inner();
}
outer();
- 大家想一下打印出来的 name 是啥呢?
- 此时假装过大家思考了10秒~
- 打印出来的是
outer
- 原因是什么呢?
- 明明 inner 函数中是没有 name 这个属性的,他怎么会打印出外层的 outer 函数中定义的 name 呢?
- 因为 js 在执行函数的过程中,如果某个变量找不到,他会沿着「!!!:
函数定义时
」的作用域链去外层找是否有这个变量,如果外层没有,继续向外层的外层去找找找。。。最终找到全局对象上(一般情况下是window)如果还没有,那么就会报错;在我们这个案例中,查找的顺序为:inner ? -> outer ? -> window ?
- 如果按照上面的作用域链最终到 window 对象上也没有找到这个变量,那么浏览器就会抛出错误
Error: name is not defiend
五种情况的 this 指向
一、函数的自然执行
先解释下什么叫自然执行?
function func() {
console.log( 'this: ', this );
};
let obj = {
a: func
};
func(); // 自然执行
obj.a(); // 不是自然执行
func.call( obj ); // 不是自然执行
总结:自然执行:为函数直接执行,这样: func();
1.直接执行,不通过 '.' 操作符执行
2.不使用 new 操作符
3.不使用 call、apply、bind
总之:就是光突突地执行:func();
函数自然执行的情况下:
- 在非严格模式下的 js 中,指向
window
- 在严格模式「“use strict”」下,指向
undefined
- 在 node 环境下,指向 node 的全局对象
global
二、被对象的访问操作符访问出来,并执行
什么是对象的访问操作符?其实就是访问对象中属性的两种方式,我们平时肯定也很常用,请看下面
function func() {
console.log( 'this: ', this );
}
let obj = {
// a 属性的值指向了 func 函数的引用,这里不清楚可以查阅下 js 中的基本数据类型和复杂数据类型的区别
name: 'obj',
a: func
}
obj.a(); // 被 obj 通过「对象.键名」的方式访问出来并执行
obj[a](); // 被 obj 通过「对象[键名]」的方式访问出来执行
上面两种执行方式中,this 的指向被隐式(偷偷)绑定到 obj 上了
这种情况我们只需要记住一句话:函数不管被多少层访问符,层层访问到,他总是指向离他最近的调用对象上,下面举例说明:
function func() {
console.log( 'this ', this );
}
let obj = {
name: 'obj',
father: {
name: 'father',
son: {
a: func,
name: 'son'
}
}
}
obj.father.son.a(); // this 指向 son ,因为是离他最近的调用者
三、通过 new 操作符执行函数
也就是调用构造函数的方式了,也就是我们用 javaScript 造出来的“类”的概念。
function func(name) {
this.name = name;
console.log( 'this: ', this );
}
let a = new func('a'); // this 指向 a 对象
let b = new func('b'); // this 指向 b 对象
只要通过 new 操作符调用函数,那么 this 会隐式(偷偷地)指向返回的实例对象。
下面出一道题,大家可以尝试思考一下,打印出的 this 是什么?
可以运行输出验证一下自己的答案哦!
function outer() {
let name = 'outer';
function inner() {
let name = 'inner';
console.log( 'this: ', this );
}
return inner;
}
let a = outer();
a();
四、call、apply、bind
先来说下 call 和 apply
call 和 apply 都是可以把其他函数借过来自己用一下,而且还不用在自己身上扩展这个方法,举例说明:
let a = {
name: '我是a'
}
let b = {
name: 'b',
sayName: function() {
console.log( 'name: ', this.name );
}
}
b.call( a ); // name: '我是a'
b.apply( a ); // name: '我是a'
// call 和 apply 的区别就是传参方式的不同
// 这里不做过多说明
// b.call( a, 1, 2, 3 ) b.apply( a, [1, 2, 3] );
我们的两个对象 a 和 b,他们都有名为 name 的属性,但是 a 中没有 sayName
的方法,但是他可以借用 b 的 sayName
方法,但是在借用 b 的 sayName
方法的过程中,this 被隐式(偷偷地)指向了被借用的对象上,在这里就是 a
bind
只讲解最简单的使用方式,也是借用函数,并隐式改变 this 的指向,但跟 call 和 apply 不同的是:bind 返回一个新函数,举例说明:
let a = {
name: 'a',
log: function () {
console.log( 'this: ', this );
}
}
let b = {
name: 'b',
}
let c = a.log.bind( b );
c(); // this 指向对象 b
// `c()` 相当于执行 `a.log.call( b )`
五、ES6 的箭头函数
我们平时写代码的时候经常听到一种说法:你的项目中要不就都用箭头函数,要不就都不用,别把 this 搞乱,对吧,但是我们搞清楚 this 在不同执行情景下的指向之后,哪种写法我们就可以自己斟酌了,或者混着用😏(不推荐)
我们先看下概念:箭头函数的 this 指向「定义时
」离他最近的一个非箭头函数的 this 指向的对象,举例说明:
function outer() {
let name = 'outer';
let inner = () => {
let name = 'inner';
console.log( 'this: ', this );
}
inner();
}
outer(); // this: window
// 1.箭头函数的 this 指向「`定义时`」离他最近的一个非箭头函数的 this 指向的对象
// 2.那么我们从 inner 向外层看,外层 outer 就是离 inner 最近的非箭头函数,
// 那么他的 this 指向就是箭头函数 inner 的 this 指向
// 3.我们分析 outer,outer 是自然执行的(上面说过自然执行了),那么 outer 的 this
// 指向为 window,所以 inner 的 this 执行也是 window
function outer() {
let name = 'outer';
let inner = () => {
let name = 'inner';
console.log( 'this: ', this );
}
inner();
}
outer.call( outer ); // this: ?
// 这个留给大家自己分析了,欢迎提问哦~
this 的优先级问题
ES6箭头函数 > call、apply、bind > 自然执行、对象访问操作符执行、new 调用
!
:大于号中间的多种情况为并列关系