zoukankan      html  css  js  c++  java
  • JavaScript中的百变大咖~this

    原文链接:http://www.jeffjade.com/2015/08/03/2015-08-03-javascript-this/

    JavaScript作为一种脚本语言身份的存在,因此被很多人认为是简单易学的。然而情况恰恰相反,JavaScript支持函数式编程、闭包、基于原型的继承等高级功能。由于其运行期绑定的特性,JavaScript 中的 this 含义要丰富得多,它可以是全局对象、当前对象或者任意对象,这完全取决于函数的调用方式。JavaScript中函数的调用有以下几种方式:作为对象方法调用,作为函数调用,作为构造函数调用,和使用 apply 或 call 调用。本文就采撷些例子以浅显说明在不同调用方式下的不同含义。

    全局的this

    全局this一般指向全局对象,浏览器中的全局对象就是 window。例如:

    console.log(this.document === document); //true
    console.log(this === window); //true
    
    this.a = 91;
    console.log(window.a); //91
    

    一般函数的 this

    function f1 () {
        return this;
    }
    console.log(f1() === window);//true, global object
    

    可以看到一般函数的 this 也指向 window,在 nodeJS 中为 global object

    function f2 () {
        "use strict";//使用严格模式
        return this;
    }
    console.log(f1() === undefined);//true
    

    严格模式中,函数的 this 为 undefined

    作为对象方法的函数的 this

    var o = {
        prop: 37,
        f: function() {
            return this.prop;
        }
    };
    console.log(o.f()); // 37
    

    上述代码通过字面量创建对象 o。

    f 为对象 o 的方法。这个方法的 this 指向这个对象,在这里即对象 o。

    var o = {
        prop: 37
    };
    
    function independent() {
        return this.prop;
    }
    o.f = independent;
    console.log(o.f()); // 37
    

    上面的代码,创建了对象 o,但是没有给对象 o,添加方法。而是通过 o.f = independent 临时添加了方法属性。这样这个方法中的 this 同样也指向这个对象 o。

    作为函数调用

    函数也可以直接被调用,此时 this 绑定到全局对象。在浏览器中,window 就是该全局对象。比如下面的例子:函数被调用时,this被绑定到全局对象,接下来执行赋值语句,相当于隐式的声明了一个全局变量,这显然不是调用者希望的。

    function makeNoSense(x) { 
        this.x = x; 
    } 
    makeNoSense(5); 
    x;// x 已经成为一个值为 5 的全局变量
    

    对于内部函数,即声明在另外一个函数体内的函数,这种绑定到全局对象的方式会产生另外一个问题。以下面moveTo方法为例,内定义两个函数,分别将 x,y 坐标进行平移。结果可能出乎大家意料,不仅 point 对象没有移动,反而多出两个全局变量 x,y。

    var point = { 
        x : 0, 
        y : 0, 
        moveTo : function(x, y) { 
            // 内部函数
            var moveX = function(x) { 
                this.x = x;//this 绑定到了哪里?
            }; 
            // 内部函数
            var moveY = function(y) { 
                this.y = y;//this 绑定到了哪里?
            }; 
    
            moveX(x); 
            moveY(y); 
        } 
    }; 
    point.moveTo(1, 1); 
    console.log(point.x) //0
    console.log(point.x) //0
    console.log(x)       //1
    console.log(y)       //1
    

    这属于 JavaScript 的设计缺陷,正确的设计方式是内部函数的this应该绑定到其外层函数对应的对象上,为了规避这一设计缺陷,聪明的JavaScript程序员想出了变量替代的方法,约定俗成,该变量一般被命名为 that。

    对象原型链上的this

    var o = {
        f: function() {
            return this.a + this.b;
        }
    };
    var p = Object.create(o);
    p.a = 1;
    p.b = 2;
    console.log(p.f()); //3
    

    通过 var p = Object.create(o) 创建的对象,p 是基于原型 o 创建出的对象。

    p 的原型是 o,调用 f() 的时候是调用了 o 上的方法 f(),这里面的 this 是可以指向当前对象的,即对象 p。

    get/set 方法与 this

    function modulus() {
        return Math.sqrt(this.re * this.re + this.im * this.im);
    }
    var o = {
        re: 1,
        im: -1,
        get phase() {
            return Math.atan2(this.im, this.re);
        }
    };
    Object.defineProperty(o, 'modulus', {
        get: modulus,
        enumerable: true,
        configurable: true
    });
    console.log(o.phase, o.modulus); // -0.78 1.4142
    

    get/set 方法中的 this 也会指向 get/set 方法所在的对象的。

    构造器中的 this

    function MyClass() {
        this.a = 25;
    }
    var o = new MyClass();
    console.log(o.a); //25
    

    new MyClass() 的时候,MyClass()中的 this 会指向一个空对象,这个对象的原型会指向 MyClass.prototype。MyClass()没有返回值或者返回为基本类型时,默认将 this 返回。

    function C2() {
        this.a = 26;
        return {
            a: 24
        };
    }
    
    o = new C2();
    console.log(o.a); //24
    

    因为返回了对象,将这个对象作为返回值

    call/apply 方法与 this

    function add(c, d) {
        return this.a + this.b + c + d;
    }
    var o = {
        a: 1,
        b: 3
    };
    add.call(o, 5, 7); // 1 + 3 + 5 + 7 = 16
    add.apply(o, [10, 20]); // 1 + 3 + 10 + 20 = 34
    function bar() {
        console.log(Object.prototype.toString.call(this));
    }
    bar.call(7); // "[object Number]"
    bar.call(); //[object global]
    bar.call("7");//[object String]
    bar.call(true);//[object Boolean]
    console.log(add.call(o,5,7));//16
    

    bind 方法与 this

    function f() {
        return this.a;
    }
    var g = f.bind({
        a: "test"
    });
    console.log(g()); // test
    var o = {
        a: 37,
        f: f,
        g: g
    };
    console.log(o.f(), o.g()); // 37, test
    

    绑定之后再调用时,仍然会按绑定时的内容走,所以 o.g() 结果是 test


    JavaScript中this的些许看似怪异现象

    <body>
        <!--JavaScript伪协议和内联事件对于this的指向不同-->
        <a href="#" onclick="alert(this.tagName);">click me</a> <!--弹出A-->
        <a href="javascript:alert(this.tagName);">click me</a>  <!--弹出undefined-->
        <a href="javascript:alert(this==window);">click me</a>  <!--弹出true-->
    
        <input id="btn" type="button" value="this demo" name="button"/>
    </body>
    
    var name = 'somebody';
    var angela = {
        name: 'angela',
        say: function () {
            alert("I'm " + this.name);
        }
    };
    var btn = document.getElementById('btn');
    

    setTimeout和setInterval也会改变this的指向

    angela.say();//I'm  angela
    setTimeout(angela.say, 1000);  //I'm  somebody
    setInterval(angela.say, 1000); //I'm  somebody
    

    on...也会改变this的指向

    angela.say(); //I'm  angela
    btn.onclick = angela.say; //I'm  button
    

    click等回调也会改变this指向

    $("#btn").click = angela.say;  // I'm  button
    $("#btn").click(angela.say);   // I'm  button
    

    如果在say中用了this,this会绑定在angela上么?显然这里不是,赋值以后,函数是在回调中执行的,this会绑定到$(“#btn”)元素上。这个函数被完整复制到onclick属性(现在成为了函数)。因此如果这个even thandler被执行,this将指向HTML元素;因此,结果显示的是"I'm button"。而,匿名函数可以调整this指向,EG:

    $("#btn").click(function(){ 
        angela.say();  //I'm  angela
    });
    

    匿名函数调整this指向比如:

        setTimeout(function () { angela.say(); }, 1000); //I'm  angela
        setInterval(function () { angela.say(); }, 1000) //I'm  angela
        btn.onclick = function () { angela.say(); };     //I'm  angela
        setTimeout(function () { alert(this == window); }, 1000);//true
        btn.onclick = function () { alert(this == btn); }//true
    

    匿名函数赋值给了click属性(好吧,现在成了函数),此时这个匿名函数指向的即是Html属性。因此所调用的函数(比如angela.say())this上下文没有被更改,所以其打印出来的结果就是'I'm angela'。事实上,也用这样的方法来消解this在回调函数中不堪使用的'特色'。

    $("#btn").click(function(){ 
        if(window == this){
            alert("window == this");
        }else{
            alert("window != this")  //弹出来
        }
        alert(this.name); // button
        angela.say();    //I'm  angela
    });
    

    将this指向的对象保存到变量(一般用that)

        var mydemo = {
            name: 'angela',
            say: function () { alert("I'm " + this.name); },
            init: function () {
                var that = this;
                document.getElementById('btn').onclick = function () {
                    that.say();  //弹出Alert:I'm angela
                    this.say();  //这儿报错: undefined is not a function (evaluating 'this.say()')  
                }
            }
        };
        mydemo.init();
    

    Javascript中的eval 方法

    JavaScript 中的 eval 方法可以将字符串转换为 JavaScript 代码,使用 eval 方法时,this 指向哪里呢?答案很简单,看谁在调用 eval 方法,调用者的执行环境(ExecutionContext)中的 this 就被 eval 方法继承下来了。(悪,还没用过,有待实践下)

    后记:由于javascript的动态性(解释执行,当然也有简单的预编译过程),this的指向在运行时才确定,因此在只要足够留心其运行时的上下文,即可无痛挥霍this的强大。

    参考AJavaScript 中的 this
    参考BJavaScript中this的一些怪异现象
    参考CJavascript的this用法-阮一峰
    参考D深入浅出 JavaScript 中的 this

  • 相关阅读:
    《ASP.NET Core跨平台开发从入门到实战》Web API自定义格式化protobuf
    .NET Core中文分词组件jieba.NET Core
    .NET Core 2.0及.NET Standard 2.0
    Visual Studio 2017 通过SSH 调试Linux 上.NET Core
    Visual Studio 2017 ASP.NET Core开发
    Visual Studio 2017正式版离线安装及介绍
    在.NET Core 上运行的 WordPress
    IT人员如何开好站立会议
    puppeteer(二)操作实例——新Web自动化工具更轻巧更简单
    puppeteer(一)环境搭建——新Web自动化工具(同selenium)
  • 原文地址:https://www.cnblogs.com/jadeboy/p/4710440.html
Copyright © 2011-2022 走看看