【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绑定优先级>显式绑定

参考

相关推荐
dot.Net安全矩阵1 分钟前
.NET内网实战:通过命令行解密Web.config
前端·学习·安全·web安全·矩阵·.net
叫我:松哥5 分钟前
基于Python flask的医院管理学院,医生能够增加/删除/修改/删除病人的数据信息,有可视化分析
javascript·后端·python·mysql·信息可视化·flask·bootstrap
Hellc00712 分钟前
MacOS升级ruby版本
前端·macos·ruby
UestcXiye15 分钟前
面试算法题精讲:求数组两组数差值和的最大值
面试·数据结构与算法·前后缀分解
严格格16 分钟前
三范式,面试重点
数据库·面试·职场和发展
前端西瓜哥21 分钟前
贝塞尔曲线算法:求贝塞尔曲线和直线的交点
前端·算法
又写了一天BUG22 分钟前
npm install安装缓慢及npm更换源
前端·npm·node.js
cc蒲公英35 分钟前
Vue2+vue-office/excel 实现在线加载Excel文件预览
前端·vue.js·excel
Java开发追求者36 分钟前
在CSS中换行word-break: break-word和 word-break: break-all区别
前端·css·word
好名字082140 分钟前
monorepo基础搭建教程(从0到1 pnpm+monorepo+vue)
前端·javascript