• this关键字


    原文地址:https://wangdoc.com/javascript/

    涵义

    前一张提到this可以用在构造函数之中,表示实例对象。除此之外,this还可以用在别的场合。但是不管是什么场合,this都有一个共同点:它总是返回一个对象。
    简单的说,this就是属性或方法当前所在的对象。由于对象的属性可以赋给另一个对象。所以属性所在的当前对象是可变的,即this的指向是可变的。

    var A = {
        name: "张三",
        describe: function () {
            return "姓名:" + this.name;
        }
    };
    
    var B = {
        name = "李四"
    };
    
    B.describe = A.describe;
    B.describe() // "姓名:李四"
    

    只要函数被赋值给另一个变量,this的指向就会变。

    var A = {
        name: "张三",
        describe: function () {
            return "姓名:" + this.name;
        }
    };
    
    var name = "李四";
    var f = A.describe;
    f() // "姓名:李四"
    

    实质

    JavaScript语言之所以有this的涉及,跟内存里面的数据结构有关系。

    var obj = {foo: 5};
    

    上面的代码将一个对象赋值给变量obj。JavaScript引擎现在内存里面生成一个对象{foo:5},然后把这个对象的内存地址赋值给变量obj。也就是说,变量obj是一个地址(reference)。
    原始的对象以字典结构保存,每一个属性名都对应一个属性描述对象。

    {
        foo: {
            [[value]]: 5,
            [[writable]]: true,
            [[enumerable]]: true,
            [[configurable]]: true
        }
    }
    

    注意,foo属性的值保存在属性描述对象的value属性里面。
    这样的结构是很清晰的,问题在于属性的值可能是一个函数。

    var obj = { foo: function () {} };
    

    这时,引擎会将函数单独保存在内存中,然后再将函数的地址赋给foo属性的value属性。由于函数是一个单独的值,所以它可以在不同的环境(上下文)执行。
    JavaScript允许在函数体内部,引用当前环境的其他变量。所以需要一种机制,能够在函数体内部获取当前运行环境。所以this就出现了。

    使用场合

    this主要有以下几个使用场合。
    (1)全局环境
    全局环境使用this,它指的就是顶层对象window

    this === window // true
    
    function f() {
        console.log(this === window);
    }
    f() // true
    

    (2)构造函数
    构造函数中的this,指的是实例对象。
    (3)对象的方法
    如果对象的方法里面包含thisthis指向的就是方法运行时所在的对象。该方法赋值给另一个对象,就会改变this的指向。

    var obj = {
        foo: function () {
            console.log(this);
        }
    };
    
    obj.foo() // obj
    
    // 注意
    (obj.foo = obj.foo)() // window
    (false || obj.foo)() // window
    (1, obj.foo)() // window
    

    如果this所在的方法不在对象的第一层,这时this只是指向当前一层的对象,而不会继承跟上面的层。

    var a = {
        p: "hello",
        b: {
            m: function () {
                console.log(this.p);
            }
        }
    };
    
    a.b.m() // undefined
    

    如果这是将嵌套对象内部的方法赋值给一个变量,this依然会指向全局对象。

    var hello = a.b.m;
    hello() // 在window下运行
    // 如果需要在引用b对象
    var hello = a.b;
    hello.m();
    

    使用注意点

    避免多层this

    由于this的指向是不确定的,所以切勿在函数中包含多层的this

    var o = {
        f1: function () {
            console.log(this);
            var f2 = function () {
                console.log(this);
            } ();
        }
    };
    
    o.f1()
    // object
    // window
    

    解决方案

    var o = {
        f1: function () {
            console.log(this);
            var that = this;
            var f2 = function () {
                console.log(that);
            } ();
        }
    };
    o.f1()
    // object
    // object
    

    避免数组处理方法中的this

    数组的mapforeach方法,允许提供一个函数作为参数。这个函数内部应该使用this

    var o = {
        v: "hello",
        p: ["a1", "a2"],
        f: function () {
            this.p.forEach(function (item) {
                console.log(this.v + " " + item);
            });
        }
    };
    
    o.f()
    // undefined a1
    // undefined a2
    

    上面代码中,forEach方法回调函数中的this,其实指向window对象,因此取不到o.v的值。解决这个问题的一种方法,使用中间变量固定this;另一个方法是将this当做forEach方法的第二个参数,固定它的运行环境。

    避免回调函数中的this

    回调函数中的this往往改变指向,最好避免使用。

    绑定this的方法

    this的动态切换,固然伟JavaScript创造了巨大的灵活性,但也使得编程变得困难和模糊。有时需要把this固定下来,避免出现意想不到的情况。JavaScript提供了callapplybind这三个方法来切换/固定this的指向。

    Function.prototype.call()

    函数实例的call方法,可以指定函数内部this的指向(即函数执行时所在的作用域),然后在所指定的作用域中,调用该函数。

    var obj = {};
    var f = function () {
        return this;
    };
    f() === window // true
    f.call(obj) === obj // true
    

    call方法的参数应该是一个对象。如果参数为空、nullundefined,则默认传入全局对象。
    如果call方法的参数是一个原始值,那么这个原始值会自动转成对应的包装对象,然后传入call方法。

    var f = function () {
        return this;
    };
    
    f.call(5) // Number {[[PromitiveValue]]: 5}
    

    call方法还可以接受多个参数。
    call的第一个参数就是this所要指向的那个对象,后面的参数则是函数调用时所需的参数。

    function add(a, b) {
        return a + b;
    }
    add.call(this, 1, 2) // 3
    

    call方法的一个应用是调用对象的原生方法。

    var obj = {};
    obj.hasOwnProperty("toString"); // false
    // 覆盖掉继承的 hasOwnProperty方法
    obj.hasOwnProperty = function () {
        return true;
    };
    obj.hasOwnProperty("toString") // true
    Object.prototype.hasOwnProperty.call(obj, "toString") // false
    

    Function.prototype.apply()

    apply方法的作用与call方法类似,也是改变this指向,然后再调用该函数。唯一的区别就是,它是接受一个数组作为函数执行时的参数。
    apply第一个参数也是this所要指向的对象,如果设为nullundefined,则等同于指定全局对象。

    function f(x, y) {
        console.log(x + y);
    }
    f.call(null, 1, 1) // 2
    f.apply(null, [1, 1]) // 2
    

    利用这一点可以做一些有趣的应用:
    (1)找出数组最大元素

    var a = [10, 2, 4, 15, 9];
    Math.max.apply(null, a) // 10
    

    (2)将数组的空元素变为undefined
    通过apply方法,利用Array构造函数。

    Array.apply(null, ["a",  , "b"])
    // ["a", undefined, "b"]
    

    空元素与undefined的区别在于,数组的forEach方法会跳过空元素。
    (3)转换类似数组对象
    利用数组对象的slice方法,可以将一个类似数组的对象转为真正的数组。

    Array.prototype.slice.apply({0: 1, length: 1}) // [1]
    Array.prototype.slice.apply({0: 1}) // []
    Array.prototype.slice.apply({0: 1, length: 2}) // [1, undefined]
    Array.prototype.slice.apply({length: 1}) // [undefined]
    

    (4)绑定回调函数

    var o = new Object();
    o.f = function () {
        console.log(this === o);
    };
    
    var f = function () {
        o.f.apply(o);
    };
    
    // JQuery 写法
    $("#button").on("click", f);
    

    Function.prototype.bind()

    bind方法用于将函数体内的this绑定到某个对象,然后返回一个新的函数。

    var d = new Date()
    d.getTime() // 
    
    var print = d.getTime;
    print() // Uncaught TypeError: this is not a Date object.
    

    上面代码中,我们将d.getTime方法赋值给变量print,然后调用print就报错了。因为getTime方法内部的this,绑定Date对象的实例,赋给变量print以后,内部的this已经不指向Date对象的实例了。
    bind方法可以解决这个问题。

    var print = d.getTime.bind(d);
    print();
    

    bind方法的参数就是所要绑定的this对象。

    var counter = {
        count: 0,
        inc: function () {
            this.count++;
        }
    };
    var func = counter.inc.bind(counter);
    func();
    counter.count // 1
    

    因为成员方法中使用了this,指向counter对象的实例。所以需要使用bind来绑定thiscounter
    bind还可以接受更多参数,将这些参数绑定原函数的参数。

    var add = function (x, y) {
        return x * this.m + y * this.n;
    };
    
    var obj = {
        m: 2,
        n: 2
    };
    
    var newAdd = add.bind(obj, 5);
    newAdd(5) // 20
    

    上面代码中bind方法除了绑定this对象,还将add函数的第一个参数x绑定为5,然后返回一个新的函数newAdd,这个函数只需要在接受一个参数y就能运行了。
    如果bind方法的第一个参数是nullundefined,等于将this绑定到全局对象,函数运行时this指向顶层对象。
    bind方法有一些使用注意点。
    (1)每一次返回一个新函数
    bind方法每运行一次,就会返回一个新的函数,这会产生一些问题。比如,监听事件的时候不能写成下面这样。

    element.addEventListener("click", o.m.bind(o));
    

    上面代码,click事件绑定bind方法生成的一个匿名函数。这样会导致无法取消绑定。

    element.removeEventListener("click", o.m.bind(o));
    

    正确的写法是:

    var listener = o.m.bind(o);
    element.addEventListener("click", listener);
    element.removeEventListener("click", listener);
    

    (2)结合回调函数使用
    回调函数是JavaScript最常用的模式之一,但是很常见的一个错误是,将包含this的方法直接当做回调函数。解决方法是使用bind方法。

    var obj = {
        name: "hello",
        times: [1, 2, 3],
        print: function () {
            this.times.forEach(function (n) {
                console.log(this.name); 
            });
        }
    };
    
    obj.print() // 没有输出
    

    上面代码中回调函数的this指向全局对象。解决这个问题也是通过bind方法绑定this

    obj.print = function () {
        this.times.forEach(function (n) {
            console.log(this.name);
        }.bind(this));
    };
    
    obj.print() // hello
    

    (3)结合call方法使用
    利用bind方法,可以改写一些JavaScript原生方法的使用形式,以数组的slice方法为例。

    [1, 2, 3].slice(0, 1) // [1]
    // 相当于
    Array.prototype.slice.call([1, 2, 3], 0, 1) // [1]
    

    call方法实质上是调用Function.prototype.call方法,因此上面的表达式可以用bind方法改写。

    var slice = Function.prototype.call.bind(Array.prototype.slice);
    slice([1, 2, 3], 0, 1) // [1]
    

    如果再进一步,将Function.prototype.call方法绑定到Function.prototype.bind对象,就意味着bind的调用形式也可以被改写。

    function f () {
        console.log(this.v);
    }
    
    var o = { v: 123 };
    var bind = Function.prototype.call.bind(Function.prototype.bind);
    bind(f, o)() // 123
    

    第一个参数,函数实例,第二个参数this指向的对象。

  • 相关阅读:
    asp.net FckEditor配置
    您请求的报表需要更多信息...
    水晶报表中如何动态增加字段
    使用JavaMail发送SMTP认证的邮件给多个收信人
    vim中删除每行行尾的空格
    转载:STUN在SIP中的工作原理及过程
    转载 URL和URI的区别
    转载 Android深入浅出Binder机制
    链接静态库的时候,命令行中库和源文件的位置问题
    使用dumpbin来查看程序的依赖
  • 原文地址:https://www.cnblogs.com/chris-jichen/p/10137893.html
Copyright © 2020-2023  润新知