随记:JavaScript中的this
前言
小编学习前端的随记,个人理解,欢迎大佬纠错
正文
this关键字是函数运行时自动生成的一个内部对象,只能在函数内部使用,总指向调用它的对象
举个例子:
function baz() {
// 当前调⽤栈是:baz
// 因此,当前调⽤位置是全局作⽤域
console.log( "baz" );
bar(); // <-- bar的调⽤位置
}
function bar() {
// 当前调⽤栈是:baz --> bar
// 因此,当前调⽤位置在baz中
console.log( "bar" );
foo(); // <-- foo的调⽤位置
}
function foo() {
// 当前调⽤栈是:baz --> bar --> foo
// 因此,当前调⽤位置在bar中
console.log( "foo" );
}
baz(); // <-- baz的调⽤位置
同时,this在函数执行过程中,this一旦被确定了,就不可以再更改
var a = 10;
var obj = {
a: 20
}
function fn() {
this = obj; // 修改this,运⾏后会报错
console.log(this.a);
}
fn();
this的指向——也可以说是this的绑定,可以分为普通函数以及箭头函数两个板块
普通函数
谁调用,绑定谁
根据不同的使用场合,this有不同的值,主要分为默认绑定、隐式绑定、new绑定、显示绑定。
默认绑定
var name = 'Jenny';
function person() {
return this.name;
}
console.log(person()); //Jenny
默认绑定的意思就是window绑定,为什么是输出Jenny?因为在全局环境下定义的person函数,并且直接调用的话,person()前会省略window,即window.person()。
那么此时就很清晰明了了,person函数是window调用的,那么this就指向window。所以代码就变成了:
return window.name
也就是控制台会输出Jenny。
注意:严格模式下,不能将全局对象window用于默认绑定,this会绑定到 undefined,只有函数运行在非严格模式下,默认绑定才能绑定到全局对象。
隐式绑定
function test() {
console.log(this.x);
}
var obj = {};
obj.x = 1;
obj.m = test;
obj.m(); // 1
上述函数中,test函数被赋值给了obj对象中的m属性,那么obj.m()这行代码,显然m函数是在被obj对象调用,此时this指向obj,那么代码也就变成了:
console.log(obj.x);
那么因此也就打印obj对象中的x属性,也就是1
var o = {
a:10,
b:{
fn:function(){
console.log(this.a); //undefined
}
}
}
o.b.fn();
this绑定遵循就近原则。
上述代码中,尽管fn是被多层对象调用,但是this依旧只指向它的上一级对象。因此fn的上一级对象是b,b中不存在a属性,因此返回undefined
这里再举一个特殊情况:
var o = {
a:10,
b:{
a:12,
fn:function(){
console.log(this.a); //undefined
console.log(this); //window
}
}
}
var j = o.b.fn;
j();
很奇怪吧,为什么this不指向b,反而跳过了b指向了window呢?
我们着重看这行代码:
var j = o.b.fn;
在这行代码中你看到了什么?
是=,没错,这是一个赋值操作,相当于把fn赋值给了j,此时j自己就是一个函数,并且不再于o和o.b有任何关系。所以当你执行j()时,相当于window.j(),也就是默认绑定,自然this指向了window
new绑定
通过构建函数new关键字生成一个实例对象,此时this指向这个实例对象
function test() {
this.x = 1;
}
var obj = new test();
obj.x // 1
上述代码之所以输出1,是因为new关键字会强行改变this的指向,令其指向这个实例对象obj。
特殊情况:
new过程遇到return一个对象,此时this指向为返回的对象。
function fn()
{
this.user = 'xxx';
return {};
}
var a = new fn();
console.log(a.user); //undefined
如果返回一个简单类型的时候,则this指向实例对象。
function fn()
{
this.user = 'xxx';
return 1;
}
var a = new fn;
console.log(a.user); //xxx
注意的是null虽然也是对象,但是此时this仍然指向实例对象。
function fn()
{
this.user = 'xxx';
return null;
}
var a = new fn;
console.log(a.user); //xxx
显示绑定
apply()、call()、bind() 是函数的一个方法,作用是改变函数的调用对象。它的第一个参数就表示改变后的调用这个函数的对象。因此,这时 this 指的就是这第⼀个参数
var x = 0;
function test() {
console.log(this.x);
}
var obj = {};
obj.x = 1;
obj.m = test;
obj.m.apply(obj) // 1
箭头函数
只看定义位置,this不变
先看一段代码:
const obj = {
sayThis: () => {
console.log(this);
}
};
obj.sayThis(); // window
const globalSay = obj.sayThis;
globalSay(); // window 浏览器中的 global 对象
判断箭头函数的规则:
1.箭头函数没有自己的this,它只能蹭别人的this,然而能产生this的只有普通函数以及window,箭头函数会一直向上找,直到找到一个有this的大哥,随后箭头函数的this,就会指向这个大哥。
注意:这里我是进行了形象的解释,想要深入了解的话,可以去了解一下作用域。
那么我们来看这段代码,sayThis()是箭头函数,所以当它被调用时,会一直向上找,obj是对象,没有this,再向上找就找到了window,所以this指向的时window
let foo = () => console.log(this)
foo.call({ a:1 }); // window,call无效
2.箭头函数一旦定义,它的this就被永久锁定了。即使你用call或bind去修改,也会无效。
那么我再举一个特殊情况:
绑定事件监听
const button = document.getElementById('mngb');
button.addEventListener('click', ()=> {
console.log(this === window) // true
})
我们想要this指向为点击的button,但是它却指向了window。我明白你们的疑问,箭头函数向外跳,不应该被addEventListener函数接住吗?
那么好,小编来解释一下个人理解:
我们要分清楚,在函数内部以及作为函数参数的情况,函数内部我们都知道,但是如果是作为参数的话,代码可以理解为:
const button = document.getElementById('mngb');
// 1.在全局定义一个箭头函数(此时它的this已锁定为window)
const myCallback = () => {
console.log(this === window)
}
// 2.把这个已经锁定this的函数传给addEventListener
button.addEventListener('click', myCallback);
原型上添加方法
Cat.prototype.sayName = () => {
console.log(this === window) //true
return this.name
}
const cat = new Cat('mm');
cat.sayName()
箭头函数的this不得被修改,new失效;箭头函数在被定义时就已确定,sayName被定义时外面并没有函数包裹,所以它的this再向外跳就只能找到window。