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

《看透了》这篇综合解析

时间:2023-03-26 23:00:39 JavaScript

关于这个在JavaScript中很常用。关于这个,要了解这个,首先得知道这是什么?为什么要用这个?这是什么当函数被调用时,会创建一个活动记录(有时称为执行上下文)。这条记录会包含函数的调用位置(调用栈)、函数的调用方式、传入的参数等信息,这是记录的属性之一,在函数执行过程中会用到。this在运行时绑定,而不是在编写时绑定,它的上下文取决于函数调用的各种条件。this的绑定与函数的声明位置无关,只与函数的调用方式有关。为什么使用thisThis提供了一种更优雅的方式来隐式“传递”对象的引用,因此可以将API设计得更简洁,更易于重用。如果没有提供,我们当然可以传递上下文。functionsayHi(context){vargreeting=`嗨,我是${sayName(context)}`console.log(greeting)}functionsayName(context){returncontext.name.toLowerCase()}varme={name:'Winfar'}sayHi(me)//嗨,我是winfar,没关系,但是随着使用模式越来越复杂,会使得代码越来越难以显示和传递上下文对象,使用事实并非如此。functionsayHi(){vargreeting=`嗨,我是${sayName.call(this)}`console.log(greeting)}functionsayName(){returnthis.name.toLowerCase()}varme={name:'Winfar'}sayHi.call(me)//嗨,我是winfarvaryou={name:'Jack'}sayHi.call(you)//嗨,我是jack此代码可用于不同的上下文对象中重用了sayHi()和sayName()函数。this的绑定对象将由绑定规则函数执行过程中的调用位置决定,大致可分为以下四种绑定方式:默认绑定、隐式绑定、显式绑定、new绑定。默认绑定当没有其他规则适用时,我们可以将默认绑定规则视为包罗万象的规则。最常用的独立函数调用属于此规则。functiongetName(){console.log(this.name)}varname='winfar'getName()//winfar从结果中发现this.name指向全局变量名。这里独立函数调用getName(),属于默认绑定规则,this指向全局对象window,全局变量是全局对象的同名属性,所以this.name等价于window。姓名。如果是严格模式,全局对象不能使用默认绑定,this会绑定到undefined。functiongetName(){'usestrict'console.log(this)}getName()//undefined隐式绑定一个对象属性来引用一个函数,函数执行时,this的绑定使用隐式绑定规则,即在函数调用中将this绑定到上下文对象。functiongetName(){console.log(this.name)}varname='winfar'varobj={name:'jack',getName:getName}obj.getName()//jack如果对象属性有多层嵌套,被调用函数中的this只会指向最后一层对象,可以理解为指向直接调用它的对象。functiongetName(){console.log(this.name)}varname='winfar'varobj={name:'jack',bar:{name:'rose',getName:getName}}obj.bar.getName()//Rose值得注意的是,如果把对象中的函数抽取出来赋值给一个新的变量,然后再执行引用变量,this的隐式模式就会丢失。functiongetName(){console.log(this.name)}varname='winfar'varobj={name:'jack',getName:getName}varbar=obj.getNamebar()//winfarbar是obj.getNameAreference,其实就是指getName这个函数本身。此时bar()是一个没有任何修饰(context对象)的函数调用,这里应用默认的绑定规则。还有一种比较常见的情况,就是作为回调函数传递给另一个函数执行。回调函数中的this点呢?functiongetName(){console.log(this.name)}varname='winfar'varobj={name:'jack',getName:getName}functionemitFn(fn){fn()}emitFn(obj.getName)//winfarsetTimeout(obj.getName)//winfar无论我们将回调函数传入自定义函数emitFn还是内置函数setTimeout,都应用默认的绑定规则,回调函数中的this指向全局对象.在emitFn中,fn参数是对getName函数的引用,并且在没有上下文对象的情况下调用fn()。在setTimeout内置函数的实现中,伪代码类似于functionsetTimeout(fn,delay){fn()},同样在没有上下文对象的情况下调用fn()。所以我们可以得出调用了obj.[xxx].bar.getName(),getName中的this指向bar对象。如果单独以getName()的形式调用,则this指向全局对象。显式绑定我们从隐式绑定中知道对象包含属性引用函数,所以this间接绑定到这个对象上。如果该函数不在对象的属性引用中并且您想强制this绑定到对象怎么办?JavaScript在bind、call和apply函数上提供了原型方法,以强制将对象绑定到this。functiongetName(){console.log(this.name)}varname='winfar'varobj={name:'jack',getName:getName}varbar={name:'rose'}obj.getName.call(bar)//roseobj.getName.apply(bar)//rosevarbar=obj.getName.bind(bar)bar()//如果原始值(字符串、布尔值或数字)作为this的绑定传入则上升对于给定的对象,这个原始值将被转换成它的对象形式,即newString()、newBoolean()或newNumber(),这通常被称为“装箱”。functiongetName(){console.log(this)}varobj={getName:getName}obj.getName.call(1)//数字{1}obj.getName.call('winfar')//字符串{'winfar'}obj.getName.call(true)//Boolean{true}传入null或undefined时,this绑定到全局对象。obj.getName.call(undefined)//Windowobj.getName.call(null)//Windownew绑定在JavaScript中并使用new来执行函数(构造函数)。一般函数中的this会指向生成的实例对象。functionGetName(){console.log(this)}newGetName()//GetName{}为什么说“一般”?因为当构造返回data作为引用对象时,this指向返回对象本身。functionGetName(){console.log(this)return{name:'winfar'}}newGetName()//{name:'winfar'}模拟new运算符的实现首先创建一个对象,对象原型指向到构造函数原型;其次调用构造函数并将this绑定到对象;最后构造函数执行返回值,如果是非引用类型,返回创建的对象,否则直接返回构造函数的返回值;functionmyNew(Fn){//在ES6中new.target指向构造函数myNew.target=Fn//constobj={}//obj.__proto__=Fn.prototype//创建一个对象,对象原型指向构造函数原型constobj=Object.create(Fn.prototype)//调用构造函数并将this绑定到对象constresult=Fn.apply(obj,[...arguments])//构造函数执行返回值,如果是非引用类型,则返回创建的对象,否则直接返回构造函数的返回值consttype=typeofresultreturn(type==='object'&&result!==null)||这个绑定规则,那么他们的优先级呢?先看隐式绑定和显式绑定的优先级:getName}obj.getName()//winfarbar.getName()//jackobj.getName.call(bar)//jackbar.getName.call(obj)//winfar可以看到绑定优先级高于隐式绑定。让我们比较一下隐式绑定和新绑定的优先级console.log(bar.name)//jackconsole.log(obj.name)//winfarnew绑定比隐式绑定具有更高的优先级。显式绑定和新绑定优先级如何?new运算符不能与call和apply一起使用,例如newobj.getName.call('winfar')。但是绑定可以函数('jack')console.log(bar.name)//jackconsole.log(obj.name)//winfar从上面可以看出,bind将this绑定到obj对象上,并将属性name添加到obj中object,new执行完函数后,this并没有执行obj对象,而是生成了一个新的对象,并添加了name属性。因此,新绑定比显示绑定具有更高的优先级。总结一下,this的四种绑定的优先级顺序是newbinding>displaybinding>implicitbinding>defaultbinding。箭头函数中的this使用箭头=>函数来简化ES6中的function关键字,不适用于上面this的四种绑定规则。这里我们回顾一下箭头函数使用中的几个注意点。(1)箭头函数没有自己的this对象。(2)不能作为构造函数使用,即new命令不能用于箭头函数,否则会报错。(3)不能使用arguments对象,它在函数体中不存在。如果要使用它,可以使用rest参数代替。(4)不能使用yield命令,所以箭头函数不能作为Generator函数使用。对于箭头函数没有自己的this对象,this是根据外层作用域(function或者global)来判断的,看下面的例子。函数getName(){返回()=>{控制台。log(this.name)}}varname='winfar'varobj={name:'jack'}varbar={name:'rose'}varfn=getName.call(obj)fn.call(bar)//jackgetName函数内部创建的箭头函数,调用时会捕获getName的this。由于调用getName时this绑定到obj对象,所以箭头函数中的this绑定到obj对象,即使绑定到其他对象后面执行也不能改变指向。另外值得注意的是,如果箭头函数外层作用域中this的指针发生变化,那么箭头函数内部的this也会随之改变。函数getName(){返回()=>{控制台。log(this.name)}}varname='winfar'varobj={name:'jack'}varbar={name:'rose'}varfn=getName.call(obj)fn()//jackvarfn1=getName.call(bar)fn1()//this在rose的第一个getName函数中绑定到obj,内层箭头函数的this继承了外层作用域this,fn是内部箭头函数引用。执行fn时,这是默认绑定,但箭头函数不适用。箭头函数的内部this仍然是外部this。同样,在第二个getName函数中,this绑定了bar,fn1箭头函数内部的this也是继承了外部的this。this指向的判断过程我们已经知道this指向的四种绑定规则和箭头函数中的this绑定。现在可以整体来看this指向的判断过程。箭头函数中的this继承了外作用域的this;通过new调用函数,this绑定到新创建的对象上;通过bind绑定调用函数或者调用`apply,this指向绑定的对象(不是undefined,null`);该函数通过上下文对象调用,并且this绑定到上下文对象;在严格模式下,this指向undefined,否则绑定到Window对象;分析综合题的实践是考试真理的唯一标准,下面我们来看一道综合题来检验我们的学习成果。varage=1varobj={age:2,getAge:function(){varage=3this.age*=2age*=3return()=>{varg=this.agethis.age*=4console.log(g)age*=5console.log(age)}}}varfn=obj.getAgevarbar=fn.call(null)bar.call(obj)console.log(window.age)fnisgetAgefunctionReference,fn.call(null)显式绑定了函数getAge的this到window对象,此时变量在scope中的分布//globalscopethis=windowage=1//getAgefunctionscopethis=windowage=3this.age*=2改变全局age,age*=3改变getAge函数的局部变量age//globalscopethis=windowage=1*2=2//getAgefunctionscopethis=windowage=3*3=9在getAge函数中的箭头函数中,this继承getAge函数this,bar是箭头函数的引用。虽然bar.call(obj)将this绑定到obj,但是绑定原则不适用于箭头函数,仍然是getAge函数this。//全局作用域this=windowage=2//getAge函数作用域this=windowage=9//箭头函数作用域this=windowg=2箭头函数中this.age*=4改变的全局年龄,没有箭头函数声明自己的环境变量age,继承getAge函数变量,age*=5改变外层函数变量//globalscopethis=windowage=2*4=8//getAgefunctionscopethis=windowage=9*5=45//箭头函数作用域this=windowg=2所以,最后的结果是2458.有很多种变体,比如obj.getAge().call(obj)console.log(window.age)结果是什么?哈哈哈,留给大家自己去想吧。结束~

猜你喜欢