对于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");
这里输出的name
为Alice
,new
操作符会创建一个新对象,
并且this
会指向这个新对象,所以这里输出的name
为Alice
。
那我们就会想,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']);
首先bind
和apply
都是用来改变函数的this
指向的
但这里我们来讲一下bind
和apply
的区别,
1.apply
的话,它是会立即调用函数的,并且可以传递一个数据或类数组对象作为函数的参数
2.bind
的话,它不会立即调用函数,而是会返回一个新的函数,并且传递的第一个参数是this
的指向,后面每次都可以传入实参进去
3.因此,apply
和bind
适用的环境并不一样。
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"
虽然我们发现apply
和bind
方法得到的结果都是一样的,但是这里我更加推荐使用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
仍然按照默认规则确定,不会受到bind
或apply
的影响,故输出Window.
8.不在函数里
不在函数里的this
有两种场景
- 是在浏览器的
<script>
标签里 - 是在
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.name2
是undefined
呢?
有没有同学刚开始以为this.name2
会输出window2
呢,结果是undefined
.
分析得到一个结论就是变量 name2
是在函数外部定义的,`它直接附加在全局对象上,成为全局变量。
然而,按照常规的变量访问规则,直接访问全局变量应当使用变量名,而不是通过 this
访问。
即,虽然 name2
在全局作用域中定义,但并不意味着它是全局对象(window
)的一个属性。
因此,尝试通过 this.name2
访问该变量,实际上是在查找全局对象是否有名为 name2
的属性,而并没有这样的属性,所以得到的结果是 undefined
。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。