前言
本文将通过一个个小案例,详细的讲解 this 在各种情况下所指向的内容是如何变化的。
章节一
/**
* this 是 JavaScript 的关键字
* 当前环境执行期上下文对象的一个属性
* this 在不同的环境、不同作用下,表现是不同的
*
* 获取全局对象
* web:window、self、frames、this
* node:global
* worker:self
* 通用:globalThis
*/
var a = 'global -> a';
var obj = {
a: 'obj -> a',
test: function () {
console.log(this.a); // 调用当前方法的对象
console.log(window.a); // web 环境下全局对象, node 环境下不存在
// console.log(global.b); // node 环境下全局对象, web 环境下不存在
console.log(globalThis.a);
}
}
obj.test();
function useStrict() {
'use strict'
return this;
}
/**
* 使用严格模式的话,会返回 undefined,因为 useStrict() 没说明谁调用它
* 但是 window.useStrict() 这样调用的话,严格模式也会返回 window,因为明确表明了谁调用了它
*
* 使用非严格模式的话,都会返回 window
*/
console.log(useStrict())
console.log(window.useStrict())
章节二
class People {
constructor() {
// 将会定义到实例对象 this 的属性上 -> new -> this -> {}
this.print = function () {
console.log('实例属性:', this);
}
}
// 类的原型上的方法 -> People.prototype
// new -> this -> {} -> __proto__ -> People.prototye
print() {
console.log('类原型上的方法:', this);
}
// 类的静态方法
static print() {
console.log('静态属性');
}
}
/**
* Class 其实就是 函数
*/
function People() {
this.print = function () {
console.log('实例属性:' + this);
}
}
People.prototype.print = function () {
console.log('类原型上的属性:' + this);
}
People.print = function () {
console.log('静态属性:' + this);
}
const man = new People();
/**
* 输出:实例属性: People {print: ƒ}
* 这里需要知道的是为什么不是输出“类原型上的方法”,那是因为“类原型上的方法”在 People 在定义的时候就进行的赋予了
* 而“实例属性”是在 new 的时候,对 constructor 的执行,并且改变 this 为实例对象的时候赋予的一个实例上的方法
*/
man.print();
People.print(); // 输出:静态属性
章节三
const PeopleA = Object.create(null); // 输出的对象,没有 __proto__
const PeopleB = Object.create({ a: 1 }); // 输出的对象的 __proto__ 指向了传入的 {a: 1}
class Father {
constructor(age) {
this.age = age;
}
swim() {
console.log('Go swimming!!!');
}
}
class Son extends Father{
constructor() {
/**
* 类似于 call 的继承:在这里 super 相当于把 A 的 constructor 给执行了,
* 并且让方法中的 this 是 B 的实例,super 当中传递的实参都是在给 A 的 constructor 传递。
* super(18) 相当于 Father.prototype.constructor.call(this, 18)
* super.swim() 相当于 Father.prototype.swim()
*/
super(18);
this.hooby = 'traval';
console.log(this.age);
}
study() {
console.log(this);
this.swim();
}
}
const son = new Son();
son.study();
// 需要注意,由于 super 指向父类的原型对象,所以定义在父类实例上的方法或属性,是无法通过 super 调用的。
章节四
var obj = {
a: 1
}
var obj2 = {
a: 100
}
var a = 2;
function test(b, c, d, e, f) {
// this 默认 -> 全局对象 window
console.log(this.a, b, c, d, e, f);
}
test(); // 2 undefined undefined undefined undefined undefined
test.call(obj); // 1 undefined undefined undefined undefined undefined
test.apply(obj); // 1 undefined undefined undefined undefined undefined
test.call(obj, 3, 4); // 1 3 4 undefined undefined undefined
test.apply(obj, [3, 4]); // 1 3 4 undefined undefined undefined
var test1 = test.bind(obj, 3, 4);
test1(); // 1 3 4 undefined undefined undefined
var test2 = test1.bind(obj2, 5, 6);
test2(7); // 1 3 4 5 6 7
/**
* 这里的 test2 输出为 1 3 4 5 6 7 为什么呢?
* 首先 bind 绑定的时候会返回一个新的函数 test2,
* 其次,bind 内部也是使用 call 来改变 this 指向。在 test1 的时候执行, 内部 test 指向定义为 obj
* test1.bind() 的执行的时候,返回 test2,test2 执行的时候,内部 test1 的 this 会指向 obj2,
* 随后 test1 执行,内部的 test 的 this 指向还是 obj,所以 this.a 为 1,至于参数会不断透传下去
*/
章节五
'use strict'
const test = () => {
console.log(this);
}
function test1() {
console.log(this);
}
const test2 = function () {
console.log(this);
}
test(); // window
test1(); // undefined
test2(); // undefined
// 严格模式下,箭头函数绑定了 window -> this
章节六
var obj = {
a: 1
}
var a = 2;
const test = () => {
console.log(this.a);
}
test(); // 2
test.call(obj); // 2
test.apply(obj); // 2
var test1 = test.bind(obj);
test1(); // 2
new test(); // test is not constructor
obj.test = () => {
console.log(obj);
console.log(this);
}
obj.test(); // obj/window
/**
* 箭头函数是忽略任何形式的 this 指向改变
* 箭头函数一定不是一个构造函数
* 箭头函数不是谁调用 this 就会指向谁
*/
章节七
obj.test = function () {
const t1 = () => {
console.log(this);
}
t1();
}
obj.test(); // obj
obj.test = () => {
const t1 = () => {
console.log(this);
}
t1();
}
obj.test(); // window
obj.test = function() {
const t1 = () => {
// t1 是箭头函数 this -> obj
const t2 = () => {
console.log(this);
}
t2();
}
t1();
}
obj.test(); // obj
obj.test = function() {
const t1 = function() {
const t2 = () => {
// t1 是普通函数 this -> window
console.log(this);
}
t2()
}
t1();
}
obj.test(); // window
/**
* 总结:
* 箭头函数 this 总是指向外层非箭头函数的 this 指向
* 箭头函数是忽略任何形式的 this 指向改变
* 箭头函数一定不是一个构造函数
* 箭头函数不是谁调用 this 就会指向谁
*/
章节八
// test3 输入变量赋值一个函数,这个在执行的时候才会进行声明和赋值,所以必须写在前面
const test3 = () => {
console.log(this);
}
var obj = {
a: 1,
b: 2,
test: function() {
console.log(this.a);
},
test2: test2,
test3: test3,
c: {
d: 4,
test4: function () {
console.log(this.d);
}
},
test5: function () {
function test6() {
console.log(this);
}
test6();
}
}
obj.__proto__ = {
e: 20
}
// 预编译的时候函数 test2 就声明了和定义了
function test2() {
console.log(this.b);
}
obj.test(); // 1
obj.test2(); // 2
obj.test3(); // window
obj.c.test4(); // 4 这里按照对象中的 function 的最近谁调用 this 就指向那个宿主的原则即可了解
obj.test5(); // window, 要知道的是 test6 的调用寄主就是 window·
console.log(obj.e); // 20
var obj2 = Object.create({
test0: function () {
console.log(this.a + this.b);
}
});
obj2.a = 1;
obj2.b = 2;
obj2.test0(); // 1 + 2 = 3 这里的 this 指向就是 obj2
/**
* 总结:
* this 的指向的基本原则:谁是调用 this 的寄主,this 就指向谁
* 另类的就是对于箭头函数不同,箭头函数内部 this 的指向为最近外层非箭头函数的作用域
*/
章节九
// 使用的是 function Object() {} 构造函数构造的
var obj1 = {
a: 1,
b: 2
}
// 使用 Object.create() 进行构造,可传入一个 prototype 对象, 传入 null 为一个无 prototype 对象
var obj2 = Object.create({
a: 1,
b: 2
});
// 使用 Object.defineProperty 进行属性定义
var obj3 = {};
Object.defineProperty(obj3, 'a', {
get() {
console.log(this)
return 1;
}
})
console.log(obj3.a); // 这里输出 obj3
// 这里 this 指向的就是 obj3
function Test() {
this.a = 1;
this.b = 2;
console.log(this);
// return this; // 作为构造函数,默认行为范围 this
return {
a: 3,
b: 4
}
// return null; // 作为构造函数,也是返回 this
// return undefined; // 作为构造函数,也是返回 this
// return 非 object 也返回 this
}
var obj4 = new Test(); // obj4 -> { a: 3, b: 4 }、console.log(this) -> Test {a: 1, b: 2}
console.log(obj4);
/**
* new 过程:
* var _obj = {};
* _obj.__proto__ == Test.prototype;
* var res = Test.call(_obj);
* return typeof res === 'object' && res !== null ? res : obj;
*
* 总结:
* 构造函数默认隐式返回 this,或者手动返回 this,这个 this 指向的新对象的构造都是成功的
* 如果手动返回一个新对象,那么这个 this 指向的那个对象将会被忽略,失效掉,因为失去了引用,相当于没有 new,比如如下:
*/
var obj5 = new Test(); // { a: 3, b: 4 }
var obj6 = Test(); // { a: 3, b: 4 }
章节十
var oBtn1 = document.getElementById('btn1');
oBtn1.onclick = function () {
console.log(this); // 输出了:<button id="btn1">click</button>
}
oBtn1.addEventListener('click', function () {
console.log(this); // 输出了:<button id="btn1">click</button>
}, false);
// 事件处理函数内部的 this 总是指向被绑定 DOM 元素
!(function (doc) {
var oBtn2 = doc.getElementById('btn2');
function Plus() {
this.a = 1;
this.b = 2;
this.init();
}
Plus.prototype.init = function () {
this.bindEvent();
}
Plus.prototype.bindEvent = function () {
oBtn2.addEventListener('click', this.handleBtnClick, false);
}
Plus.prototype.handleBtnClick = function () {
console.log(this); // <button id="btn2">+</button>
console.log(this.a + this.b); // NaN
}
window.Plus = Plus;
})(document)
var plus = new Plus();
/**
* 这里点击 btn2 事件,handleBtnClick 内部的 this 为 dom 元素本身
* 如果需要内部 this 为 Plus 实例,可有以下方式:
*/
// 方式一
Plus.prototype.bindEvent = function () {
oBtn2.addEventListener('click', this.handleBtnClick.bind(this), false);
}
// 方式二
function Plus() {
this.a = 1;
this.b = 2;
this.handleBtnClick = this.handleBtnClick.bind(this);
this.init();
}
// 方式三
Plus.prototype.bindEvent = function () {
oBtn2.addEventListener('click', () => this.handleBtnClick(), false);
}
/**
* 总结:
* DOM 事件处理函数内部的 this 总是指向被绑定 DOM 元素
*/
章节十一
/**
* 类中是严格模式
*/
/**
* 父亲有一个吃水果的方法 还有一个水果
* 儿子有自己的水果 -> 儿子使用父亲吃水果的方法吃自己的水果
*/
class Father {
// constructor() {
// this.eat = this.eat.bind(this); // bind 会重新返回一个匿名函数
// }
get fruit() {
return 'apple';
}
eat() {
console.log('I am eating an ' + this.fruit);
}
}
class Son {
get fruit() {
return 'orange';
}
}
var father = new Father();
var son = new Son();
son.eat = father.eat;
father.eat(); // I am eating an apple
son.eat(); // I am eating an orange
/**
* 如何让 son 也吃父亲的水果内?
* 其实就是把父亲的 eat 内部绑定住父亲的 this
* 可以这么做,给 Father 增加一个构造函数,如下:
* constructor() {
* this.eat = this.eat.bind(this); // bind 会重新返回一个匿名函数
* }
*/
总结
总结:
- this 是 JavaScript 的关键字,当前环境执行期上下文对象的一个属性,this 在不同的环境、不同作用下,表现是不同的
- 获取全局对象
- web:window、self、frames、this
- node:global
- worker:self
- 通用:globalThis
- 扩展:Class 中由于 super 指向父类的原型对象,所以定义在父类实例上的方法或属性,是无法通过 super 调用的
- 扩展:Object.create(null); // 输出的对象,没有 proto
- 扩展:Object.create({ a: 1 }); // 输出的对象的 proto 指向了传入的 {a: 1}
- 使用 bing、call、apply 的时候会变更非箭头函数内的 this 指向
- 箭头函数 this 总是指向外层非箭头函数的 this 指向
- 箭头函数是忽略任何形式的 this 指向改变
- 箭头函数一定不是一个构造函数
- 箭头函数不是谁调用 this 就会指向谁
- 对象中的 function 的最近谁调用 this 就指向那个宿主的原则
- this 的指向的基本原则:谁是调用 this 的寄主,this 就指向谁
- 对于箭头函数不同,箭头函数内部 this 的指向为最近外层非箭头函数的作用域
- 构造函数默认隐式返回 this,或者手动返回 this(返回 null, undefined 也是返回 this),这个 this 指向的新对象的构造都是成功的
- DOM 事件处理函数内部的 this 总是指向被绑定 DOM 元素
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!