this指向

本文深入解析JavaScript中this的五种绑定规则:默认、隐式、显式、new及箭头函数绑定,阐述不同情景下this指向的变化及原因,帮助开发者理解和避免常见错误。

大部分情况可遵循:谁调用的,this就指向谁,没有指定谁调用的就默认是全局对象(浏览器环境是window,在nodejs中指向这个文件的module对象)

一、默认绑定

概念:函数调用时无任何调用前缀的情景

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

function fn1() {
    "use strict";
    console.log(this); //undefined
    console.log(this.name);//报错
};

var name = '123';

fn(); 
fn1() //TypeError: Cannot read property 'a' of undefined

函数调用时前面没有指定任何对象,这种情况下this指向全局对象window(非严格模式),严格模式指向undefined

为什么this指向全局对象window(非严格模式)?
在这个上下文(执行环境)中函数并没有绑定任何一个对象,往上找,所以 this 指向 window
从作用域和调用链方面看就很好理解了,函数 fn 的上一级是全局, 相当于是全局调用的fn,所以fnthis 指向全局

为什么严格模式this指向undefined
在严格模式下,this将保持他进入执行环境时的值,如果 this 没有被执行环境(execution context)定义,那它将保持为 undefined

二、隐式绑定

概念:如果函数调用时,前面存在调用它的对象,那么this就会隐式绑定到这个对象上,如果函数调用前存在多个对象,this指向距离调用自己最近的对象

function fn() {
    console.log(this.name);
};
let obj = {
    func: fn,
};
let obj1 = {
    name: '听风是风',
    o: obj
};
obj1.o.func() //undefined

为什么?
obj1.o === obj
obj.func === fn
综上:是obj调用的fn,所以this指向objthis.nameobj.nameobj并没有name属性,所以是undefined

隐式丢失

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

var name = '111';
let obj = {
    name: '222',
    fn: function () {
        console.log(this.name);
    }
};
let obj1 = {
    name: '333'
}

function fn1(param) {
    param();
};

fn1(obj.fn);//111

let fn2 = obj.fn;
fn2(); //111

obj1.fn = obj.fn;
obj1.fn(); //333

分析:

  • obj.fn作为fn1的参数传入,调用obj.fn的函数就是fn1,而fn1并没有绑定到任意对象上,所以this指向全局对象window,所以输出111
  • obj.fn赋值给fn2执行fn2(),fn2默认绑定的是全局对象window,所以this指向window,所以输出111
  • obj1添加fn属性,赋值为obj.fn,执行obj1.fn()this就指向了调用它的对象 obj1,所以输出333

三、显式绑定

概念通过callapply以及bind方法改变this的行为,相比隐式绑定,我们能清楚的感知 this 指向变化过程。

在javascript中调用一个函数时,函数处于一个被动的状态,称之为函数调用

callapply让函数从被动变主动,函数能主动选择自己的上下文,称之为函数应用

let obj1 = {
    name: '111'
};
let obj2 = {
    name: '222'
};
let obj3 = {
    name: '333'
}
var name = '444';

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

//call和apply改变this指向的同时还会执行函数
fn.call(obj1); //111
fn(); //444

fn.apply(obj2); //222
fn(); //444

//bind返回的是一个bound func, 所以要加个()执行
fn.bind(obj3)(); //333

let boundFn = fn.bind(obj1);
boundFn.call(obj2);//111
boundFn.apply(obj2);//111
boundFn.bind(obj2)();//111
fn(); //444

注意
如果在使用call之类的方法改变this指向时,指向参数提供的是null或者undefined,那么 this 将指向全局对象。

call、apply与bind区别

  • callapplybind都用于改变this绑定。但callapply改变this指向的同时还会执行函数,而bind在改变this后是返回一个全新的boundFunction绑定函数
    这也是为什么上方例子中bind后还加了一对括号 ()的原因。
  • bind属于硬绑定,返回的 boundFunction的 this 指向无法再次通过bindapplycall 修改;callapply的绑定只适用当前调用,调用完就没了,下次要用还得再次绑。
  • callapply功能完全相同,唯一不同的是 call方法的形参是散列形式 ,而 apply方法的形参是一个数组 。在传参的情况下,call的性能要高于apply,因为apply在执行时还要多一步解析数组。
let obj = {
    name: 'aaa'
};

function fn(age,describe) {
    console.log(`我是${this.name},我的年龄是${age}${describe}!`);
};
fn.call(obj,'26','ccc');//我是aaa,我的年龄是26,ccc
fn.apply(obj,['26','ccc']);//我是aaa,我的年龄是26,ccc

四、new绑定

new一个函数三大步
  • 以构造器的prototype属性为原型,创建新对象;
  • this(可以理解为:上句创建的新对象)和调用参数传给构造器,执行;
  • 如果构造器没有手动返回对象,则返回第一步创建的对象
function Fn(){
    this.name = '111';
};
let echo = new Fn();
echo.name//111

构造调用创建了一个新对象echo,而在函数体内,this将指向新对象echo上(可以抽象理解为新对象就是this)。

this绑定优先级

  • 显式绑定 > 隐式绑定 > 默认绑定
  • new绑定 > 隐式绑定 > 默认绑定
  • 显示和new同时出现会报错。

为什么显示和new同时出现会报错?
new Fn()返回的是一个对象,而callFunction的方法,所以报错 call is not a function

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

优先级示例:

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

obj1 = {
    name:'222'
};

//显式>隐式,
obj.fn.call(obj1);// 222

//new>隐式
let echo = new obj.fn();
//相当于把obj.fn当做构造器 new出一个对象,新对象的name属性来自原构造方法
echo.name;//111

五、箭头函数绑定

准确来说,箭头函数中没有this箭头函数的this指向取决于外层作用域中的this,外层作用域或函数的this指向谁,箭头函数中的this便指向谁。

function fn() {
    return () => {
        console.log(this.name);
    };
}
let obj1 = {
    name: '111'
};
let obj2 = {
    name: '222'
};

fn.call(obj1)(); // 111,fn this指向obj1,箭头函数this也指向obj1
fn.call(obj2)(); // 222,fn this 指向obj2,箭头函数this也指向obj2

let bar = fn.call(obj1); //fn this指向obj1,bar返回的是一个函数
bar.call(obj2); //111

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

为什么箭头函数的this绑定成功后无法被再次修改?
箭头函数的this是在词法层面就绑定到了外层作用域,他的this只能是来自外层作用域的this,无论你通过什么方式都不能改变,除非你修改了外层作用域的this

如何修改箭头函数this的指向?
箭头函数的this就像作用域继承一样从上层作用域找,因此我们可以修改外层函数this指向达到间接修改箭头函数this的目的。

setTimeout中的this指向

setTimeout(超时调用)都是在全局作用域中执行的,所以函数中的this的值在非严格模式下指向window对象,在严格模式下是undefined

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值