JavaScript重点——this指向

目录

一、默认绑定

1.1 调用全局函数

1.2 调用函数内的嵌套函数

1.3  调用对象的方法内的嵌套函数

二、隐式绑定

三、显式绑定

四、new绑定

五、箭头函数绑定

六、优先级


this指向虽然概念抽象,变化多端,但总结起来只有两句话:

        1)this指向在函数定义的时候是确定不了的,只有函数执行或调用的时候才能确定。

        2)哪个对象调用函数,函数中的this就指向哪个对象

一、默认绑定

数调用时没有明显前缀,也就是单纯作为独立函数调用的时候,将对函数中的this采用默认绑定的方式,指向全局对象window。但严格模式中,函数环境下的this指向undefined,全局环境下的this指向window。

1.1 调用全局函数

function fn() {    
    console.log(this === window);  //fn()是全局函数,this指向window
}
fn();  //true

补充1——严格模式:

function fn() {    
    'use strict';
    console.log(this === window);  //fn()是全局函数,严格模式下,this指向undefined
}
fn();  //false

补充2——ES6:

顶层对象在浏览器环境指的是window对象,在Node指的是global对象。ES5之前,顶层对象的属性和全局变量是等价的,而ES6开始规定:var命令和function命令声明的全局变量,依旧是顶层对象的属性,但let命令、const命令和class命令声明的全局变量,不属于顶层对象的属性,示例:

let name = 'Mark';  //let声明的变量不属于顶层对象window
function fn() {    
    console.log(this.name);  //this指向window
}
fn();  //undefined

1.2 调用函数内的嵌套函数

function fn() {
    function inner() {
        console.log(this === window); //inner是函数内部的函数,this指向window
    }
    inner();
}
fn();  //true

示例:(注意与作用域的区别)

var a = 1;
console.log(this.a);  //1 
function fn() {
    var a = 2;
    console.log(this.a); //1 

    function inner() {
        var a = 3;
        console.log(this.a); //1 
    }
    inner();
}
fn();  //1

1.3  调用对象的方法内的嵌套函数

var obj = {
    fn:function() { //fn函数用到了隐式绑定
        function inner(){  //inner函数没有明确的调用对象
            console.log(this === window);  //inner是对象的方法内的嵌套函数,this指向window
        }
        inner();
    }
}
obj.fn();  //true

示例:(注意与调用函数方法区别)

var a = 1;
console.log(this.a); //1 this指向全局变量window
var obj = {
    a: 2,
    fn: function () {
        var a = 3;
        console.log(this.a); //2 因为是obj.fn(),调用了fn函数,因为this指向了obj,输出了obj下的a=2
        function inner() {
            var a = 4;
            console.log(this.a); //1 未明确调用对象,this指向window
        }
        inner(); //没有明确调用的对象
        console.log(this.a); //2 this指向obj
    }
}
obj.fn();

二、隐式绑定

当函数调用时,前面有明确的调用对象,这时this就会隐式绑定到这个对象上。

var obj = {
    name:'Mark',
    fn: function () {  //fn()的this隐式绑定到obj
        console.log(this === obj); //true
        console.log(this.name);  //Mark
    }
}
obj.fn();  

fn函数并不会因为它定义在obj对象的内部和外部有任何区别,示例:

【注意】fn和obj.fn两者的区别,前者单独拿出来依然是隐式绑定,后者则隐式丢失

function fn() {  
    console.log(this === obj); //true
    console.log(this.name);  //Mark
}
var obj = {
    name:'Mark',
    f:fn
}
obj.f();  

如果函数调用前有多个对象,this会指向离自己最近的对象,示例:

var obj = {
    name:'Mark',
    f1: {
        name:'John',
        f2: function () {
            console.log(this.name);
        }
    }
}
obj.f1.f2(); //John

隐式绑定丢失问题

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

(1)参数传递

var a = 1;
var obj = {
    a:2,
    fn:function () {
        console.log(this.a);
    }
}
function f2(f) {
    f();
}
f2(obj.fn); //参数传递导致隐式丢失,输出1

由于JS的内存机制,函数的引用类型,obj和obj.fn储存在两个内存地址,分别为地址1和地址2。obj.fn()直接调用时,是从地址1调用地址2,因此地址2的运行环境是地址1,this指向obj,但是,obj.fn这样表示时,是直接取出地址2再进行调用,如上代码,运行环境是f2所在的全局环境,this此时应指向window。

(2)变量赋值

var a = 1;
var obj = {
    a:2,
    fn:function () {
        console.log(this.a);
    }
}

var fglobal = obj.fn;
fglobal();  //变量赋值导致隐式丢失,输出1

(3)重新建立新的隐式绑定

隐式绑定丢失并不是都会指向全局对象,还可以重新建立新的隐式绑定,示例:

var a = 1;
var obj = {
    a:2,
    fn:function () {
        console.log(this.a);
    }
}
var obj1 = {
    a:3
}
obj1.fn = obj.fn;
obj1.fn(); //输出3

三、显式绑定

通过call、apply和bind方法改变this指向,称为显式绑定,其中当参数为null、undefined时,this将指向全局对象。

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

var obj1 = {
    name:'Mark'
}
var obj2 = {
    name:'John'
}

fn.call(obj1);  //Mark
fn.apply(obj2); //John

var tmp = fn.bind(obj1);
tmp(); //Mark

JS中部分方法也内置了显式绑定,示例:

var obj = {
    name:'Mark'
};

[1,2,3].forEach( function (){
    console.log(this.name); //Mark*3
}, obj);

call、apply和bind的区别:

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

var obj1 = {
    name:'Mark'
}
var obj2 = {
    name:'John'
}
//new_fn是返回的新函数,bind绑定后,this指向不能再改
var new_fn = fn.bind(obj1);
new_fn(); //Mark
new_fn.call(obj2); //Mark

//fn函数不是返回的新函数,bind绑定后,this指向仍可改
fn.bind(obj1)(); //Mark
fn.call(obj2);  //John

1. call、apply是立即执行函数fn,而bind是返回一个绑定了this的新函数,需要调用才能执行;

2. bind属于硬绑定返回的新函数的this指向不能再通过call、apply或bind修改;如果多次绑定,也以第一次为准;而call、apply的绑定只适用当前调用,下次要改变this指向还要再次绑定; 

3. call和apply的区别只在于参数的形式,call的参数是以散列形式,apply的参数是一个数组,因此call的性能高于apply,不需要解析数组

另外,当需要改变this指向时,除了以上三个方法,还可以存储this指向到变量中

function fn () {
    var _this = this;  //将this储存在变量中,而且不会改变定时器的指向
    setTimeout( function () {
        console.log(_this);  //obj
        console.log(this);   //window,定时器指向没有改变
    }, 1000)
}
var obj = {
    f:fn
};
obj.f();

四、new绑定

        new会创建一个连接到构造函数的prototype的新对象,同时,this始终指向实例对象

function Person(age, name) {
    this.age = age;
    this.name = name;
    console.log(this); //this指向实例对象p  Person {age: 17, name: 'Mark'}
}
var p = new Person(17, 'Mark');

        如果构造函数里返回的是一个对象(对象、数组、函数,不包含null),则this指向的就是这个返回对象,如果返回的不是对象,this就还是指向实例对象。

五、箭头函数绑定

        实际上,箭头函数根本没有自己的this,因此它内部的this指向的是外层this,这个指向在定义时就已经确定

1. 箭头函数的this指向取决于外层作用域的this,外层作用域的this指向谁,箭头函数的this就指向谁

2.我们可以通过改变外层作用域中的this指向,来改变箭头函数中this的指向

普通函数

function foo() {
    console.log(this.id);
    console.log(this);
    setTimeout(function(){
        console.log(this.id);
        console.log(this)
    },0)
}
var id = 2;
foo();
// 2  window  2  window

foo.call({id:5});
// 5  {id:5}  2   window

        setTimeout回调函数的代码都是在全局作用域下执行的,因此setTimeout回调函数中的this值在非严格模式下指向window,严格模式下指向undefined。

箭头函数:

function foo() {
    console.log(this.id);
    console.log(this);
    setTimeout(() => {
        console.log(this.id);
        console.log(this)
    },0)
}
var id = 2;
foo();
// 2  window  2  window

foo.call({id:5});
// 5  {id:5}  5   {id:5}

由于箭头函数没有自己的this,因此箭头函数的内部this指向的是外层作用域的this(foo函数)。我们可以通过改变外层作用域的this指向(foo.call方法)从而改变箭头函数的this指向。

六、优先级

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

                                          

 参考资料:

1. js 五种绑定彻底弄懂this,默认绑定、隐式绑定、显式绑定、new绑定、箭头函数绑定详解

2. 前端js中this指向及改变this指向

3. JS this指向总结

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值