玩转this指向

对于this来说

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

5 讲一下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的话,我们可以传递一个数组进去,这样就更加灵活了。

6 call

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

6.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.

6.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

7 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会自动绑定到该对象。

8 直接调用

玩个简单的:

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.

9 不在函数里

  • 不在函数里的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关键字的值取决于函数调用的上下文

  • 21
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值