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

  • 相关阅读:
    Python调用C++的DLL
    Go-map
    Go-切片
    Go-数组
    Go-流程控制
    Go-运算符
    Go-变量和常量
    Go-VS Code配置Go语言开发环境
    Go-跨平台编译
    Go-从零开始搭建Go语言开发环境
  • 原文地址:https://www.cnblogs.com/jadeboy/p/4710440.html
Copyright © 2011-2022 走看看