JS中this关键字详解

JS中this是什么

它实际是在函数被调用时才发生的绑定,也就是说this具体指向什么,取决于你是怎么调用的函数。也就是说谁调用的this,this就指向谁。this的指向只取决于函数的调用方式

今天我们就从this绑定的五种场景出发,讲一下这个this

  1. 默认绑定
  2. 隐式绑定
  3. 显式绑定
  4. new绑定
  5. 箭头函数绑定

1.默认绑定
this默认绑定我们可以理解为函数调用时无任何调用前缀的情景,它无法应对我们后面要介绍的另外四种情况,所以称之为默认绑定,默认绑定时this指向全局对象 (或者说函数的独立调用)

function fn1() {
    let fn2 = function () {
        console.log(this); //window
        fn3();
    };
    console.log(this); //window
    fn2();
};

function fn3() {
    console.log(this); //window
};

fn1();

这个例子中无论函数声明在哪,在哪调用,由于函数调用时前面并未指定任何对象,这种情况下this指向全局对象window。

2.隐式绑定
什么是隐式绑定呢,如果函数调用时,前面存在调用它的对象,那么this就会隐式绑定到这个对象上
(对象调用,谁调用就指向谁)

function fn() {
    console.log(this.name);
};
let obj = {
    name: 'zs',
    func: fn
};
obj.func() //zs

如果函数调用前存在多个对象,this指向距离调用自己最近的对象,比如这样:

function fn() {
    console.log(this.name);
};
let obj = {
    name: 'zs',
    func: fn,
};
let obj1 = {
    name: 'lisi',
    o: obj
};
obj1.o.func() //zs

那如果我们将obj对象的name属性注释掉,现在输出什么呢

function fn() {
    console.log(this.name);
};
let obj = {
    func: fn,
};
let obj1 = {
    name: 'zs',
    o: obj
};
obj1.o.func()

这里输出undefined,大家千万不要将作用域链和原型链弄混淆了,obj对象虽然obj1的属性,但它两原型链并不相同,并不是父子关系,由于obj未提供name属性,所以是undefined

既然说到原型链,我们再改写例子,看看下面输出多少

function Fn() {};
Fn.prototype.name = 'zs';

function fn() {
    console.log(this.name);
};

let obj = new Fn();
obj.func = fn;

let obj1 = {
    name: 'lisi',
    o: obj
};
obj1.o.func()

这里输出zs,虽然obj对象并没有name属性,但顺着原型链,找到了产生自己的构造函数Fn,由于Fn原型链存在name属性,所以输出zs

作用域链与原型链的区别:
当访问一个变量时,解释器会先在当前作用域查找标识符,如果没有找到就去父作用域找,作用域链顶端是全局对象window,如果window都没有这个变量则报错。
当在对象上访问某属性时,首先i会查找当前对象,如果没有就顺着原型链往上找,原型链顶端是null,如果全程都没找到则返一个undefined,而不是报错。

2.2 隐式丢失
在特定情况下会存在隐式绑定丢失的问题,最常见的就是作为参数传递以及变量赋值,先看参数传递:

var name = 'zs';
let obj = {
    name: 'lisi',
    fn: function () {
        console.log(this.name);
    }
};

function fn1(param) {
    param();
};
fn1(obj.fn);//zs

这个例子中我们将 obj.fn 也就是一个函数传递进 fn1 中执行,这里只是单纯传递了一个函数而已,this并没有跟函数绑在一起,所以this丢失这里指向了window

第二个引起丢失的问题是变量赋值,其实本质上与传参相同

var name = 'zs';
let obj = {
    name: 'lisi',
    fn: function () {
        console.log(this.name);
    }
};
let fn1 = obj.fn;
fn1(); //zs

3. 显示绑定
显式绑定是指我们通过call、apply、bind方法来改变this的指向的 (相比隐式绑定,我们能清楚的感知 this 指向变化过程)

function fn(a, b, c, d, e) {
  console.log(this);
  console.log(a, b, c, d, e)
}
var obj = {
  a: 2,
  fn: fn,
}
var fn1 = obj.fn;
fn1() //this是指向window的
fn1.call(obj, 1, 2, 3, 4, 5); 强制改变this指向为obj 
fn1.apply(obj, [1, 2, 3, 4, 5]);强制改变this指向为obj 
fn1.bind(obj)(1, 2, 3, 4, 5); || bar.bind(obj,1,2,3,4,5)() || bar.bind(obj,1,2,3)(4,5) //强制改变this指向为obj

区别:
call:后面的参数是一个参数列表,一个一个传进去的
apply:里面的第二个参数是一个参数数组
bind:bind方法返回值是函数,没有立即执行。实际在使用 bind 的时候,你可以有两次机会传入参数,第一次是在 bind函数里面,第二次是在运行 bind 返回的匿名函数的时候

注意:
bar.call(null); //call和apply 指向参数提供的是null或者undefined,会绑定失败,他会使用默认绑定方式。
bind 第一个传null得话不改变this指向,并且能够在后续的调用中去传入参数

4.new绑定
js中的构造函数只是使用new 调用的普通函数 最终返回的对象我们叫实例化对象

函数通过new调用后,会返回一个新对象,并将新对象会绑定到函数调用的 this上 。
使用 new 调用函数,会自动执行下面的操作:
1.创建一个新对象。
2. 将新对象绑定到函数调用的 this上。这个新对象会被执行 [[prototype]] 连接。对象.proto = 构造函数.prototype
3. 执行构造函数
4. 如果函数没有返回其他对象,那么 new 表达式中的函数调用会自动返回这个新对象。

var a = "global";
function obj(){
  this.a = "obj";
}
var o = new obj();
console.log(o.a);   //obj

5.this绑定优先级
我们先介绍前四种this绑定规则,那么问题来了,如果一个函数调用存在多种绑定方法,this最终指向谁呢?这里我们直接先上答案,this绑定优先级为:

显式绑定 > 隐式绑定 > 默认绑定
new绑定 > 隐式绑定 > 默认绑定

为什么显式绑定不和new绑定比较呢?因为不存在这种绑定同时生效的情景,如果同时写这两种代码会直接抛错,所以大家只用记住上面的规律即可

function Fn(){
    this.name = 'fn';
};
let obj = {
    name:'obj'
}
let echo = new Fn().call(obj);//报错 call is not a function

那么我们结合几个例子来验证下上面的规律,首先是显式大于隐式:

//显式>隐式
let obj = {
    name:'obj',
    fn:function () {
        console.log(this.name);
    }
};
let obj1 = {
    name:'obj1'
};
obj.fn.call(obj1);// obj1

其次是new绑定大于隐式:

//new>隐式
obj = {
    name: 'obj',
    fn: function () {
        this.name = 'fn';
    }
};
let echo = new obj.fn();
echo.name;//fn

6.箭头函数的this
箭头函数中没有this,箭头函数的this指向取决于外层作用域中的this,外层作用域或函数的this指向谁,箭头函数中的this便指向谁。因为箭头函数没有this,因此它自身不能进行new实例化

function fn() {
    return () => {
        console.log(this.name);
    };
}
let obj1 = {
    name: 'zs'
};
let obj2 = {
    name: 'lisi'
};
let bar = fn.call(obj1); // fn this指向obj1
bar.call(obj2); //zs

为啥我们第一次绑定this并返回箭头函数后,再次改变this指向没生效呢?
前面说了,箭头函数的this取决于外层作用域的this,fn函数执行时this指向了obj1,所以箭头函数的this也指向obj1。除此之外,箭头函数this还有一个特性,那就是一旦箭头函数的this绑定成功,也无法被再
次修改(无法通过call和apply给箭头函数绑定this指向的)
,有点硬绑定的意思

当然,箭头函数的this也不是真的无法修改,我们知道箭头函数的this就像作用域继承一样从上层作用域找,因此我们可以修改外层函数this指向达到间接修改箭头函数this的目的。

function fn() {
    return () => {
        console.log(this.name);
    };
};
let obj1 = {
    name: 'zs'
};
let obj2 = {
    name: 'lisi'
};
fn.call(obj1)(); // fn this指向obj1,箭头函数this也指向obj1
fn.call(obj2)(); //fn this 指向obj2,箭头函数this也指向obj2
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值