this 全面解析(笔记)

this 既不指向函数自身,也不指向函数的词法作用域。它实际上是在函数被调用时发生的绑定,指向什么取决于函数的调用位置或调用方法。

调用位置不是声明位置。有些编程模式可能会隐藏真正的调用位置。

使用浏览器的调试工具,可以查看调用栈,调用栈的第二个元素,就是真正的调用位置。

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

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

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

以上就是函数执行过程中的调用位置。

this有四条绑定规则。

规则1. 默认绑定。

最常用的函数调用类型:独立函数调用。

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

调用foo()时候,this.a 被解析成了全局变量 a。函数调用时应用了this的默认绑定,因此this指向全局对象。

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

    function foo(){
        "use strict";
        console.log(this.a);
    }
    var a = 2;
    foo(); // TypeError: Cannot read property 'a' of undefined

this 的绑定规则完全取决于调用位置,但是只有在非严格模式下,默认绑定才能绑定到全局对象。

规则2. 隐式绑定

函数调用位置是否有上下文对象。

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

foo()被当作引用属性添加到obj中。调用位置使用obj上下文来引用函数,隐式绑定规则将函数调用中的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 = "全局对象";
bar(); //"全局对象"

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

另外一种情况发生在传入回调函数时:

function foo() {
    console.log(this.a);
}
function doFoo(fn) {
    fn();
}
var obj={
    a:2,
    foo:foo
};
var a = "全局对象";
doFoo(obj.foo); //"全局对象"

参数传递其实就是一种隐式赋值,因此传入函数时也会被隐式赋值。

如果把函数传入语言内置的函数而不是自己声明的函数,结果也是一样的:

function foo() {
    console.log(this.a);
}
function doFoo(fn) {
    fn();
}
var obj={
    a:2,
    foo:foo
};
var a = "全局对象";
setTimeout(obj.foo, 100); //"全局对象"

回调函数丢失this绑定是非常常见的。除此之外,调用回调函数的函数可能会修改this。一些流行的javascript库中,事件处理器常会把回调函数的this强制绑定到触发事件的DOM元素上。

由于回调函数的执行方式无法控制,影响绑定的调用位置也就没有办法控制。那么如何固定this来修复呢。

规则3. 显示绑定

使用函数的call()和apply()方法,可以在某个对象上强制调用函数。这两个方法的第一个参数是一个对象,它们会把这个对象绑定到this,接着做调用函数时指定这个this。

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

var obj={
    a:2
};
foo.call(obj); //2

显示绑定也无法解决前面的丢失绑定问题。但是显示绑定的一个变种可以解决这个问题:

3.1 硬绑定

function foo(something) {
    console.log(this.a, something);
    return this.a+something;
}
var obj={
    a:2
};
var bar = function () {
    return foo.apply(obj, arguments);
};
var b=bar(3); //2 3
console.log(b); //5

创建函数bar(),在内部手动调用foo.apply(),强制把foo的this绑定到了obj。之后无论如何调用函数bar,它总会手动在obj上调用foo。这就是显示的强制绑定。

另外一个例子,创建一个 i 可以重复使用的辅助函数:

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

ES5提供内置方法:Function.prototype.bind,因此可以这样:

function foo(something) {
    console.log(this.a, something);
    return this.a+something;
}
var obj={
    a:2
};
var bar = foo.bind(obj);
var b=bar(3); //2 3
console.log(b); //5

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

2.2 API调用的“上下文”

javascript的内置函数,基本都提供了一个可选的参数,通常被称为“上下文”(context),其作用和bind()一样。确保你的回调函数使用指定的this。

function foo(ele) {
    console.log(ele, this.id);
}
var obj={
    id: "awesome"
};

['a','b','c'].forEach(foo,obj);
//a "awesome"   b "awesome"  c "awesome"

调用foo()时把this绑定到obj。

规则4. new 绑定

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

使用new来调用Foo()时,我们会构造一个新对象并把它绑定到foo()调用中的this上。如果函数没有返回其他对象,new表达式中的函数调用会自动返回这个新对象。

 

posted @ 2017-05-26 16:44  kiera  阅读(274)  评论(0编辑  收藏  举报