【JavaScript】【表达式和运算符】this

一、介绍

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

在绝大多数情况下,函数的调用方式决定了this的值(运行时绑定)。this不能在执行期间被赋值,并且在每次函数被调用时this的值也可能会不同

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

常见的this指向:

  • 全局作用域中或者普通函数中 => this指向全局对象window
  • 立即执行函数 => this指向window
  • 定时器 => this指向window
  • 事件 => this指向事件源对象
  • 方法 => this指向调用者
  • 构造函数 => this指向对象实例

二、绑定规则

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

  • 默认绑定
  • 隐式绑定
  • new 绑定
  • 显示绑定

2.1 默认绑定

  • 默认绑定全局对象window

    js 复制代码
    console.log(this === window);   //true
  • 全局作用域下独立调用函数,this指向window

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

    上面代码中,全局环境中定义person函数,内部使用this关键字。输出结果是Jenny。原因是:调用函数的对象在浏览器中位于window,因此this指向window,所以输出Jenny

    我们可以看看window中是否存在name这个变量:

    如上图所示,window中是存在name变量的。

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

2.2 隐式绑定

简单理解:谁调用指向谁

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

    js 复制代码
    function test() {
      console.log(this.x);
    }
    var name = "张三"
    var obj = {};
    obj.x = 1;
    obj.m = test;
    
    obj.m(); // 1

    上述代码,x是挂在obj下的,所以this.x的值是1

    在控制台中,name是挂在window下,所以我们可以尝试在test函数中打印this.name,看看是什么:

    从上图,我们可以发现,this.name的结果是undefined。这说明this的指向不是window,所以找不到name这个字段,所以是undefined

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

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

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

  • this永远 指向的是最后调用它的对象

    js 复制代码
    var o = {
        a:10,
        b:{
            c: 'zhangsan',
            fn:function(){
                console.log(this.a); // undefined
                console.log(this.c); // undefined
                console.log(this); // window
            }
        }
    }
    var func = o.b.fn;
    
    func()

    从上述结果,我们可知,此时this指向的是window。因为 this永远指向的是最后调用它的对象 。虽然fn是对象b的方法,但是fn赋值给j时候并没有执行,所以最终指向window

2.3 new 绑定

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

    js 复制代码
    function test() {
     this.x = 1;
    }
    
    var obj = new test();
    obj.x // 1

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

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

    js 复制代码
    function fn() {  
        this.user = 'xxx';  
        return {
            name: "张三"
        };  
    }
    var a = new fn();  
    console.log(a.user); //undefined
  • new过程遇到return一个简单类型的对象,此时this指向为实例对象

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

    【注意】如果是return的是null,此时this指向仍为实例对象 ,因为null是简单类型

2.4 显示绑定

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

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

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

三、 箭头函数

3.1 箭头函数的this指向

在ES6的语法中提供了箭头函数语法,让我们在代码书写时就能确定this的指向(编译时绑定)。

举个栗子:

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

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

3.2 this指向的潜在坑

虽然箭头函数的this能够在编译的时候就确定了this的指向,但也需要注意一些潜在的坑

  • 绑定监听事件

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

    通过上述代码和截图,我们可得:此时this指向了window,但是实际上,我们想要的是this为点击的button

    我们直接打印this,查看this的值,可以更直观:

  • 构建函数

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

3.3 箭头函数this不遵循上述的四种规则:

  • 独立调用对箭头函数无效:

    js 复制代码
    var a = 0
    function foo() {
      let test = () => {
        console.log(this)
      }
      return test
    }
    let obj = { a: 1, foo: foo }
    obj.foo()()
    //  obj.foo()返回test  obj.foo()()调用test  而且是独立调用  但是this还是指向obj
  • 隐式绑定对箭头函数无效:

    js 复制代码
    let a = 0
    let obj1 = {
      a: 1,
      foo: () => {
        console.log(this);
      }
    }
    obj1.foo() //指向window
  • 显式绑定对箭头函数无效:

    js 复制代码
    var a = 0
    function foo() {
      let test = () => {
        console.log(this)
      }
      return test
    }
    let obj1 = {
      a: 1,
      foo: foo
    }
    let obj2 = {
      a: 2,
      foo: foo
    }
    obj1.foo().call(obj2)
    
    //obj1.foo()返回test   obj1.foo.call(obj2)把test的指向绑定到obj2上,无效,this依然指向obj1

3.4 总结

综上所述,我们可以总结出:

  1. 创建箭头函数时,就已经确定了它的 this 指向。
  2. 箭头函数没有子级的this指向,它的 this 指向外层的 this
  3. 箭头函数没有子级的作用域,绑定的是父级作用域的上下文

四、优先级

this绑定的优先级:new 绑定 > 显示绑定 > 隐式绑定 > 默认绑定

4.1 隐式绑定 VS 显示绑定

js 复制代码
function foo() {
    console.log( this.a );
}

var obj1 = {
    a: 2,
    foo: foo
};

var obj2 = {
    a: 3,
    foo: foo
};

obj1.foo(); // 2
obj2.foo(); // 3

obj1.foo.call( obj2 ); // 3
obj2.foo.call( obj1 ); // 2

4.2 new绑定 VS 隐式绑定

js 复制代码
function foo(something) {
    this.a = something;
}

var obj1 = {
    foo: foo
};

var obj2 = {};

obj1.foo( 2 );
console.log( obj1.a ); // 2

obj1.foo.call( obj2, 3 );
console.log( obj2.a ); // 3

var bar = new obj1.foo( 4 );
console.log( obj1.a ); // 2
console.log( bar.a ); // 4

可以看到,new绑定的优先级>隐式绑定

4.3 new 绑定 VS 显示绑定

因为newapply、call无法一起使用,但硬绑定也是显式绑定的一种,可以替换测试

ini 复制代码
function foo(something) {
    this.a = something;
}

var obj1 = {};

var bar = foo.bind( obj1 );
bar( 2 );
console.log( obj1.a ); // 2

var baz = new bar( 3 );
console.log( obj1.a ); // 2
console.log( baz.a ); // 3

bar被绑定到obj1上,但是new bar(3) 并没有像我们预计的那样把obj1.a修改为3。但是,new修改了绑定调用bar()中的this

我们可认为new绑定优先级>显式绑定

参考

相关推荐
大怪v7 小时前
【Virtual World 04】我们的目标,无限宇宙!!
前端·javascript·代码规范
努力学算法的蒟蒻7 小时前
day27(12.7)——leetcode面试经典150
算法·leetcode·面试
狂炫冰美式8 小时前
不谈技术,搞点文化 🧀 —— 从复活一句明代残诗破局产品迭代
前端·人工智能·后端
xw58 小时前
npm几个实用命令
前端·npm
!win !9 小时前
npm几个实用命令
前端·npm
代码狂想家9 小时前
使用openEuler从零构建用户管理系统Web应用平台
前端
dorisrv10 小时前
优雅的React表单状态管理
前端
蓝瑟10 小时前
告别重复造轮子!业务组件多场景复用实战指南
前端·javascript·设计模式
dorisrv11 小时前
高性能的懒加载与无限滚动实现
前端
韭菜炒大葱11 小时前
别等了!用 Vue 3 让 AI 边想边说,字字蹦到你脸上
前端·vue.js·aigc