当前位置: 首页 > Web前端 > HTML

深入理解JavaScript——this关键字

时间:2023-03-28 13:24:50 HTML

先说结论:谁调用谁,this就指向谁定义哪里,哪里就有作用域。与词法作用域相对应的,还有一个作用域叫做动态作用域,用于调用时查找其位置。当时作者说this机制和dynamicscope很像。关于为什么要用this,下面就解释一下为什么要用this。使用一个例子console.log(greeting);}varme={name:'johan',};varyou={name:'elaine',};identify.call(me);//JOHANidentify.call(你);//ELAINEspeak.call(me);//你好,我是JOHANspeak.call(you);//你好,我是ELAINE,此代码有效在不同的上下文对象(我和你)中重用函数identity()和speak(),而不是为每个对象编写不同版本的函数。如果这不适用,那么你需要显式地identify()和speak()传入一个上下文对象functionidentify(context){returncontext.name.toUpperCase();}functionspeak(context){vargreeting="Hello,我是"+identify(context);控制台日志(问候语);}识别(你);//ELAINEspeak(我);//你好,我是JOHAN看到这里,你可能明白这是一种更优雅的“传递”对象引用的方式。这个例子太简单了。当你遇到n个函数(或方法)之间的调用时,显式传值无疑会变得混乱。此外,在原型中,构造函数自动引入适当的上下文对象是极其重要的。这到底是什么,这是一种什么样的机制?这是在运行时绑定,而不是在编写时绑定,它的上下文取决于调用函数时的各种条件;this的绑定与函数声明的位置无关,只取决于函数的调用方式;当调用一个函数时,JavaScript创建一个执行上下文,携带所有信息(包括this、词法环境和变量环境)。这是执行上下文(context)中的一条信息,代表谁调用了它。如上所述,this在运行时是绑定的,它的上下文取决于调用函数时的各种条件。在JavaScript中有几种调用函数的方法:作为对象方法调用、作为函数调用、作为构造函数调用和调用/应用/绑定调用。下面我们根据不同的调用方式来讨论this的含义。作为对象方法调用在JavaScript中,函数也是对象,因此函数可以作为对象的属性来使用。这时,函数被称为对象的方法。在调用这种调用方式的时候,this自然会绑定到对象上varpeople={name:'elaine',age:28,sayName:function(){console.log(this.name);},};people.sayName();//elaine也可以作为函数调用函数直接调用,此时this绑定到全局对象上。在浏览器中,window是全局对象。比如下面这个例子:调用函数的时候,this绑定到全局对象上,然后执行赋值语句,相当于隐式声明了一个全局变量,这显然不是调用者想要的functionsayAge(年龄){这个。age=age;}sayAge(5);//age变成了一个全局变量,值为5对于内部函数,也就是声明在另一个函数体中的函数,这种绑定到全局对象的方式会带来另一个问题。我们以上面写的people对象为例。这次我们想在sayName方法中定义一个函数来打印年龄。发现people.age没有变,但是全局多了一个age变量varpeople={name:'elaine',age:28,sayName:function(age){varsayAge=function(age){this.age=年龄;};说年龄(年龄);},};people.sayName(5);people.age;//28岁;//5这是JavaScript的一个设计缺陷,正确的设计方法是内层函数的this应该绑定到外层为了避免这个设计缺陷,我们的方法是替换层函数对应的对象。按照惯例,这个变量通常被称为thatvarpeople={name:'elaine',age:28,sayName:function(age){varthat=this;varsayAge=function(age){that.age=age;};说年龄(年龄);},};people.sayName(5);people.age;//5岁;//None定义为箭头函数调用当然,当我们在ES6中使用箭头函数时,我们认为它可以达到同样的效果varpeople={name:'elaine',age:28,sayName:(age)=>{console.log(this)varsayAge=function(age){this.age=age;};说年龄(年龄);},};people.sayName(5);people.age;//28岁;//5但答案不尽如人意,箭头函数不应该没有this吗?难道它的this不需要在外部词法环境中找到吗?其实箭头函数很简单。作用域(lexicalscope)是相关的。this本身的机制和dynamicscope非常相似,箭头函数的出现在一定程度上避免了JavaScript的设计缺陷(理想的设计方式应该是内部函数的this应该绑定到set对应的对象上)外部函数)varpeople={name:'eliane',age:28,sayName:()=>console.log(this.name,this),sayName2:function(){console.日志(这个。名字,这个);},};people.sayName();//'',Windowpeople.sayName2();//elaine,{name:'eliane',age:28}使用箭头函数后,调用者是谁不重要,只关心在哪里定义varfoo={bar:{a:()=>console.log(this),},};foo.bar.a();//窗口回看这个问题:varpeople={name:'elaine',age:28,sayName:(age)=>{console.log(this)varsayAge=function(age){this.age=age;};说年龄(年龄);},};箭头函数下,函数下的this指向外部词法环境,与调用者无关。而本题sayName函数中的printthis,如果要实现题中的功能只能找window,把print放在sayAge中,这样this会指向它外层的sayName函数varpeople={姓名:'elaine',年龄:28,sayName:function(age){varsayAge=(age)=>{console.log(this)this.age=age;};说年龄(年龄);},};people.sayName(5);people.age;//5岁;//调用JavaScript作为构造函数未定义为支持面向对象编程。与主流的面向对象编程语言不同,JavaScript没有类(Class)的概念,而是采用基于原型(prototype-base)的继承方式。同样的约定俗成,首字母大写的函数称为构造函数,当我们使用new调用时,this会绑定到实例对象functionPeople(name,age){this.name=name;this.age=age;}varelaine=newPeople('elaine',28)console.log(elaine)//{name:"elaine",age:28}使用call/apply/bind调用让我们重申一下函数在JavaScript中也是对象,对象是有方法的,call、apply、bind都是函数对象的方法。这三个方法非常强大,它们允许切换函数执行的上下文(context),即this绑定的对象。JavaScript中的许多技巧和库都使用此方法。让我们看一个具体的例子:functionPerson(name,age){this.name=name;这个。年龄=年龄;this.sayName=function(name,age){this.name=name;this.age=年龄;}}varelaine=newPerson('elaine',28);varjohan={name:'johan',age:28};elaine.sayName('elaine1',281);elaine.sayName.apply(johan,['johan1',281])//如果调用elaine.sayName.call(johan,'johan1',281)console.log(elaine.name)//elaine1;console.log(elaine.age)//281console.log(johan)//{name:"johan1",age:281}在上面的例子中,我们使用构造函数生成了一个对象elaine,它也有一个sayName方法;使用对象字面量创建另一个对象Johan,我们看到使用apply可以将elaine上的方法应用到johan。这时候this也绑定了对象johan,另外一个call也是一样的功能,不同的是最后一个参数没有统一作为一个数组传入,而是分开传入回头看apply和call的语义是elaine的方法sayName作用于johan,sayName需要传入的参数,我从第二个参数传值;或者johan调用elaine的sayName方法从第二个参数开始传值调用,apply,bind有能力弯到这一点。对于call/apply/bind更详细的介绍,笔者将在本文详细描述函数的执行环境——call、apply、bind的三将。之前我们一直在讲一件事,this是怎么调用的,怎么调用说了this是什么,我们来看看一个函数执行时会发生什么。当一个函数被执行时,会创建一个执行环境(或执行上下文,英文名称ExecutionContext),函数的所有行为都发生在这个执行环境中,在构建执行环境时,JavaScript会首先创建arguments变量,其中包含调用函数时传入的参数。接下来创建作用域链。然后初始化变量,首先初始化函数的形参表,值为arguments变量中对应的值,如果arguments变量中没有对应的值,则将形参初始化为undefined。如果函数包含内部函数,则初始化这些内部函数。如果不是,则继续初始化函数中定义的局部变量。需要注意的是,此时这些变量被初始化为undefined,它们的赋值操作只有在执行环境(ExecutionContext)创建成功后执行函数时才会执行。这对于我们理解JavaScript中变量的作用域非常重要。最后是这个变量的赋值。上面说了,会根据函数调用方式赋值给这个全局对象,当前对象等。至此,函数的执行环境(ExecutionContext)创建成功,函数开始逐行执行,需要的变量全部从之前搭建的执行环境(ExecutionContext)中读取。将在文章ExecutionContextandCallStack中详细介绍。在全局执行上下文中:this指向window对象,方便我们调用全局window对象函数。在执行上下文中:this指向函数的调用者。对象,参数传递的减少,函数内部需要怎么操作被调用的对象,当然需要把对象作为参数传递,这个就不需要了,直接用this操作的属性就可以了thecallingobject总结一下,构造器就是一个模板,this以后会指向新的对象。在创建Person的实例时,this.name将引用新创建的对象,并将名为name的属性放入新对象中。这其实很容易理解。是代词,意思是“这个”在生活中遇到一些规律,我们总结,得出结论,用一个名词来代替这个规律,比如马太效应,墨菲定律,我们约定这个词就是这些意思.通过这样的抽象,减少了相互信息消耗。这个其实很好理解,this指的是“this”varfoo={value:1,};函数栏(){console.log(this.value);}栏();调用函数bar,函数中的this默认指的是window。如果window上没有值,则结果未定义。varfoo={value:1,};functionbar(){console.log(this.value);}bar.call(foo);call/apply可以硬核弯曲this的点,将this指向第一个parameter,所以在这段代码中,this指的是foo,而foo上是有值的,所以打印出来的结果是1。对于JavaScript中this指向的问题,知乎上曾有人回答:this的灵活指向属于JavaScript自己发明的语言指出存在的问题是公认的,这种设计不利于代码可读性,也不利于性能优化。this之间的冲突是万恶之源,大家都在(词法)静态作用域中,玩转动态引用再重新学习this关键字,完整复习this指点面试三招——读懂JavaScript系列文章indepth-深入理解JavaScript的开始——什么是JavaScript深入理解JavaScript——JavaScript是由什么组成的?深入理解JavaScript——一切皆对象深入理解JavaScript——复制的秘密深入理解JavaScript——Prototype深入理解JavaScript——继承深入理解JavaScript——始皇JavaScript深入理解JavaScript——instanceof——找祖宗深入理解JavaScript——Function深入理解JavaScript——深入理解JavaScript--this关键字深入理解JavaScript--call,apply,bind三大必将深入理解JavaScript--立即执行函数(IIFE)深入理解JavaScript--词法环境深入理解JavaScript--执行上下文和调用栈深入理解JavaScriptJavaScript——作用域VS执行上下文深入理解JavaScript——闭包深入理解JavaScript——防抖与节流深入理解JavaScript——函数式编程深入了解深入理解JavaScript-垃圾回收机制深入理解JavaScript-数组深入理解JavaScript-loopscomehere深入理解JavaScript-strings