JavaScript中this终极理解(2)

fsrookie

在上一篇我们了解过每个函数的this是在调用的时候绑定的,完全却决于函数的调用位置(也就是函数的调用方法)。

1. 调用位置

在理解this的绑定过程之前,首先要理解调用位置:调用位置就是函数在代码中被调用的位置,而不是声明的位置。

找到函数的调用位置最重要的是要分析调用栈(就是为了到达当前执行位置所调用的所有函数)。我们关心的调用位置就在当前正在执行的函数的前一个调用中。

function baz() {
    //当前调用栈是:baz
    //因此,当前调用位置是全局作用域
    console.log("baz");
    bar(); //<--bar的调用位置
}

function bar() {
    //当前调用栈是baz->bar
    //因此,当前调用位置在baz中
    console.log("bar")
    foo(); 
}

function foo() {
    //当前调用栈是baz->bar->foo
    //因此,当前调用位置在bar中
    console.log("foo")
}
baz() //<---baz得调用位置

2. 绑定规则

那么调用位置如何决定this得绑定对象呢。首先,我们要找到调用位置,然后按照下面四条规则进行应用。首先我们先了解下这几条规则:

(1)默认绑定

首先介绍最常用得函数调用类型:独立函数调用。可以将这条规则看作是无法应用其他规则得默认规则。

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

上面得代码中,在全局环境中声明了一个变量a,那么a就是全局对象得一个同名属性。当调用foo()时,this.a被解析成了全局变量a。因为函数调用得时候应用了this得默认绑定,因此this指向全局对象。
在代码中,foo()是直接使用不带任何修饰的函数引用进行调用,因此只能使用默认绑定

如果使用严格模式,那么全局对象将无法使用默认绑定,因此this会绑定到undefined。

(2)隐式绑定

另一条需要考虑的规则是调用位置是否有上下文对象,或则说是否被某个对象拥有或者包含。

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

首先需要注意的是foo()的声明方式,以及之后是如何被当作引用属性添加到obj中的。但是无论是直接在obj定义还是先定义再添加为引用属性,这个函数严格来说都不属于obj对象。

调用位置会使用obj上下文来引用函数,因此你可以说函数被调用时,obj对象"拥有"或者"包含"它。

当foo()调用时,它的落脚点确实指向obj对象。当函数引用有上下文对象时,隐式绑定规则会把函数调用中的this绑定到这个上下文对象。因为调用foo()时this被绑定到obj,因此this.a和obj.a时一样的。

对象属性引用链中只有最顶层或者最后一层会影响调用位置。

function foo() {
    console.log(this.a)
}
var obj2 = {
    a: 42,
    foo: foo
}
var obj1 = {
    a: 2,
    obj2: obj2
}
obj1.obj2.foo() //42
  • 隐式丢失

this绑定一个常见的问题就是被隐式绑定的函数会丢失绑定对象,也就是说它会应用默认绑定,从而把this绑定到全局对象或者undefined上,这要取决于是否式严格模式。

function foo() {
    console.log(this.a)
}
var obj = {
    a:2,
    foo: foo
}
var bar = obj.foo; //函数别名
var a = 'oops, globas'; //a式全局对象的属性
bar(); //'oops, globas'

虽然bar是obj.foo的引用,但实际上,它引用的是foo函数本身,因此此时的bar()其实是一个不带任何修饰的函数调用,因此应用默认绑定。

再看下面一组代码:

function foo() {
    console.log(this.a)
}
function doFoo(fn) {
    fn();
}

var obj = {
    a:2,
    foo:foo
}
var a = 'oops,global'
doFoo(obj.foo); //'oops, global'

参数传递其实就是一种隐式赋值,因此我们传入函数时也会被隐式赋值,所以结果和之前一样。

(3)显示绑定

我们可以使用函数的call()apply()方法,通过这两个方法可以在某个对象上强制调用函数。
这两个方法的作用都是一样的,第一个参数是一个对象,它们会把这个对象绑定到this,接着在调用函数时指定这个this。因为可以直接指定this的绑定对象,因此叫做显示绑定

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

通过foo.call(..),我们可以在调用foo时强制把它的this绑定到obj上。

如果你传入了一个原始值(字符串类型,布尔类型等)来当作this的绑定对象,这个原始值会被转换成对象形式,也就是(new String(..)),这通常被称为"装箱"。

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

如上,我们不管怎么调用bar,它总会手动在obj上调用foo,这种绑定时一种显示的强制绑定,因此我们称为为硬绑定

硬绑定的典型应用场景就是创建一个包裹函数,传入所有的参数并返回接收到的所有值:

function foo(something) {
    console.log(this.a, something)
    return this.a + something
}

//简单的辅助绑定函数
function bind(fn, obj) {
    return function() {
        return fn.apply(obj, arguments)
    }
}

var obj = {
    a: 2
};
var bar = bind(foo, obj)

var b = bar(3) //2, 3
console.log(b) //5

bind(...)会返回一个硬编码的新函数,它会把参数设置为this的上下文并调用原始函数。

(4) new绑定

在JavaScript中,构造函数只是一些使用new操作符时被调用的函数。他们并不会属于某个类,也不会实例化一个类。实际上他们甚至都不能说时一种特殊的函数类型。只是被new操作符调用的普通函数。

使用new来调用函数时会执行以下操作:

1. 创建一个全新的对象
2. 这个新对象会被执行[[原型]]连接
3. 这个新对象会绑定到函数调用的this
4. 如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象。
阅读 858

目前很多文章都是摘抄记录其他教程。见谅。

2.8k 声望
250 粉丝
0 条评论
你知道吗?

目前很多文章都是摘抄记录其他教程。见谅。

2.8k 声望
250 粉丝
文章目录
宣传栏