谈谈this对象的理解以及this的指向问题

文章目录

this的定义

函数的 this 关键字在 JavaScript 中的表现略有不同,此外,在严格模式和非严格模式之间也会有一些差别

在绝大多数情况下,函数的调用方式决定了 this 的值(运行时绑定)

this 关键字是函数运行时自动生成的一个内部对象,只能在函数内部使用,总指向调用它的对象

clike 复制代码
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一旦被确定了,就不可以再更改

clike 复制代码
var a = 10;
var obj = {
  a: 20
}

function fn() {
  this = obj; // 修改this,运行后会报错
  console.log(this.a);
}

fn();

绑定规则

根据不同的使用场合,this有不同的值,主要分为下面几种情况:

  • 默认绑定

  • 隐式绑定

  • new绑定

  • 显示绑定

默认绑定

全局环境中定义person函数,内部使用this关键字

clike 复制代码
var name = 'Jenny';
function person() {
    return this.name;
}
console.log(person());  //Jenny

上述代码输出Jenny,原因是调用函数的对象在游览器中位window,因此this指向window,所以输出Jenny

注意:

严格模式下,不能将全局对象用于默认绑定,this会绑定到undefined,只有函数运行在非严格模式下,默认绑定才能绑定到全局对象

隐式绑定

函数还可以作为某个对象的方法调用,这时this就指这个上级对象

clike 复制代码
function test() {
  console.log(this.x);
}

var obj = {};
obj.x = 1;
obj.m = test;

obj.m(); // 1

这个函数中包含多个对象,尽管这个函数是被最外层的对象所调用,this指向的也只是它上一级的对象

clike 复制代码
var o = {
    a:10,
    b:{
        fn:function(){
            console.log(this.a); //undefined
        }
    }
}
o.b.fn();

上述代码中,this的上一级对象为bb内部并没有a变量的定义,所以输出undefined

这里再举一种特殊情况

clike 复制代码
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指向的是window,这里的大家需要记住,this永远指向的是最后调用它的对象,虽然fn是对象b的方法,但是fn赋值给j时候并没有执行,所以最终指向window

new绑定

通过构建函数new关键字生成一个实例对象,此时this指向这个实例对象

clike 复制代码
function test() {
 this.x = 1;
}

var obj = new test();
obj.x // 1

上述代码之所以能过输出1,是因为new关键字改变了this的指向

new过程遇到return一个对象,此时this`指向为返回的对象

clike 复制代码
function fn()  
{  
    this.user = 'xxx';  
    return {};  
}
var a = new fn();  
console.log(a.user); //undefined

如果返回一个简单类型的时候,则this指向实例对象

clike 复制代码
function fn()  
{  
    this.user = 'xxx';  
    return 1;
}
var a = new fn;  
console.log(a.user); //xxx

注意的是null虽然也是对象,但是此时new仍然指向实例对象

clike 复制代码
function fn()  
{  
    this.user = 'xxx';  
    return null;
}
var a = new fn;  
console.log(a.user); //xxx

显示修改

apply()、call()、bind()是函数的一个方法,作用是改变函数的调用对象。它的第一个参数就表示改变后的调用这个函数的对象。因此,这时this`指的就是这第一个参数

clike 复制代码
var x = 0;
function test() {
 console.log(this.x);
}

var obj = {};
obj.x = 1;
obj.m = test;
obj.m.apply(obj) // 1

箭头函数

clike 复制代码
const obj = {
  sayThis: () => {
    console.log(this);
  }
};

obj.sayThis(); // window 因为 JavaScript 没有块作用域,所以在定义 sayThis 的时候,里面的 this 就绑到 window 上去了
const globalSay = obj.sayThis;
globalSay(); // window 浏览器中的 global 对象

绑定事件监听

clike 复制代码
const button = document.getElementById('mngb');
button.addEventListener('click', ()=> {
    console.log(this === window) // true
    this.innerHTML = 'clicked button'
})

上述可以看到,我们其实是想要this为点击的button,但此时this指向了window

包括在原型上添加方法时候,此时this指向window

clike 复制代码
Cat.prototype.sayName = () => {
    console.log(this === window) //true
    return this.name
}
const cat = new Cat('mm');
cat.sayName()

this的指向问题

this的指向大致可以分为以下4种。

❏ 作为对象的方法调用。

❏ 作为普通函数调用。

❏ 构造器调用。

❏ Function.prototype.call或Function.prototype.apply调用。

1.作为对象的方法调用

当函数作为对象的方法被调用时,this指向该对象:

2.作为普通函数调用

当函数不作为对象的属性被调用时,也就是我们常说的普通函数方式,此时的this总是指向全局对象。在浏览器的JavaScript里,这个全局对象是window对象。

有时候我们会遇到一些困扰,比如在div节点的事件函数内部,有一个局部的callback方法,callback被作为普通函数调用时,callback内部的this指向了window,但我们往往是想让它指向该div节点,见如下代码

此时有一种简单的解决方案,可以用一个变量保存div节点的引用

在ECMAScript 5的strict模式下,这种情况下的this已经被规定为不会指向全局对象,而是undefined:

3.构造器调用

JavaScript中没有类,但是可以从构造器中创建对象,同时也提供了new运算符,使得构造器看起来更像一个类。除了宿主提供的一些内置函数,大部分JavaScript函数都可以当作构造器使用。构造器的外表跟普通函数一模一样,它们的区别在于被调用的方式。当用new运算符调用函数时,该函数总会返回一个对象,通常情况下,构造器里的this就指向返回的这个对象,见如下代码:

但用new调用构造器时,还要注意一个问题,如果构造器显式地返回了一个object类型的对象,那么此次运算结果最终会返回这个对象,而不是我们之前期待的this:

如果构造器不显式地返回任何数据,或者是返回一个非对象类型的数据,就不会造成上述问题:

4.Function.prototype.call或Function.prototype.apply调用

call和apply方法能很好地体现JavaScript的函数式语言特性,在JavaScript中,几乎每一次编写函数式语言风格的代码,都离不开call和apply。在JavaScript诸多版本的设计模式中,也用到了call和apply。

丢失的this

当调用obj.getName时,getName方法是作为obj对象的属性被调用的,根据2.1.1节提到的规律,此时的this指向obj对象,所以obj.getName()输出'sven'。

当用另外一个变量getName2来引用obj.getName,并且调用getName2时,根据2.1.2节提到的规律,此时是普通函数调用方式,this是指向全局window的,所以程序的执行结果是undefined。再看另一个例子,document.getElementById这个方法名实在有点过长,我们大概尝试过用一个短的函数来代替它,如同prototype.js等一些框架所做过的事情

我们也许思考过为什么不能用下面这种更简单的方式:

在Chrome、Firefox、IE10中执行过后就会发现,这段代码抛出了一个异常。这是因为许多引擎的document.getElementById方法的内部实现中需要用到this。这个this本来被期望指向document,当getElementById方法作为document对象的属性被调用时,方法内部的this确实是指向document的。但当用getId来引用document.getElementById之后,再调用getId,此时就成了普通函数调用,函数内部的this指向了window,而不是原来的document。我们可以尝试利用apply把document当作this传入getId函数,帮助"修正"this:

有需要的请私信博主,还请麻烦给个关注,博主不定期更新,或许能够有所帮助!!请关注公众号

相关推荐
new出一个对象2 小时前
uniapp接入BMapGL百度地图
javascript·百度·uni-app
你挚爱的强哥3 小时前
✅✅✅【Vue.js】sd.js基于jQuery Ajax最新原生完整版for凯哥API版本
javascript·vue.js·jquery
y先森3 小时前
CSS3中的伸缩盒模型(弹性盒子、弹性布局)之伸缩容器、伸缩项目、主轴方向、主轴换行方式、复合属性flex-flow
前端·css·css3
前端Hardy3 小时前
纯HTML&CSS实现3D旋转地球
前端·javascript·css·3d·html
susu10830189113 小时前
vue3中父div设置display flex,2个子div重叠
前端·javascript·vue.js
IT女孩儿4 小时前
CSS查缺补漏(补充上一条)
前端·css
吃杠碰小鸡5 小时前
commitlint校验git提交信息
前端
虾球xz6 小时前
游戏引擎学习第20天
前端·学习·游戏引擎
我爱李星璇6 小时前
HTML常用表格与标签
前端·html
疯狂的沙粒6 小时前
如何在Vue项目中应用TypeScript?应该注意那些点?
前端·vue.js·typescript