zoukankan      html  css  js  c++  java
  • JS中this的那些事儿

    this是JavaScript中最复杂的机制之一。它是一个很特别的关键字,被自动定义在所有函数的作用域中。

    一、this到底指向什么?

    this既不指向函数自身,也不指向函数的词法作用域,具体指向什么,取决于你是怎么调用函数。

    1. 直接使用不带任何修饰的函数引用进行调用(即:方法名 + 括号), this指向全局对象(非严格模式)或者undefined(严格模式), 这种绑定称为默认绑定
    function foo() {
        console.log(this.a);
    }
    var a = 2;
    foo();  // 2
    

    严格模式:

    function foo() {
        "use strict"
        console.log(this.a);
    }
    var a = 2;
    foo();  // undefiend
    
    1. 如果方法是某个对象的一个属性,通过该对象调用方法(即调用位置存在上下文对象),则this指向该对象, 这种绑定称为隐式绑定.
    function foo() {
        console.log(this.a);
    }
    var obj = {
        a: 2,
        foo: foo
    }
    obj.foo();  // 2
    

    注意事项

    • 如果多个对象之间形成了引用链,方法中的this指向其最顶层或者最后一层对象。
    function foo() {
        console.log(this.a);
    }
    var obj1 = {
        a: 42,
        foo: foo
    }
    
    var obj2 = {
        a: 3,
        obj1: obj1
    }
    obj2.obj1.foo() // 42
    
    • 隐式丢失,被隐式绑定的函数会丢失绑定对象,也即会应用默认绑定,从而把this绑定到全局对象或者undefined上。
      这种情况常出现在回调函数上:
    function foo() {
        console.log(this.a);
    }
    function doFoo(fn) {
        // fn 其实引用的是foo
        fn();
    }
    var obj = {
        a: 2,
        foo: foo
    }
    var a = "oops, global";
    
    doFoo(obj.foo)      // 输出"oops, global"
    
    1. 使用call, apply方法调该函数时,this指向call,apply方法所传入的对象,这种方式成为显示绑定

      js中几乎所有的函数都有call()和apply()方法,这两个方法的第一个参数是一个对象,他们会把这个对象绑定到this.

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

    显示绑定仍然存在绑定丢失的问题:例如

    function foo() {
    	console.log(this.a);
    }
    function doFoo(fn) {
    	fn();
    }
    var obj = {
    	a: 2,
    }
    var a = "oops, global";
    
    doFoo.call(obj, foo);   // 输出oops, global
    

    解决办法:

    • ++硬绑定++
      在doFoo函数内,直接绑定obj对象
    function doFoo(fn) {
        fn.call(obj);
    }
    

    硬绑定一种常见的应用常见是创建一个可重复使用的辅助函数

    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的上下文并调用原始函数。
    4. new绑定

    在javascript中,所有的函数都可以用new来调用,这种函数调用被称为构造函数调用。使用new来调用函数时,会自动执行下面操作:

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

    示例:

    function foo(a) {
        this.a = a; // 在这里,this绑定到bar对象
    }
    var bar = new foo(2);
    console.log(bar.a);     // 2
    

    二、如果函数调用时,既满足隐式绑定、又满足显示绑定或new绑定,该怎么办?

    new绑定优先级 > 显示绑定 > 隐式绑定 > 默认绑定

    • 比较显示绑定和隐式绑定,new绑定和隐式绑定
    function foo(something) {
        this.a = something;
    }
    var obj1 = {foo: foo};
    var obj2 = {};
    
    obj1.foo.call(obj2, 3);     // foo函数的调用同时出现了隐式绑定和显示绑定,则显示绑定优先,this指向 obj2
    console.log(obj2.a);        // obj2.a = 3
    
    var bar = new obj1.foo(4);  // 同时出现了隐式绑定和new绑定, this指向new绑定创建的对象bar, 而不是obj1
    console.log(bar.a);         // 输出4
    
    • 比较new绑定和显示绑定:
    function foo(something) {
        this.a = something;
    }
    var obj1 = {};
    
    var bar = foo.bind( obj1 );
    
    bar( 2 );
    
    console.log( obj1.a ); // 2
    
    var baz = new bar(3);
    
    console.log( obj1.a ); // 2
    
    console.log( baz.a ); // 3
    

    bar 被硬绑定到 obj1 上,但是 new bar(3) 并没有像我们预计的那样把 obj1.a
    修改为 3。相反, new 修改了硬绑定(到 obj1 的)调用 bar(..) 中的 this

    三、特殊情况

    1. 调用call或者apply时,参数传入null或者undefined, 此时this会采用默认绑定
    function foo() {
        console.log(this.a);
    }
    var a = 2;
    foo.call(null); // 输出2
    
    1. 函数的间接引用,此时this采用默认绑定。间接引用最常在赋值时发生:
    function foo() {
        console.log(this.a);
    }
    
    var a = 2;
    var o = { a: 3, foo: foo };
    var p = { a: 4 };
    o.foo(); // 3
    (p.foo = o.foo)(); // 2
    

    赋值表达式 p.foo = o.foo 的返回值是目标函数的引用,因此调用位置是 foo() 而不是p.foo() 或者 o.foo()

    1. 箭头函数

    前面提到的四种绑定规则对箭头函数不适用,箭头函数中的this是根据外层作用域来决定的。

    function foo() {
        return (a) => {
            console.log(this.a);
        }
    }
    
    var obj1 = {a:2};
    var obj2 = {a:3};
    
    var bar = foo.call(obj1); // 箭头函数的外层作用域中的this指向 obj1, 所以箭头函数中this也指向obj1,箭头函数的绑定无法被修改
    bar.call(obj2);     // 输出2,
    

    四、 小结

    判断一个运行中函数的this绑定,就需要找到这个函数的直接调用位置,找到后就可以顺序应用这四条规则判断this的绑定对象。

    1. 由new调用? 绑定到新创建的对象;
    2. 由call或者apply调用?绑定到指定对象;
    3. 由上下文调用?绑定到上下文对象;
    4. 默认:严格模式下绑定到undefined,否则绑定到全局对象;

    另外还需要注意上面提到的几种特殊情况。

  • 相关阅读:
    WINDOWS操作系统中可以允许最大的线程数
    OCP-1Z0-新051-61题版本-36
    OCP-1Z0-新051-61题版本-37
    OCP-1Z0-新051-61题版本-38
    OCP-1Z0-新051-61题版本-39
    OCP-1Z0-新051-61题版本-40
    OCP-1Z0-新051-61题版本-31
    OCP-1Z0-新051-61题版本-32
    OCP-1Z0-新051-61题版本-33
    OCP-1Z0-新051-61题版本-34
  • 原文地址:https://www.cnblogs.com/Jingge/p/10237412.html
Copyright © 2011-2022 走看看