this绑定

this绑定问题

this绑定共有5种场景,包括默认绑定隐式绑定显式绑定new绑定箭头函数绑定

1、默认绑定

this默认绑定我们可以理解为函数调用时无任何调用前缀的情景,它无法应对我们后面的另外四种情况,所以称之为默认绑定,默认绑定时this指向全局对象(非严格模式,严格模式会指向undefined):

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

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

fn1();

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

但需要注意的是,在严格模式环境中,默认绑定的this将会指向undefined

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

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

var name = 'aaa';

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

再例如函数以及调用都暴露在严格模式中:

"use strict";
var name = 'aaa';
function fn() {
    console.log(this); //undefined
    console.log(this.name);//报错
};
fn();

但是如果在严格模式下调用不在严格模式中的函数,并不会影响this指向:

var name = 'aaa';
function fn() {
    console.log(this); //window
    console.log(this.name); //aaa
};

(function () {
    "use strict";
    fn();
}());

当访问一个变量时,解释器会先在当前作用域查找标识符,如果没有找到就去父作用域找,作用域链顶端是全局对象window,如果window都没有这个变量则报错。

当在对象上访问某属性时,首选i会查找当前对象,如果没有就顺着原型链往上找,原型链顶端是null,如果全程都没找到则返一个undefined,而不是报错。

2、隐式丢失

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

参数传递
var name = '穿梭时间的画面的钟';
let obj = {
    name: '从反方向开始移动',
    fn: function () {
        console.log(this.name);
    }
};

function fn1(param) {
    param();
};
fn1(obj.fn);//穿梭时间的画面的钟

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

变量赋值

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

var name = '穿梭时间的画面的钟';
let obj = {
    name: '从反方向开始移动',
    fn: function () {
        console.log(this.name);
    }
};
let fn1 = obj.fn;
fn1(); //穿梭时间的画面的钟

隐式绑定丢失并不是都会指向全局对象:

var name = '穿梭时间的画面的钟';
let obj = {
    name: '从反方向开始移动',
    fn: function () {
        console.log(this.name);
    }
};
let obj1 = {
    name: '人面祇今何处去'
}
obj1.fn = obj.fn;
obj1.fn(); //人面祇今何处去

虽然丢失了 obj 的隐式绑定,但是在赋值的过程中,又建立了新的隐式绑定,这里this就指向了对象 obj1

3、显式绑定

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

let obj1 = {
    name: '从反方向开始移动'
};
let obj2 = {
    name: '人面祇今何处去'
};
let obj3 = {
    name: '桃花依旧笑春风'
}
var name = '穿梭时间的画面的钟';

function fn() {
    console.log(this.name);
};
fn(); //穿梭时间的画面的钟
fn.call(obj1); //从反方向开始移动
fn.apply(obj2); //人面祇今何处去
fn.bind(obj3)(); //桃花依旧笑春风

在上面的代码中,我们分别通过callapplybind改变了函数fnthis指向

在js中,当我们调用一个函数时,我们习惯称之为函数调用,函数处于一个被动的状态;而callapply让函数从被动变主动,函数能主动选择自己的上下文,所以这种写法又被称之为函数应用

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

let obj1 = {
    name: '从反方向开始移动'
};
let obj2 = {
    name: '人面祇今何处去'
};
var name = '穿梭时间的画面的钟';

function fn() {
    console.log(this.name);
};
fn.call(undefined); //穿梭时间的画面的钟
fn.apply(null); //穿梭时间的画面的钟
fn.bind(undefined)(); //穿梭时间的画面的钟

另外,在js API中部分方法也内置了显式绑定,如forEach()

let obj = {
    name: '从反方向开始移动'
};

[1, 2, 3].forEach(function () {
    console.log(this.name);//从反方向开始移动*3
}, obj);
tips:call、apply和bind的区别:

1.callapply与bind都用于改变this绑定,但callapply在改变this指向的同时还会执行函数,而bind在改变this后是返回一个全新的boundFcuntion绑定函数,这也是为什么上面的例子中bind后还加了一对括号 ()的原因。

2.bind属于硬绑定,返回的 boundFunctionthis 指向无法再次通过bindapplycall 修改;callapply的绑定只适用当前调用,调用完就没了,下次要用还得再次绑。

3.callapply功能完全相同,唯一不同的是call方法传递函数调用形参是以散列形式,而apply方法的形参是一个数组。在传参的情况下,call的性能要高于apply,因为apply在执行时还要多一步解析数组。

4、new绑定

准确来说,js中的构造函数只是使用new 调用的普通函数,它并不是一个类(但可以看成一个类,es6中的class也只是语法糖),最终返回的对象也不是一个实例,只是为了便于理解习惯这么说罢了。

new一个函数:

  1. 以构造器的prototype属性为原型,创建新对象;

  2. this(可以理解为上句创建的新对象)和调用参数传给构造器,执行;

  3. 如果构造器没有手动返回对象,则返回第一步创建的对象

这个过程称之为构造调用:

function Fn(){
    this.name = '从反方向开始移动';
};
let 桃花依旧笑春风 = new Fn();
桃花依旧笑春风.name//从反方向开始移动

在上方代码中,构造调用创建了一个新对象桃花依旧笑春风,而在函数体内,this将指向新对象桃花依旧笑春风上(可以抽象理解为新对象就是this)。

5、this绑定的优先级

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

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

*显式绑定new绑定不能同时存在

function Fn(){
    this.name = '从反方向开始移动';
};
let obj = {
    name:'穿梭时间的画面的钟'
}
let 桃花依旧笑春风 = new Fn().call(obj);//报错 call is not a function

6、箭头函数的this

箭头函数懂得都懂,没有this,箭头函数的this是外层作用域的this:即外层的~指向谁,箭头函数的 this就指向谁

另外,一旦箭头函数的this绑定成功,也无法被再次修改。

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

function fn() {
    return () => {
        console.log(this.name);
    };
};
let obj1 = {
    name: '从反方向开始移动'
};
let obj2 = {
    name: '人面祇今何处去'
};
fn.call(obj1)(); // fn this指向obj1,箭头函数this也指向obj1
fn.call(obj2)(); //fn this 指向obj2,箭头函数this也指向obj2
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

27岁干全栈

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值