当前位置: 首页 > 科技观察

不要再担心这个了:JS中this的机制

时间:2023-03-20 00:11:31 科技观察

题记:JavaScript中有很多令人困惑的地方,或者说机制。但正是这些东西让JavaScript如此美丽和与众不同。比如函数也是对象,闭包,原型链继承等等,其中就包括了比较费解的这种机制。不管你是新手还是老手,如果不仔细深挖,真的搞不懂这到底是怎么回事。今天就让我们看看这个掉在地上是怎么回事吧,别再担心这个了。1.这是什么?简而言之,这是JavaScript语言中定义的众多关键字之一。它的特殊之处在于在每个函数域中自动定义,但是这个指向地面是什么让很多人感到困惑。没脑子。这里我们留一点悬念。我希望在阅读本文后,您可以回答本指南的内容。2、这个有什么用?那边的观众又要问了,既然这么难懂,为什么还要用呢?让我们看一个例子:functionidentify(){returnthis.name.toUpperCase();}functionsayHello(){vargreeting="Hello,I'm"+identify.call(this);console.log(greeting);}varperson1={name:"Kyle"};varperson2={name:"Reader"};identify.call(person1);//KYLEidentify.call(person2);//READERsayHello.call(person1);//你好,我'mKYLEsayHello.call(person2);//你好,我是READER这段代码很简单,我们定义了两个函数,identify和sayHello。并且它们在不同的对象环境中执行,以达到复用的效果,而不是为了在不同的对象环境中执行,而必须针对不同的对象环境编写相应的函数。简而言之,这带来了功能的重用。客官又问了,我不用这个也能实现,如:console.log(greeting);}varperson1={name:"Kyle"};varperson2={name:"Reader"};identify(person1);//KYLEidentify(person2);//READERsayHello(person1);//你好,I'mKYLEsayHello(person2);//你好,我是READER仔细一看,客官给的方案确实达到了类似的效果。竖起大拇指!我想说的是,随着代码的增长,函数嵌套,各级调用等变得越来越复杂,那么传递一个对象的引用就会变得越来越不明智,它会把你的代码乱七八糟,你自己都看不懂。这种机制提供了一种更加优雅和灵活的解决方案。传递隐式对象引用使代码更加简洁和可重用。好了,既然知道了这个的用处,那我们就来看看我们对它的误解。3.关于这个的误区相信很多童鞋都学过其他语言。在许多编程语言中都有这种机制。惯性思维把其他语言对它的理解带到了JavaScript中。同时,由于对this这个词的理解,我们对它也有各种各样的误解。所以,在我们开始之前,让我们澄清一下对它的误解。3.1误解一:this指的是函数本身。我们都知道在函数中引用函数可以达到递归和函数属性赋值的效果。而这在很多应用场景中是非常有用的。因此,很多人误以为这就是引导功能本身。例如:functionfn(num){console.log("fn:"+num);//count用来记录fn被调用的次数this.count++;}fn.count=0;vari;for(i=0;i<10;i++){if(i>5){fn(i);}}//fn:6//fn:7//fn:8//fn:9console.log(fn.count);//0-是吗?为什么不是4捏?上面我们想记录fn被调用的次数,但是明明fn被调用了四次但是计数还是0,这是怎么回事?这是一个简短的解释。fn第4行的自动递增隐式地创建了一个全局变量count。由于初始值未定义,所以每次自增实际上都不是一个数字。你在全局环境中打印count(window.count)应该输出NaN。第6行定义的函数熟悉的变量计数保持不变,还是0。如果对这次执行的结果不清楚,欢迎看我前几天的博文(讲范围和闭包闭包作用域和闭包在JS),这里你只要知道this指的是function这个理解错就行了。这里又有人会问了,既然这不是对函数的引用,那我要实现一个递归函数,应该怎么引用呢?这里简单回答下一个问题,两种方法:①在函数体中使用函数名来指代函数本身②在函数体中使用arguments.callee来指代函数(不推荐)。那么既然不推荐第二种方式,那么如何引用匿名函数呢?使用第一种方法,给匿名函数起一个函数名(推荐)。3.2误解二:这里指的是函数的词法作用域。这种误解可能会欺骗更多的人。首先,澄清一下,这不是指函数的词法范围。诚然,词法作用域在JS引擎中的实现确实像一个对象,有属性和功能,但这只是JS引擎的一种实现,对代码是不可见的,也就是说词法作用域JS代码中获取不到“object”。(关于词法作用域,不明白的可以参考之前的一篇博文《聊一下JS中的作用域scope和闭包closure scope和closure》)。看一个错误的例子:functionfn1(){vara=2;this.fn2();//认为this指的是fn1的词法范围}functionfn2(){console.log(this.a);}fn1();//ReferenceError上面的代码显然没有执行到想要的结果,所以我们可以看出this没有引用函数的词法作用域。甚至,可以肯定地说,本例中fn2能在fn1中正确执行是偶然的(如果理解词法作用域,就知道为什么这里执行不报错了)。4、这有什么关系?好了,说了这么多,没有真正的内容,一些观众已经开始关闭当前页面离开了。在此,我们郑重声明:this与函数的定义位置无关,函数的调用位置决定了this指的是什么。也就是说,这与函数的定义无关,而与函数的执行有很大关系。所以,请记住,“调用函数的位置决定了this指的是什么”。5、本机制的四大规则。this绑定或引用了哪个对象环境取决于调用函数的地方。函数的调用有不同的方式,不同方式的调用决定了this指向哪个对象,由四个规则决定。让我们一一看看。5.1默认绑定全局变量这条规则是最常见的,也是默认的。当单独定义和调用函数时,适用的规则是绑定全局变量。如下:functionfn(){console.log(this.a);}vara=2;fn();//2--fn是单独调用的,this指的是window5.2隐式绑定,隐式调用是指函数使用上下文对象调用,就好像该函数属于该对象一样。例如:functionfn(){console.log(this.a);}varobj={a:2,fn:fn};obj.fn();//2--this引用obj。需要澄清的一件事是,调用此函数的最后一个对象是传递给该函数的上下文对象(混淆)。如:functionfn(){console.log(this.a);}varobj2={a:42,fn:fn};varobj1={a:2,obj2:obj2};obj1.obj2.fn();//42——这里指的是obj2。另外需要注意的是丢失隐式绑定的情况如下:functionfn(){console.log(this.a);}varobj={a:2,fn:fn};varbar=obj.fn;//函数引用传递vara="global";//定义全局变量bar();//"Global"如上,第8行虽然有隐式绑定,但是执行的效果很明显是将fn赋值给bar。这样执行bar的时候,默认还是绑定了全局变量,所以输出如上。5.3显示绑定学过bind()\apply()\call()函数的人应该都知道,它接收的第一个参数是context对象,然后赋值给this。看下面的例子:functionfn(){console.log(this.a);}varobj={a:2};fn.call(obj);//2如果我们把第一个值作为简单值传递,那么后台将自动转换为相应的包对象。如果传null,结果是绑定默认的全局变量,如:functionfn(){console.log(this.a);}varobj={a:2};vara=10;fn.call(null);//105.4new新对象绑定如果是构造函数,那么用new来调用,那么新创建的对象就会被绑定。如:functionfn(a){this.a=a;}varbar=newfn(2);console.log(bar.a);//2注意一般构造函数名首字母大写,原因这里不大写是为了提醒读者,构造函数只是普通的函数。6.结论既然你已经读完了,你应该能够自己回答1中提出的问题。上面介绍的关于这个绑定的四种情况和规则,在实际写代码的过程中肯定比这更复杂,更复杂,但再复杂、再乱,都是以上规则和情况的混合应用。.只要你的思想和认识是清楚的,你就可以。