zoukankan      html  css  js  c++  java
  • js中this关键字测试集锦

    this是Javascript语言的一个关键字它代表函数运行时,自动生成的一个内部对象,只能在函数内部使用。定义:this是包含它的函数作为方法被调用时所属的对象。总原则,this指的是,调用函数的那个对象,this永远指向函数运行时所在的对象!而非创建时的。

     以下是基于浏览器环境做的测试:

    作为函数调用: 

    1
    2
    3
    4
    5
    6
    7
    function $(){
        this.count = 1;
        return this;
    }
    window.onload = function(){
        console.info($());
    }

    控制台返回结果如下:

    一个window对象。

    对于内部函数,即声明在另外一个函数体内的函数,这种绑定到全局对象的方式会产生一个问题,即它会隐式声明全局变量。代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    var point = {
        x: 0,
        y: 0,
        moveTo: function(x, y) {
            var fn1 = function(x) {
                this.x = this.x + x;
                return this.x;
            };
            var fn2 = function(y) {
                this.y = this.y + y;
            };
            return fn1();
        }
    }
    console.log(point.moveTo());

    结果是:

    而若将fn1中return的值改为this的话,打印结果:

    一个window全局对象。这属于 JavaScript 的设计缺陷,正确的设计方式是内部函数的 this 应该绑定到其外层函数对应的对象上,这个设计错误错误的后果是方法不能利用内部函数来帮助它工作,因为内部函数的this被绑定了错误的值,所以不能共享该方法对对象的访问权。为了规避这一设计缺陷,聪明的 JavaScript 程序员想出了变量替代的方法,约定俗成,该变量一般被命名为 that。如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    var point = {
        x: 0,
        y: 0,
        moveTo: function(x, y) {
            var that = this;
            var x = x;
            var y = y;
            var fn1 = function(x) {
                that.x = that.x + x;
                return that;
            };
            var fn2 = function(y) {
                that.y = that.y + y;
            };
            return fn1(x);
        }
    }
    console.log(point.moveTo(1,1));  

    返回结果:

    函数调用中,this总是指向函数的直接调用者(而非间接调用者)。如果在严格模式下,有可能输出undefined,严格模式下,禁止this关键字指向全局对象。如下:

    1
    2
    3
    4
    5
    6
    7
    8
    'use strict';
        console.log(this === window); // true
        var foo = function() {
            console.log(this === window);
            console.log('this:',this);
        };
        foo();
        window.foo();

    控制台打印结果是:

     使用that的另一个例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    var myObj = {
        value: 0,
        increment: function(inc) {
            this.value += typeof inc === 'number' ? inc: 1;
        }
    }
    myObj.increment();
    document.writeln(myObj.value);
    myObj.increment(2);
    document.writeln(myObj.value);
    function add(num1, num2){
        return num1 + num2;
    }
    console.log(add(40,27));
    myObj.double = function() {
        var that = this;
        var helper = function() {
            that.value = add(that.value, that.value);
        }
        helper();
    }
    myObj.getValue = function() {
        var that = this;
        return that.value;
    }
    myObj.double();
    document.writeln(myObj.getValue());

    作为对象方法调用:

    如果一个调用表达式包含一个属性存取表达式(即一个.点表达式或者[subscript]下标表达式),那么它被当做一个方法来调用。另一段代码:

    1
    2
    3
    4
    5
    6
    7
    8
    var o = {};
    o.fn = $;
    function $(){
        console.log(this);
    }
    window.onload = function(){
        o.fn();
    }

    控制台返回结果如下:

    调用函数的o对象。

    在事件中,this指向触发这个事件的对象,特殊的是,IE中的attachEvent中的this总是指向全局对象Window;

    另外一种嵌套调用:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    var personA={
        name:"xl",
        showName:function(){
            console.log(this.name);
        }
    }
    var personB={
        name:"XL",
        sayName:personA.showName
    }
    personB.sayName();

    控制台输出结果:

    从这里很明显的看出,在js中this关键字指向是直接调用它的对象,而非间接的。

    作为函数及对象方法的混合调用:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    var myObject={
     
        foo : "",
        func : function(){
     
        var self = this;
        console.log("outer func : this.foo = " this.foo );
        console.log("outer func : self.foo = " + self.foo );
     
        (function(){
            console.log("inner func : this.foo = " this.foo );
            console.log("inner func : self.foo = " + self.foo );
        }());
    }
    }
    myObject.func();

    输出结果如下:

    证明函数内部函数的调用是由全局对象引发的,这在上面有所阐述。

    将构造函数作为函数调用:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    function Person(name) {
        this.name = name;
    }
    var personA = Person("xl");
    // console.log(personA.name);
    console.log(window.name);
    console.log(name);
    var personB = new Person("xl");
    console.log(personB.name);

    控制台打印结果:

    注释掉的console因为那么已经成为全局对象的属性,因此打印为未定义变量报错。第二个显式调用,第三个隐式调用。最后一个则是作为构造方法调用。

    作为构造函数调用:

    JavaScript 支持面向对象式编程,与主流的面向对象式编程语言不同,JavaScript 并没有类(class)的概念,而是使用基于原型(prototype)的继承方式。相应的,JavaScript 中的构造函数也很特殊,如果不使用 new 调用,则和普通函数一样。作为又一项约定俗成的准则,构造函数以大写字母开头,提醒调用者使用正确的方式调用。如果调用正确,this 绑定到新创建的对象上。

    通过new实例化一个函数:

    1
    2
    3
    4
    function $(){
        console.log(this);
    }
    var fn = new $();

    控制台输出如下:

    this就指这个新对象。

    使用apply或call调用:

    使用apply方法((当然使用Function.call也是可以的)),另一个方法 call 也具备同样功能,不同的是最后的参数不是作为一个数组统一传入,而是分开传入的。这两个方法异常强大,他们允许切换函数执行的上下文环境(context),即 this 绑定的对象。很多 JavaScript 中的技巧以及类库都用到了该方法。

    1
    2
    3
    4
    5
    6
    function $(){
        console.log(this);
    }
    var o = {};
    o.m = $;
    o.m.apply();  

    控制台打印结果:

    注:Function.apply(obj,args)方法能接收两个参数

    obj:这个对象将代替Function类里this对象

    args:这个是数组,它将作为参数传给Function(args-->arguments)

    apply()的参数为空时,默认调用全局对象。

    如果将call的第一个destination的值设为一个对象,如下:

    1
    2
    3
    4
    5
    6
    7
    function $(){
        console.log(this);
    }
    var u = {};
    var o = {};
    o.m = $;
    o.m.apply(u,null);

    控制台打印结果如下:

    也即this指向了apply绑定的那个对象。

    作为构造函数及apply/call的混用:

    //下面这段代码模拟了new操作符(实例化对象)的内部过程
        function person(name){
            var o={};
            o.__proto__=Person.prototype;  //原型继承
            Person.call(o,name);
            return o;
        }
        var personB=person("xl");
        
        console.log(personB.name);  // 输出  xl

    person里面首先创建一个空对象o,将o的proto指向Person.prototype完成对原型的属性和方法的继承。Person.call(o,name)这里即函数Person作为apply/call调用(具体内容下方),将Person对象里的this改为o,即完成了o.name=name操作。返回对象o。

    作为回调函数的this:

    我们来看看 this 在 JavaScript 中经常被误用的一种情况:回调函数。JavaScript 支持函数式编程,函数属于一级对象,可以作为参数被传递。请看下面的例子 myObject.handler 作为回调函数,会在 onclick 事件被触发时调用,但此时,该函数已经在另外一个执行环境(ExecutionContext)中执行了,this 自然也不会绑定到 myObject 对象上。

     button.onclick = obj.handler;

    代码如下:

    1
    <p id="p">click me</p>

    执行结果如下:

    很显然指向是触发click事件的dom元素对象,而非obj对象。这是 JavaScript 新手们经常犯的一个错误,为了避免这种错误,许多 JavaScript 框架都提供了手动绑定 this 的方法。比如 Dojo 就提供了 lang.hitch,该方法接受一个对象和函数作为参数,返回一个新函数,执行时 this 绑定到传入的对象上。使用 Dojo,可以将上面的例子改为:button.onclick = lang.hitch(myObject, myObject.handler);在新版的 JavaScript 中,已经提供了内置的 bind 方法供大家使用。

    eval方法中this的指向:

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

    var name="XL";
    var person={
         name:"xl",
         showName:function(){
         eval("console.log(this.name)");
      }
    }

    person.showName(); //输出 "xl"

    var a=person.showName;

    a(); //输出 "XL"

    第一次的执行环境是person对象,第二个是global全局环境。

    Function.prototype.bind()方法:

    1
    1.var name = "XL";

               function Person(name) {
                  this.name = name;
                   console.log(this);
                  this.sayName = function() {
                      console.log(this);
                      setTimeout(function(){
                      console.log(this);
                      console.log("my name is " + this.name);
                  },50)
               }
           }
    var person = new Person("xl");
    person.sayName();

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    2.var name="XL";
        function Person(name){
            this.name=name;
            this.sayName=function(){
                setTimeout(function(){
                    console.log("my name is "+this.name);
                }.bind(this),50)  //注意这个地方使用的bind()方法,绑定setTimeout里面的匿名函数的this一直指向Person对象
            }
        }
        var person=new Person("xl");
        person.sayName(); //输出 “my name is xl”;

    这里setTimeout(function(){console.log(this.name)}.bind(this),50);,匿名函数使用bind(this)方法后创建了新的函数,这个新的函数不管在什么地方执行,this都指向的调用它的对象,而非window。而如果不加bind在第一段代码中,window执行环境中创建了一个变量person,被赋值了Person的实例,此时的调用顺序是person调用了sayName这个方法,这个方法被赋值了一个函数(此处有问题,该赋值函数是匿名还是非匿名?下篇讨论),创建了一个执行环境,此时setTimeOut函数开始执行,创建一个环境。匿名函数及setTimeout/setInterval在非手动改变指向额情况下都在全局作用域当中。

    SO,第一段代码的打印结果是:

    sf的解释:setTimeout/setInterval/匿名函数执行的时候,this默认指向window对象,除非手动改变this的指向。在《javascript高级程序设计》当中,写到:“超时调用的代码(setTimeout)都是在全局作用域中执行的,因此函数中的this的值,在非严格模式下是指向window对象,在严格模式下是指向undefined”。本文都是在非严格模式下的情况。

    第二段代码的打印结果是:

     

    这相当于手动将this进行了强制转向。

     另一种手动转向的方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    var name="XL";
        function Person(){
            this.name="xl";
            var that=this;
            this.showName=function(){
                console.log(that.name);
            }
            setTimeout(this.showName,50)
        }
        var person=new Person(); //输出 "xl"

    借用了上面提到的that保存this指针值进行复用的技巧。

    匿名函数中的this:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    var name="XL";
        var person={
            name:"xl",
            showName:function(){
                console.log(this.name);
            }
            sayName:function(){
                (function(callback){
                    callback();
                })(this.showName)
            }
        }
        person.sayName();  //输出 XL
        var name="XL";
        var person={
            name:"xl",
            showName:function(){
                console.log(this.name);
            }
            sayName:function(){
                var that=this;
                (function(callback){
                    callback();
                })(that.showName)
            }
        }
        person.sayName() ;  //输出  "xl"

    此处采用了that技巧保存this指针。

    箭头函数(点击这里):

     箭头函数看上去是匿名函数的一种简写,但实际上,箭头函数和匿名函数有个明显的区别:箭头函数内部的this是词法作用域,由上下文确定。示例代码:

    var obj = {
         birth: 1990,
         getAge: function (year) {
         var b = this.birth; // 1990
         var fn = (y) => y - this.birth; // this.birth仍是1990
         return fn.call({birth:2000}, year);
    }
    };
    console.log(obj.getAge(2015)); // 25

    控制台打印结果:

    this的四种使用场景

    面向对象的语言中,this 关键字的含义是明确且具体的,即指代当前对象。一般在编译期确定下来,或称为编译期绑定。而在 JavaScript 中,this 是动态绑定,或称为运行期绑定,这就导致 JavaScript 中的 this 关键字有能力具备多重含义,它可以是全局对象、当前对象或者任意对象,这完全取决于函数的调用方式,带来灵活性也带来困惑。js“超级”迟绑定( very late binding)使得函数可以对this高度复用。通过this可取得它们所属对象的上下文的方法称为公共方法。使用this关键字在面向对象语言中多数情况下是为了避免命名冲突。总的来说,JavaScript 中函数的调用有以上几种方式:作为对象方法调用,作为函数调用,作为构造函数调用,和使用 apply 或 call 调用。JavaScript 中的函数既可以被当作普通函数执行,也可以作为对象的方法执行,这是导致 this 含义如此丰富的主要原因。

    函数的执行环境

    JavaScript 中的函数既可以被当作普通函数执行,也可以作为对象的方法执行,这是导致 this 含义如此丰富的主要原因。一个函数被执行时,会创建一个执行环境(ExecutionContext),函数的所有的行为均发生在此执行环境中,构建该执行环境时,JavaScript 首先会创建 arguments变量,其中包含调用函数时传入的参数。接下来创建作用域链。然后初始化变量,首先初始化函数的形参表,值为 arguments变量中对应的值,如果 arguments变量中没有对应值,则该形参初始化为 undefined。如果该函数中含有内部函数,则初始化这些内部函数。如果没有,继续初始化该函数内定义的局部变量,需要注意的是此时这些变量初始化为 undefined,其赋值操作在执行环境(ExecutionContext)创建成功后,函数执行时才会执行,这点对于我们理解 JavaScript 中的变量作用域非常重要,鉴于篇幅,我们先不在这里讨论这个话题。最后为 this变量赋值,如前所述,会根据函数调用方式的不同,赋给 this全局对象,当前对象等。至此函数的执行环境(ExecutionContext)创建成功,函数开始逐行执行,所需变量均从之前构建好的执行环境(ExecutionContext)中读取。

    本文是综合了互联网多篇文章结论之后亲测的结论,目的也在于细化自己的知识体系,并进而更深刻的理解js的内核实现。经过此番梳理,对this的灵活指向已经有了一个比较清晰的认识,不过还需在实际的工程中不断运用以达到完全掌握。不过经过此一番,没必要死记硬背以上多种场景,若需使用的时候,在对印位置打印出来看看不就知道了?

  • 相关阅读:
    R语言:提取路径中的文件名字符串(basename函数)
    课程一(Neural Networks and Deep Learning),第三周(Shallow neural networks)—— 0、学习目标
    numpy.squeeze()的用法
    课程一(Neural Networks and Deep Learning),第二周(Basics of Neural Network programming)—— 4、Logistic Regression with a Neural Network mindset
    Python numpy 中 keepdims 的含义
    课程一(Neural Networks and Deep Learning),第二周(Basics of Neural Network programming)—— 3、Python Basics with numpy (optional)
    课程一(Neural Networks and Deep Learning),第二周(Basics of Neural Network programming)—— 2、编程作业常见问题与答案(Programming Assignment FAQ)
    课程一(Neural Networks and Deep Learning),第二周(Basics of Neural Network programming)—— 0、学习目标
    课程一(Neural Networks and Deep Learning),第一周(Introduction to Deep Learning)—— 0、学习目标
    windows系统numpy的下载与安装教程
  • 原文地址:https://www.cnblogs.com/libin-1/p/6231306.html
Copyright © 2011-2022 走看看