• 理解JavaScript中的this


    这里来个倒叙,先说一下this的几种情况指向:

    • 形如obj.fn()的this指向obj,形如fn()的this指向window
    • 匿名函数的this指向window
    • 事件回调函数的this指向dom元素
    • 定时器的回调函数指向window
    • 构造函数中的this指向实例
    • 可以使用call、apply、bind来改变this的指向

    下面开始:

    我自己结合网上的定义,给出了this的定义:

    函数中this指向了执行时,调用它的并且离它最近的对象

    不过这个定义只适用于大部分情况:

    先看一段代码:

    function fn() {
        console.log(this);  
    }
    fn();//window

    如果你知道在全局作用域中定义函数变量和函数实际是在window变量上添加属性和方法的话,那么上面的代码就很好理解了,上面的代码相当于:

    function fn() {
        console.log(this);  
    }
    window.fn();//window

    最后调用这个函数的对象是window对象,所以this就指向了window,再看:

    var obj = {
        fn: function() {
            console.log(this);
        }
    }
    obj.fn(); //this指向obj,相当于window.obj.fn();

    上面的代码我们可以看到是window的obj对象调用了fn这个函数,所以定义的时候我们强调是离它最近的调用对象,这里obj离得近,this就指向了obj。

    定义时还强调了是执行时,是什么意思呢?接着看代码:

    function fn() {
        console.log(this)
    }
    var obj = {
        fn: fn
    }
    obj.fn(); //this指向obj

    定义时,函数是在全局中定义的,但是执行时我们是利用obj来调用,它就指向了obj,如果还不太确定,我们再看:

    var obj = {
        fn: function() {
            console.log(this)
        }
    }
    var fn = obj.fn;
    fn(); //this指向了window

    这下相信了吧,下面介绍几种比较特殊的情况:

    匿名函数中的this

    匿名函数具有全局性,所以this指向的是window。

    (function(){
        console.log(this); //window
    })()
    var obj = {
        fn: function() {
            (function(){
                console.log(this);
            })()
        }
    }
    obj.fn(); //window

    上面第二段代码中,尽管是obj调用了fn函数,但是在fn函数中的匿名函数仍然具有全局性,所以this仍然指向window。

    函数普通调用的this

    普通调用是什么意思呢?就是形如:fn();不是作为某个对象的方法。上面有段代码:

    var obj = {
        fn: function() {
            console.log(this);
        }
    }
    obj.fn(); //this指向obj

    我们稍微修改一下:

    var obj = {
        fn: function() {
            function innerFn() {
                console.log(this);
            }
            innerFn();
        }
    }
    obj.fn(); //输出window

    是不是有些懵逼了,尽管innerFn是在obj的fn函数中被调用的,但是它的作用域链上活动对象只有innerFn和全局本身(ES6以前,JavaScript作用域只有函数域),我猜测,在利用obj.fn()调用的时候,JavaScript内部是做了this指向处理的,而普通调用就指向了全局。

    有人可能会问,如果我要调用外层中的this怎么办?通常我们会使用一个变量来保存this,例如:

    var obj = {
        fn: function() {
            var self = this;
            function innerFn() {
                console.log(self);
            }
            innerFn();
        }
    }
    obj.fn(); //输出obj

    定时器回调函数中的this

    setTimeout(function(){
        console.log(this);  //window
    }, 1000)
    var obj = {
        fn: function() {
            console.log(this);
        }
    }
    setTimeout(obj.fn, 1000) //输出window

    setTimeout回调函数你可以看做是下面这样:

    var obj = {
        fn: function() {
            console.log(this);
        }
    }
    var callback = obj.fn;
    //在设定时间后执行回调函数
    callback();

    上面你可能就很熟悉了,调用fn函数的是全局对象,所以指向了window对象,如果你想改变定时器中函数this的指向,可以使用bind函数:

    var obj = {
        fn: function() {
            console.log(this);
        }
    }
    setTimeout(obj.fn.bind(obj), 1000) //输出obj

    事件处理函数

    在JavaScript中我们可以这样绑定一个事件:

    <div id="div">这是一个div元素</div>
    function doClickDiv(e) {
        //to do something
        console.log(this);
    }
    var oDiv = document.getElementById('div');
    //绑定点击事件
    oDiv.addEventListener('click', doClickDiv, false);

    当点击时,输出时this指向了div这个节点,早期绑定事件的写法可以帮助我们理解:

    function doClickDiv(e) {
        //to do something
        console.log(this);
    }
    var oDiv = document.getElementById('div');
    //绑定点击事件
    oDiv.onclick = doClickDiv;

    当点击div时,会触发oDiv.onclick函数,相当于oDiv.onclick(),这和使用对象调用是一样的。

    构造函数中的this

    JavaScript中的函数是可以作为构造函数的,使用new即可,那么this在这种情况下指向是什么?

    function Person() {
        this.age = 18;
        this.job = 'student';
    }
    var person = new Person();
    console.log(person)//{age: 18, job: 'student'}

    我们可以知道person是一个实例,所以函数作为构造函数时,this是指向实例的,在构造函数中实际是这样的:

    function Person() {
        //隐藏着的语句
        //this = {} 这里只是简单说明this是一个对象,它还要关联Person函数的原型
        this.age = 18;
        this.job = 'student';
        //隐藏着的语句
        //return this;
    }
    var person = new Person();
    console.log(person)//{age: 18, job: 'student'}

    从上面我们可以看出,构造函数隐式return了this,所以person就是this,但是当构造函数有return语句时,this并不一定指向person。

    当return返回一个对象时:

    function Person() {
        this.age = 18;
        this.job = 'student';
        return {
            tip: 'this is an object'
        }
    }
    var person = new Person();
    console.log(person)//{tip: "this is an object"}

    当return回一个非对象值时:

    function Person() {
        this.age = 18;
        this.job = 'student';
        return 1;
    }
    var person = new Person();
    console.log(person)//{age: 18, job: 'student'}

    call、apply、bind改变this指向

    有时我们需要改变this的指向,就可以通过这三个方法函数来实现:

    call和apply:

    var prop = 'window';
    
    function fn() {
        console.log(this.prop);
    }
    
    var obj1 = {
        prop: 'obj1',
        fn: function(){
            console.log(this.prop);
        }
    }
    var obj2 = {
        prop: 'obj2',
        fn: function(){
            console.log(this.prop);
        }
    }
    
    fn();                // 'window'
    obj1.fn();           // 'obj1'
    obj2.fn();           // 'obj2'
    fn.call(obj1)        // 'obj1'
    fn.apply(obj1)       // 'obj1'
    obj1.fn.call(obj2);  // 'obj2'
    obj1.fn.apply(obj2); // 'obj2'

    从改变this指向来说,call和apply是一样的,它们两个函数的区别在于传参数形式的不同:

    var obj = {
        a: 1,
        b: 2,
        c: 3
    }
    
    function add(a, b, c) {
        console.log(this);
        return a + b + c;
    }
    
    add.call(obj, obj.a, obj. b, obj.c);
    add.apply(obj, [obj.a, obj.b, obj.c]);

    可以清楚的看出:call接受的参数是一个一个传进去的,而apply是传一个参数数组进去的。这里插播一个问题:为什么要有call、apply同时存在?其实,有时候改变参数的形式是很有必要,下面分享一个小技巧,当你要求一个数组的最大值时,你会怎么做?传统的做法是用for遍历一遍数组,然后挑出最大值,但是利用apply你就可以直接利用js的内置函数:

    var arr = [3, 4, 2, 78];
    var max = Math.max.apply(Math, arr);  //Math.max的用法是Math.max(1, 3, 6, 2)  //6
    console.log(max); //78

    bind:

    bind函数的作用是绑定参数,其中第一个参数就是传入this指向对象,当时undefined或者null时,this指向window,它会返回一个新函数,看代码;

    function add(a,b,c){
        return a + b + c;
    }
    
    add(1,2,3) //6
    
    var addOne = add.bind(null, 1);
    
    addOne(2, 3) //6,相当于add(1, 2, 3);
    

    bind和call的区别在于call是参入参数并执行函数,而bind是传入绑定参数,返回一个新函数。

  • 相关阅读:
    Mysql学习总结(19)——Mysql无法创建外键的原因
    Java Web学习总结(21)——http协议响应状态码大全以及常用状态码
    Java Web学习总结(21)——http协议响应状态码大全以及常用状态码
    Tomcat学习总结(5)——Tomcat容器管理安全的几种验证方式
    Android学习总结(1)——好的 Android 开发习惯
    phabricator
    linux-kernel 学习计划
    【华为云技术分享】Linux内核编程环境 (1)
    7 分钟全面了解位运算
    【华为云技术分享】鲲鹏弹性云服务器GCC交叉编译环境搭建指南
  • 原文地址:https://www.cnblogs.com/kang-xjtu/p/5800932.html
Copyright © 2020-2023  润新知