this 豁然开朗!

this 豁然开朗的四种规则

调用点(call-site)

调用点:“找到一个函数是在哪里被调用的”,但不总是那么简单,比如某些特定的编码模式会使 真正的 调用点变得不那么明确。

调用栈(call-stack)

调用栈:使我们到达当前执行位置而被调用的所有方法的堆栈。

我们来展示一下调用栈和调用点:

function baz() {
    // 调用栈是: `baz`
    // 我们的调用点是global scope(全局作用域)

    console.log( "baz" );
    bar(); // <-- `bar`的调用点
}

function bar() {
    // 调用栈是: `baz` -> `bar`
    // 我们的调用点位于`baz`

    console.log( "bar" );
    foo(); // <-- `foo`的call-site
}

function foo() {
    // 调用栈是: `baz` -> `bar` -> `foo`
    // 我们的调用点位于`bar`

    console.log( "foo" );
}

baz(); // <-- `baz`的调用点

1. this what?

  1. this是JavaScript的关键字之一。它是 对象 自动生成的一个内部对象,只能在 对象 内部使用。随着函数使用场合的不同,this的值会发生变化。

  2. this指向什么,完全取决于 什么地方以什么方式调用,而不是 创建时。(就是找到它的 调用点!)


2. this 绑定的四种规则

-2.1 默认绑定(Default Binding)

第一种规则来源于函数调用的最常见的情况:独立函数调用。

考虑这个代码段:

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

var a = 2;

foo(); // 2

我们看到当 foo() 被调用时,this.a解析为我们的全局变量a。为什么?因为在这种情况下,对此方法调用的this实施了 默认绑定,所以使this指向了全局对象。

这种就是典型的 默认绑定,我们看看foo调用的位置,”光杆司令“,像 这种直接使用而不带任何修饰的函数调用 ,就默认且只能应用 默认绑定。

那默认绑定到哪呢,一般是window上,严格模式下 是undefined。因为如果 strict mode 在这里生效,那么对于 默认绑定 来说全局对象是不合法的,所以this将被设置为undefined

function foo() {
    "use strict";

    console.log( this.a );
}

var a = 2;

foo(); // TypeError: `this` is `undefined`

一个细节问题:

即便所有的this绑定规则都是完全基于调用点,如果foo()的 内容 没有在strint mode下执行,对于 默认绑定 来说全局对象是 唯一 合法的;foo()的调用点的strict mode状态与此无关。

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

var a = 2;

(function(){
    "use strict";

    foo(); // 2
})();

-2.2 隐含绑定(Implicit Binding)

调用点是否有一个环境对象(context object),也称为拥有者(owning)或容器(containing)对象,虽然这些名词可能有些误导人。

看如下代码:

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

obj.foo();            // 10

foo(); 就是上面的默认绑定,等价于打印 window.a,故输出 undefined 。

obj.foo(); 就是隐含绑定。

函数foo执行的时候有了上下文对象,即 obj。这种情况下,函数里的this默认绑定为上下文对象,等价于打印obj.a,故输出10 。

注意:只有对象属性引用链的最后一层是影响调用点的,如下代码:

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

var obj2 = {
    a: 42,
    foo: foo
};

var obj1 = {
    a: 2,
    obj2: obj2
};

obj1.obj2.foo();  // 42

隐含绑定的限制(Implicitly Lost)

隐性绑定中有一个致命的限制,就是上下文必须包含我们的函数 ,例:var obj = { foo : foo },如果上下文不包含我们的函数用隐性绑定明显是要出错的,不可能每个对象都要加这个函数 ,那样的话扩展,维护性太差了,我们接下来聊的就是直接 给函数强制性绑定this.


- 2 .3 显性绑定

call、 apply 、 bind

函数 call 和 apply,它们的作用都是改变函数的this指向,第一个参数都是 设置this对象。

两个函数的区别:

  1. call从第二个参数开始所有的参数都是 原函数的参数。
  2. apply只接受两个参数,且第二个参数必须是数组,这个数组代表原函数的参数列表。 例如:
function foo(a,b){
    console.log(a+b);
}
foo.call(null,'海洋','饼干');        // 海洋饼干  这里this指向不重要就写null了
foo.apply(null, ['海洋','饼干'] );     // 海洋饼干

除了 call,apply函数以外,还有一个改变this的函数 bind ,它和call,apply都不同。

bind 只有一个函数,且不会立刻执行,只是将一个值绑定到函数的this上,并将绑定好的函数返回。例:

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

foo = foo.bind(obj);
foo();                    // 10

开始我们的显性绑定

function foo(){
    console.log(this.a);
}
var obj = {
    a : 10            //去掉里面的foo
}
foo.call(obj);        // 10

我们将隐性绑定例子中的 上下文对象 里的函数去掉了,显然现在不能用 上下文.函数 这种形式来调用函数,大家看代码里的显性绑定代码foo.call(obj),看起来很怪,和我们之前所了解的函数调用不一样。

其实call 是 foo 上的一个函数,在改变this指向的同时执行这个函数。


-2.4 new 绑定

js中的只要用new修饰的 函数就是’构造函数’,准确来说是 函数的构造调用,因为在js中并不存在所谓的’构造函数’。

用 new 做到函数的构造调用后,js帮我们做了什么工作呢: 1. 创建一个新对象。 2. 把这个新对象的proto属性指向 原函数的prototype属性。或者说是将构造函数的作用域给这个新对象。(即继承原函数的原型,因此 this 就指向了这个新对象); 3. 将这个新对象绑定到 此函数的this上 。(执行构造函数中的代码); 4. 返回新对象;

第三条就是我们下面要聊的new绑定。

function foo(a) {
    this.a = a;
}

var bar = new foo( 2 );
console.log( bar.a ); // 2

通过在前面使用new来调用foo(..),我们构建了一个新的对象并这个新对象作为foo(..)调用的this。

new是函数调用可以绑定this的最后一种方式,我们称之为 new绑定(new binding)。


- 2 .5 this绑定优先级

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


-3. 总结

1. 如果函数被new 修饰

this绑定的是新创建的对象,例:var bar = new foo(); 函数 foo 中的 this 就是一个叫foo的新创建的对象 , 然后将这个对象赋给bar , 这样的绑定方式叫 new绑定 .

2. 如果函数是使用call,apply,bind来调用的

this绑定的是 call,apply,bind 的第一个参数.例: foo.call(obj); , foo 中的 this 就是 obj , 这样的绑定方式叫 显性绑定 .

3. 如果函数是在某个 上下文对象(容器) 下被调用

this绑定的是那个上下文对象,例 : var obj = { foo : foo }; obj.foo(); foo 中的 this 就是 obj . 这样的绑定方式叫 隐性绑定 .

4. 如果都不是,即使用默认绑定

例:function foo(){…} foo() ,foo 中的 this 就是 window.(严格模式下默认绑定到undefined). 这样的绑定方式叫 默认绑定 .


例题如下:

var x = 10;
var obj = {
    x: 20,
    f: function(){ console.log(this.x); }
};
var bar = obj.f;
var obj2 = {
    x: 30,
    f: obj.f
}
obj.f(); // 隐含绑定
bar();  // 默认绑定
obj2.f(); // 隐含绑定

答案:20 10 30。

js  基础