美文网首页
js this用法

js this用法

作者: day_day_up | 来源:发表于2016-10-24 16:39 被阅读0次

    参考链接

    基本含义

    var showName = function() {
        console.log('My name is', this.name);
    };
    var zs = {
            name: 'Zhang San',
            describe: showName
        },
        ls = {
            name: 'Li Si',
            describe: showName
        };
    zs.describe();      // My name is Zhang San
    ls.describe();      // My name is Li Si
    showName();         // My name is
    name = 'window';        // 等价于 window.name = 'window'; 和this.name = 'window'; 因为此时window===this
    showName();         // My name is window
    

    以上示例中实际都是执行的showName方法,但是由于环境不同,输出的结果也不同,根本原因是不同情况下的this是不一样的。
    zs.describe(); // this === zs
    ls.describe(); // this === ls
    showName(); // this === window

    绑定机制导致的this不同指代

    <input type="button" name="按钮1" onclick="showName()" value="按钮1">
    <input id="btn1" type="button" name="按钮2"  value="按钮2">
    <input id="btn2" type="button" name="按钮3" value="按钮3">
    <script>
    window.name = 'window';
    var showName = function() {
        console.log('My name is', this.name);
    };
    var btn1 = document.getElementById('btn1');
    btn1.onclick = showName;
    var btn2 = document.getElementById('btn2');
    btn2.addEventListener('click', showName, false);
    </script>
    

    点击三个按钮,控制台输出结果分别是什么呢?
    第一个为:My name is window 第二个为:My name is 按钮2 ,第三个为My name is 按钮3
    这是为什么呢?这个和绑定事件的机制有关系。第一种形式是HTML事件,onclick="showName()"表示在点击时执行showName方法,此时执行环境为全局环境,this为window,所以输出window。
    第二种和第三种分别为DOM0级事件和DOM2级事件,其实质是给点击事件指定了一个回调函数,其为showName。在点击事件的回调函数中,this是指当前这个dom元素,因此输出的值为这两个按钮的name属性。

    使用场合

    • this的使用是很广泛的,其作用也非常强大。我们可以将this的使用归为一下几类。
      1.构造函数
    function Person(name, gender) { this.name = name;
        this.gender = gender; }
    Person.prototype.showSelf = function() {
        return this; }
    zs = new Person('zs', 'male'); // {name: "zs", gender: "male"}
    zs.showSelf(); // {name: "zs", gender: "male"}
    zs === zs.showSelf(); // true
    

    2.对象的方法

    var box = {
        name: 'box',
        getName: function() {
            return this.name;
        }
    };
    var bag = {
        name: 'bag'
    };
    bag.getName = box.getName;
    bag.getName(); // "bag"
    

    虽然bag.getName实际是对box.getName的一个引用, 由于运行时使用的是bag.getName(), 此时是在bag对象下运行的, this也就指的是bag了。
    再看一点奇怪的:

    // 注意 box.getName 没有括号
    (false || box.getName)(); // window
    (false ? alert : box.getName)(); // window

    上面这两种情况下, 输出的都不再是box对象的name属性, 而是window( 之前设置了window.name = 'window')。
    表示此时方法内部的this指向的是浏览器顶层对象window。
    可以这么理解:
    box对象指向了一个地址M1, box.getName作为box的一个方法, 但本身也是对象, 它自己也有一个地址M2, 只有通过box.getName() 调用时, 是从M1中调用M2, 所以this指向的是box。 上面两种情况都是直接拿到M2来调用, 此时和M1已经没有任何关系了, this的指向当前代码块所在的对象。

    ES6箭头函数

    ES6中新增的箭头函数里面所使用的this和之前介绍的情况都不一样了, 在箭头函数中this不随其运行环境的改变而改变, 而是在声明箭头函数时, 就已经固定下来了。 箭头函数中this的指向就是声明箭头函数是所在的对象。

    先看一个常规的例子:

    function foo() {
        setTimeout(function() {
            console.log('name:', this.name);
        }, 100);
    }
    foo(); // name: window
    foo.call({ name: 'an obj' }); // name: window
    

    定义一个函数foo内部使用定时器调用一个匿名函数, 此时函数有多层了, this的指向应该是全局对象window, 输出结果证明了这一点。 使用foo.call结果也相同的原因是, call替换的是foo函数内的this指向, 而输出的this是在定时器的回调中的, 故结果依然是window。

    我们再看一下箭头函数中这一点的表现:

    function arrow_foo() {
      setTimeout(() => {
          console.log('name:', this.name);
      }, 100);
    }
    arrow_foo(); // name: window
    arrow_foo.call({ name: 'an obj' }); // name: an obj
    

    我们发现结果, 居然和上面不一样了。 为什么呢? 我们将其转化成ES5的结果来看一下, 上面代码转化后的结果是这样的:

    function arrow_foo() {
        var $__1 = this;
        setTimeout(function() {
            console.log('name:', $__1.name);
        }, 100);
    }
    arrow_foo();
    arrow_foo.call({ name: 'an obj' });
    

    看一下转换后的结果, 原因就一目了然了, 箭头函数中this直接固定成了其定义时所在的对象, 此处为foo。 实际在箭头函数中的所有this都是一个对象, 这个对象就是其定义时所在对象的this, 上面转换后的结果中在foo中首先使用一个变量记录下this, 而在箭头函数中的this被替换成了之前存储this的那个变量。
    因此直接运行时, this是指全局对象, 而使用call时, 将foo内的this替换成了指定的对象 { name: 'an obj' },从而输出的上面的结果。

    使用注意点

    1.避免多层this

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

    var box = {
        name: 'box',
        size: {
            width: 300,
            height: 300
        },
        show: function() {
            console.log('name', this.name);
            (function() {
                console.log('size', this.size);
            })();
        }
    };
    box.show();
    // name box
    // size undefined
    

    我们本意是想在show方法内部输出name, 并输出size, 但是结果却并不是想要的这样, 这是因为在立即执行的函数内部, this的执行不再是box对象而变成了顶层对象window, 因此第二行输出为undefined。
    解决方法为, 在外层用一个变量记录下this, 在要使用的地方使用那个变量。

    改写上列子

    var box = {
        name: 'box',
        size: {
            width: 300,
            height: 300
        },
        show: function() {
            console.log('name', this.name);
            var that = this;
            (function() {
                console.log('size', that.size);
            })();
        }
    };
    box.show();
    // name box
    // size Object {width: 300, height: 300}
    

    还用一种做法是JavaScript提供的严格模式use strict, 如果函数内部的this直接指向了顶层对象会直接报错。

    var box = {
        name: 'box',
        size: {
            width: 300,
            height: 300
        },
        show: function() {
            'use strict'
            console.log('name', this.name);
            (function() {
                console.log('size', this.size);
            })();
        }
    };
    box.show();
    // Uncaught TypeError: Cannot read property 'size' of undefined(…)
    

    2.避免在回调函数中使用this

    通常回调函数中的this都有其特定的, 如果在回调函数中使用this, 应该需要了解其含义, 否则可能出现意料之外的结果。
    事件处理函数作为一种特殊的回调函数, 其this是指当前的DOM对象, 最开始的例子已经说明了这个问题。
    回调函数本身是一个函数, 其作为另一个函数的参数传递进去, 然后在那个函数内部执行, 这本身已经构成了多层this, 此时this的指向是不确定的, 需要慎用。

    3.绑定this的方法

    • function.prototype.call()

    使用函数的call方法,可以指定函数内部this的指向,使其在指定的作用域中运行。

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

    上面代码中, 在全局环境运行函数f时, this指向全局环境; call方法可以改变this的指向, 指定this指向对象obj, 然后在对象obj的作用域中运行函数f。
    call方法的第一个参数为一个对象, 其表示要为函数所指定的运行上下文环境的对象( 当指定为undefined或null是默认传入window), 之后的参数依次作为原函数的参数。

    • function.prototype.apply()

    使用函数的apply方法同样可以指定函数运行的环境, 作用和call相同, 使用方法也类似, 都是第一个参数传入要指定的上下文对象。 不同点在于, apply方法最多接收两个参数, 第二个参数为一个数组( 无论原函数需要的参数是何种类型, 此数组中的每个元素将依次传递给原函数), 表示传递给原函数的参数, 而call可以接收多个参数, 从第二个参数开始, 之后的所有参数都传递给原函数。
    由于apply第二个参数接收的是数组, 其有很多巧用。 由于此文重点是描述this关键字, 就不再赘述了。

    • function.prototype.bind()

    ES5中有bind这样一个方法, 也可以指定函数的运行环境, 但是和call、 apply有所不同, bind方法可接收一个参数, 用于指定函数运行的上下文环境, 返回一个函数作为绑定指定上下文环境后的新函数。
    这样bind和call、 apply的区别就出来了: 前者是根据指定的上下文环境返回一个新函数, 而后两者是使用指定的上下文坏境运行原函数。
    其实bind和jQuery.proxy() 类似, 虽然没有后者处理多种情况, 但作为JavaScript原生方法, 更轻量、 高效。
    用本文最开始的例子来演示此方法的使用, 某对象下有某方法, 我们要将此对象这个方法作为作为一个事件处理函数, 但不希望方法内部的this被改变:

    < input id = "btn1" type = "button" name = "按钮1" value = "张三的名字是" > 
    < input id = "btn2" type = "button" name = "按钮2" value = "张三的名字是" > 
    < script > 
    window.name = 'window';
    var showName = function() { 
        console.log(this.name); 
    };
    var zs = { name: '张三' };
    var btn1 = document.getElementById('btn1');
    btn1.addEventListener('click', showName, false);
    var btn2 = document.getElementById('btn2');
    btn2.addEventListener('click', showName.bind(zs), false); 
    < /script>
    

    这样点击第二个按钮,将可以正确输出张三的名字。
    bind
    第一个参数为一个对象,为undefined或null是默认传入window
    除此之外,bind还可以接收额外参数,用于在生成新函数时,从原函数的第一个参数开始替换一部分参数(和jQuery.proxy()
    类似)。比如原函数要接收两个参数,使用bind产生新函数时,除了第一个参数的外,可以再传入一个参数,此参数将替换原函数的第一个参数,这样生成的新函数就只用接收一个参数了,详情见jQuery 工具方法简析jQuery.proxy( function, context [, additionalArguments ] )

    相关文章

      网友评论

          本文标题:js this用法

          本文链接:https://www.haomeiwen.com/subject/uryluttx.html