对于this来说

我们都是很熟悉的,在很多语言里面我们都能看见this,特别是面向对象的语言,比如Java,C++,JavaScript等等。
其实this就是指向当前对象的一个关键字,
它指向谁在函数被调用的时候才能确定。
比如在JavaScript中,this指向函数运行时所在的对象。
其实关于this指向的就几个点
箭头函数,new,bind,apply和call,obj.(不要忘记点哦),直接调用,不在函数里
上面这个既是口诀又是优先级,背下来,就能记住大部分情况下的this指向了。
接下来就让我们开始玩转this

1.箭头函数

 func = () => {
   console.log(this);
 }
 func();

我们发现输出的this为{},我就在想为什么是{},箭头函数它不会去绑定this,它会继继承所在上下文的this
所以按照逻辑上面来说我们这里打印的在非严格模式下面是window,在严格模式下面是undefined
结果后面我发现是因为我用的vscode,这个编译工具默认展示为空对象。

非严格模式和严格模式又是个啥呢?
请去玩转非严格模式和严格模式这篇博客,让我们一起去玩一下这两个东西吧。

2.new

 function Person(name) {
   console.log(name);
 }
 new Person("Alice");

这里输出的nameAlicenew操作符会创建一个新对象,
并且this会指向这个新对象,所以这里输出的nameAlice
那我们就会想,new的时候修改this,那我们去new箭头函数是不是就能修改this呢?
答案是:不能。
来来来,让我们玩一下

 fuc = () => {
   console.log(this);
 }
 new fuc();  报错: TypeError: fuc is not a constructor

所以我们得出一个结论就是:箭头函数不能使用new操作符。它不能被当作构造函数。

3.bind

其实bind就是Function.prototype的一个原生方法,bind方法会创建一个新的函数,
这个新函数会忽略原始函数的this绑定,
而是将this绑定到bind方法传入的参数上。
比如我们给一个函数bind一个对象,那这个函数在调用的时候,this就指向这个对象了。
并且我们在bind的过程中,this是会设置为bind传入的第一个参数,实参是会进行传递的。

 function greet(greeting) {
   console.log(this);
   console.log(`${greeting}, ${this.name}!`);
 }
 const user = { name: "Alice" };
 使用bind绑定this为user对象
 const boundGreet = greet.bind(user);
 并且即使在apply中传入了不同的thisArg,也不会改变this的指向
 boundGreet.apply({ name: "Bob" }, ["Hello"]);  输出 "Hello, Alice!"

那我们玩一下假如先调用apply,再去调用bind呢?

 greet.apply({ name: "Bob" }, ["Hello"]);
 const boundGreet = greet.bind(user);
 boundGreet("Hi");
让我们来猜猜会打印什么?
 { name: 'Bob' }
 Hello, Bob!
 { name: 'Alice' }
 Hi, Alice!

所以我们得出结论:apply并不会改变bind已经绑定的this指向,而且bind不会受到apply先绑定this的影响。

4.apply

apply方法是Function.prototype的一个原生方法,
它接受两个参数,第一个参数是this的指向,第二个参数是一个数组,
这个就跟我们bind的玩法不一样了
数组中的元素会作为参数传给这个函数。

 bind
 function myFunction(arg1, arg2) {
   console.log(this, arg1, arg2);
 }

 创建一个新的函数,将myFunction的this上下文绑定到obj上
 var obj = { value: 'Object Value' };
 var boundFunction = myFunction.bind(obj, 'presetArg1');

 调用boundFunction
 boundFunction('arg2Value');  输出将是 { value: 'Object Value' }, 'presetArg1', 'arg2Value'

 apply
 function myFunction(arg1, arg2) {
   console.log(this, arg1, arg2);
 }
 var obj = { value: 'Object Value' };

 var boundFunction = myFunction.apply(obj, ['presetArg1', 'arg2Value']);

首先bindapply都是用来改变函数的this指向的
但这里我们来讲一下bindapply的区别,
1.apply的话,它是会立即调用函数的,并且可以传递一个数据或类数组对象作为函数的参数
2.bind的话,它不会立即调用函数,而是会返回一个新的函数,并且传递的第一个参数是this的指向,后面每次都可以传入实参进去
3.因此,applybind适用的环境并不一样。
apply的话,它适合于传入参数个数不确定,或者参数个数不确定,同时需要传递数组作为参数的情况。
bind的话,它适合于传入参数个数确定,同时需要传递参数个数确定的情况。
适用环境的差异:
apply 更适合于那些需要立即调用函数,并且可以将参数作为数组传递的场景。
比如,当一个函数需要接受不定数量的参数,或者参数已经是数组形式时,使用 apply 就非常方便。
bind 则更适合于需要预先设置好函数的 this 上下文,并可能需要预设某些参数,但又不想立即执行函数的场景。
它返回的新函数可以存储起来供后续使用,或者作为回调函数传递给其他方法,这时候 this 的指向已经固定,避免了在复杂调用链中丢失正确的上下文。

 举个例子,我们来玩一下
 const person1 = {
   fullName: function () {
     return this.firstName + " " + this.lastName;
   }
 };

 const person2 = {
   firstName: "John",
   lastName: "Doe"
 };

 console.log(person1.fullName.apply(person2));  输出 "John Doe"
 console.log(person1.fullName.bind(person2)());  输出 "John Doe"

虽然我们发现applybind方法得到的结果都是一样的,但是这里我更加推荐使用apply,因为这里我们不确定fullName执行的函数里面我们需要
什么参数,所以使用apply的话,我们可以传递一个数组进去,这样就更加灵活了。

5.call

call方法是Function.prototype的一个原生方法,
它接受一个参数列表,并且会立即调用这个函数。
call方法中第一个参数为this的值,后面的参数为函数的参数。并且你想要传多少参数都可以,以逗号分隔开就行
call方法适用的环境:

1.改变上下文:

 当你需要在不同对象上使用同一个方法,或者需要在全局作用域中调用对象的方法时,call 很有用。
 const person1 = { name: 'Alice', age: 25 };
 const person2 = { name: 'Bob', age: 30 };
 function sayHello(greeting) {
   console.log(`${greeting}, my name is ${this.name} and I am ${this.age} years old.`);
 }
 在 person2 上调用 sayHello 方法
 sayHello.call(person2, 'Hello');  输出: Hello, my name is Bob and I am 30 years old.

2.继承与原型链:

 当你需要在不同对象上使用同一个方法,或者需要在全局作用域中调用对象的方法时,call 很有用。
 function Animal(name) {
   this.name = name;
 }

 在Animal的原型上定义了speak这么一个方法,它保证了所有的Animal实例都可以调用这个方法
 Animal.prototype.speak = function () {
   console.log(`I am ${this.name}`);
 };

 function Dog(name, breed) {
   Animal.call(this, name);  使用call继承Animal属性
   this.breed = breed;
 }
 
 这两步有什么用呢?
 Dog.prototype = Object.create(Animal.prototype);  让Dog的原型指向Animal的原型
 Dog.prototype.constructor = Dog;  修复Dog的原型链上的constructor指向, 让constructor指向Dog否则它会指向Animal
 在完成这两步操作之后,我们的Dog可以继承Animal的原型方法了
关于JavaScript的继承,到时候我们具体去玩一下。

 let dog = new Dog('Rex', 'German Shepherd')
 dog.speak()  输出: I am Rex

6.obj.

 function myFunction() {
   console.log(this);
 }

 const obj = {
   name: 'John',
   myFunction: myFunction
 };

obj.myFunction();  输出: { name: 'John', myFunction: [Function: myFunction] }
 这里我们发现,当调用obj.myFunction()的时候,this指向的是obj对象。

 现在我们来玩一下obj.里面的箭头函数
const obj = {
   name: 'John',
   myFunction: () => {
     console.log(this);
   }  输出: Window
};
obj.myFunction();
obj.myFunction.bind({ name: 'Bob' })();

刚开始我以为这里会输出: { name: 'Bob', myFunction: [Function: myFunction] }
结果发现,这里会输出: Window
分析发现,这里的上下文是箭头函数定义时的上下文,而非调用时的上下文
所以其所在上下文的this值为window对象
当调用obj.myFunction()的时候,this指向的是Window对象。
所以obj.是不能改变箭头函数的this的,就算使用了bind也是没有办法修改的
如果想要在obj.myFunction中得到obj作为this,你应该使用传统的函数表达式或声明方式,因为常规函数在作为对象方法被调用时,this会自动绑定到该对象。

7.直接调用

玩个简单的:

function func() {
   console.log(this)  输出: Window
}
func()

 玩个复杂的:
function outerFunc() {
  console.log(this)  输出: { x: 1 }
  function func() {
     console.log(this)  输出: Window
  }
  func()
}
outerFunc.apply({ x: 1 })
outerFunc.bind({ x: 1 })()

这里我们发现,当调用outerFunc.apply({ x: 1 })的时候,this指向的是{ x: 1 }对象。这个我们很好理解, 但是里面的func为什么是window呢?
因为内部的func函数调用时,它自己的this仍然按照默认规则确定,不会受到bindapply的影响,故输出Window.

8.不在函数里

不在函数里的this有两种场景

  1. 是在浏览器的<script>标签里
  2. 是在Node.js的模块文件里
    无论是浏览器环境还是Node.js环境,
    如果代码处于严格模式(通过在文件或函数顶部声明 'use strict';)中,顶层的 this 会被设定为 undefined
    这是为了减少因全局上下文的 this 引起的意外行为,增强代码的可预测性和模块化。
    如果代码不处于严格模式下,那么 this 的值会根据函数的调用方式而有所不同。顶层的 this 会被设定为 window 对象。

下面就让我们来玩一下

function func(num) {
   this.count = num
}

func.count = 1
func(2)
 你们认为输出是什么
console.log(func.count)

为什么输出是1呢?
让我们来分析一波: func.count = 1 表示给func设置了一个count属性,值为1
但是func(2)这个直接调用func函数,但是func函数中的this指向的是window对象,在执行this.count = num这段代码的时候,它进行的操作是在window对象上添加了一个count属性,值为2
所以输出func.count结果的时候,我们得到的是func对应的count,并不是window对象中count属性,因此输出为1

这个是我在某一篇文章看到的题目,大家可以玩一下

obj = {
   func() {
     const arrowFunc = () => {
       console.log(this._name)
     }

     return arrowFunc
   },

   _name: "obj",
}
obj.func()()  输出: obj
func = obj.func
func()()  输出: window
obj.func.bind({ _name: "newObj" })()()  输出: newObj
obj.func.bind()()()  输出: window
obj.func.bind({ _name: "bindObj" }).apply({ _name: "applyObj" })()  输出: bindObj

let name2 = 'window2';
let doSth2 = function () {
console.log(this);
console.log(this.name2);
}
doSth2()  window, undefined

又发现新情况了,为什么输出this.name2undefined呢?
有没有同学刚开始以为this.name2会输出window2呢,结果是undefined.
分析得到一个结论就是变量 name2 是在函数外部定义的,`它直接附加在全局对象上,成为全局变量。
然而,按照常规的变量访问规则,直接访问全局变量应当使用变量名,而不是通过 this 访问。
即,虽然 name2 在全局作用域中定义,但并不意味着它是全局对象(window)的一个属性。
因此,尝试通过 this.name2 访问该变量,实际上是在查找全局对象是否有名为 name2 的属性,而并没有这样的属性,所以得到的结果是 undefined

总结: this关键字的值取决于函数调用的上下文


肉夹馍
4 声望2 粉丝

努力学习,想要做全栈工程师的前端从事人员😁