To understand 'this' deeply

159 阅读4分钟

this的5种绑定:默认绑定、隐式绑定、显式绑定、new绑定、箭头函数绑定。

1**默认绑定

函数调用时无任何调用前缀的情景,this指向全局对象,即window(非严格模式),严格模式下指向undefined。

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

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

fn1();

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

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

var name = 'C罗';
fn(); 
fn1() //TypeError: Cannot read property 'name' of undefined

另严格模式下调用不在严格模式中的函数,不影响this指向。

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

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

2**隐式绑定

如果函数调用时,前面存在调用它的对象,this就会隐式绑定到该对象

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

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

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

function fn() {
    console.log(this.name);
};
let obj = {
    name: '梅西',    
    func: fn,
};
let obj1 = {
    name: 'C罗',   
    o: obj,
};
obj1.o.func() //梅西

作用域链与原型链不同,

function fn() {
    console.log(this.name);
};
let obj = {
    func: fn,
};
let obj1 = {
    name: 'C罗',    
    o: obj,
};
obj1.o.func() //undefined的

二者区别:

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

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

隐式丢失

a) 参数传递

var name = '梅西';
let obj = {
    name: 'C罗',    
    fn: function () {
        console.log(this.name);
    }
};
function fn1(param) {
    param();
};
fn1(obj.fn);//梅西

b) 变量赋值

var name = '梅西';
let obj = {
    name: 'C罗',    
    fn: function () {
        console.log(this.name);
    }
};
let fn1 = obj.fn;
fn1(); //梅西

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

var name = '梅西';
let obj = {
    name: 'C罗',    
    fn: function () {
        console.log(this.name);
    }
};
let obj1 = {
    name: '伊布拉辛诺维奇'
}
obj1.fn = obj.fn;
obj1.fn(); //伊布拉辛诺维奇

3**显式绑定

通过call、apply以及bind方法改变this的行为,相比隐式绑定,我们能清楚的感知 this 指向变化过程(通过call、apply、bind改变了函数fn的this指向)

let obj1 = {
    name: 'C罗'
};
let obj2 = {
    name: 'C罗'
};
let obj3 = {
    name: 'echo'
}
var name = '梅西';
function fn() {
    console.log(this.name);
};
fn(); //梅西
fn.call(obj1); //C罗
fn.apply(obj2); //伊布拉辛诺维奇
fn.bind(obj3)(); //echo

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

let obj1 = {
    name: 'C罗'
};
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: 'C罗'
};
[1, 2, 3].forEach(function () {
    console.log(this.name);//C罗*3
}, obj);

call、apply与bind之区别

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

2.bind属于硬绑定,返回的 boundFunction 的 this 指向无法再次通过bind、apply或 call 修改;call与apply的绑定只适用当前调用,调用完就没了,下次要用还得再次绑。

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

4**new绑定

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

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

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

1.以构造器的prototype属性为原型,创建新对象;2.this(可以理解为上句创建的新对象)和调用参数传给构造器,执行;3.如果构造器没有手动返回对象,则返回第一步创建的对象

function Fn(){
    this.name = 'C罗';
};
let echo = new Fn();
echo.name//C罗

5**this绑定优先级

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

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

6**箭头函数的this

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