资讯专栏INFORMATION COLUMN

换个思路理解Javascript中的this

ninefive / 1960人阅读

摘要:事件处理函数中的把函数绑定到事件时,可以当作在上增加一个函数方法,当触发这个事件时调用上对应的事件方法。总结在需要判断的指向时,我们可以安装这种思路来理解判断在全局中函数中,若在全局中则指向全局,若在函数中则只关注这个函数并继续判断。

在网上很多文章都对 Javascript 中的 this 做了详细的介绍,但大多是介绍各个绑定方式或调用方式下 this 的指向,于是我想有一个统一的思路来更好理解 this 指向,使大家更好判断,以下有部分内容不是原理,而是一种解题思路。

从call方法开始

call 方法允许切换函数执行的上下文环境(context),即 this 绑定的对象。

大多数介绍 this 的文章中都会把 call 方法放到最后介绍,但此文我们要把 call 方法放在第一位介绍,并从 call 方法切入来研究 this ,因为 call 函数是显式绑定 this 的指向,我们来看看它如何模拟实现(不考虑传入 nullundefined 和原始值):

Function.prototype.call = function(thisArg) {
    var context = thisArg;
    var arr = [];
    var result;

    context.fn = this;

    for (let i = 1, len = arguments.length; i < len; i++) {
        arr.push("arguments[" + i + "]");
    }

    result = eval("context.fn(" + arr + ")");

    delete context.fn;

    return result;
}

从以上代码我们可以看到,把调用 call 方法的函数作为第一个参数对象的方法,此时相当于把第一个参数对象作为函数执行的上下文环境,而 this 是指向函数执行的上下文环境的,因此 this 就指向了第一个参数对象,实现了 call 方法切换函数执行上下文环境的功能。

对象方法中的this

在模拟 call 方法的时候,我们使用了对象方法来改变 this 的指向。调用对象中的方法时,会把对象作为方法的上下文环境来调用。

既然 this 是指向执行函数的上下文环境的,那我们先来研究一下调用函数时的执行上下文情况。

下面我门来看看调用对象方法时执行上下文是如何的:

var foo = {
    x : 1,
    getX: function(){
        console.log(this.x);
    }
}
foo.getX();

从上图中,我们可以看出getX方法的调用者的上下文是foo,因此getX方法中的 this 指向调用者上下文foo,转换成 call 方法为foo.getX.call(foo)

下面我们把其他函数的调用方式都按调用对象方法的思路来转换。

构造函数中的this
function Foo(){
    this.x = 1;
    this.getX = function(){
        console.log(this.x);
    }
}
var foo = new Foo();
foo.getX();

执行 new 如果不考虑原型链,只考虑上下文的切换,就相当于先创建一个空的对象,然后把这个空的对象作为构造函数的上下文,再去执行构造函数,最后返回这个对象。

var newMethod = function(func){
    var context = {};
    func.call(context);
    return context;
}
function Foo(){
    this.x = 1;
    this.getX = function(){
        console.log(this.x);
    }
}
var foo = newMethod(Foo);
foo.getX();

DOM事件处理函数中的this
DOMElement.addEventListener("click", function(){
    console.log(this);
});

把函数绑定到DOM事件时,可以当作在DOM上增加一个函数方法,当触发这个事件时调用DOM上对应的事件方法。

DOMElement.clickHandle = function(){
    console.log(this);
}
DOMElement.clickHandle();

普通函数中的this
var x = 1;
function getX(){
    console.log(this.x);
}
getX();

这种情况下,我们创建一个虚拟上下文对象,然后普通函数作为这个虚拟上下文对象的方法调用,此时普通函数中的this就指向了这个虚拟上下文。

那这个虚拟上下文是什么呢?在非严格模式下是全局上下文,浏览器里是 window ,NodeJs里是 Global ;在严格模式下是 undefined

var x = 1;
function getX(){
    console.log(this.x);
}

[viturl context].getX = getX;
[viturl context].getX();

闭包中的this
var x = 1;
var foo = {
    x: 2,
    y: 3,
    getXY: function(){
        (function(){
            console.log("x:" + this.x);
            console.log("y:" + this.y); 
        })();
    }
}
foo.getXY();

这段代码的上下文如下图:

这里需要注意的是,我们再研究函数中的 this 指向时,只需要关注 this 所在的函数是如何调用的, this 所在函数外的函数调用都是浮云,是不需要关注的。因此在所有的图示中,我们只需要关注红色框中的内容。

因此这段代码我们关注的部分只有:

(function(){
    console.log(this.x);
})();

与普通函数调用一样,创建一个虚拟上下文对象,然后普通函数作为这个虚拟上下文对象的方法立即调用,匿名函数中的 this 也就指向了这个虚拟上下文。

参数中的this
var x = 1;
var foo = {
    x: 2,
    getX: function(){
        console.log(this.x);
    }
}
setTimeout(foo.getX, 1000);

函数参数是值传递的,因此上面代码等同于以下代码:

var getX = function(){
    console.log(this.x);
};
setTimeout(getX, 1000);

然后我们又回到了普通函数调用的问题。

全局中的this

全局中的 this 指向全局的上下文

var x = 1;
console.log(this.x);

复杂情况下的this
var x = 1;
var a = {
    x: 2,
    b: function(){
        return function(){
            return function foo(){
                console.log(this.x);
            }        
        }
    }
};

(function(){
    var x = 3;
    a.b()()();
})();

看到上面的情况是有很多个函数,但我们只需要关注 this 所在函数的调用方式,首先我们来简化一下如下:

var x = 1;
(function(){
    var x = 3;
    var foo = function(){
        console.log(this.x);
    }
    foo();
});

this 所在的函数 foo 是个普通函数,我们创建一个虚拟上下文对象,然后普通函数作为这个虚拟上下文对象的方法立即调用。因此这个 this指向了这个虚拟上下文。在非严格模式下是全局上下文,浏览器里是 window ,NodeJs里是 Global ;在严格模式下是 undefined

总结

在需要判断 this 的指向时,我们可以安装这种思路来理解:

判断 this 在全局中OR函数中,若在全局中则 this 指向全局,若在函数中则只关注这个函数并继续判断。

判断 this 所在函数是否作为对象方法调用,若是则 this 指向这个对象,否则继续操作。

创建一个虚拟上下文,并把this所在函数作为这个虚拟上下文的方法,此时 this 指向这个虚拟上下文。

在非严格模式下虚拟上下文是全局上下文,浏览器里是 window ,Node.js里是 Global ;在严格模式下是 undefined

图示如下:

欢迎关注:Leechikit
原文链接:segmentfault.com

到此本文结束,欢迎提问和指正。
写原创文章不易,若本文对你有帮助,请点赞、推荐和关注作者支持。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/84283.html

相关文章

  • 「前端面试题系列4」this的原理以及用法

    摘要:但是有一个总的原则那就是总会指向,调用函数的那个对象。作为对象方法的调用函数作为某个对象的方法调用,这时就指这个上级对象。 showImg(https://segmentfault.com/img/bVbnvF7?w=750&h=422); 这是前端面试题系列的第 4 篇,你可能错过了前面的篇章,可以在这里找到: 伪类与伪元素的区别及实战 如何实现一个圣杯布局? 今日头条 面试题和思...

    fnngj 评论0 收藏0
  • 2017-07-25 前端日报

    摘要:前端日报精选三思而后行想提高团队技术,来试试这个套路如何开发一个插件学习笔记块级作用域绑定译文详解带来的个重大变化中文周二放送画图知乎专栏第期新特性译配置译高性能视差滚动行代码构建区块链知乎专栏渲染器修仙之路之拷贝对象已 2017-07-25 前端日报 精选 SSR 三思而后行想提高团队技术,来试试这个套路!如何开发一个 Atom 插件ES6学习笔记:块级作用域绑定【译文】详解VUE2...

    bluesky 评论0 收藏0
  • 和少妇白洁一起学JavaScript

    摘要:我们已经回答了的构造函数和原型都是谁的问题,现在牵扯出来一个,我们继续检查的构造函数是全局对象上属性叫的对象的原型是个匿名函数,按照关于构造函数的约定,它应该是构造函数的属性我们给这个对象起个名字,叫。 我不确定JavaScript语言是否应该被称为Object-Oriented,因为Object Oriented是一组语言特性、编程模式、和设计与工程方法的笼统称谓,没有一个详尽和大家...

    DevTTL 评论0 收藏0
  • javascript自定义事件原理

    摘要:我们就需要我们自己去定义事件其实就是我们写的函数,尤其是组件开发过程中,用的尤为多。可能有确定按钮取消按钮等操作。但是自定义事件的基本原理就是如上描绘的那样 我们都知道,鼠标点击click,触屏的touch等事件,可以触发相应的事件处理程序,也可以为这些事件添加事件处理程序,实际开发过程中可供我们使用的事件很少,click、doubleclick,mouseover、mousemove...

    JowayYoung 评论0 收藏0
  • vue你不知道的奇淫绝技

    摘要:之前做过的组件配置系统核心就是它。但是如果你想改动到这个元素的样式,但是又不想改动公用模板。 1.placeholder与computed巧用 表单开发肯定是日常开发中必不可少的环节,可是设计图经常会有表单默认值的设计,比如:showImg(https://segmentfault.com/img/remote/1460000012955169?w=559&h=392); 需求方的需求...

    ixlei 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<