当前位置:首页 > 文章 > 正文内容

你小子,又在偷偷学this指向

廖万里2年前 (2022-11-06)文章72198

你小子,又在偷偷学this指向


深入了解V8执行流程,执行上下文和范围!如中所述,每当JavaScript执行一段可执行代码时,它都会创建一个相对于。每个执行上下文包含三个重要的属性:

词汇环境成分;

可变环境组件;

初始化此的值;

如果你对作用域和执行上下文不太了解,可以看一下上面的文章,里面讲述了V8的编译过程,以及作用域和执行上下文等一些比较难懂的概念。相信你看完会收获很多!

这是什么

与其他语言相比,JavaScript中函数的这个关键字的表达方式略有不同。另外,严格模式和非严格模式也有一些区别。在全局上下文中,它指向顶层对象(浏览器中的窗口),无论是否处于严格模式。

在大多数情况下,调用函数的方式决定了这个(运行时绑定)的值。这不能在执行过程中赋值,并且每次调用函数时其值可能不同。

这是在运行时绑定的,而不是在编写时绑定的,它的执行上下文取决于调用函数时的各种条件。

this的绑定与函数声明的位置无关,只取决于如何调用函数。

约束规则

默认绑定

首先要介绍的是最常用的一类函数调用:独立函数调用。想想下面的代码:

函数f00() {

console . log(this . a);

}

var a = 2;

foo();// 2

复制代码

在开头提到的文章中,说的是在全局范围内用var关键字声明的变量和在全局范围内声明的函数会被挂载到全局对象(window)上。

当我们调用foo()时,我们都知道全局声明的函数的作用域是浏览器中的顶级globalObject,也就是window。

通过观察我们可以看到,在代码中,foo()是用函数引用直接调用的,没有任何修饰,所以只能使用默认绑定。所以函数中的这个是window,也就是window.a,所以自然输出2。

如果使用严格模式,全局对象将不会用于默认绑定,因为这将被绑定到undefined

函数f00() {

“使用严格”;

console . log(this . a);

}

var a = 2;

f00();//无法读取未定义的属性(读取“a”)

//因为严格默认情况下,这是默认绑定为undefined的,所以this.a等于undivided.a

//因为undefined下没有A的属性,所以会报错类型。

复制代码

值得注意的是,如果foo()运行在非严格模式下,默认绑定可以绑定到全局对象,但严格模式下的foo()不影响默认绑定。

函数f00() {

console . log(this . a);

}

var a = 2;

(函数(){

“使用严格”;

f00();// 2

})();

复制代码

隐式结合

隐式绑定的规则是在调用位置是否有一个上下文对象,或者它是否被一个对象拥有或包含。但是,也不一定能这么说。首先,考虑下面的代码:

函数foo() {

console . log(this . a);

}

var obj = {

甲:111,

foo,

};

obj . foo();// 111

复制代码

首先要注意的是foo()是如何声明的,然后如何作为引用属性添加到abj对象中。但无论是直接在obj中定义,还是先定义后作为引用属性添加,这个函数严格来说都不是obj对象。

但是,调用位置会使用obj上下文来引用函数,所以在调用函数时,可以说obj对象“拥有”或“包含”了函数引用。

当一个函数引用一个上下文对象时,隐式绑定规则将在对这个上下文对象的函数调用中绑定它。所以this.a和obj.a是一样的。

例如,只有对象引用链的上层或最后一层在调用位置起作用

函数foo() {

console . log(this . a);

}

var obj2 = {

甲:111,

foo,

};

var obj1 = {

答:777,

obj2,

};

obj 1 . obj 2 . foo();// 111

//对象obj2是最后一层

// obj1.obj2仅用于属性查找,尚未调用。

复制代码

脱离原始上下文的函数

最常见的绑定问题之一是隐式绑定的函数将丢失绑定对象,这意味着它将应用默认的绑定默认值。

函数foo() {

console . log(this . a);

}

var obj = {

答:2,

foo,

};

var bar = obj.foo//函数别名

Var = "我是窗下";

bar();//我是一个窗下的人

复制代码

bar虽然是对obj.foo的引用,但实际上是引用foo函数本身,所以此时的bar()实际上是一个普通的函数调用,应用了默认绑定。

这实际上重新定义了一个bar函数,它和对象的结构一样,是重新赋值的。请参考代码:

函数foo() {

console . log(this . a);

}

var obj = {

答:2,

foo,

};

var { foo } = obj//这相当于重新定义一个函数或者它是一个函数别名。

Var = "我是窗下";

foo();//我是一个窗下的人

var object = {

时刻:777,

年龄:18,

};

console.log(对象);//{时刻:777,年龄:18}

var { moment } = object

Moment = "牛逼";

console.log(时刻);//牛逼

console.log(对象);//{时刻:777,年龄:18}

复制代码

上面的代码,解构后的变量moment,实际上是在全局范围内创建了一个变量moment,赋给777。后面对变量的直接修改并不会修改object对象中的属性矩。

作为参数的功能

函数foo() {

console . log(this . a);

}

功能栏(fn) {

// fn其实指的是foo。

fn();

}

var obj = {

答:777,

foo,

};

Var = "牛逼,那也行";

bar(obj . foo);//牛逼,这个就行。

复制代码

传递参数实际上是一种隐式赋值,所以我们在传入函数的时候也会被隐式赋值。上面的代码实际上是下面代码的变体:

函数foo() {

console . log(this . a);

}

功能栏(){

const fn = obj.foo

fn();

}

var obj = {

答:777,

foo,

};

Var = "牛逼,那也行";

bar();//牛逼,这个就行。

复制代码

显示绑定

在JavaScript中,您可以使用调用(...)并申请(...)方法,无论是宿主环境提供的一些函数,还是自己创建的函数。

它们的第一个参数是一个对象,是为此准备的,然后在调用函数时绑定到这个。因为可以直接指定这个的绑定对象,我们称之为显示绑定,

这里就不说apply和call的语法规则了。如有需要,可以咨询mdn官网。

硬装订

硬绑定可以将此强制绑定到指定的对象(new除外)。既然有硬绑定,自然就有软绑定,后面会讲到。

函数foo() {

console . log(this . a);

}

var obj = {

答:2,

};

var bar = function () {

foo . call(obj);

};

bar();// 2

setTimeout(巴,1000);// 2

//硬绑定栏不能再修改他的这个

bar.call(窗口);// 2

复制代码

应用方法具有相同的结果,但是参数的方式不同。

bind方法将返回一个硬编码的新函数,该函数将您指定的参数设置为这个上下文调用的原始参数。

API调用的“上下文”

JavaScript语言和宿主环境提供了许多内置函数,它们都提供了一个可选的参数,这个参数通常会成为一个上下文。其功能与bind(...).确保您的回调函数使用指定的this。

函数回调(元素){

console.log(element,this . id);

}

var obj = {

Id:“真好”,

};

//当foo(...)叫做

[1, 2, 3].forEach(回调,obj);

// 1 '真好看' 2 '真好看' 3 '真好看'

//我的地图也一样。

[1, 2, 3].map(回调,obj);

// 1 '真好看' 2 '真好看' 3 '真好看'

复制代码

新绑定

在我们开始讨论绑定之前,我想你已经知道当使用new调用构造函数时会发生什么。我们再来回顾一下:

在内存中创建一个新对象;

这个新对象内部的[[prototype]]属性被赋值为构造函数的prototype属性(不知道这个也可以点这里);

这个在构造函数里面被赋给这个新对象(也就是这个指向新对象);

执行构造函数内部的代码(给新对象添加属性);

如果构造函数返回非空对象,则返回该对象;否则,返回新创建的新对象;

函数Foo(力矩){

this.moment = moment

}

var bar = new Foo(777);

console . log(bar . a);// 777

复制代码

当使用new调用Foo(...),我们将构造一个新的对象,并在Foo中将其绑定到这个(...)打电话。

让我们考虑一下代码输出是什么:

var mayDay = {

瞬间:“瞬间”,

};

函数Foo() {

this.moment = 777

返回五月天;

}

var bar = new Foo();

console . log(bar . moment);

复制代码

输出的最终结果是moment,也就是这个绑定了五月天对象,那么为什么会这样呢?

答案在new的最后一个程序里“如果构造函数返回非空对象,就返回对象;否则,返回到新创建的“新对象”规则。

换句话说,如果构造函数返回一个对象,该对象将作为整个表达式的值返回,而传递的构造函数的这个将被丢弃。

如果构造函数返回非对象类型,则返回值被忽略,并返回新创建的对象。

var mayDay = {

瞬间:“瞬间”,

};

函数Foo() {

this.moment = 777

返回111;//这里的返回值已经更改

}

var bar = new Foo();

console . log(bar . moment);// 777输出的是新对象的矩。

复制代码

类上下文

这个类中的表现和函数中的表现类似,因为类本质上也是函数,但是有一些区别和考虑。在类的构造函数中,这是一个常规对象。类中的所有非静态方法都将添加到此的原型中:

课程示例{

构造函数(){

const proto = object . getprototypeof(this);

console . log(object . getownpropertymanames(proto));

}

first() {}

second() {}

Static third() {} //这不是在this上,而是在类本身上

}

新示例();// ['构造函数','第一个','第二个']

复制代码

箭头函数调用

箭头表达式的语法比函数表达式更简洁,它没有自己的this、arguments、super或new.target箭头表达式更适合那些需要匿名函数的地方,它不能作为构造函数使用。因为arrow函数不具备这一点,所以不能自然使用new运算符。

var moment = " moment

var bar = {

时刻:777,

常规:函数(){

console . log(this . moment);

},

箭头:()=> {

console . log(this . moment);

},

nest: function () {

var回调= () => {

console . log(this . moment);

};

回调();

},

};

bar . general();// 777

bar . arrow();//时刻

bar . nest();// 777

复制代码

第一个常见的功能是隐式绑定,我们前面提到过。

第二个调用,因为arrow函数没有自己的this,所以会在arrow函数的上一级寻找普通函数的this,然后就变成默认绑定,这是一个全局调用。

第三个和第二个差不多,但是它找的上层是函数嵌套,是隐式绑定,自然在对象内部输出monent。

虽然arrow函数不能通过call、applu、bind来绑定这个,但是它可以在cache arrow函数的上层绑定这个普通的函数,例如:

var foo = {

时刻:777,

常规:函数(){

console . log(this . moment);

return () => {

console.log("箭头:",this . moment);

};

},

};

var obj = {

瞬间:“瞬间”,

};

foo.general()。call(obj);// 777 "箭头:777 "

foo . general . call(obj)();//'时刻' '箭头:' ' '时刻'

复制代码

注意,这在settimeout和自执行函数中指向窗口。

setTimeout(函数foo() {

console . log(this);//窗口

}, 0);

(函数(){

console . log(this);//窗口

})();

复制代码

因为settimeout方法是挂载在window对象上的,所以在执行settimeout的时候,这个在执行回调中指向调用settimeout的对象,所以是window。

优先

如果可以对某个呼叫位置应用多个规则会怎么样?为了解决这个问题,必须优先考虑这些规则。显然,默认绑定的优先级是四个规则中最低的。

函数foo() {

console . log(this . a);

}

var obj1 = {

甲:666,

foo,

};

var obj2 = {

答:777,

foo,

};

obj 1 . foo();// 666

obj 2 . foo();// 777

obj 1 . foo . call(obj 2);// 777

obj 2 . foo . call(obj 1);// 666

复制代码

从上面的代码可以看出,显式绑定的优先级比隐式绑定高,也就是说,首先要考虑显式绑定能不能存在。

函数foo(年龄){

this.age =年龄;

}

var obj1 = {

foo,

};

var obj 2 = { };

obj 1 . foo(2);

console . log(obj 1 . age);// 2

obj1.foo.call(obj2,3);

console . log(obj 2 . age);// 3

var bar = new obj 1 . foo(7);

console . log(obj 1 . age);// 2

console . log(bar . age);// 7

复制代码

可以看出,新绑定的优先级高于隐式绑定,但是新绑定和显示绑定谁的优先级更高呢?

因为new和call/apply不能一起用,所以不能用new foo.call直接测试(...),但是我们可以用硬绑定来测试它们的优先级。

函数foo(年龄){

this.age =年龄;

}

var obj 1 = { };

var bar = foo . bind(obj 1);

酒吧(2);

console . log(obj 1 . age);// 2

var baz =新酒吧(3);

console . log(obj 1 . age);// 2

console . log(baz . age);// 3

复制代码

出乎意料的是,bar被绑定到obj1,但是new bar(3)并没有像我们语句中那样把obj1.age改为3。相反,new修改了硬绑定(到obj1)以在bar(...).

这是因为调用new时,bind之后的函数会忽略bind的第一个参数。稍后,我们将使用bind方法的ployfill实现来解释为什么会发生这种情况。

综上所述,他们的优先顺序是:

新电话;

调用、应用、绑定调用;

隐式绑定(对象方法调用);

默认绑定(普通函数调用);

绑定的Ployfill实现

function . prototype . bind = function(指针){

如果(键入此!== "函数"){

抛出新类型错误(

" Function.prototype.bind -试图绑定的内容是不可调用的"

);

}

//将参数转换为数组

const args = array . prototype . slice . call(arguments,1);

const self = this

const new func = function(){ };

const fBound = function () {

返回self.apply(

//如果是新的运算符,重新绑定这个

NewFunc && pointer的这个实例?这个:指针,

args . concat(array . prototype . slice . call(参数))

);

};

new func . prototype = this . prototype;

fbound . prototype = new new func();

返回fBound

};

复制代码

其中,以下是与新修改本相关的代码:

NewFunc && pointer的这个实例?这个:指针;

//...和;

new func . prototype = this . prototype;

fbound . prototype = new new func();

复制代码

软装订

正如我们前面提到的,这种硬绑定方法可以强制将其绑定到指定的对象(除非使用new),并防止函数调用应用默认的绑定规则。

但问题是硬绑定会大大降低函数的灵活性。在使用硬绑定之后,你不能使用隐式绑定或显式绑定来修改这个的能力。具体来说,请参见实现:

function . prototype . softbind = function(object){

设fn = this

//捕获所有定制的参数

const curried =[]. slice . call(arguments,1);

const bound = function () {

返回(

fn.apply(!this || this ===(窗口||全局)?对象:这个),

curried.concat.apply(curried,参数)

);

};

bound . prototype = object . create(fn . prototype);

返回绑定;

};

函数foo() {

console . log(this . name);

}

const obj = {

名称:“obj”,

};

const obj2 = {

名称:“obj2”,

};

const obj3 = {

名称:“obj3”,

};

const foo obj = foo . softbind(obj);

foo obj();// obj

obj 2 . foo = foo . softbind(obj);

obj 2 . foo();// obj2

foo obj . call(obj 3);// obj3

setTimeout(obj2.foo,1000);// obj

复制代码

如您所见,foo()的软绑定版本可以手动将其绑定到不同的对象。

参考文章

不知道JavaScript的书都卷起来了

MDN

结局

一点点this指向涵盖了new、call、apply、bind、arrow函数等的用法。从而延伸到范围、闭包、原型链、继承、严格模式,这个实力不可小觑。


本文链接:https://www.kkkliao.cn/?id=242 转载需授权!

分享到:

添加博主微信共同交流探讨信息差网赚项目: 19528888767 , 请猛戳这里→点我添加

版权声明:本文由廖万里的博客发布,如需转载请注明出处。

“你小子,又在偷偷学this指向” 的相关文章

“双11”,如何守住钱袋子?

“双11”,如何守住钱袋子?

“双十一”快到了,“剁手”的大斧已高高举起。购物车收藏83件,有的商品开启定价预售模式小姐姐的化妆品要不少钱呀,也不知比平时便宜多少最近几天,同事们都在热议“双十一”。 今年的“双十一”跟以往相比有新变化,比如,缩短预售时间、扩容购物车、差价一键退,不少平台还从晚上8点开始销售等。自从2009年开始...

iframe如何实现全屏,高度自适应浏览器实现

iframe编写<iframe id="iframe"         name="iframe"      ...

这又是一篇狗屁不通的文章不要在意

  赚钱网赚项目推荐匿名信一封来信万策云廖万里,到底应该如何实现。 所谓赚钱网赚项目推荐匿名信一封来信万策云廖万里,关键是赚钱网赚项目推荐匿名信一封来信万策云廖万里需要如何写。 既然如此, 了解清楚赚钱网赚项目推荐匿名信一封来信万策云廖万里到底是一种怎么样的存在,是解决一切问题的关键。 经过上述讨论...

特斯拉再降价,为何受伤最大的不是比亚迪?

特斯拉再降价,为何受伤最大的不是比亚迪?

特斯拉降价算新闻吗?素有“韭菜收割机”之称的特斯拉,不论涨价或者降价都是再普通不过的常规操作,但是这一次,轻轻松松就闯上了热搜。降一次价,上一次热搜?当然这一方面,是因为本次降价幅度不小,Model 3官降1.3万到1.8万元不等,主销车型Model Y最高降幅甚至达到3.7万元。就算结合客观情况,...

你是怎么发现你的同事很有钱的?

你是怎么发现你的同事很有钱的?

躲在舒适区09月10日关注曾经就职的一家公司里有一个文员,大专学历,胖乎乎的很可爱,一看就没什么心机。穿着打扮也看不出什么特别的地方。文员工作嘛,也没看出来她有什么特长。结果公司有一次外事活动,随行的翻译临时生病,反正公司大部分人都会些英语,就没额外配置翻译。外商最后和公司财务交流时沟通有些不顺,这...

win7系统复制文件到U盘提示权限不足的解决方法

win7系统复制文件到U盘提示权限不足的解决方法

  U盘是我们经常使用的移动存储设备,可是也会伴随着各种各样的烦恼。比如有的用户在win7系统中将一些文件复制到U盘里面的时候,系统却提示我们权限不足,很多用户都不懂怎么办,那么如果遇到这种情况我们应该怎么办呢?下面就来看小编是如何解决这种问题的。推荐:笔记本专用win7系统下载1、首先把...

发表评论

访客

看不清,换一张

◎欢迎参与讨论,请在这里发表您的看法和观点。