12

下一篇:《你不知道的javascript》笔记_对象&原型

写在前面

上一篇博客我们知道词法作用域是由变量书写的位置决定的,那this又是在哪里确定的呢?如何能够精准的判断this的指向?这篇博客会逐条阐述

书中有这样几句话:

this是在运行时进行绑定的,并不是在编写时绑定,它的上下文取决于函数调用时的各种条件
this的绑定和函数声明的位置没有任何关系,只取决于函数的调用方式

当一个函数被调用时,会创建一个活动记录(有时候也称为执行上下文)。这个记录会包含函数在哪里被调用(调用栈)、函数的调用方法、传入的参数等信息。this 就是记录的其中一个属性,会在函数执行的过程中用到。

关于执行上下文,可以参考《javascript高级程序设计》笔记:内存与执行环境

一、 绑定规则

1.1 默认绑定

最常用的函数调用类型——独立函数调用,使用的即为默认绑定规则,在非strict mode下,this指向全局对象
function foo1() {
    console.log(this.a);
}
var a = 10;
foo1(); // 10

// 即使函数嵌套比较深
function foo2() {
    foo1();
}
function foo3() {
    foo2();
}
foo3();

当然,我们实际使用中,难以判别的并不是直接型的默认绑定模式,而是隐式绑定丢失型的默认绑定(下面会着重说明)

1.2 隐式绑定【重点】

调用的位置是否有上下文对象,或者说被某个对象拥有或包含
// 基本形式
function foo() {
    console.log(this.a);
}
var obj = { a: 10, foo };
obj.foo(); // 10

隐式绑定中的几个雷区:

1. 多个对象嵌套引用时,只有最后一层在调用位置中起作用

function foo() {
    console.log(this.a);
}
var obj2 = { a: 42, foo };
var obj1 = { a: 10, obj2 };
obj1.obj2.foo(); // 42

2.【隐式丢失】当调用函数被重新赋值为新变量,调用新变量时this指向会有不同

// 共用部分
function foo(){
    console.log(this.a);
}
var obj = { a: 10, foo };
var a = 'opps, global';

// 直接赋值
var bar = obj.foo;
bar(); // 'oops, global'

// 回调间接赋值1
function doFoo(fn) {
    fn();
}
doFoo(obj.foo); // 'oops, global' 相当于间接赋值

// 回调间接赋值2
setTimeout(obj.foo, 100); // 'oops, global' 内置的setTimeout也相当于间接赋值

经典综合案例:

var length = 10;
function fn(){
    console.log(this.length);
}
var obj = {
    length: 5,
    method: function (fn) {
        fn();
        arguments[0]();
    }
};

obj.method(fn, 123);

分析:fn()为函数fn的引用,默认绑定,指向全局;arguments[0]();相当于下面的引用,数据隐式绑定,绑定对象为arguments,其属性length值为参数数量2

arguments: {
    '0': function fn(){
        console.log(this.length);
    }
}

答案:10 2

1.3 显式绑定

call()/apply()/bind()能够显式修改this指向

通过上述方法调用的方式为显示绑定,它们第一个参数是一个对象,在调用函数时,绑定在this中。

关于三者的基本用法和说明在之前博客《javascript高级程序设计》函数调用模式 & this深度理解中已作说明,在此不做唠述

两点注意

1. 通过显式绑定的不能再修改它的this指向

function foo() {
    console.log(this.a);
}
var obj = { a: 2 };
var bar = function() {
    foo.call(obj);
}
bar(); // 2
setTimeout(bar, 200); // 2

bar.call(window); // 2

2. 将null/undefined作为第一个参数时,调用会忽略这些值,采用默认绑定规则

function foo() {
    console.log(this.a);
}
var a = 2;
foo.call(null); // 2

1.4 new绑定

使用关键字new执行函数,当函数无返回值或返回值非对象时,this指向为实例对象

new关键字执行函数流程:

  1. 创建一个全新的对象
  2. 这个新对象会被执行[[prototype]]连接
  3. 这个新对象会绑定到函数调用的this
  4. 如果函数没有返回其他对象,所执行函数会自动返回这个新的对象

须知:构造函数与普通函数无异,作为区分,我们一般讲通过new调用的函数称为构造函数,并大写第一个单词。所有函数均可由关键字new调用

function foo(a) {
    this.a = a;
}
var bar = new foo(2);
console.log(bar.a); // 2

二、优先级&判断规则

2.1 优先级

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

2.2. 判断规则【重点】

  1. new绑定】函数是否在new中调用?如果是,this绑定的是新创建的对象
  2. 显式绑定】函数是否在call/aplly/bind中调用?如果是,this绑定的是指定对象
  3. 隐式绑定】函数是否在某个上下文中调用?如果是,this绑定到那个上下文对象
  4. 默认绑定】如果都不是,this绑定严格模式下为undefined,非严格模式下为全局对象

三、箭头函数中的this

ES6中箭头函数不使用上面this的四种标准规格,而是根据外层(函数或者全局)作用域来决定this指向

下面是一个普通函数和箭头函数的对比:

function foo1() {
    setTimeout(() => {
        console.log(this.a)
    }, 100)
}
function foo2() {
    setTimeout(function() {
        console.log(this.a)
    }, 100)
}

var a = 10;
var obj = { a: 2 };

foo1.call(obj); // 2 箭头函数this指向外层(obj)
foo2.call(obj); // 10 隐式丢失,默认绑定

【利用闭包】理解箭头函数中的this:

// 上例中的箭头函数相当于
function foo1() {
    var self = this;
    setTimeout(function() {
        console.log(self.a)
    }, 100)
}

上一篇:《你不知道的javascript》笔记_作用域与闭包
下一篇:《你不知道的javascript》笔记_对象&原型


同梦奇缘
4.1k 声望1.1k 粉丝

生于忧患死于安乐