学会JS的this这一篇就够了,根本不用记

作者: cbw100 | 来源:发表于2016-08-12 17:48 被阅读598次

以前看某本书上讲:
掌握了JS中this的用法才算真正的跨过了JS的门槛 我深以为是!
但是JS的this却并不是那么简单的内容,许多同学都很困惑,也肯定有同学像我曾经一样查阅各种资料想知道How to understand this of javascript。很幸运的是,网上有非常非常多的文章关于this的,简直就是琳琅满目…曾经,我看到某些文章非常开心,因为他们讲得确实非常好,以至于我确信我已掌握了this的用法。然而可能是由于我太笨了,经常过一段时间就忘得一干二净了,这真的是一件很尴尬的事情……直到后来我仔细地琢磨又琢磨,终于感觉我可能以后不会再忘记了。所以想把我琢磨的内容和大家分享一下
说到this,就不得不提到function,相信看过其它类似文章的同学也知道,正是由于调用function的对象不同,才导致了this的指向不同。所以以前老是去记忆每种调用function的情况所对应的this,因为情况有限而且很少,所以这当然是可行的——对于聪明人来说。所以我不得不思考另外一些方式来让我记住。
那么首先我们需要明确的一个事情是:function也是对象
同时我们还需要明确的一个事情是:function执行时是在某个特定的上下文中执行的。那什么是上下文呢?打个比方,比如你练会了辟邪剑谱,那这时候你的掌门让你用辟邪剑谱砍人。如果仅仅是这样的话,你是没法完成这个任务的,因为你必须得知道要砍谁吧,其次去哪儿砍吧,那么是个地下通道还是一望无尽的大草原,要是地下通道你走路都困难,还怎么用辟邪剑谱呢对吧。这就是上下文,函数执行时它也需要一些额外的信息来支撑它的运行。那么既然function是对象的话,就会有方法。而function中最核心的方法是call方法。因此我们就从这儿入手。

call方法

先来看一下如何使用call方法:

function say(content) { console.log("From " + this + ": Hello "+ content); } 
say.call("Bob", "World"); //==> From Bob: Hello World 

接下来仔细分析一下call的用法:

  • Step1: 把第二个到最后一个参数作为函数执行时要传入的参数
  • Step2: 把函数执行时的this指向第一个参数
  • Step3: 在上面这个特殊的上下文中执行函数
    上面例子中,我们通过call方法,让say函数执行时的this指向"Bob",然后把"World"作为参数传进去,所以输出结果是可以预见的。
js执行函数时会默认完成以上的步骤,你可以把直接调用函数理解为一种语法糖

比如

function say(word) { console.log(world); } 
say("Hello world"); say.call(window, "Hello world"); 

以上可以把say("Hello world")看做是say.call(window,"Hello world")的语法糖。
这个结论非常关键 所以以后每次看见functionName(xxx)的时候,你需要马上在脑海中把它替换为functionName.call(window,xxxx),这对你理解this的指向非常重要。不过也有例外,在ES5的strict mode中call的第一个参数不是window而是undefined。之后的例子我假设总是不在strictmode下,但你需要记住strictmode有一点儿不同。 对于匿名函数来说,上面的结论也是成立的

 (function(name) { // })("aa"); //等价于 (function(name) { // }).call(window, "aa");

函数作为对象的方法被调用
直接来看代码:

var person = { 
name : "caibirdme", 
run : function(time) { 
console.log(this.name + "has been running for over "+ time+ " minutes"); 
} }; 
person.run(30); //==> caibirdme has been running for over 30 minutes //等价于 person.run.call(person, 30); // the same 

你会发现这里call的第一个参数是person而不是window。
当你明白了这两点,下意识地把函数调用翻译成foo.call()的形式,明确call的第一个参数,那基本上this的问题就难不住你了。
还是来举几个例子吧
例一:

function hello(thing) { 
console.log(this + " says hello " + thing); 
} 
person = { name: "caibirdme" } 
person.hello = hello; 
person.hello("world") // 相当于执行 person.hello.call(person, "world") //caibirdme says hello world hello("world") // 相当于执行 hello.call(window, "world") //[object DOMWindow]world

例二:

var obj = {
 x: 20,
 f: function(){ 
console.log(this.x);
 } }; 
obj.f(); // obj.f.call(obj) //==> 20 
obj.innerobj = {
 x: 30, 
f: function(){ 
console.log(this.x); 
} } 
obj.innerobj.f(); // obj.innerobj.f.call(obj.innerobj) // ==> 30 

例三:

var x = 10; 
var obj = { 
x: 20, f: function(){ 
console.log(this.x); //this equals obj // ==> 20 
var foo = function(){ 
console.log(this.x); 
} 
foo(); // foo.call(window) //foo中this被指定为window,所以==> 10 } }; obj.f(); // obj.f.call(obj) // ==> 20 10 

由例三引出一个非常common的问题,如果我想让foo输出20怎么办?这时候需要用到一点小技巧
例四:

var x = 10; 
var obj = { 
x: 20, 
f: function(){ 
console.log(this.x); 
var that = this; //使用that保留当前函数执行上下文的this 
var foo = function(){ console.log(that.x); } //此时foo函数中的this仍然指向window,但我们使用that取得obj foo(); // foo.call(window) } }; 
obj.f(); 
obj.f.call(obj) // ==> 20 20 

再来一个稍微难一点点的(但其实用call替换法一点儿也不难)
例五:

var x = 10; 
var obj = { 
x: 20,
f: function(){ 
console.log(this.x); 
} }; 
obj.f(); // obj.f.call(obj) // ==> 20 
var fOut = obj.f;
 fOut(); // fOut.call(window) //==> 10 
var obj2 = { x: 30, f: obj.f } obj2.f(); // obj2.f.call(obj2) //==> 30 

例五有些同学会可能出错的原因,是没有明确我上面说的:

this是在执行是才会被确认的

他可能会认为说obj.f那个函数定义在obj里面,那this就该指向obj。如果看完这篇文章你还这么想的话,我会觉得我的表达水平太失败了…… 用于构造函数 先看一段代码:

 func person(name) { this.name = name; } 
var caibirdme = new person("deen"); // caibirdme.name == deen

我上面也说了,函数在用作构造函数时同样可以用call方法去代替,那这里怎么代替呢?
这里你又需要明确一点:

new constrcut()是一种创建对象的语法糖 它等价于
function person(name) { this.name = name; } 
var foo = new person("deen"); //通过new创建了一个对象 //new是一种语法糖,new person等价于 var bar = (function(name) { var _newObj = { constructor : person, __proto__ : person.prototype, };
 _newObj.constructor(name); // _newObj.constructor.call(_newObj, name) return _newObj; })(); 

So you can see……为什么new的时候this就指向新的对象了吧?
通过我这篇文章,我希望学会通过把一个函数调用替换成funcName.call的形式,从而理解运行时上下文中this到底指向谁。
总结来说就是下面两个等价变形:

  • foo() ---> foo.call(window)
  • obj.foo() --> obj.foo.call(obj) 只要理解以上两个变形,this就不再是问题啦!!希望我的这种方法对各位同学认识this有所帮助,不要再像我曾经一样掉入this的坑中,相关面试题也不再怕怕啦,哈哈。如果还有问题的话,欢迎下面留言。

相关文章

网友评论

  • 9e9169b974ef:机制,更加深了我对this的理解
    cbw100:@传说中的那个人 :+1:
  • 浪子丿封尘:新入JS坑的小白看了以后 懂了不少 马上就要找工作了(是不是觉得又来一个坑) 多看看你的文章 以后打算从JS往C++上努力
    cbw100:@浪子丿封尘 加油
  • BryantHe:根据作者想传达的,我自己总结了一下:

    - foo() ---> foo.call(window)

    - obj.foo() --> obj.foo.call(obj)

    首先,call函数的第一个参数,在这是window和obj是this,即指针的指向的对象。

    然后,第一个函数foo是全局函数,相当于是window这个全局对象的一个方法,所以在调用foo的时候,相当于foo.call(window),即指针指向的是window对象,this的上下文是window。

    而第二个foo是对象obj的方法,这也相当于指针指向的是obj,即obj.foo.call(obj),this的上下文是obj

    最后,这里反映的是JS里this的问题,“函数”这个对象的this指向都可以转换为后面call函数的应用。只要记得**this的上下文是在执行时才会被确认**,而不是在被创建的时候,即每次执行时this的指向都会被更新,也就是call函数反映出来的。

    但call函数或者apply函数由此也可用来改变this的指向,比如obj.foo.call(obj2),那么这里就是使用了obj的方法,但由于上下文用的是obj2的this,所以结果被赋给了obj2。
    BryantHe:@极客教程 人在重庆,感觉这里的互联网不是很好,,纯个人感觉,信息还是比较闭塞。。
    BryantHe:@极客教程 作者大大,我大学毕业一年,专业非IT,想做程序员,自学了半年,但感觉效率比较低,没学很热的框架,学的相对基础的东西,项目做的不多,现在很纠结,不知道是不是在浪费时间。。。
    cbw100:@何玉龙 嗯,理解得很好
  • 越IT:学习了!
  • 剽悍一小兔:加油,打赏支持一下 :smile:
    cbw100:@剽悍一小兔 谢谢,加油
  • 剽悍一小兔:竟然连数据类型都是Object的一个实例,原来如此,谢谢,这下子我明白了。
  • 剽悍一小兔:文章大部分都很赞同,只是我认为函数是一种数据类型,而并非一个对象。我觉得 json 和以函数作为构造器new出来的东西属于Object的犯愁。函数就是函数,我是这么记的。
    剽悍一小兔:@cllgeek var Person = function(name){
    this.name = name;

    }

    var p = new Person('Tom');

    alert(typeof Person); // function
    alert(typeof p); // object 小写的o


    alert(Person instanceof Object);//true
    alert(p instanceof Object);//true

    嗯,确实。
    cbw100:@剽悍一小兔 var a=function(){};
    typeof a
    "function"
    a instanceof Object
    true
    剽悍一小兔:@剽悍一小兔 范畴
  • 剽悍一小兔:写得好棒,支持一下!
    剽悍一小兔:@cllgeek 今天又来看一遍,深入理解this确实挺难的。
    cbw100:@彪悍一小兔 :blush:

本文标题:学会JS的this这一篇就够了,根本不用记

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