JavaScript重点——this指向

        this指向是JS开发中非常重要的语法点,本文参考多篇优质博文进行归纳整理,欢迎讨论!

目录

一、默认绑定

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对象的内部和外部有任何区别,示例:

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.foo这样表示时,是直接取出地址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命令时,它后面的构造函数依次执行下面步骤:

(1)创建一个空对象,作为将要返回的对象实例

(2)将这个空对象的原型,指向构造函数的prototype属性

(3)将这个空对象赋值给函数内部的this关键字

(4)开始执行构造函数内部的代码

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

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

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

如果返回的不是对象,this就还是指向实例对象。

五、箭头函数绑定

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

    也就是说,函数体内的this就是定义时所在的对象,而非调用时所在的对象。

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

function fn () {
    return () => {
        console.log(this.name);
    }
}
var obj1 = {
    name:'Mark'
};
var obj2 = {
    name: 'John'
};
//箭头函数的this绑定成功,不能再修改
var tmp = fn.call(obj1);  
tmp();  //Mark
tmp.call(obj2); //Mark

//通过修改外层函数this指向,来间接修改箭头函数this指向
fn.call(obj1)(); //Mark
fn.call(obj2)(); //John

六、优先级

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

                                          

 参考资料:

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

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

3. JS this指向总结

猜你喜欢

转载自blog.csdn.net/huaf_liu/article/details/115344912