对于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
指向的
但这里我们来
5 讲一下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
的话,我们可以传递一个数组进去,这样就更加灵活了。
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
仍然按照默认规则确定,不会受到bind
或apply
的影响,故输出Window.
9 不在函数里
- 不在函数里的
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
。